Error Budget and Burn Rate
Problem Statement
An SLO of 99.9% availability is really a budget: over the window you are allowed to fail 0.1% of requests. Spending that budget slowly is normal operation; spending it fast is an incident. Compute how much budget a service has left and how fast it is burning.
Background
For an availability SLO over one window:
budget_rate = 1 - slo_target # fraction you may fail
allowed_failures = budget_rate * total_requests
error_rate = failed_requests / total_requests
burn_rate = error_rate / budget_rate
budget_consumed_pct = 100 * failed_requests / allowed_failures
budget_remaining_pct= 100 - budget_consumed_pct
Burn rate is the number that pages someone. A burn rate of 1.0 means you will spend exactly the whole budget over exactly the window — sustainable. A burn rate of 10 means the month's budget is gone in three days, which is why fast-burn alerts fire on burn rate rather than on raw error rate.
Projecting forward at the current burn rate:
hours_to_exhaustion = (budget_remaining_pct / 100) * window_hours / burn_rate
Two clamps: once the budget is exhausted (budget_remaining_pct <= 0), report 0.0 for both the remaining percentage and the hours; and with a burn rate of 0 the budget never runs out, so report the full window_hours.
Your Task
Implement:
def error_budget(slo_target, total_requests, failed_requests, window_hours):
Return a dict with keys "allowed_failures", "burn_rate", "budget_consumed_pct", "budget_remaining_pct", "hours_to_exhaustion", in that order, each rounded to 2 decimal places.
Input Format
- slo_target: float in (0, 1), e.g. 0.999.
- total_requests, failed_requests: ints.
- window_hours: number of hours in the SLO window (720 for 30 days).
Output Format
- A dict of five values, each rounded to 2 decimals.
Sample
print(error_budget(0.999, 2000000, 1200, 720))
Output:
{'allowed_failures': 2000.0, 'burn_rate': 0.6, 'budget_consumed_pct': 60.0, 'budget_remaining_pct': 40.0, 'hours_to_exhaustion': 480.0}
A 99.9% SLO over 2M requests buys 2000 failures. 1200 are spent, so 40% of the budget is left and the service is burning at 0.6x — under 1.0, so it will finish the window inside budget.
Example:
print(error_budget(0.999, 2000000, 1200, 720))
{'allowed_failures': 2000.0, 'burn_rate': 0.6, 'budget_consumed_pct': 60.0, 'budget_remaining_pct': 40.0, 'hours_to_exhaustion': 480.0}budget_rate = 1 - 0.999 = 0.001, so 0.001 * 2,000,000 = 2000 failures are allowed. The observed error rate is 1200 / 2,000,000 = 0.0006, giving burn_rate = 0.0006 / 0.001 = 0.6. 1200 of 2000 failures is 60% consumed, 40% left, and projecting 0.4 * 720 / 0.6 gives 480 hours before the budget is gone.
Constraints:
- 0 < slo_target < 1
- 1 <= total_requests <= 10**9, 0 <= failed_requests <= total_requests
- window_hours > 0
- When the budget is exhausted, report
budget_remaining_pctandhours_to_exhaustionas 0.0 (never negative) - When burn_rate is 0,
hours_to_exhaustionis the fullwindow_hours - Round every returned value to 2 decimal places
1. Background Knowledge
Service Level Objectives (SLOs) are targets for service reliability, often expressed as a percentage of successful requests over a specific time window. For example, a 99.9% availability SLO means that 0.1% of requests are allowed to fail. This allowable failure margin is called the error budget. The error budget is not just a static number; it is a dynamic resource that teams spend as they incur errors. Monitoring how quickly this budget is consumed is critical for maintaining service health.
The burn rate is a key metric in observability that measures how fast the error budget is being consumed relative to the allowed rate. A burn rate of 1.0 indicates that the service is consuming its error budget at a sustainable pace, meaning it will exactly exhaust the budget by the end of the window. A burn rate greater than 1.0 indicates unsustainable consumption (e.g., a burn rate of 10 means the budget will be exhausted in 1/10th of the window), which typically triggers alerts. Conversely, a burn rate less than 1.0 indicates the service is performing better than the SLO target.
Understanding the relationship between error rate, budget rate, and burn rate is essential. The error rate is the actual fraction of failed requests. The budget rate is the maximum allowable fraction of failed requests (1−slo_target). The burn rate is the ratio of the actual error rate to the budget rate. This normalization allows teams to compare performance across different SLO targets and time windows using a single, intuitive metric.
2. Algorithm Approach
The problem requires implementing a straightforward calculation based on provided formulas. The approach is direct computation rather than an iterative or search-based algorithm. You will calculate each required metric sequentially, ensuring that intermediate values are used correctly for subsequent calculations.
Key steps involve:
- Calculating the budget rate from the SLO target.
- Determining the allowed failures based on total requests and the budget rate.
- Computing the error rate from actual failed requests.
- Deriving the burn rate by dividing the error rate by the budget rate.
- Calculating the budget consumed percentage and remaining percentage.
- Projecting the hours to exhaustion using the burn rate and remaining budget, with specific edge-case handling for zero burn rate or exhausted budget.
The implementation must handle floating-point arithmetic carefully and apply rounding to two decimal places for the final output.
3. Step-by-Step Strategy
- Calculate Budget Rate: Compute budget_rate = 1 - slo_target. This represents the fraction of requests that can fail.
- Calculate Allowed Failures: Multiply budget_rate by total_requests to get allowed_failures. This is the absolute number of failures permitted in the window.
- Calculate Error Rate: Compute error_rate = failed_requests / total_requests. Ensure you handle the case where total_requests is zero to avoid division by zero, though the problem constraints imply valid inputs.
- Calculate Burn Rate: Divide error_rate by budget_rate to get burn_rate. This normalizes the error rate against the SLO target.
- Calculate Budget Consumed Percentage: Compute budget_consumed_pct = 100 * failed_requests / allowed_failures. This shows what portion of the error budget has been used.
- Calculate Budget Remaining Percentage: Compute budget_remaining_pct = 100 - budget_consumed_pct.
- Handle Edge Cases for Hours to Exhaustion:
- If budget_remaining_pct <= 0, set hours_to_exhaustion = 0.0.
- If burn_rate == 0, set hours_to_exhaustion = window_hours.
- Otherwise, calculate hours_to_exhaustion = (budget_remaining_pct / 100) * window_hours / burn_rate.
- Round and Return: Round all five values to two decimal places and return them in a dictionary with the specified keys.
4. Common Pitfalls
- Division by Zero: Always check for total_requests == 0 or allowed_failures == 0 before dividing. While the problem constraints may prevent this, robust code should handle it.
- Floating-Point Precision: Intermediate calculations should be performed with full precision. Only round the final results to two decimal places. Rounding intermediate values can lead to cumulative errors.
- Edge Case Handling: The problem specifies specific behaviors for burn_rate == 0 and budget_remaining_pct <= 0. Failing to check these conditions before calculating hours_to_exhaustion can lead to division by zero or incorrect results.
- Order of Operations: Ensure that budget_consumed_pct is calculated correctly. It is based on failed_requests and allowed_failures, not directly on error_rate and budget_rate, although they are mathematically equivalent. Using the direct formula avoids potential floating-point discrepancies.
- Dictionary Key Order: The problem requires the dictionary keys to be in a specific order. In Python 3.7+, dictionaries maintain insertion order, so ensure you insert the keys in the correct sequence: "allowed_failures", "burn_rate", "budget_consumed_pct", "budget_remaining_pct", "hours_to_exhaustion".
5. Time & Space Complexity
- Time Complexity: The solution involves a constant number of arithmetic operations, regardless of the input size. Therefore, the time complexity is O(1).
- Space Complexity: The solution uses a fixed number of variables to store intermediate results and the final dictionary. Therefore, the space complexity is O(1).
The efficiency of this solution is optimal, as it directly computes the required values without any loops or recursive calls. The primary focus should be on correctness and handling edge cases rather than performance optimization.