Repository navigation
feat(usage-statistics): define schema v1 and collect its metrics - #9038
maia-andre wants to merge 5 commits into
Conversation
Defines the 22 metrics of schema v1 in the format the usage statistics server registers, under appinfo/usage-statistics/schema-v1.json. The server accepts only the metric fields it knows, so the source of truth and the historical reconstruction rule of each metric are documented in the README next to it. Snapshot metrics are optional because historical reports cannot know past environment values. The monthly counters that can be rebuilt for every month are required. Part of LibreSign#8932 Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: André Maia <andrefnkmm@gmail.com>
Adds the report model, the schema reader and the report builder. The builder turns collector answers into a monthly report (UTC, start-inclusive and end-exclusive) for the current or a historical period, without side effects, so the same report can later be previewed, stored and sent. A collector answers each of its metrics with a value, including a known zero, or states that the value is unavailable for the period. The whole report fails when a schema metric has no collector or more than one, when an answer is missing or invalid, when a required metric is unavailable, or when a current snapshot is claimed for a past period. The schema reader applies the same checks as the usage statistics server, so a schema it would refuse fails here first. UsageActivityReader counts the stored events by their own timestamps. Signing activity is counted on top-level files only, because an envelope and each of its files carry a request for the same signer. Identification documents are left out, and observers are counted apart from signers. Part of LibreSign#8932 Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: André Maia <andrefnkmm@gmail.com>
Registers one collector for each group of schema v1 metrics. A test checks that every schema metric has exactly one collector. - Environment and LibreSign configuration snapshots are reported only for the current period, as coarse values: versions reduced to major.minor (major.minor.patch for Nextcloud), the architecture normalized, unknown settings reported as "other". They are read from stored settings without building the certificate engine, so a report never creates the PKI directory or contacts CFSSL. - Document and signing counters are reported for every period. Rejections, observers and envelopes arrived with the columns that record them, so earlier months are a known zero. - Certificates are recorded only since LibreSign 12.1/12.2. Their counters are reported for every month on a fresh install, and on an upgraded instance only for months that start after the first recorded certificate. Part of LibreSign#8932 Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: André Maia <andrefnkmm@gmail.com>
…report period DateTimeImmutable is immutable by definition, and discarding the result of modify() triggers a warning on PHP 8.5, which fails the unit suite there. Part of LibreSign#8932 Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: André Maia <andrefnkmm@gmail.com>
| */ | ||
| class EnvironmentCollector implements IMetricCollector { | ||
| private const ACTIVE_USERS_DAYS = 30; | ||
| private const LAST_LOGIN_BATCH_SIZE = 500; |
There was a problem hiding this comment.
Nextcloud 36 caps IUserManager::getLastLoggedInUsers() at 100 results per call, even when a larger limit is requested. With this batch size set to 500, the first call returns at most 100 users and the loop exits because count($userIds) === 500 is false. As a result, nextcloud.users_active_30d is capped at 100 on larger installations.
Please paginate using Nextcloud's actual maximum batch size and add a regression test with more than 100 active users.
| private const LAST_LOGIN_BATCH_SIZE = 500; | |
| private const LAST_LOGIN_BATCH_SIZE = 100; |
There was a problem hiding this comment.
I think part of this README would fit better in LibreSign/documentation, published at docs.libresign.coop.
It makes sense to keep the schema-specific information here, such as the source of truth, historical availability, reconstruction rules, and the meaning of known zero and unavailable.
The broader explanation about what LibreSign collects and how these statistics should be interpreted would be easier to maintain in the official documentation.
There was a problem hiding this comment.
This PR is quite large, which is partly expected because LibreSign needs application-specific collectors for each metric.
However, I am not sure all of the new infrastructure belongs here. We already have the generic usage_statistics_client, which provides concepts such as Report, Metric, ReportingPeriod, and Protocol v1 validation. Its integration into LibreSign is tracked in #8933.
The LibreSign-specific part should clearly stay here: how each metric is collected, its historical availability, and the rules needed to interpret LibreSign data. But I would like to avoid creating parallel report/metric/protocol abstractions that #8933 will later need to replace or adapt to the client library.
Could you clarify which of these classes are intentionally temporary until #8933, and which ones are expected to remain after the generic client is integrated? It may be worth reducing this PR to the LibreSign-specific collection layer where possible.
Nextcloud returns at most 100 users per getLastLoggedInUsers() call, so asking for 500 ended the loop after the first call and capped nextcloud.users_active_30d at 100. Request batches of 100, advance by what each call returned and stop on an empty page, so a lower server cap cannot end the count early either. Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: André Maia <andrefnkmm@gmail.com>
|
Thanks for the review, @vitormattos. Active users. Fixed in 371ff74. I confirmed the cap in Which classes stay after #8933. I checked against Stays in LibreSign:
Overlaps with the client:
Since nothing is sent before #8933, I'd remove the overlap now instead of marking it temporary:
That leaves this PR with the schema, the collectors and the LibreSign availability rules. Does that match what you had in mind? README. I'll keep the schema-specific parts here (sources of truth, history, rebuild rules, known zero vs. unavailable). The broader part, what LibreSign collects and how to read it, fits the admin manual. Do you want that documentation PR now, or together with the opt-in from #8934, so it doesn't describe a feature admins can't see yet? |
Resolves: #8932
📝 Summary
This adds LibreSign usage statistics schema v1 and the backend that builds a monthly report from it. Opt-in, storage and submission stay in #8933/#8934. This PR has no dependency on
usage_statistics_client.Composition (2384 added lines, 3 commits; review in commit order):
feat(usage-statistics): add the LibreSign usage statistics schema v1feat(usage-statistics): build usage reports that fail safelyfeat(usage-statistics): collect the schema v1 metricsStart with
appinfo/usage-statistics/README.md, thenUsageReportBuilder.Schema
appinfo/usage-statistics/schema-v1.jsondefines the 22 metrics of the issue, in the exact format the server registers.appinfo/usage-statistics/README.md, next to the JSON.required: false, because historical reports cannot know past environment values.required: true.required: false(see below).UsageStatisticsSchemaruns the server's validation rules plus the fixed v1 rules from the issue, so a schema the server would refuse fails CI here first.Report builder
UsageReportBuilder::build(ReportPeriod, ReportMode)builds the report of one UTC month (start inclusive, end exclusive). The mode isCURRENTorHISTORICAL.firstActivityPeriod()gives the month where the historical series starts (Add historical backfill and monthly usage statistics reporting #8935).Every collector answers each of its metrics with either a value (
0is a known zero) orMetricValue::unavailable(). The whole report fails (UsageStatisticsException) instead of leaving a metric out when:Counting rules (agreed in #8932)
files_created. An envelope counts once inenvelopes_created, including one stored beforenode_typeexisted: a top-level file that has children.participants.observers_added, not inrequests_created.leafrows.Historical availability
0.libresign_crlexists since 12.1 for OpenSSL and since 12.2/13 for CFSSL. The certificate counters are available for every month when the first recorded certificate is not later than the first LibreSign file (a fresh install, where the root CA comes first). Otherwise they are available only for months that start after the first recorded certificate.crl:cleanup, deleting a request or the signed file) make every rebuilt month a lower bound. This is documented in the README.Snapshots
x86_64,aarch64orother, and unknown settings are reported asother.php_uname()is called only with'm', so no host name or kernel string can leak. No path, CA identifier, serial or URL becomes a metric value.Notes for review
certificate.certificates_revokedcurrently counts everyleafrevocation, including the automaticSUPERSEDEDrevocation of click-to-sign certificates. I asked in Define LibreSign usage statistics schema v1 and metric collectors #8932 whether those should be excluded; that would be a one-line change inUsageActivityReader, plus its test.database.version: OCP has no getter for it, soRuntimeEnvironmentasks the server (VERSION(),SHOW server_version,sqlite_version()). Oracle is reported as unavailable. A version string that cannot be parsed fails the report instead of being guessed.nextcloud.users_active_30dwalksgetLastLoggedInUsers()(most recent first) and stops at the first user outside the window. It is cheap in a background job, but the Add installation opt-in, report persistence and report administration APIs #8934 preview will pay this cost during the request.OCP\ServerVersionisreadonlyand cannot be doubled in tests, so it is read throughRuntimeEnvironment.🧪 How to test
Build both reports of a month in a Nextcloud shell:
In the devcontainer (MariaDB, OpenSSL):
nextcloud.version = 36.0.0,database.version = 10.6,certificate.root_ca_configured = true, with the counters at0;Validation evidence
node_type, an identification document, observers, a rejection in the next month,root/intermediate/leafcertificates, building through the DI container, and no setting changed by a report.composer cs:checkis clean.psalmadds no issue; locally I only see the same environmentMissingDependency(psr/clock) reported for existing files.⚙️ API / Back-end changes
OCA\Libresign\Service\UsageStatisticsservice: schema reader, report builder, collectors. No controller, route, job or migration.appinfo/usage-statistics/README.mdcomposer openapiwhen necessary: not applicable, the API is unchanged✅ Checklist
🤖 AI (if applicable)