monitor.uptime-bars
One bar per UTC day for a status page's uptime history: seconds in each state, uptime and a colour state, oldest first.
1.0.0 · published 2026-10-03 by charlie · Anterra
Pinned by 17 tests, run in TypeScript, Python and Rust.
What it does
The row of daily bars under each component on a status page ("90 days ago ... today"): one `DayBar` per UTC day, oldest first, each with the seconds up, degraded, down and unknown, the day's uptime in basis points and a state to colour it by.
Each day is measured by `monitor.uptime` over that day's window, with the same `maxGapSeconds` rule: a check's status holds until the next check, for at most `maxGapSeconds`, and the latest check before midnight carries into the day. So an outage that crosses midnight shows on both days.
For example
uptime_bars(checks ×1, 1,728,003,600, 3, 259,200)→ ×3 three days of up: two full days and today up to nowuptime_bars(checks ×5, 1,728,007,200, 3, 86,400)→ ×3 an hour down shows an outage bar at 95.83%, a slow hour a degraded bar at 100%uptime_bars(, 1,728,000,100, 2, 60)→ ×2 days with no checks are no-data bars, not operational ones
The function
The same function in TypeScript, Python and Rust, pinned by the same tests. Pick your language; the choice follows you around the registry.
pub fn uptime_bars(checks: &[Check], now: i64, days: i64, max_gap_seconds: i64) -> Vec<DayBar>
| checks | Check[] | in strictly ascending time order |
| now | int | Unix seconds; today's bar ends here, not at midnight |
| days | int | how many UTC days ending with today, 1 to 366 (90 is the usual status page) |
| max_gap_seconds | int | how long one check's status is trusted before time counts as unknown, as in monitor.uptime |
| returns | DayBar[] | oldest day first |
The types it declares, generated into your project
// BarState is a string in Rust, one of: "operational", "degraded", "outage", "no-data".
// Parameters take it as &str and results hold it as String.
/// One UTC day of a status page's uptime history.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DayBar {
pub date: String,
/// null when nothing that day is known
pub uptime_basis_points: Option<i64>,
pub up_seconds: i64,
pub degraded_seconds: i64,
pub down_seconds: i64,
pub unknown_seconds: i64,
/// outage if any down second, else degraded if any degraded, else operational; no-data when nothing is known
pub state: String,
}
Your code names it in one line, in the file that uses it
fune!(monitor.uptime-bars@^1); // then call uptime_bars(…)
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::monitor_check_status::{checks_from_value, Check}; ← from monitor.check-status ^1.0.0 · built alongside by fune
use super::monitor_uptime::uptime; ← from monitor.uptime ^1.0.0 · built alongside by fune
use super::time_unix_to_iso::unix_to_iso; ← from time.unix-to-iso ^1.0.0 · built alongside by fune
const DAY: i64 = 86400;
/// The last `days` UTC days ending with today, oldest first, each measured by
/// monitor.uptime. Today's bar ends at `now`, so it never shows the rest of
/// today as unknown.
///
/// # Panics
/// Panics when days is outside 1..=366, and on anything monitor.uptime refuses.
pub fn uptime_bars(checks: &[Check], now: i64, days: i64, max_gap_seconds: i64) -> Vec<DayBar> {
if !(1..=366).contains(&days) {
panic!("days must be 1 to 366, received {}", days);
}
// Floored, not truncated: before 1970 today starts on the earlier midnight.
let today_start = now - now.rem_euclid(DAY);
let mut bars = Vec::new();
for i in (0..days).rev() {
let start = today_start - i * DAY;
let end = if i == 0 { now } else { start + DAY };
let r = uptime(checks, start, end, max_gap_seconds);
let state = if r.uptime_basis_points.is_none() {
"no-data"
} else if r.down_seconds > 0 {
"outage"
} else if r.degraded_seconds > 0 {
"degraded"
} else {
"operational"
};
bars.push(DayBar {
date: unix_to_iso(start)[..10].to_string(),
uptime_basis_points: r.uptime_basis_points,
up_seconds: r.up_seconds,
degraded_seconds: r.degraded_seconds,
down_seconds: r.down_seconds,
unknown_seconds: r.unknown_seconds,
state: state.to_string(),
});
}
bars
}
pub fn day_bar_to_value(b: &DayBar) -> Value {
Value::obj(vec![
("date", Value::str(&b.date)),
("uptimeBasisPoints", b.uptime_basis_points.map(Value::Int).unwrap_or(Value::Null)),
("upSeconds", Value::Int(b.up_seconds)),
("degradedSeconds", Value::Int(b.degraded_seconds)),
("downSeconds", Value::Int(b.down_seconds)),
("unknownSeconds", Value::Int(b.unknown_seconds)),
("state", Value::str(&b.state)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
let checks = checks_from_value(&args[0]);
let now = match &args[1] {
Value::Int(i) => *i,
_ => panic!("now must be whole seconds"),
};
let days = match &args[2] {
Value::Int(i) => *i,
other => panic!("days must be 1 to 366, received {}", other),
};
let gap = match &args[3] {
Value::Int(i) => *i,
other => panic!("maxGapSeconds must be a whole number of at least 1, received {}", other),
};
Value::Arr(uptime_bars(&checks, now, days, gap).iter().map(day_bar_to_value).collect())
}Install
fune build
With that line in your source, in a Rust project (language rust in fune.project), fune build resolves it and its 3 dependencies, pins them in fune.lock, downloads only the Rust package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. A crate’s build.rs runs it before every compile. Or pin a range in fune.project and build in one step:
fune add monitor.uptime-bars
The manifest, vectors and README with only the Rust implementation. Install it without the registry with fune add ./monitor.uptime-bars-1.0.0-rust.fune, or fetch it from a terminal with fune pull monitor.uptime-bars@1.0.0:rust.
The whole function, every language, is one file too: monitor.uptime-bars-1.0.0.fune, 17,393 bytes, sha256 acd1ac7feb44a8df71bcace52d4de9ffefbaaa76498627388340399962540227. It installs into a project of any language.
Customise it in your app
The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.
before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.
// fune: before monitor.uptime-bars
after — your function gets the result and the arguments, and returns the final result.
// fune: after monitor.uptime-bars
replace — inside this capability’s code only, calls to a dependency go to your function, with the same signature. Other capabilities that use it are unaffected; write in * to replace it everywhere.
// fune: replace monitor.check-status in monitor.uptime-bars
// fune: replace monitor.uptime in monitor.uptime-bars
// fune: replace time.unix-to-iso in monitor.uptime-bars
step — your function runs at a numbered point inside the function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show monitor.uptime-bars --steps.
// fune: step monitor.uptime-bars after <n|label>
Tests
A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.
| Case | Arguments | Expected | |
|---|---|---|---|
| three days of up: two full days and today up to now | checks ×1, 1,728,003,600, 3, 259,200 | → | ×3 |
| an hour down shows an outage bar at 95.83%, a slow hour a degraded bar at 100% | checks ×5, 1,728,007,200, 3, 86,400 | → | ×3 |
| days with no checks are no-data bars, not operational ones | , 1,728,000,100, 2, 60 | → | ×2 |
| at exactly midnight today's bar is empty and no-data | checks ×1, 1,728,000,000, 2, 86,400 | → | ×2 |
| one day: a check from before midnight carries into today | checks ×2, 1,728,000,120, 1, 300 | → | ×1 |
| unknown time alone does not make a bar degraded | checks ×1, 1,728,001,000, 1, 600 | → | ×1 |
| before 1970 the day is floored, so today starts at the earlier midnight | checks ×1, -1, 2, 200,000 | → | ×2 |
| down outranks degraded on the same day; uptime floors 2/3 to 6666 | checks ×3, 1,728,000,300, 1, 100 | → | ×1 |
| checks after now are ignored | checks ×2, 1,728,000,100, 1, 1,000 | → | ×1 |
| a down check at 23:59:59 marks only that day | checks ×2, 1,728,000,010, 2, 60 | → | ×2 |
Show the other 7 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| zero days is an error | , 1,728,000,000, 0, 60 | → | error: days must be 1 to 366, received 0 |
| more than 366 days is an error | , 1,728,000,000, 367, 60 | → | error: days must be 1 to 366, received 367 |
| a fractional day count is an error | , 1,728,000,000, 1.5, 60 | → | error: days must be 1 to 366, received 1.5 |
| a fractional now is an error | , 0.5, 1, 60 | → | error: now must be whole seconds |
| a maxGapSeconds of zero is an error | , 1,728,000,000, 1, 0 | → | error: maxGapSeconds must be a whole number of at least 1, received 0 |
| misordered checks are an error | checks ×2, 1,728,000,010, 1, 60 | → | error: checks must be in strictly ascending time order: 1727999995 follows 1728000000 |
| an unknown status is an error | checks ×1, 1,728,000,010, 1, 60 | → | error: unknown check status: offline |
More from the author
## Days
- Days are UTC calendar days, `date` is `YYYY-MM-DD`. Status pages that show local days shift `now` first. - The last bar is today, and it ends at `now`, not midnight, so the part of today that has not happened yet is not reported as unknown. At exactly midnight today's bar is empty (`no-data`). - Midnight is found by flooring, so a `now` before 1970 still lands on the right day (`-1` is 1969-12-31). - Checks after `now` are ignored. - `days` is 1 to 366; 90 is the usual status page.
## State
In order: `no-data` when nothing that day is known; `outage` if any second was down; `degraded` if any second was degraded; otherwise `operational`. Unknown time alone does not change the colour of a day that has some known time: a monitor gap is not an outage. The worst moment decides, the way status pages colour their bars; the uptime figure says how bad the day was.
## Errors
- `now must be whole seconds` - `days must be 1 to 366, received X` - Everything `monitor.uptime` refuses: `maxGapSeconds must be a whole number of at least 1`, `checks must be in strictly ascending time order`, `unknown check status: X`.
Files
| Path | Bytes |
|---|---|
| README.md | 1,701 |
| impl/python.py | 1,761 |
| impl/rust.rs | 2,745 |
| impl/typescript.ts | 1,628 |
| vectors.json | 5,686 |