For the past 3 months, collector-rs has been making snowflake-stats tarballs in an incorrect format. The recently committed fix for this problem implies that the incorrect format was the result of the Snowflake broker changing the format of its metrics. But this is not the case: Snowflake broker metrics have always (since 2019) contained multiple days of metrics. This was by design, and Java Collector was always able to parse that format. Since the recent fix, collector-rs treats snowflake-stats as a special case ("skip_seed_for_archive"), but Java Collector didn't treat snowflake-stats as exceptional—it parsed snowflake-stats in the same way as other types that have multiple days of data in one file, like bridgedb-metrics and bridgestrap-stats, using a method called parseOneOrMoreDescriptors. In summary, the problem was with collector-rs not parsing the metrics files in the same way as Java Collector did, not because of any change in the output of the Snowflake broker. snowflake-stats is not really a special case, but the collector-rs code now treats it as a special case, which makes me wonder if it could be handled in a more uniform way. This is the issue for collector-rs serving an incorrect format for snowflake-stats tarballs: "Snowflake stats format is broken for April" https://gitlab.torproject.org/tpo/network-health/metrics/collector-rs/-/work... This is the merge request that closes #48 along with many other things: "Rework archive/publish/sync pipeline; add bridge import; vote & dirauth fixes" https://gitlab.torproject.org/tpo/network-health/metrics/collector-rs/-/merg... I understand that the fix has two parts: 1. Changing the collector-rs to ignore all but the last "snowflake-stats-end": https://gitlab.torproject.org/tpo/network-health/metrics/collector-rs/-/merg... 2. Running a one-time fix_snowflake script that rewrites past downloaded files so that they contain only the last "snowflake-stats-end": https://gitlab.torproject.org/juga/collector-fixes/-/commit/7b0aa3170313cb4b... I'll quote from fix_snowflake.rs, because I think it reveals the misunderstanding on the collector-rs side: //! `fix-snowflake` - trim bloated snowflake-stats files down to a single //! reporting period, both in `out/recent/snowflakes/` and in the monthly //! `out/archive/snowflakes/snowflake-stats-*.tar.xz` tarballs. //! //! ## Background //! //! The snowflake broker's `/metrics` endpoint is meant to return a single //! `snowflake-stats-end <date> <time> (86400 s)` reporting block per fetch. //! Starting around collector-rs's deployment in 2026, the broker began //! returning the **entire cumulative history** since 2024-09-19 on every //! fetch instead: every previous day's block concatenated, with the new //! day's block appended at the end. Every file collector-rs wrote during //! that window inherited that bloat verbatim - both the daily files in //! `out/recent/snowflakes/` and the entries written into the monthly //! archive tarballs. The format emitted by the broker /metrics endpoint is not "bloat" and it is not "incorrect". Nor did the format change at any point. Here's a Wayback capture that shows it worked like that in 2020: https://web.archive.org/web/20200619131042/https://snowflake-broker.torproje... The format was intentionally designed this way. Here is part of a conversation with Karsten when the metrics feature was originally designed: https://gitlab.torproject.org/tpo/network-health/metrics/collector/-/work_it... To answer your earlier question above: having just the last 24 hours of data might be problematic if the metrics host goes down for, say, a weekend. Ideally, there would be at least a week of statistics available. Or maybe just accumulate new statistics forever, given the tiny amount of statistics per day. This is the way it is intended to work and the way it worked before. collector-rs should not treat the format as anomalous or unexpected. The old metrics/library had a test case with 2 descriptors in it, since 2019: https://gitlab.torproject.org/tpo/network-health/metrics/library/-/blob/2b6b... This is how the old metrics/library handled snowflake-stats descriptors: https://gitlab.torproject.org/tpo/network-health/metrics/library/-/blob/2b6b... } else if (firstLines.startsWith("@type snowflake-stats 1.") || firstLines.startsWith(Key.SNOWFLAKE_STATS_END.keyword + SP) || firstLines.contains(NL + Key.SNOWFLAKE_STATS_END.keyword + SP)) { return this.parseOneOrMoreDescriptors(rawDescriptorBytes, sourceFile, Key.SNOWFLAKE_STATS_END, SnowflakeStatsImpl.class); } It called parseOneOrMoreDescriptors with "snowflake-stats-end" as a delimiter. Then it iterated over parsed descriptor and handled each one individually: https://gitlab.torproject.org/tpo/network-health/metrics/collector/-/blob/0a... DescriptorParser descriptorParser = DescriptorSourceFactory.createDescriptorParser(); SortedSet<LocalDateTime> snowflakeStatsEnds = new TreeSet<>(); this.outputPathName = config.getPath(Key.OutputPath).toString(); for (Descriptor descriptor : descriptorParser.parseDescriptors( downloadedBytes, null, null)) { ... The important thing to note is that Snowflake is not at all a special case; the detectTypeAndParseDescriptors method calls parseOneOrMoreDescriptors for many different descriptor types (with different delimiters): https://gitlab.torproject.org/tpo/network-health/metrics/library/-/blob/2b6b...
Hi David, On 6/30/26 6:06 PM, David Fifield via network-health wrote:
For the past 3 months, collector-rs has been making snowflake-stats tarballs in an incorrect format. The recently committed fix for this problem implies that the incorrect format was the result of the Snowflake broker changing the format of its metrics. But this is not the case: Snowflake broker metrics have always (since 2019) contained multiple days of metrics. This was by design, and Java Collector was always able to parse that format. Since the recent fix, collector-rs treats snowflake-stats as a special case ("skip_seed_for_archive"), but Java Collector didn't treat snowflake-stats as exceptional—it parsed snowflake-stats in the same way as other types that have multiple days of data in one file, like bridgedb-metrics and bridgestrap-stats, using a method called parseOneOrMoreDescriptors.
[...] thanks for reporting this and apologies for the misunderstanding on snowflake-stats format. It was me trying to solve an issue fast in production (after some months with my mind on other projects) to be able to continue re-designing and re-factoring [1] a minimal version [2] that was just storing all the descriptors as they were fetched without parsing (not really mentioned here [3], but we agreed that a MVP could just do this). IMO, it's better to re-design a new software (and ensure it has all the needed functionality, tests, etc.) before getting it in production. In this case, it was put into production early, because Java CollecTor was already having other issues and we ended up with a production code for collector-rs we need to maintain, while still working on having a better collector-rs. I'm currently working on #35 and changing the base structs (File and Filegen) into different structs for different descriptors/documents and having models for both single and "multi" descriptor/documents content. This's going to be a major (in the internal API) change and "skip_seed_for_archive" will disappear. Meanwhile, i can create a mr to correct the wrong comment on snowflake-stats. Would this temporal fix work for you? I'd ask if at least !26 solves #48, but #70, the issue to deploy this in production, is not solved yet. juga [1] eg. https://gitlab.torproject.org/tpo/network-health/metrics/collector-rs/-/work..., https://gitlab.torproject.org/tpo/network-health/metrics/collector-rs/-/work... [2] https://gitlab.torproject.org/tpo/network-health/metrics/collector-rs/-/comm... [3] https://juga.pages.torproject.net/collector_docs/collector_new/analysis.html...
On Wed, Jul 01, 2026 at 05:58:00AM +0000, juga via anti-censorship-team wrote:
I'm currently working on #35 and changing the base structs (File and Filegen) into different structs for different descriptors/documents and having models for both single and "multi" descriptor/documents content. This's going to be a major (in the internal API) change and "skip_seed_for_archive" will disappear.
Meanwhile, i can create a mr to correct the wrong comment on snowflake-stats. Would this temporal fix work for you?
Thanks for taking a look. If you're working on a larger refactoring, then there's no need.
I'd ask if at least !26 solves #48, but #70, the issue to deploy this in production, is not solved yet.
Thanks for this information. I noticed that the fix was not deployed, but I had not seen that there was a work item for the deployment.
participants (2)
-
David Fifield -
juga