# Uptime Kuma Uptime Calculator: How It Computes Uptime Percentages from Heartbeat Data

> Learn how the Uptime Kuma uptime calculator computes percentages by aggregating heartbeat data into minutely, hourly, and daily buckets. Understand your service availability.

- Repository: [Louis Lam/uptime-kuma](https://github.com/louislam/uptime-kuma)
- Tags: deep-dive
- Published: 2026-02-28

---

**The Uptime Kuma uptime calculator aggregates raw monitor heartbeats into rolling minutely, hourly, and daily buckets, then computes uptime percentages by dividing total UP time by the combined UP and DOWN time.**

The `UptimeCalculator` class in the `louislam/uptime-kuma` repository transforms raw heartbeat results into precise availability statistics. Located in [`server/uptime-calculator.js`](https://github.com/louislam/uptime-kuma/blob/main/server/uptime-calculator.js), this core component processes UP, DOWN, and MAINTENANCE statuses to generate the uptime percentages displayed throughout the application's badges and charts.

## How the Uptime Kuma Uptime Calculator Aggregates Data

### Processing Monitor Status Updates

The `update(status, ping, date)` method in [`server/uptime-calculator.js`](https://github.com/louislam/uptime-kuma/blob/main/server/uptime-calculator.js) (starting at line 12) receives incoming heartbeat results and maps them to a **flat status** value. The `flatStatus()` helper (lines 42-51) consolidates `MAINTENANCE` and `PENDING` states into UP for calculation purposes, while the original status remains preserved for accurate UI display.

### Rolling Window Buckets

The calculator maintains three in-memory queues using the `LimitQueue` utility from [`src/util.js`](https://github.com/louislam/uptime-kuma/blob/main/src/util.js):

- **Minutely**: 24 hours of one-minute buckets (`minutelyUptimeDataList`)
- **Hourly**: 30 days of one-hour buckets (`hourlyUptimeDataList`)  
- **Daily**: 365 days of one-day buckets (`dailyUptimeDataList`)

### Persisting Statistics to the Database

After updating in-memory counters, the calculator writes aggregates to three database tables: `stat_minutely`, `stat_hourly`, and `stat_daily` (see the `R.store()` calls around lines 302-355). A retention policy prunes entries older than their window limits (lines 562-670), preventing unbounded database growth while preserving historical accuracy.

## Computing Uptime Percentages

### Retrieving Time-Window Data

Clients request uptime summaries through `getData(num, type)` or convenience wrappers including `get24Hour()`, `get7Day()`, `get30Day()`, and `get1Year()`. The method constructs a UTC-based time window using `getCurrentDate()` (line 331) and iterates backwards over the relevant buckets to sum counters.

### The Uptime Calculation Formula

During aggregation (lines 715-718), the calculator sums the `up` and `down` counters while accumulating `avgPing * up` for average latency calculations. The final uptime percentage is computed at line 781 as:

```js
uptime = total.up / (total.up + total.down);

```

If no data exists for the requested window, the calculator implements a lazy fallback to the latest known bucket (lines 738-768). The method returns an `UptimeDataResult` object containing the **uptime ratio** (0 to 1) and the **average ping** (or `null` when no UP data exists).

## Key Design Decisions

**Three Rolling Windows**: Using separate minute, hour, and day granularity allows fast badge calculations for common timeframes without scanning the entire heartbeat table.

**UTC-Based Timestamps**: The calculator uses `date.utc()` (line 999) to generate consistent keys across time zones, eliminating daylight-saving anomalies.

**Flat Status Conversion**: Treating maintenance and pending states as UP ensures uptime calculations reflect actual service availability rather than administrative states.

**Lazy Fallback Mechanism**: When requested windows lack data (such as for newly created monitors), the calculator returns the last available bucket to prevent empty results.

## Practical Implementation Examples

### Recording a New Heartbeat

```js
const { UptimeCalculator } = require("./server/uptime-calculator");

(async () => {
  const calc = await UptimeCalculator.getUptimeCalculator(monitorID);
  // Status: UP (0), DOWN (1), MAINTENANCE (2), PENDING (3)
  await calc.update(0, 34);  // Record UP with 34ms ping
})();

```

### Retrieving 30-Day Uptime Statistics

```js
const { UptimeCalculator } = require("./server/uptime-calculator");

(async () => {
  const calc = await UptimeCalculator.getUptimeCalculator(monitorID);
  const result = calc.get30Day();
  console.log(`Uptime: ${(result.uptime * 100).toFixed(2)}%`);
  console.log(`Avg Ping: ${result.avgPing?.toFixed(1)}ms`);
})();

```

### Querying Custom Durations

```js
const result = calc.getDataByDuration("2w");
// Supports: m (minutes), h (hours), d (days), w (weeks), 
//           M (months ≈ 30d), y (years ≈ 365d)

```

## Core Source Files

- **[`server/uptime-calculator.js`](https://github.com/louislam/uptime-kuma/blob/main/server/uptime-calculator.js)**: Core class implementing aggregation logic and percentage calculations.
- **[`src/util.js`](https://github.com/louislam/uptime-kuma/blob/main/src/util.js)**: Provides `LimitQueue` for sliding-window memory management.
- **[`src/util-frontend.js`](https://github.com/louislam/uptime-kuma/blob/main/src/util-frontend.js)**: Frontend formatting helpers for badge rendering.
- **`db/knex_migrations/`**: Database migrations creating `stat_minutely`, `stat_hourly`, and `stat_daily` tables.
- **[`test/backend-test/test-uptime-calculator.js`](https://github.com/louislam/uptime-kuma/blob/main/test/backend-test/test-uptime-calculator.js)**: Unit tests verifying calculator logic and edge-case handling.

## Summary

- The **UptimeCalculator** class in [`server/uptime-calculator.js`](https://github.com/louislam/uptime-kuma/blob/main/server/uptime-calculator.js) aggregates heartbeats into minutely, hourly, and daily buckets to optimize query performance.
- **Flat status** conversion treats MAINTENANCE and PENDING as UP for availability math while preserving original states for display.
- Uptime percentages derive from `total.up / (total.up + total.down)` using pre-aggregated database tables rather than raw heartbeat scans.
- Convenience methods like `get24Hour()` and `get30Day()` provide quick access to common reporting windows.
- UTC-based timestamp keys ensure consistent calculations across global time zones and daylight-saving transitions.

## Frequently Asked Questions

### How does Uptime Kuma handle maintenance windows in uptime calculations?

The `flatStatus()` method maps `MAINTENANCE` and `PENDING` statuses to UP values for mathematical calculations, ensuring administrative actions do not artificially penalize availability metrics. The original status remains stored separately in the database for accurate UI display and reporting.

### What database tables store the uptime calculator data?

The calculator persists aggregates to three tables: `stat_minutely` (24-hour retention), `stat_hourly` (30-day retention), and `stat_daily` (365-day retention). These tables store `up` and `down` counters plus ping statistics for each time bucket, enabling fast historical queries without scanning the full heartbeat log.

### How does the calculator handle monitors with no historical data?

When a requested time window contains no records, the calculator implements a lazy fallback mechanism (lines 738-768) that returns data from the most recent available bucket. This ensures newly added monitors display reasonable defaults rather than empty or null results in the dashboard.

### What is the retention policy for uptime statistics?

Old rows are automatically pruned during the update cycle (lines 562-670) when they exceed their respective window limits: 24 hours for minutely data, 30 days for hourly data, and 365 days for daily data. This prevents unbounded database growth while maintaining sufficient granularity for standard uptime reporting periods.