Skip to content

feat(stats): add counter-backed usage and trending stats#746

Open
camathieu wants to merge 6 commits into
masterfrom
improved-stats
Open

feat(stats): add counter-backed usage and trending stats#746
camathieu wants to merge 6 commits into
masterfrom
improved-stats

Conversation

@camathieu

Copy link
Copy Markdown
Member

Description

What

Adds backend Stats V1 with DB-backed usage counters, download counters, daily rollups, and admin trending endpoints.

Why

The previous stats path relied on aggregate queries and did not support current vs lifetime usage, trending downloads, token usage stats, or multi-instance-safe counter updates.

Changes

  • Add user_usage_stats, server_usage_stats, token_usage_stats, and download_stats_daily.
  • Track current and lifetime uploads/files/size, including anonymous usage and startedAt.
  • Add upload/file download counters and lastDownloadedAt.
  • Count file downloads with range-aware policy: full GETs and byte-0 range GETs only.
  • Record archive downloads after archive close, counting the upload once and each included file once.
  • Add admin trending endpoints for uploads and files.
  • Extend /me/stats and /stats response fields.
  • Export/import daily download rollups and regenerate 0011-stats migration dumps.
  • Add metadata, handler, concurrency, and e2e coverage.

Testing

  • git diff --check
  • make lint
  • make client server
  • NO_RACE=1 make test
  • make test
  • make docs
  • make test-backends passed on rerun
  • make vuln completed, with existing Go 1.26.1 stdlib and npm audit findings reported

@camathieu
camathieu force-pushed the improved-stats branch 12 times, most recently from 23342fe to d65e46d Compare May 1, 2026 18:22
ca.mathieu added 5 commits July 8, 2026 12:08
…ata layer

One usage_stats row per scope — per-user (id,''), per-token (owner,token),
anonymous ('__anonymous__',''), and a '__deleted__' tombstone that user
deletion folds lifetime counters into so totals never regress. ~50 counters
(current/lifetime uploads, files, sizes, feature/TTL/file-size distributions,
downloads, transferred bytes) are driven by a single registry that generates
the increment updates, the sum-on-read server totals, and the tombstone fold;
a reflection test pins registry <-> schema drift. Daily download/upload
rollups (31-day retention, UTC buckets, user-attributed) power activity
windows, charts, and trending. Every counter mutation runs inside the same
transaction as the metadata change under one canonical lock order (upload row
-> files -> rollups -> user/anon -> token usage), making deadlocks impossible
rather than retried; SQLite opens transactions IMMEDIATE with a busy timeout.
Migration 0011 backfills all counters from existing metadata, with generated
dump fixtures for sqlite3/postgres/mysql/mariadb. The tombstone and rollups
ride exports; everything else rebuilds from metadata on import (fresh-DB
contract). Best-effort single-shot recorders account served/received bytes —
partial transfers included — without ever failing or retrying a request.
…oints

Downloads count through a counting response writer after streaming, so a
client that disconnects mid-stream records exactly its egress; multi-range
requests record the requested range bytes, not multipart framing. Uploads
count wire bytes through a counting reader, including partial and failed
transfers. Archives follow the same partial-bytes policy as single files,
including on mid-archive errors. Recording never blocks, fails, or retries
a user request. New endpoints: GET /stats/activity/daily and
/stats/trending/{uploads,files} (admin), plus self-scoped /me/stats
equivalents including /me/stats/trending/uploads; trending uploads sort by
downloads or downloadedBytes (strict 400 on unknown sort values), rank only
currently-available uploads, and populate both metrics in a single query.
Non-owners never see download counts, bytes, or timestamps (Sanitize).
Daily rollups are pruned with the existing cleaning cycle. The Go client
suite gains stats end-to-end coverage.
fakedb now attributes uploads to the demo admin account, spreads each
upload's downloads across the 30-day window with a deterministic
FNV-seeded day split (sums stay exact), and seeds per-upload and per-scope
transferred bytes consistently with the rollups, so a fresh instance shows
a realistic dashboard: populated activity chart, trending panels, and
per-user figures.
…ding

Admin and Home share the same dashboard components, scoped server-wide or
to the current user: an Activity panel whose 4-metric selector (downloads,
uploads, downloaded data, uploaded data) drives both the Today/7d/30d/
Lifetime tiles and a hand-rolled SVG daily chart; current-vs-lifetime
distribution cards with explanatory captions; and a trending panel with
window chips and a downloads/downloaded-data metric toggle, reused
self-scoped on Home. Upload cards show total size and downloaded data
(owner/admin only), lists sort by downloaded data, and the users tab
displays per-user transferred data. Dates and number grouping follow the
active locale; byte metrics show an em dash rather than a false zero when
the daily series is unavailable; a fresh user gets an encouraging
zero-state instead of a wall of zeros. All strings are translated across
the 12 locales, guarded by a parity test plus an allowlist-based
identical-to-English tripwire.
Covers owner-vs-viewer stat visibility, live sidebar download counts on
preview, the activity metric selector and chart tooltips, trending window
chips and metric toggle, list sorts, localized rendering, and the Home
self-scoped trending panel.
Documents the counter ledger, canonical lock order, sum-on-read totals,
tombstone fold, retention, and egress accounting in server/ARCHITECTURE.md;
the dashboard component contracts in webapp/ARCHITECTURE.md; every new
endpoint, field, sort value, and visibility rule in the API reference; and
the requirement that code comments be self-contained and production-ready
in the contributor guidelines.
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