Skip to content

engine.EngineMetrics.Throughput is exported, documented, and assigned by no engine: it reports a plausible 0 instead of "not measured" #653

Description

@FumingPower3925

engine.EngineMetrics.Throughput is exported, documented as "the recent requests-per-second rate", and never assigned by any engine. Verified against 468ce53:

engine/engine.go:173      // Throughput is the recent requests-per-second rate.
engine/engine.go:174      Throughput float64
engine/epoll/loop.go:1150 // RequestCount (plus Throughput and the adaptive controller's   <- comment only
adaptive/engine.go:929    Throughput: pm.Throughput + sm.Throughput,                        <- 0 + 0

std, epoll and io_uring never write it. The only line that touches the field at all is adaptive's aggregation, which sums two values that are always zero. So every Metrics() call from every engine reports Throughput: 0.

The rate is computed — at adaptive/telemetry.go:85, snap.ThroughputRPS = float64(deltaReqs) / elapsed — but into adaptive's own controller snapshot, not into EngineMetrics. The exported field and the working one are different structs that happen to share a name.

Why it matters more than it looks

A field that is exported, documented, and always zero is indistinguishable from a real measurement of zero. A consumer reading Metrics().Throughput gets a plausible number and no signal that nothing measured it — which is strictly worse than the field not existing.

This is not hypothetical. probatorium's /debug/vars publisher was just widened to export all 52 EngineMetrics scalar fields (goceleris/probatorium#389), so celeris.engine_throughput now appears in every refapp's metrics endpoint, reading a flat 0 on every engine on every cell. It was published deliberately, with a comment saying so, on the reasoning that a field which exists and is never published cannot be told apart from one that is published and never moves — but the right end to fix is this one.

Options

  1. Populate it. RequestCount and a timestamp are already tracked per engine; the adaptive controller derives the rate from exactly those. The awkward part is that "recent" needs a window, and Metrics() is a snapshot with no natural interval — whatever is chosen should be stated in the doc comment.
  2. Remove it, and let consumers derive a rate from RequestCount and their own sampling interval, which is what probatorium already does.

Option 2 is a breaking change to an exported struct, so it belongs in v2.0.0 unless the field is judged too new to matter. Filing against v1.6.0 for the decision, not for a particular fix — if the answer is "remove", it should move to the v2.0.0 milestone rather than ship half-done.

No engine regression test would have caught this: there is nothing to assert against a field nobody writes.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions