kumulant

stat.event

Stats whose output is discrete-temporal in shape: state transitions, dwell times, last-seen timestamps, level crossings, peak excursions. They share the streaming-stats discipline but their results carry counts of state changes and timestamps rather than numeric aggregates.

Picking an event stat

StatResultQuestion
ExcursionStatExcursionResultWhat's the largest peak-to-subsequent-trough excursion seen?
RunLengthStatRunLengthResultWhat's the current and longest consecutive truthy-run length?
CrossingStatCrossingResultHow often does the input cross a configured level?
RecencyStatRecencyResultHow long since the most recent observation?
SojournStatSojournResultHow long has each categorical state been occupied?

When to reach for what

  • ExcursionStat for drawdown / recovery monitoring, or any "how far has the signal fallen from its running high" question. Tracks the running peak, the lowest value seen since the peak was last set, and the largest peak-to-subsequent-trough excursion observed.

  • RunLengthStat for the classic "longest streak" diagnostic. Truthy means a nonzero, non-NaN value, so feeding it the output of a predicate produces consecutive-match-count metrics. Tracks both the current run and the longest observed run.

  • CrossingStat when the useful signal is "how active is this around a threshold"; zero-crossings of a centred series, threshold-touch counts on an SLO, transitions through any level. Reports up-crossings and down-crossings separately.

  • RecencyStat for staleness checks and last-error-seen monitors. Compose with com.eignex.kumulant.schema.filter on the spec side for "time since the last matching event" diagnostics. The result reads elapsed time at snapshot time, not at update time, so a stale stream still reports a growing elapsed time even without new updates.

  • SojournStat for uptime / availability breakdowns and any dwell-time accounting where the state alphabet is known up front. The result carries per-state total nanos, per-state transition counts, the current state, and the current dwell. Configure the alphabet at construction; out-of-alphabet values are dropped.

Compose patterns

  • Mean.transform(IfExpr(X gt threshold, 1.0, 0.0)).windowed(window) for windowed fraction-meeting-threshold (SLO compliance).

  • RunLength.filter(predicate) for "longest consecutive run satisfying a predicate".

  • Recency.filter(predicate) for "time since last matching event".

  • Compose CrossingStat with ExcursionStat for both "how often" and "how far" diagnostics over the same signal.

Merge

CrossingStat, RecencyStat, and SojournStat merge exactly: crossing counts sum cell-wise, recency takes the later last-seen timestamp, sojourn sums per-state nanos and transition counts. ExcursionStat and RunLengthStat merge approximately; peak/trough and run-length state is order-dependent, so the merge combines the extremes (max peak, min trough, longest run) without reconstructing the exact sequence.

Concurrency

CrossingStat and RecencyStat keep a single coupled "previous value

Types

Link copied to clipboard
@Serializable
@SerialName(value = "CrossingResult")
data class CrossingResult(val level: Double, val upCrossings: Long, val downCrossings: Long) : Result

Counts of upward and downward crossings of a fixed level.

Link copied to clipboard
class CrossingStat(val level: Double, val concurrency: Concurrency = Concurrency.None) : SeriesStat<CrossingResult>

Counts strict upward and downward crossings of a configured level.

Link copied to clipboard
@Serializable
@SerialName(value = "ExcursionResult")
data class ExcursionResult(val peak: Double, val peakTimestampNanos: Long, val trough: Double, val troughTimestampNanos: Long, val maxExcursion: Double, val currentRecovery: Double) : Result

Running peak / trough excursion summary.

Link copied to clipboard
class ExcursionStat(val concurrency: Concurrency = Concurrency.None) : SeriesStat<ExcursionResult>

Tracks the running peak of the input stream, the lowest value seen since the peak was last set, and the largest peak-to-subsequent-trough excursion observed across history.

Link copied to clipboard
@Serializable
@SerialName(value = "RecencyResult")
data class RecencyResult(val lastObservedTimestampNanos: Long, val timestampNanos: Long, val hasObservation: Boolean) : Result

Time since the most recent observation; hasObservation is false until the first update.

Link copied to clipboard
class RecencyStat(val concurrency: Concurrency = Concurrency.None) : SeriesStat<RecencyResult>

Reports the time elapsed since the most recent observation. Compose with .filter(...) to track recency of observations matching a predicate (for example: "how long since the last error").

Link copied to clipboard
@Serializable
@SerialName(value = "RunLengthResult")
data class RunLengthResult(val current: Long, val longest: Long) : Result

Current and longest consecutive truthy-run lengths.

Link copied to clipboard
class RunLengthStat(val concurrency: Concurrency = Concurrency.None) : SeriesStat<RunLengthResult>

Tracks the length of the current consecutive truthy run and the longest observed run. A value is treated as truthy when it is non-zero and not NaN; compose with .transform(...) upstream to project arbitrary predicates onto 0.0 / 1.0 before they reach the stat.

Link copied to clipboard
@Serializable
@SerialName(value = "SojournResult")
data class SojournResult(val states: List<Long>, val totalNanosByState: List<Long>, val transitionsByState: List<Long>, val currentState: Long, val currentStateEnterTimestampNanos: Long, val timestampNanos: Long, val hasState: Boolean) : Result

Per-state cumulative time, transition counts, and current-state dwell.

Link copied to clipboard
class SojournStat(val states: List<Long>, val concurrency: Concurrency = Concurrency.None) : DiscreteStat<SojournResult>

Tracks total time spent in each member of a declared categorical state alphabet, the number of transitions into each state, and the dwell time of the current state. Update values not in states raise IllegalArgumentException.