fix(api): weight self-hosted analytics avg response time by request count#191
Merged
Hydralerne merged 1 commit intoJul 24, 2026
Merged
Conversation
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
server_analytics.response_timeis 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'savgResponseTimeMsis wrong whenever minutes carry unequal traffic.The OpenShip Cloud path already does this correctly:
summariseCloudBuckets/buildCloudHourlyPeriodscomputeresponse_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:
(1·1000 + 99·100)/100)(1000 + 100)/2)A single slow request in an otherwise-quiet minute drags the whole period's reported latency up ~5×.
Fix
summariseBucketsandbuildHourlyPeriodsnow 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 returns0instead ofNaN(again matchingsummariseCloudBuckets). 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:summariseBucketsandbuildHourlyPeriodsreport the request-weighted 109 ms for the table above.0, notNaN.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.npx prettier --check; the file has two pre-existing unrelated drifts onmain(an import block and aqueryBucketscall) that I deliberately left untouched to keep the diff surgical.