Skip to content

fix(api): weight self-hosted analytics avg response time by request count#191

Merged
Hydralerne merged 1 commit into
oblien:mainfrom
shuvamk:fix/analytics-weighted-avg-response-time
Jul 24, 2026
Merged

fix(api): weight self-hosted analytics avg response time by request count#191
Hydralerne merged 1 commit into
oblien:mainfrom
shuvamk:fix/analytics-weighted-avg-response-time

Conversation

@shuvamk

@shuvamk shuvamk commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Problem

server_analytics.response_time is documented in the schema as a per-minute average response time (seconds). The self-hosted analytics reducers average those per-minute averages unweighted — dividing by the number of minute-buckets rather than by total requests. So a period's avgResponseTimeMs is wrong whenever minutes carry unequal traffic.

The OpenShip Cloud path already does this correctly: summariseCloudBuckets / buildCloudHourlyPeriods compute response_time_sum / requests (request-weighted). Only the self-hosted path (summariseBuckets / buildHourlyPeriods) was unweighted — so self-hosters see a distorted number that the cloud dashboard does not.

Concrete failing input

Two minutes inside one period:

minute requests per-minute avg
0 1 1000 ms
1 99 100 ms
avgResponseTimeMs
Correct (request-weighted: (1·1000 + 99·100)/100) 109 ms
Reported before this fix ((1000 + 100)/2) 550 ms

A single slow request in an otherwise-quiet minute drags the whole period's reported latency up ~5×.

Fix

summariseBuckets and buildHourlyPeriods now weight each minute by its request count and divide by total requests — the same computation the cloud path already uses. Both guard a zero-request period so it returns 0 instead of NaN (again matching summariseCloudBuckets). No wire/DB shape changes; the two pure functions are exported so they can be unit-tested (existing precedent in this repo).

Tests

Added apps/api/test/modules/analytics/response-time-weighting.test.ts:

  • summariseBuckets and buildHourlyPeriods report the request-weighted 109 ms for the table above.
  • A no-traffic period reports 0, not NaN.

Verified the test fails against unpatched source (reports 550 ms) and passes with the fix.

Verification

  • bun run test → all 6 turbo tasks successful (748 passed in @repo/api, incl. the 3 new tests).
  • bun run --cwd apps/api lint → clean.
  • Touched files pass npx prettier --check; the file has two pre-existing unrelated drifts on main (an import block and a queryBuckets call) that I deliberately left untouched to keep the diff surgical.

…ount

`server_analytics.response_time` is a per-minute AVERAGE (documented in the
schema). `summariseBuckets` and `buildHourlyPeriods` averaged those per-minute
averages unweighted (÷ number of minute-buckets), so a period's
`avgResponseTimeMs` is skewed whenever minutes carry unequal traffic. The
OpenShip Cloud path already computes the correct request-weighted average
(`response_time_sum / requests`); the self-hosted path did not.

Example — two minutes in one period: 1 request at 1000ms, then 99 at 100ms.
Correct request-weighted average is (1·1000 + 99·100) / 100 = 109ms; the
unweighted mean reported 550ms.

Both self-hosted reducers now weight each minute by its request count and
divide by total requests (guarding zero-request periods against NaN, matching
the cloud path). Exported both pure functions and added
response-time-weighting.test.ts (verified failing before the fix, passing
after). Full `bun run test` green (6/6 turbo tasks); `bun run --cwd apps/api
lint` clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Hydralerne
Hydralerne merged commit f23e13b into oblien:main Jul 24, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants