mirror of
https://github.com/projectsend/projectsend.git
synced 2026-09-12 06:48:55 +00:00
6340b71dca
projectsend:status could say what an installation holds and how many accounts it has, but nothing about whether anybody was using it. Adds a `usage` block -- downloads split staff/clients/anonymous, uploads, and five allowlisted action counts -- plus `activity.last_client_login_at`, `health.scheduler` and `health.failed_jobs_latest_at`. The scheduler is the one worth having on its own. `health.queues` catches a dead worker; nothing caught a dead scheduler, and its first symptom is not a stalled feature but an expired file that is still downloadable, because the job that was going to remove it stopped running weeks ago. Nothing about the installation looks wrong while that is true. `failed_jobs_latest_at` exists because the count beside it cannot say whether anything is wrong *now*, and reading it as though it could is a category error rather than a threshold wanting tuning. The table is swept daily, so the count spans a retention window -- one the installation chooses, and one that can be set to keep-forever by somebody who treats a failed job as evidence rather than debris. Two identical installations therefore report different numbers, and on a keep-forever one the count grows until any fixed threshold trips. A timestamp is independent of how long rows are kept: 27 failures whose newest is three weeks old is an installation that has been healthy for three weeks and has not been swept yet. `usage` is a rolling window with no lifetime totals, and that is a correctness decision rather than a presentational one: activity_log is never pruned, so a lifetime count over it gets slower every day of the installation's life while a windowed one stays flat. The window is emitted as `window_days` rather than left for the reader to assume. The actions are an allowlist, not a `group by action`. This document leaves the installation and Action gains cases most weeks, so an open group-by would ship new action names outward with nobody having decided they should go -- and some of them (account.erased, two_factor.reset) are somebody's compliance event, not a business metric. It is also ~30x cheaper: five keyed counts ride (action, created_at) while a group-by starts from created_at and reads rows. The scheduler's failure message and the queue exception text are omitted for the same reason; they are the fields here that can carry a path or a stack trace, and a count with a timestamp says "go and look", which is all a watcher is owed. The two indexes ship as a pair and the migration explains at length why. Measured at 2.1M rows: adding (action, created_at) alone fixes the windowed counts and takes last_staff_login_at -- already running hourly on every tenant -- from 0.63s to 7.7s, because the planner switches to it, still needs actor_type, and does a scattered primary-key lookup per row. With both, that query is answered from the index without reading a row at all (0.0004s) and the whole new usage block costs ~70ms. Also documents the keys as a contract, the way `capabilities` already is. This one fails worse: a renamed capability key breaks a comparison somebody is watching, a renamed usage key produces a chart that is silently empty, and nobody gets paged for a flat line.