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 |
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. |
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. |
|
exclude the failed-attempt wait (shared Operation.exclude_time primitive is this getting compute somewhere else now ? |
|
Sylvain Boily (@djsly) good catch — that line in the description was stale (it drifted across the review rounds). Current state at
throttle_wait = throttle_makespan_delay(request.chunk_throttle) # makespan − max(finish − backoff)
op.add_metadata("command_execution_time", max(0.0, command_end - command_t0 - throttle_wait))
op.add_metadata("throttle_wait_seconds", throttle_wait)
# no op.exclude_time on the machine path -> operation duration is unchanged
I've refreshed the PR description to reflect this (the old text still mentioned |
9155788 to
61ba4ec
Compare
|
Addressed the readability feedback in
Tests green (only the pre-existing Py3.10-only |
568f8f3 to
96cd6ff
Compare
…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.
96cd6ff to
e21c97a
Compare
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.aks_machine_client) viaBatchPutMachine: the429throttle backoff runs insidecommand_execution_time.Fix
AKS node pool — measure from the accepted (2xx) attempt; exclude the queue-wait from
durationbegin_create_or_update_with_retrystamps each attempt's start and returns(request_started_at, retry_occurred)for the attempt ARM accepts. A409 OperationNotAllowedraises before a poller exists, so its stamp is discarded.instrument_nodepool_provisioningmeasurescommand_execution_time/node_readiness_timefromrequest_started_at(counts the accepted request's own frontend time + async provisioning; the failed prior attempts are excluded as queue-wait).durationviaOperation.exclude_time()and recorded (clamped) asin_progress_wait_seconds. Failure paths carry the timing too: accepted-then-failed and K8s-readiness-failed exclude the pre-accept wait before re-raising; an exhausted never-admittedOperationNotAllowed/EtagMismatchanchors the exclusion at give-up; a non-retryable terminal error (400/403/404) anchors nothing (real ARM latency retained).Machine API — exclude the throttle from
command_execution_timeonly (NOTduration)429backoff is recorded with its finish time as(finish, backoff)inrequest.chunk_throttle— on success and failure (recorded in afinally, so a chunk that exhausts its retries still contributes).utils.throttle_makespan_delaycomputes the throttle's delay to the concurrent PUT-phase makespan:makespan − max(finishᵢ − backoffᵢ). Off-critical-path backoff (a chunk that slept but wasn't last to finish) contributes 0.scale_machinesubtracts that only fromcommand_execution_time(max(0, command_end − command_t0 − throttle_wait)) and recordsthrottle_wait_seconds. It does not callOperation.exclude_time, so the operationdurationis unchanged — the PUT-phase critical path can differ from the end-to-end one (the last-Ready machine may be a different chunk), so PUT-phase throttle doesn't necessarily delay completion. (Per Karen Chen (@karenychen)'s review.)Files
crud/operation.py—exclude_time()primitive + clamped duration adjustment (used by the AKS path;Operation.end()is the single owner ofin_progress_wait_seconds).utils/provisioning_instrumentation.py— accept-based AKS timing + failure-path timing anchors.utils/common.py—throttle_makespan_delay()critical-path helper.clients/aks_machine_client.py— per-chunk(finish, backoff)recording + scopedcommand_execution_timethrottle exclusion.Tests
throttle_makespan_delaycases (off-critical-path → 0 / tie / runner-up cap); accept-based timing incl. failure-path exclusion (K8s-fail, accepted-then-fail, never-admitted anchor, non-retryable → no anchor); per-chunk backoff-on-failure recording;Operationexcess-wait clamp.pytestgreen (pre-existingtest_log_gpu_mode_console_echois Python-3.10-onlyassertNoLogs, unrelated); pylint clean on changed files.