Skip to content

Commit 5a3541a

Browse files
cyntwang99claude
andcommitted
fix(tasks): emit task_stream.active as an absolute StatsD gauge
statsd increment/decrement submit a COUNT, so the active metric rendered as a rate of change rather than the live concurrency level. DogStatsD does not honor gauge deltas, so track a process-local running total and report it via statsd.gauge as an absolute value. The OTel UpDownCounter path already handled this natively and is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 6fe03ed commit 5a3541a

2 files changed

Lines changed: 26 additions & 9 deletions

File tree

agentex/src/utils/stream_metrics.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,14 @@
6262
_stall_counter: Counter | None = None
6363
_instruments_initialized = False
6464

65+
# Process-local count of currently-open streams, used solely to emit the StatsD
66+
# ``active`` gauge as an absolute value. DogStatsD gauges are last-value-wins and
67+
# do not honor deltas (unlike Etsy StatsD); increment/decrement would instead
68+
# submit a COUNT — the rate of change — never the concurrency level. So the
69+
# StatsD path keeps the running total here and reports it via ``statsd.gauge``.
70+
# The OTel ``UpDownCounter`` expresses this natively and needs no bookkeeping.
71+
_active_stream_count = 0
72+
6573

6674
def _ensure_instruments() -> None:
6775
"""Create the OTel instruments on first use. No-op if OTel is not configured."""
@@ -111,6 +119,7 @@ def record_stream_opened() -> None:
111119
Pair every call with exactly one ``record_stream_closed`` so the active gauge
112120
stays balanced. Never raises: see ``record_stream_closed``.
113121
"""
122+
global _active_stream_count
114123
try:
115124
_ensure_instruments()
116125

@@ -121,15 +130,17 @@ def record_stream_opened() -> None:
121130

122131
if _STATSD_ENABLED:
123132
statsd.increment("agentex.task_stream.opened")
124-
statsd.increment("agentex.task_stream.active")
133+
# Report the concurrency level as an absolute gauge, not a delta.
134+
_active_stream_count += 1
135+
statsd.gauge("agentex.task_stream.active", _active_stream_count)
125136
except Exception:
126137
logger.debug("Failed to emit agentex.task_stream.opened metric", exc_info=True)
127138

128139

129140
def record_stream_closed(outcome: StreamOutcome, duration_seconds: float) -> None:
130141
"""
131142
Record a task-event stream closing: bumps the closed counter (tagged by
132-
outcome), records the stream lifetime, and decrements the active gauge.
143+
outcome), records the stream lifetime, and lowers the active gauge.
133144
134145
Args:
135146
outcome: One of "completed", "client_disconnect", "error".
@@ -138,6 +149,7 @@ def record_stream_closed(outcome: StreamOutcome, duration_seconds: float) -> Non
138149
Never raises: emission failures (e.g. a StatsD UDP socket error or an OTel
139150
SDK fault) are swallowed so instrumentation can never disrupt the SSE path.
140151
"""
152+
global _active_stream_count
141153
try:
142154
_ensure_instruments()
143155

@@ -152,7 +164,9 @@ def record_stream_closed(outcome: StreamOutcome, duration_seconds: float) -> Non
152164
statsd.increment("agentex.task_stream.closed", tags=[f"outcome:{outcome}"])
153165
# Datadog histograms conventionally take milliseconds for durations.
154166
statsd.histogram("agentex.task_stream.duration", duration_seconds * 1000)
155-
statsd.decrement("agentex.task_stream.active")
167+
# Report the concurrency level as an absolute gauge, not a delta.
168+
_active_stream_count -= 1
169+
statsd.gauge("agentex.task_stream.active", _active_stream_count)
156170
except Exception:
157171
logger.debug("Failed to emit agentex.task_stream.closed metric", exc_info=True)
158172

agentex/tests/unit/utils/test_stream_metrics.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ def test_record_functions_swallow_emission_errors():
4646
patch.object(stream_metrics, "statsd") as mock_statsd,
4747
):
4848
mock_statsd.increment.side_effect = OSError("socket in a bad state")
49-
mock_statsd.decrement.side_effect = OSError("socket in a bad state")
5049
mock_statsd.histogram.side_effect = OSError("socket in a bad state")
50+
mock_statsd.gauge.side_effect = OSError("socket in a bad state")
5151

5252
# None of these should raise despite the backend blowing up.
5353
stream_metrics.record_stream_opened()
@@ -62,13 +62,15 @@ def test_record_stream_opened_emits_statsd_when_enabled():
6262
patch.object(stream_metrics, "_instruments_initialized", True),
6363
patch.object(stream_metrics, "_opened_counter", None),
6464
patch.object(stream_metrics, "_active_updown", None),
65+
patch.object(stream_metrics, "_active_stream_count", 0),
6566
patch.object(stream_metrics, "statsd") as mock_statsd,
6667
):
6768
stream_metrics.record_stream_opened()
6869

69-
# Opening bumps both the opened counter and the active gauge.
70-
mock_statsd.increment.assert_any_call("agentex.task_stream.opened")
71-
mock_statsd.increment.assert_any_call("agentex.task_stream.active")
70+
# Opening bumps the opened counter and reports the active concurrency level
71+
# as an absolute gauge (DogStatsD gauges are last-value-wins, not deltas).
72+
mock_statsd.increment.assert_called_once_with("agentex.task_stream.opened")
73+
mock_statsd.gauge.assert_called_once_with("agentex.task_stream.active", 1)
7274

7375

7476
@pytest.mark.unit
@@ -79,6 +81,7 @@ def test_record_stream_closed_emits_statsd_when_enabled():
7981
patch.object(stream_metrics, "_closed_counter", None),
8082
patch.object(stream_metrics, "_duration_histogram", None),
8183
patch.object(stream_metrics, "_active_updown", None),
84+
patch.object(stream_metrics, "_active_stream_count", 1),
8285
patch.object(stream_metrics, "statsd") as mock_statsd,
8386
):
8487
stream_metrics.record_stream_closed("client_disconnect", 3.25)
@@ -91,8 +94,8 @@ def test_record_stream_closed_emits_statsd_when_enabled():
9194
mock_statsd.histogram.assert_called_once_with(
9295
"agentex.task_stream.duration", 3250.0
9396
)
94-
# Closing decrements the active gauge.
95-
mock_statsd.decrement.assert_called_once_with("agentex.task_stream.active")
97+
# Closing lowers the active gauge to the new absolute level (1 -> 0).
98+
mock_statsd.gauge.assert_called_once_with("agentex.task_stream.active", 0)
9699

97100

98101
@pytest.mark.unit

0 commit comments

Comments
 (0)