improve(diagnostics-otel): make agent-duration histograms usable beyond 10s (#96592)

* improve(diagnostics-otel): tune duration/context histogram bucket boundaries

The openclaw.run.duration_ms, openclaw.harness.duration_ms, and
openclaw.context.tokens histograms used the SDK default bucket
boundaries. The default duration buckets top out at 10s, so agent runs —
which routinely take minutes — all collapse into the +Inf overflow
bucket, making p95/p99 latency meaningless without collector-side
reconfiguration.

Add explicit_bucket_boundaries advice via named constants, matching the
existing GEN_AI_*_BUCKETS convention in this file:
- AGENT_DURATION_MS_BUCKETS (1s … 1h) for the run and harness duration
  histograms, which share the same range.
- CONTEXT_TOKENS_BUCKETS on a token scale (1k … 2M). openclaw.context.tokens
  records context-window limit/used token counts (e.g. 128000), so the
  boundaries must cover real context sizes rather than small integers.

Adds a test asserting the three histograms advertise the expected
boundaries and that run/harness share the same set.

* fix(diagnostics-otel): preserve prior histogram buckets

---------

Co-authored-by: Harry Chen <zhiling.chen@binance.com>
Co-authored-by: Vincent Koc <25068+vincentkoc@users.noreply.github.com>
This commit is contained in:
Zhiling Chen 2026-07-07 19:13:05 -07:00 committed by GitHub
parent 94da0a9813
commit 11a0e54ddd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 72 additions and 0 deletions

View file

@ -2345,6 +2345,38 @@ describe("diagnostics-otel service", () => {
await service.stop?.(ctx);
});
test("advertises explicit duration buckets on the openclaw run/harness/context histograms", async () => {
const service = createDiagnosticsOtelService();
const ctx = createOtelContext(OTEL_TEST_ENDPOINT, { metrics: true });
const priorSdkBoundaries = [
0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000,
];
try {
await service.start(ctx);
const runDurationOptions = histogramCreateOptions("openclaw.run.duration_ms");
expect(runDurationOptions?.unit).toBe("ms");
const runBoundaries = runDurationOptions?.advice?.explicitBucketBoundaries;
expect(runBoundaries).toEqual(expect.arrayContaining(priorSdkBoundaries));
for (const boundary of [60000, 3_600_000]) {
expect(runBoundaries).toContain(boundary);
}
const harnessDurationOptions = histogramCreateOptions("openclaw.harness.duration_ms");
const harnessBoundaries = harnessDurationOptions?.advice?.explicitBucketBoundaries;
expect(harnessBoundaries).toEqual(runBoundaries);
const contextOptions = histogramCreateOptions("openclaw.context.tokens");
const contextBoundaries = contextOptions?.advice?.explicitBucketBoundaries;
expect(contextBoundaries).toEqual(expect.arrayContaining(priorSdkBoundaries));
for (const boundary of [128000, 1_000_000]) {
expect(contextBoundaries).toContain(boundary);
}
} finally {
await service.stop?.(ctx);
}
});
test("bounds agent identifiers on model usage metric attributes", async () => {
const service = createDiagnosticsOtelService();
const ctx = createOtelContext(OTEL_TEST_ENDPOINT, { metrics: true });

View file

@ -107,6 +107,43 @@ const GEN_AI_TOKEN_USAGE_BUCKETS = [
const GEN_AI_OPERATION_DURATION_BUCKETS = [
0.01, 0.02, 0.04, 0.08, 0.16, 0.32, 0.64, 1.28, 2.56, 5.12, 10.24, 20.48, 40.96, 81.92,
];
// Preserve the SDK's existing finite boundaries so upgrades do not remove
// exported bucket series that dashboards or alerts may already reference.
const OTEL_DEFAULT_HISTOGRAM_BUCKETS = [
0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000,
];
// Agent run / harness durations routinely exceed the SDK default's 10s ceiling.
// Extend the existing layout through one hour without changing prior buckets.
const AGENT_DURATION_MS_BUCKETS = [
...OTEL_DEFAULT_HISTOGRAM_BUCKETS,
15000,
20000,
30000,
45000,
60000,
120000,
180000,
240000,
300000,
600000,
900000,
1_800_000,
3_600_000,
];
// openclaw.context.tokens records context window limit/used token counts, which
// range from a few thousand to >1M for large-context models. Keep the prior
// layout and add common context-window sizes above it.
const CONTEXT_TOKENS_BUCKETS = [
...OTEL_DEFAULT_HISTOGRAM_BUCKETS,
16000,
32000,
64000,
128000,
200000,
400000,
1_000_000,
2_000_000,
];
const MAX_RETAINED_TRUSTED_SPAN_CONTEXTS = 1024;
const RETAINED_TRUSTED_SPAN_CONTEXT_TIMEOUT_MS = 5_000;
@ -1797,14 +1834,17 @@ export function createDiagnosticsOtelService(): OpenClawPluginService {
const durationHistogram = meter.createHistogram("openclaw.run.duration_ms", {
unit: "ms",
description: "Agent run duration",
advice: { explicitBucketBoundaries: AGENT_DURATION_MS_BUCKETS },
});
const harnessDurationHistogram = meter.createHistogram("openclaw.harness.duration_ms", {
unit: "ms",
description: "Agent harness lifecycle duration",
advice: { explicitBucketBoundaries: AGENT_DURATION_MS_BUCKETS },
});
const contextHistogram = meter.createHistogram("openclaw.context.tokens", {
unit: "1",
description: "Context window size and usage",
advice: { explicitBucketBoundaries: CONTEXT_TOKENS_BUCKETS },
});
const webhookReceivedCounter = meter.createCounter("openclaw.webhook.received", {
unit: "1",