fix(instrumentation): exclude admission/queue-wait from provisioning metrics (AKS + Machine API) - #1304
fix(instrumentation): exclude admission/queue-wait from provisioning metrics (AKS + Machine API)#1304Xu Xue (xuexu6666) wants to merge 1 commit into
Conversation
|
For reviewers only: reply |
There was a problem hiding this comment.
Pull request overview
This PR adjusts Telescope’s provisioning instrumentation so node-pool provisioning latency metrics no longer include “queue-wait” time spent blocked by a previous in-progress ARM/AKS operation (OperationNotAllowed/EtagMismatch), while still recording that wait for transparency.
Changes:
begin_create_or_update_with_retrynow returns the accumulated in-progress wait time in seconds (0 if accepted immediately).instrument_nodepool_provisioningsubtracts that wait fromcommand_execution_time/node_readiness_time, callsOperation.exclude_time(...), and recordsin_progress_wait_seconds.Operationgainsexclude_time()and adjustsend()to shift the effective start time forward so timestamps and duration remain consistent; added unit tests for the new behavior.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| modules/python/utils/provisioning_instrumentation.py | Returns/consumes “in-progress wait seconds” and excludes it from provisioning timing metrics and operation duration. |
| modules/python/crud/operation.py | Adds exclude_time() and updates end() to compute duration using an effective start time that accounts for excluded seconds. |
| modules/python/tests/utils/test_provisioning_instrumentation.py | Updates tests to validate the new wait-seconds return value and exclusion behavior in instrumentation. |
| modules/python/tests/crud/test_operation.py | Adds tests covering excluded-time accumulation and end() duration/start-timestamp adjustment. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| ) | ||
| start_dt = start_dt + timedelta(seconds=excluded) | ||
| self.start_timestamp = start_dt.strftime("%Y-%m-%dT%H:%M:%SZ") | ||
| self.metadata["in_progress_wait_seconds"] = self.excluded_seconds |
There was a problem hiding this comment.
Good catch — fixed in 9e2a010. end() now records the clamped excluded value in in_progress_wait_seconds so the metadata can never exceed the wall-clock elapsed and always matches the duration adjustment applied. Added a unit test (test_operation_end_clamps_excluded_to_wall_clock) covering the excess-wait edge case (duration floors at 0, metadata == wall-clock).
a356559 to
b088fb6
Compare
b602637 to
64c400f
Compare
|
Ran a 3-reviewer pass over this PR and applied fixes (commit Fixed (issues multiple reviewers converged on):
Deliberately not changed (rationale):
Tests green (pre-existing |
64c400f to
8b6fbe6
Compare
Code reviewFound 1 issue:
telescope/modules/python/clients/aks_machine_client.py Lines 545 to 555 in 8b6fbe6 |
8b6fbe6 to
20603f8
Compare
|
Karen Chen (@karenychen) great catch — you're right, Replaced the union with a critical-path (makespan) attribution. Each chunk's backoff is pure additive delay on its own timeline, so its throttle-free finish is This is exactly "measured per worker and composed by the actual completion critical path": off-critical-path backoff doesn't move the max, so it contributes 0; and when the critical chunk's backoff would drop it below the runner-up, the exclusion is correctly capped at the runner-up's finish (not the full backoff). Changes:
Tests cover the three cases: lone-chunk backoff counts fully; off-critical-path backoff → 0 (your scenario); critical-path backoff capped by the runner-up's throttle-free finish. (Individual PUTs are single-shot / no 429 retry, so their |
20603f8 to
f814e6b
Compare
Code reviewFound 2 issues:
telescope/modules/python/clients/aks_machine_client.py Lines 914 to 925 in f814e6b
telescope/modules/python/utils/provisioning_instrumentation.py Lines 53 to 83 in f814e6b |
f814e6b to
16eedb7
Compare
Code reviewFound 1 issue:
telescope/modules/python/utils/provisioning_instrumentation.py Lines 151 to 164 in 16eedb7 |
16eedb7 to
f319aa1
Compare
|
Karen Chen (@karenychen) fixed in The terminal-rejection branch in # Terminal rejection (retries exhausted / never admitted, or non-retryable):
# the elapsed rejected attempts + backoff are all queue-wait.
e.request_started_at = time.time()
raise
Added |
Code reviewFound 1 issue:
telescope/modules/python/utils/provisioning_instrumentation.py Lines 151 to 167 in f319aa1 |
f319aa1 to
c2ca33c
Compare
|
Karen Chen (@karenychen) fixed in The terminal branch now only anchors the exclusion when the error is an exhausted in-progress rejection; a non-retryable error attaches nothing: in_progress = any(code in str(e) for code in ("OperationNotAllowed", "EtagMismatch"))
if in_progress and attempt < retries - 1:
... # retry
# Terminal:
if in_progress: # exhausted OperationNotAllowed/EtagMismatch
e.request_started_at = time.time() # all queue-wait -> exclude
raise # non-retryable (400/403/404/...) -> no anchor, real ARM latency retainedSo a slow 400/403/404 keeps its full round trip in the failed-op duration and is not mislabeled as |
Code reviewFound 1 issue:
telescope/modules/python/clients/aks_machine_client.py Lines 545 to 555 in c2ca33c |
…metrics Telescope was billing time spent waiting to be *admitted* by ARM against the operation itself, inflating reported provisioning latency across pipelines: - AKS node pool CRUD (create/scale/progressive, GPU + non-GPU) via begin_create_or_update_with_retry: ARM returns 409 OperationNotAllowed while a previous op on the cluster is still running; the sleep-retry loop ran inside the timed region. An H100 scale_up showed 606s including ~180s of pre-accept wait. - Machine API scale via BatchPutMachine: 429 throttle backoff ran inside the command timing (command_execution_time) and the operation duration. Fix (shared Operation.exclude_time primitive): - AKS: begin_create_or_update_with_retry measures from the start of the attempt that ARM *accepts* (2xx), and returns (request_started_at, retry_occurred). So command_execution_time / node_readiness_time include the accepted request's own frontend time (submit -> 2xx) plus the async provisioning, while the *failed* prior attempts (409 frontend round-trip + backoff) are excluded as queue-wait. - Machine API: 429 backoff sleeps are recorded as (start,end) intervals per concurrent worker; scale_machine excludes their wall-clock union (utils.union_seconds -- summing would over-count overlapping parallel backoffs) from command time and the operation duration. - Both record the excluded wait as in_progress_wait_seconds metadata (clamped to the wall-clock elapsed so it can never exceed the duration adjustment applied). - Operation.exclude_time()/end() shift the effective start forward by the excluded wait so start/end/duration stay consistent. EKS is unaffected (boto3 waiters, no pre-accept serialization). Tests: union_seconds interval merge; request-start return + timing exclusion incl. the excess-wait clamp; batch throttle-interval recording and scale_machine exclusion; Operation duration adjustment. pytest green (pre-existing test_log_gpu_mode_console_echo failure is Python-3.10-only assertNoLogs), pylint clean on changed files.
c2ca33c to
9155788
Compare
|
Karen Chen (@karenychen) fixed in Scoped the throttle exclusion to throttle_wait = throttle_makespan_delay(request.chunk_throttle)
op.add_metadata("command_execution_time", max(0.0, command_end - command_t0 - throttle_wait))
op.add_metadata("throttle_wait_seconds", throttle_wait) # recorded for visibility
# (op.exclude_time is no longer called on the machine path)Rationale: Updated the test to assert Note this makes the two pipelines intentionally asymmetric: the AKS node-pool path still excludes queue-wait from the duration (single PUT -> the pre-accept wait strictly delays everything downstream, one critical path), whereas the concurrent Machine path scopes it to command time only. |
Problem
Telescope over-reports provisioning latency because it bills time spent waiting to be admitted by ARM against the operation itself. This is not GPU-specific — it affects multiple pipelines:
begin_create_or_update_with_retry: ARM returns409 OperationNotAllowedwhile a previous operation on the cluster is still running, and the sleep-retry loop runs inside the timed region. Real example (H100 scale_up, run78200-2d6bbfea): reported606s, ~180s of which was pre-accept queue-wait (utils.provisioning_instrumentation - WARNING - Cluster has an in-progress operation, retrying in 30s (attempt 6/10): OperationNotAllowed).aks_machine_client) viaBatchPutMachine: the429throttle backoff runs insidecommand_execution_timeand the operationduration.Fix — measure from the accepted (2xx) request, exclude the failed-attempt wait (shared
Operation.exclude_timeprimitive)AKS node pool — measure from the start of the attempt ARM accepts (2xx):
begin_create_or_update_with_retrystamps each attempt's start and returns(request_started_at, retry_occurred)for the attempt that succeeds. A409 OperationNotAllowedraises before a poller exists, so its stamp is discarded.instrument_nodepool_provisioningmeasurescommand_execution_timeandnode_readiness_timefromrequest_started_at. This counts the accepted request's own frontend time (submit → 2xx) plus the async provisioning, while the failed prior attempts (409 frontend round-trip + backoff) are excluded as queue-wait.Machine API — exclude the throttle backoff:
429backoff sleep is recorded as a(start, end)interval per concurrent worker;scale_machineexcludes their wall-clock union (utils.union_seconds— parallel backoffs overlap, so summing would over-count) fromcommand_execution_timeand the operation duration.Shared:
in_progress_wait_secondsmetadata (clamped to the wall-clock elapsed, so it can never exceed the duration adjustment applied).Operation.exclude_time()/end()shift the effective start forward by the excluded wait sostart/end/durationstay consistent.Files
crud/operation.py—exclude_time()primitive + duration adjustment (clamped).utils/provisioning_instrumentation.py— measure from the accepted attempt's start (counts its frontend time).utils/common.py—union_seconds()interval-merge helper.clients/aks_machine_client.py— record + exclude 429 throttle backoff.Tests
union_secondsinterval merge; request-start return + timing exclusion incl. the excess-wait clamp; batch throttle-interval recording andscale_machineexclusion;Operationduration adjustment.pytestgreen (the pre-existingtest_log_gpu_mode_console_echofailure is Python-3.10-onlyassertNoLogs, unrelated); pylint clean on changed files.