[{"data":1,"prerenderedAt":17},["ShallowReactive",2],{"api:\u002Fapi\u002Fconfig:en":3,"api:\u002Fapi\u002Fnotes\u002Fp95-nearest-rank:en":8},{"email":4,"lang":5,"name":6,"role":7},"hello@ewsirdev.example","en","Ewsirdev","Web developer: websites, landing pages and Go services",{"slug":9,"lang":5,"title":10,"date":11,"summary":12,"project":13,"minutes":14,"body_md":15,"body_html":16},"p95-nearest-rank","p95 in an uptime monitor: the nearest-rank method and what it does not hide","2026-10-20","How uptime-monitor computes p50\u002Fp95 latency and uptime, why the nearest-rank method was chosen, and where those numbers can mislead.","uptime-monitor",2,"The monitor shows p50 and p95 latency of successful checks over 24 hours. The mean is a poor fit here: one outlier shifts it, while the slow tail is exactly what users feel.\n\n## Nearest rank\n\nThe p-th percentile is the element of the sorted list at rank `ceil(p\u002F100 * N)`. For 20 measurements, p95 is the 19th in ascending order. There is no interpolation: the result is always a real measurement.\n\n```go\nrank := int(math.Ceil(p \u002F 100 * float64(len(s))))\nreturn s[rank-1]\n```\n\nThe function is pure: the slice is not modified, an empty slice returns 0, and the p\u003C=0 and p>=100 bounds are handled separately. That makes it easy to cover with tests.\n\n## Uptime\n\nUptime is the share of successful checks, `ok \u002F total * 100`. An important caveat from the README: it is computed from the **number of checks**, not from durations, so figures for checks with different intervals are not equally precise.\n\n## Where the numbers can mislead\n\n- With few measurements, p95 is almost the maximum.\n- Aggregates run as queries over the raw history: instant for dozens of checks, thousands would need pre-aggregation.\n- Checks run from a single machine; this is not distributed monitoring from several regions.\n","\u003Cp>The monitor shows p50 and p95 latency of successful checks over 24 hours. The mean is a poor fit here: one outlier shifts it, while the slow tail is exactly what users feel.\u003C\u002Fp>\n\u003Ch2>Nearest rank\u003C\u002Fh2>\n\u003Cp>The p-th percentile is the element of the sorted list at rank \u003Ccode>ceil(p\u002F100 * N)\u003C\u002Fcode>. For 20 measurements, p95 is the 19th in ascending order. There is no interpolation: the result is always a real measurement.\u003C\u002Fp>\n\u003Cpre tabindex=\"0\">\u003Ccode class=\"lang-go\">rank := int(math.Ceil(p \u002F 100 * float64(len(s))))\nreturn s[rank-1]\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>The function is pure: the slice is not modified, an empty slice returns 0, and the p&lt;=0 and p&gt;=100 bounds are handled separately. That makes it easy to cover with tests.\u003C\u002Fp>\n\u003Ch2>Uptime\u003C\u002Fh2>\n\u003Cp>Uptime is the share of successful checks, \u003Ccode>ok \u002F total * 100\u003C\u002Fcode>. An important caveat from the README: it is computed from the \u003Cstrong>number of checks\u003C\u002Fstrong>, not from durations, so figures for checks with different intervals are not equally precise.\u003C\u002Fp>\n\u003Ch2>Where the numbers can mislead\u003C\u002Fh2>\n\u003Cul>\n\u003Cli>With few measurements, p95 is almost the maximum.\u003C\u002Fli>\n\u003Cli>Aggregates run as queries over the raw history: instant for dozens of checks, thousands would need pre-aggregation.\u003C\u002Fli>\n\u003Cli>Checks run from a single machine; this is not distributed monitoring from several regions.\u003C\u002Fli>\n\u003C\u002Ful>\n",1792697892653]