Skip to content

Conversation

@scottopell
Copy link
Contributor

Summary

  • Convert registry counter capture from cumulative to delta semantics
  • Add first_tick tracking to Accumulator to prevent outputting data for ticks before a key was first observed
  • Add parquet schema versioning metadata (lading.schema_version = "2", lading.counter_semantics = "delta")

Changes

StateMachine

  • Add counter_prev field to track previous counter values
  • First observation of each counter is skipped (used as baseline for delta calculation)
  • Subsequent observations emit delta = current - prev
  • Counter resets handled via saturating_sub (emit 0 if current < prev)

Accumulator

  • Add counter_first_tick and gauge_first_tick HashMaps
  • Skip output in flush() and drain() for ticks before a key's first_tick
  • This prevents outputting default zero values for ticks before a metric was first observed

Parquet Format

  • Add metadata to parquet files:
    • lading.schema_version = "2"
    • lading.counter_semantics = "delta"

Test plan

  • All existing tests updated and passing (79 tests)
  • Manual verification with fine-grained-monitor
  • Verify parquet output contains expected delta values

🤖 Generated with Claude Code

…alues

Convert registry counter capture from cumulative to delta semantics:

- Add `counter_prev` field to StateMachine to track previous counter values
- First observation of each counter is skipped (used as baseline)
- Subsequent observations emit delta = current - prev
- Counter resets handled via saturating_sub (emit 0 if current < prev)

Add first_tick tracking to Accumulator to prevent outputting data for
ticks before a key was first observed:

- Add `counter_first_tick` and `gauge_first_tick` HashMaps
- Skip output in flush() and drain() for ticks before first_tick

Add parquet schema versioning:
- `lading.schema_version = "2"`
- `lading.counter_semantics = "delta"`

This change enables downstream consumers to use counter values directly
without post-processing to calculate deltas.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Comment on lines 188 to 194
.set_key_value_metadata(Some(vec![
KeyValue::new("lading.schema_version".to_string(), "2".to_string()),
KeyValue::new(
"lading.counter_semantics".to_string(),
"delta".to_string(),
),
]))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥

@GeorgeHahn GeorgeHahn marked this pull request as ready for review January 16, 2026 20:33
@GeorgeHahn GeorgeHahn requested a review from a team as a code owner January 16, 2026 20:33
/// Previous counter values for delta calculation.
/// First observation of each counter is skipped to avoid emitting
/// the entire cumulative value as a "delta".
counter_prev: FxHashMap<Key, u64>,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm the only thing I think I have as a concern with this PR is how this new map interacts with expiration. I don't think it does, so this is a steady accumulation site?


// Verify we actually produced output
assert!(last_counter_value.is_some(), "No counter values in output");
// Note: First counter observation is skipped, so we have 599 counter values
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was confused by this, would be worth stating that it's skipped because of the delta calculation. I guess that is done later L1584 - L1586.

Comment on lines +310 to +313
/// Tick when each counter key was first written (for delta counter support)
counter_first_tick: FxHashMap<Key, u64>,
/// Tick when each gauge key was first written
gauge_first_tick: FxHashMap<Key, u64>,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than have two whole separate maps I would almost rather than we alter

counters: FxHashMap<Key, { first_tick: u64, buf: [u64; BUFFER_SIZE]}>,

That way the information is co-local in memory and we avoid two additional maps, lookups.

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.

4 participants