Skip to content

Add the extended tier's planned metrics: route by client, client patterns, daily histograms, family by age - #316

Merged
SiteRelEnby merged 2 commits into
mainfrom
feat/metrics-extended-metrics
Sep 24, 2026
Merged

SiteRelEnby merged 2 commits into
mainfrom
feat/metrics-extended-metrics

Conversation

@SiteRelEnby

Copy link
Copy Markdown
Contributor

PR 5 of the client-usage metrics plan. Everything here is sheaf_ext_*, exists only under METRICS_EXTENDED=true, and keeps per-account state under the day-salted token for at most 48 hours.

The five metrics.

  • sheaf_ext_http_requests_by_client_total{method, route, status_class, client_family}: the RED counter multiplied by family, emitted by the same middleware, family derived from the header and the credential the way the auth path derives it. The cross-client drift question in one query.
  • sheaf_ext_accounts_by_client_pattern{pattern}: distinct accounts today per exact combination of families used (web, web+android, api+web+ios, fixed order). One Redis SET of day-salted tokens per family; each combination is |INTER(combo) minus UNION(rest)| via SINTERSTORE / SDIFFSTORE / SCARD on scratch keys, so no token ever leaves Redis. Only families actually seen today are considered, so at most 2^k - 1 combinations for k families, 63 in the worst case. The default tier's pairwise overlaps give intersections; this gives the partition, which is "do people actually use more than one client" asked directly.
  • sheaf_ext_active_accounts_daily{client_family, account_age}: the age gauge split by family, one day sketch per pair. "Do new signups start on mobile."
  • sheaf_ext_account_requests_daily{client_family} and sheaf_ext_public_requests_per_profile_daily{subject_type}: histograms of requests per account per day and served public-profile requests per profile per day. Each keeps a per-token counter hash for the day; the profile one is keyed by the day-salted system token and written at the resolver only after a grant resolves, so a miss never creates a key. The hourly job now folds yesterday's hashes into the histograms, one observation per token, then deletes them. Observe-then-delete on purpose: a crash between the two doubles a day rather than losing one, which is the right way to be wrong for a distribution.

The line this crosses, said plainly. The two counter hashes are the only per-account read-back in the whole metrics surface. What is read back is a count, for one day, under a token that cannot be joined to the next day's, and the read happens exactly once before the key is deleted. The design doc made this call (section 6.1); this is the PR that implements it.

Plumbing. record_client_version became record_activity and now runs for every family including api (which gets the set, the counter and the age sketch but no version); DAILY_REQUEST_BUCKETS joins buckets.py, one order of magnitude wider than the per-minute family; the sweep job folds before it sweeps.

Tests. Unit: the activity hook's writes and TTLs, the api family, the pattern label's order independence, and the fold (each token once, an unparseable value skipped, the hash deleted). Metrics config: one account used from two families lands under android+ios and not under either alone, in both family-by-age gauges, and on the route counter, with the histogram families present and nothing account-shaped in the scrape.

…erns, daily histograms, family by age

PR 5 of the client-usage metrics plan, on the scaffolding PR 4 shipped.
Everything here is sheaf_ext_*, exists only under METRICS_EXTENDED, and
keeps per-account state under the day-salted token for at most 48 hours.

sheaf_ext_http_requests_by_client_total{method, route, status_class,
client_family}: the default-tier RED counter multiplied by family, emitted
by the same middleware, with the family derived from the header and the
credential the way the auth path derives it. "The Android app polls
/fronts/current four times as often as the web app" is one query here.

sheaf_ext_accounts_by_client_pattern{pattern}: distinct accounts today per
EXACT combination of families used (web, web+android, api+web+ios). One
Redis SET of day-salted tokens per family; the count per combination is
|INTER(combo) minus UNION(rest)| computed with SINTERSTORE / SDIFFSTORE /
SCARD on scratch keys, so no token ever leaves Redis. Only families seen
today are considered, so it is at most 2^k - 1 set operations for k
families.

sheaf_ext_active_accounts_daily{client_family, account_age}: the age
gauge split by family, one day sketch per pair.

sheaf_ext_account_requests_daily{client_family} and
sheaf_ext_public_requests_per_profile_daily{subject_type}: histograms of
requests per account per day and served public requests per profile per
day. Each keeps a per-token counter hash for the day (the profile one
under the day-salted SYSTEM token, written at the resolver only after a
grant resolves, so a miss never creates a key). The hourly job now folds
YESTERDAY's hashes into the histograms, one observation per token, and
deletes them; the fold observes before it deletes, so a crash between the
two doubles a day rather than losing one. These counters are the only
per-account read-back in the whole metrics surface: a count, for one day,
under a token that means nothing the next day.

record_client_version became record_activity, called for every family
including api (which gets the set, the counter and the age sketch but no
version). New DAILY_REQUEST_BUCKETS in buckets.py, one order of magnitude
wider than the per-minute family.

Unit tests cover the activity hook's writes and TTLs, the api family, the
pattern label, and the fold (each token once, unparseable values skipped,
hash deleted). The metrics config checks one account on two families lands
under android+ios, in both family-by-age gauges, and on the route counter.
@SiteRelEnby
SiteRelEnby merged commit 03505e4 into main Sep 24, 2026
24 checks passed
@SiteRelEnby
SiteRelEnby deleted the feat/metrics-extended-metrics branch September 24, 2026 01:20
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.

1 participant