Skip to main content
← Back to Blog

· 8 min read

How we compute uptime (the math we publish).

The exact algorithm behind every uptime percentage on our reliability pages: customer-impact weighting, interval merging, window clipping, maintenance carve-outs, and why we round to three decimal places. With a worked example you can check by hand.

By CheckUpstream Team

How we compute uptime (the math we publish).

Every vendor publishes an uptime number. Almost none of them publish the math behind it. You get "99.99% uptime" on a landing page, a green dot on a status page, and no way to check either. If you have ever tried to reconcile a vendor's claimed uptime against the outages your team actually lived through, you know how that reconciliation ends.

We publish uptime percentages for every service on our reliability leaderboard, and those numbers feed directly into SLA credit claims that finance teams sign off on. So the math cannot be a black box. This post is the entire algorithm, in the order the code runs it, with a worked example you can verify with a calculator.

The formula.

One line:

uptime % = (window length − weighted downtime) / window length × 100

Rounded to three decimal places, clamped to [0, 100]. The window is a rolling period ending now: we compute it for 7-day, 30-day, 90-day, 180-day, and 1-year windows, with 30 days as the default everywhere.

All of the interesting decisions live inside "weighted downtime." That number comes from a single function in our codebase, and every user-facing uptime figure in the product goes through it: the leaderboard, the per-service reliability pages, the SLA snapshots, and the credit claims. One function, so two pages can never disagree about the same service in the same minute. (They used to. Our reliability page and status page once computed uptime independently and showed 99.872% and 100.00% for the same service at the same time. We fixed it by deleting one of the implementations.)

Here is what that function does, step by step.

Step 1: weight each incident by customer impact.

Not all incidents are outages. A vendor's status page will report "elevated error rates on one component" and "complete service outage" as the same kind of object. Counting both as full downtime produces absurd numbers: an early unweighted version of our own pipeline scored GitHub at 60% uptime and Cloudflare at 42%, because every minor component blip counted at full weight. Nobody who used those services in that period would recognize those numbers.

So every incident gets a weight:

  • down counts at 1.0: full downtime for its duration
  • degraded counts at 0.3: partial impact, the Statuspage industry convention
  • none counts at 0: reported, but no customer-visible effect

Each incident is assessed once, when first detected, from its title and affected components. When that assessment hasn't happened yet, we fall back to the raw impact field the vendor's own status page reported, mapped to the same scale: critical and major-outage values count at 1.0, major and partial-outage at 0.7, minor and degraded-performance at 0.3, maintenance and operational at 0.

One rule in that fallback matters more than the rest: an impact value we don't recognize counts as zero. Unknown severity never inflates downtime. If we're going to be wrong, we'd rather understate a vendor's downtime than open a credit claim on a number the vendor can knock down.

Step 2: merge overlapping incidents.

Status pages routinely open multiple incident records for one event, one per affected component. If the API incident runs 09:00 to 09:40 and the dashboard incident runs 09:30 to 10:00, that's one hour of trouble, not seventy minutes. Summing durations naively double-counts the overlap.

We merge overlapping intervals into a disjoint set before summing. The merge happens within each weight level: all the 1.0-weight intervals merge together, all the 0.3-weight intervals merge together, and so on. Merging across weights would be wrong in the other direction: a degraded interval that overlaps a full outage would vanish into the outage, dropping its contribution entirely.

Step 3: clip to the window, and cap open incidents at now.

An incident only counts for the part of it that falls inside the window. If an outage started 35 days ago and resolved 2 days into a 30-day window, only those 2 days count toward the 30-day number.

Open incidents get a stricter rule: an unresolved incident is counted from its start to now, never beyond. This sounds obvious until you see what happens without it. A snapshot computed mid-window would otherwise treat an open incident as lasting until the window's end, booking downtime that hasn't happened yet. We hit this in production: one minor GitHub incident that a poller hadn't auto-resolved inflated a monthly snapshot with nine days of phantom future downtime. The cap is now unconditional in the shared function.

Step 4: subtract scheduled maintenance.

Every major vendor SLA (AWS, GCP, Azure, Cloudflare, Stripe) excludes scheduled maintenance from the uptime commitment. If we left maintenance windows in, our number would read lower than the vendor's own, and a claim filed on it would be rejected on that discrepancy before anyone read the substance. So declared maintenance windows are merged the same way incidents are, then subtracted from each weight group's downtime before the weight is applied.

