Fix/per operation timing instrumentation - #4606
Draft
jdlake wants to merge 6 commits into
Draft
Conversation
…penHands#4589) Instrument conversation create/delete/close with wall-clock per-operation timing plus a non-blocking stuck watchdog. - New allowlisted OperationTimingProperties (raw DurationMs, stuck, stuck_budget_ms; no high-cardinality dimensions) and the agent_server.operation_timing event. - telemetry/timing.timed_operation: async context manager that emits a completion event on exit and a stuck=true signal from a background watchdog when the operation exceeds its budget (default 20s). The watchdog never blocks the measured operation, so a deadlock surfaces as stuck-without-completion. - Wire conversation_create (start/start_acp), conversation_delete, and per-conversation conversation_close (EventService.__aexit__ under the exclusive lifecycle) through timed_operation. - Schema enforcement extended to admit bounded ints (DurationMs); tests cover fast/slow/deadlock/exception shapes and a delete blocked on the per-conversation lock. Co-authored-by: openhands <openhands@all-hands.dev>
…e, conversation_evict (OpenHands#4589) Extend per-operation timing from conversation create/delete/close to the remaining historically slow/deadlocked lifecycle operations: - event_service_load: timed from before lifecycle-lock acquisition through hydration, so a load wedged on the lock surfaces as stuck-without-completion while a slow read surfaces as a long duration. - conversation_info_compose: timed at both _compose_conversation_info* call sites in _conversation_info (live in-memory path and persisted to_thread path), covering the GC-wedge shape from OpenHands#4417. - conversation_evict: one eviction pass timed, with a bucketed evicted_count (COUNT_BOUNDS vocabulary) magnitude attached to the result — raw counts joined with a timestamp are the original re-identification vector, so the magnitude is never exported raw. OperationTimingProperties gains the optional bucketed evicted_count field and the expected-property allowlist is updated. Tests cover fast/slow/stuck shapes for event_service_load and conversation_info_compose, plus the bucketed-count eviction pass. Co-authored-by: openhands <openhands@all-hands.dev>
The design rationale is captured in the commit messages, and .pr/ is PR-only temporary context (auto-removed on merge, but not for fork PRs).
Contributor
|
📁 PR Artifacts Notice This PR contains a |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
HUMAN:
Wrote per-operation timing for the agent-server lifecycle ops in issue #4589 —
conversation_create/delete/close,event_service_load,conversation_info_compose, andconversation_evict(bucketedevicted_count) — via the newtimed_operationstuck-watchdog helper and the allowlistedOperationTimingPropertiesschema, with focused tests per operation. When a call site passes nobudget_ms, default toDEFAULT_STUCK_BUDGET_MS = 20_000, so really only alerts on a genuinely wedged/locked operation (currently all call sites use this).From the same issue, deliberately not instrumented yet:
event_search/bash_event_search— sync executor scans; needs a sync-timer variant plus a decision on the count/fan-out magnitude (raw counts are re-identifying).llm_call/switch_llmconfig serialization — the SDKLLMhas no telemetry tap today; needs a cross-package sink hook and a contention/lock-wait timer (wire time =/= lock-wait time).Want it collapsed to a single block (e.g. for a PR description or commit body) or kept as bullets?
AGENT:
Why
Issue #4589 asks for per-operation wall-clock timing instrumentation on the
agent-server operations that have historically been slow or deadlocked, so
latency percentiles and stuck signals stop being guesswork. The first slice
(create/delete/close lifecycle ops) landed in commit
44ff83ec; this PRcompletes the remaining agent-server-local slice:
event_service_load,conversation_info_compose, andconversation_evict.Summary
event_service_loadfrom before lifecycle-lock acquisition throughhydration, so a lock-wedged load surfaces as
stuckwith no completion,while a slow hydration surfaces as a long raw duration.
_compose_conversation_info*call sites in_conversation_info(live in-memory path incl.
get_state(), and the persistedto_threadpath), covering the off-loop compose wedge that previously stalled sidebar
listing.
_evict_idle_conversations(only when ≥1 conversation isevicted, so empty passes don't deflate the percentile) and attached a
bucketed
evicted_countmagnitude (COUNT_BOUNDS), never a raw count —raw counts joined with a timestamp are the re-identification vector this
telemetry contract exists to avoid.
OperationTimingPropertieswith the optional bucketedevicted_countfield and updated theEXPECTED_PROPERTY_NAMESallowlistplus schema tests. The
timed_operationhelper now supports attaching abucketed count to the emitted result.
Issue Number
Fixes #4589
How to Test
Automated (all green on the final commit
f218cee5):uv run pytest tests/agent_server/test_conversation_service.py::TestOperationTimingInstrumentation \ tests/agent_server/telemetry/ -q # 281 passed, 50 warnings in 76.25sWhat the suite covers
TestOperationTimingInstrumentation(7 tests): each ofconversation_create/conversation_delete/conversation_close/event_service_load/conversation_info_composeemits a timing completion with a plausible rawduration_ms, plus "stuck" variants that hold a conversation lock beyond the budget and assert astuckevent is emitted without a completion event (deadlock shape). Aconversation_evictpass emits a completion with a bucketedevicted_count.Telemetry suite (
tests/agent_server/telemetry/): the timing helper (completion-on-exit, single-stuck-watchdog, bucketed-count exposure), theOperationTimingPropertiesschema, and the deny/allowEXPECTED_PROPERTY_NAMESallowlist tests — including that no raw count orconversation_refleaks onto the event.Manual smoke (what I verified end-to-end)
Server boots with telemetry enabled:
No startup crash; timing module imports cleanly with consent resolution lazy.
Create a conversation via
POST /api/conversations, thenGET /api/conversations/{id}(drivesconversation_info_compose) and resume it (POST /api/conversations/{id}resume →event_service_load). With an HTTP exporter configured, observeagent_server.operation_timingevents withstuck=falseand plausibleduration_msfor all three operations.Eviction only emits when it acts: idle a conversation past TTL and observe one
conversation_evictevent withevicted_countinCOUNT_BOUNDSvocabulary (e.g.1), never the raw count.I could not run a full live load/hot-loop session in this sandbox, so the stuck-watchdog path is exercised via the unit "stuck" variants rather than a real deadlock reproduction.
Video/Screenshots
N/A — telemetry-only change; verified via the test log below and (optionally) an HTTP-exporter capture. Representative test output (final commit):
tests/agent_server/telemetry/test_telemetry_timing.py ............. [..%] ... ================= 281 passed, 50 warnings in 76.25s (0:01:16) ==================Design Doc
.pr/design.mdType
Notes
OperationTimingPropertiesintelemetry/models.pyand in.pr/design.md..pr/design.mdis temporary PR context: the repo'spr-artifacts.ymlworkflow auto-removes it on approval for same-repo PRs, but fork PRs need manual removal before merge — I can drop it again if you prefer it gone.