Repository navigation
Admin → Traffic: daily request counts per repository and client, with country and ASN from MaxMind - #711
Conversation
…in the bucket Every instance counts the requests it answers in memory (by minute, surface, repository, method and status, with a latency histogram), keeps the top client networks of each minute in bounded memory, and logs each request. Once a minute it writes Parquet files of its own under traffic/v1/ in Forge's bucket; the admin API lists and reads every instance's files from there, so Admin -> Traffic shows all instances without calls between them. - crates/traffic (forge-traffic): the recorder, the request-scoped slot the principal layer and the dispatchers note the client, account and repository in, the Parquet rows (pure-Rust parquet, Snappy), the file layout, flushing, pruning and the queries. - BlobStore: put_bytes, get_bytes and list (ListObjectsV2). - /admin/traffic, /admin/traffic/client and /admin/settings/traffic; the traffic worker flushes every minute and prunes every hour, and the last counts are written on shutdown. - Admin -> Traffic in the web UI: totals, an SVG chart or table over time, top clients with their request logs, repositories, surfaces, statuses, and the settings. Fixes #709 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012e6DJbpgZmQnhz5Jjjhug6
…s with MaxMind
The statistics only need a count per day for each repository and client
address, to compare where the requests come from, so the minute and hour
files, the latency histograms, the top-client lists and the request log
are replaced by one Parquet file per instance and day: a row per
repository and client network with its requests and bytes.
Clients are located with MaxMind databases: their country and autonomous
system go on each row. The databases are kept in the bucket
(traffic/v1/geoip/{country,asn}.mmdb) and loaded by every instance from
there. Forge downloads GeoLite2-Country and GeoLite2-ASN every three days
with a MaxMind account ID and license key set in the web UI (the key
sealed like the other secret settings, the download URL configurable as
traffic.maxmind_url), or an operator puts the files there.
- forge-traffic: daily recorder and flusher, geo lookups (maxminddb),
queries filtered by instance, repository, client, country and AS, and
a small MaxMind database writer for tests (feature `testing`).
- /admin/traffic takes the filters; /admin/traffic/geoip shows the
databases and sets, downloads or removes the MaxMind account.
- Admin -> Traffic: requests by day, repositories, countries, networks
and top clients, each narrowing the view; the settings get a
geolocation panel.
Fixes #709
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012e6DJbpgZmQnhz5Jjjhug6
… the stack The raw group test overflowed its 2 MiB test thread in CI: a debug build of a request that fetches a file through a group's proxy and stores it needed about 1.95 MiB of stack on main already, and the traffic statistics' per-request layers took it over. Every future up that request held the AWS SDK's upload future inline (put_if_absent, the CAS commit, the file store, the proxy fill, the group walk, the plugin dispatch), so each of their poll frames carried a copy. put_if_absent and the conditional PUT now return boxed futures: the SDK's future lives in one heap allocation, and the request needs about 1.55 MiB, measured with RUST_MIN_STACK on the test. Nothing else changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012e6DJbpgZmQnhz5Jjjhug6
|
CI: The test's thread has 2 MiB of stack. In a debug build, the request that fetches a file through the group's proxy and stores it already needed about 1.95 MiB on gdb at the overflow showed where the stack goes. The AWS SDK's upload future was held inline by every future up that request: 96b85cc makes The branch also has Generated by Claude Code |
Fixes #709
Every instance counts the requests it answers by day, repository and client network. Once a minute it writes the counts to Forge's bucket as a Parquet file of its own. Admin → Traffic reads every instance's files back, so it shows all servers (or one) with no calls between them and no Redis. Clients are located with MaxMind databases (country and autonomous system), so you can compare where each repository's requests come from.
What is recorded
traffic/v1/days/<date>/<instance>.parquet. Each row is a repository and client network, with country, ASN, AS organisation, requests and bytes.X-Forwarded-For). IPv6 is counted by its /64.trackmiddleware opens a request-scoped slot (forge_traffic::Slot), and the principal layer and the dispatchers fill it in. The request path does no I/O.other.Geolocation (MaxMind)
traffic/v1/geoip/country.mmdbandasn.mmdbin the bucket. Every instance loads them, again when their ETag changes (checked every ten minutes). Themaxminddbcrate reads them: Country or City editions for countries, ASN or ISP editions for autonomous systems. A file of another edition is refused.forge_setting(traffic_maxmind_license_key, added toSECRET_SETTINGS, so DEK rotation reseals it)..tar.gz), checks them, and stores them in the bucket.traffic.maxmind_urlcan point at a mirror..mmdbfiles in the bucket themselves (air-gapped installs, GeoIP2 editions).API and UI
GET /admin/traffic?range=today|7d|30d|90d|365d&instance=&repository=&network=&country=&asn=, orfromandto. It answers the days, totals, top repositories, countries, ASes and clients.repository=-andcountry=-select none or unknown.GET/PUT /admin/settings/traffic: enabled, client addresses, retention days (default 90).GET/PUT/DELETE /admin/traffic/geoipandPOST /admin/traffic/geoip/update. The license key is never answered back./manage/traffic):/manage/traffic/settings): recording, retention, and geolocation (account, download now, turn off after confirming).Docs
docs/deployment.md→ Traffic statistics and Countries and networks (MaxMind).forge.toml.example→[traffic].docs/changes/2.2.0/709.feature.md.Tests
cargo test -p forge-traffic(14):testingfeature); wrong editions refused; refresh from the bucket;cargo test -p forge-server --test traffic(3, Postgres, versitygw and a wiremock MaxMind):.tar.gzdatabases, clients are located, and turning off removes everything.forge-db --test encryption(13), and the server suitesadmin,dispatch,npm,raw,registry,telemetryandotlp_exportall pass.pnpm tsc,pnpm test(529, including 6 for the traffic screens) andpnpm build.cargo fmt --check,cargo clippy --workspace --all-targets -D warnings,cargo xtask changelog --checkandcargo xtask docs --check.🤖 Generated with Claude Code
https://claude.ai/code/session_012e6DJbpgZmQnhz5Jjjhug6