A worked example.

The following incidents are hypothetical, constructed to exercise every rule above. The arithmetic is exactly what the production function computes for these inputs; several of these cases are lifted straight from its unit tests.

Take a 30-day window ending 2026-07-27 00:00 UTC. That's 30 × 24 × 60 = 43,200 minutes. Three incidents:

  • Incident A, classified down: July 10, 09:00–09:40 UTC (40 minutes)
  • Incident B, classified down: July 10, 09:30–10:00 UTC (30 minutes)
  • Incident C, classified degraded: July 18, 13:00–18:00 UTC (300 minutes)

Step by step:

  1. Weights. A and B carry weight 1.0. C carries weight 0.3.
  2. Merge within each weight. A and B overlap from 09:30 to 09:40, so they merge into one interval, 09:00–10:00: 60 minutes, not 70. C stands alone: 300 minutes.
  3. Clip and cap. Everything falls inside the window and everything is resolved, so nothing changes.
  4. Maintenance. None declared, so nothing is subtracted.
  5. Weighted downtime. 60 × 1.0 + 300 × 0.3 = 60 + 90 = 150 minutes.
  6. The formula. (43,200 − 150) / 43,200 × 100 = 99.65277…%, which rounds to 99.653%.

Now compare that against the two naive computations you'll find elsewhere:

  • Count everything at full weight: 360 raw minutes → 99.167%. The degraded afternoon reads like a five-hour hard outage. It wasn't.
  • Count only full outages: 60 minutes → 99.861%. Five hours of elevated errors disappear entirely. Your customers noticed them.

Our number sits between the two, and you can reproduce it from the incident list by hand. That reproducibility is the point.

Why three decimal places.

We round to three decimal places, not two, and this is a deliberate policy, not a formatting preference. Two decimal places quantize uptime in steps of 0.01%, which is 4.3 minutes in a 30-day window, and anything above 99.995% displays as a flat 100.00%. That's the resolution at which real weighted downtime on our own leaderboard once rendered as 100.00%, hiding genuine reliability differences between vendors. It's also too coarse for the thresholds that matter: a 99.99% monthly SLA target allows about 4.3 minutes of downtime, the same size as one 2dp rounding step. Three decimal places shrink the step to 26 seconds, which keeps single-digit minutes of monthly downtime visible.

The same math, where the money is.

The percentage on a reliability page is trust-building. The place this math earns its keep is SLA credit claims.

When a vendor misses its published SLA target, the credit you're owed is a function of measured downtime against the vendor's own terms. Two things have to be true for that claim to survive contact with a support agent: the downtime has to be measured (not copied from the vendor's optimistic self-assessment), and it has to be computed the way the vendor's own SLA computes it.

The second part means the claim path goes one step further than the published percentage. Vendors don't all measure alike: AWS API SLAs bucket the month into 5-minute slots and count a slot as down if any incident overlaps it; Cloudflare's Enterprise SLA only counts outages of five minutes or longer; Stripe and Twilio define downtime by error rates. Our claim documents recompute downtime under the vendor's declared method so that our number and their number are talking about the same thing, and when a method needs data we don't have (error-rate math without request-level telemetry), the claim falls back to interval math and says so in the draft rather than guessing. The published uptime percentage and the claim math share the same weighted-downtime core; the claim layer adds the vendor's measurement rules on top.

The per-vendor claim guides at /reliability/[vendor]/sla (here's the AWS one) render the same SLA rule rows our claim deriver enforces, so the terms you read on the page can never drift from the terms the money path computes with.

What this doesn't fix.

The algorithm is deterministic, but it's only as good as its inputs. Incidents come from vendor status pages and feeds plus our own measurement (synthetic probes, traffic-level signals, SDK telemetry), and status pages under-report. When a vendor never acknowledges an incident anywhere we can see it, that downtime is missing from our number too, which means our published uptime is, if anything, an overestimate. The severity classification also involves judgment: "degraded at 0.3" is a convention, not a law of nature, and we've written down why we chose it rather than pretending there was no choice.

What we can promise is narrower and more useful: every number on the leaderboard is the output of one published algorithm over a list of recorded incidents, and given that list, you can recompute it yourself. That's the standard we think any uptime number should meet before anyone signs off on it.