Fix PET timeout telemetry attribution - #1695
Conversation
Send numeric PET diagnostics as measurements, classify RPC timeouts by method, preserve refresh failure context, and retry build metadata attribution after transient startup timeouts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
This PR adjusts PET/setup telemetry so timeout investigations keep numeric measurement context and more accurate timeout attribution (including JSON-RPC method classification), while improving resilience of PET info attribution across restarts.
Changes:
- Move refresh/configure/restart numeric context into telemetry measurements (and add refresh measurement builder helper).
- Classify
RpcTimeoutErrorby JSON-RPC method and update telemetry “timeout vs error” result attribution. - Retry transient PET
infotimeouts with bounded attempts and preserve last-known build metadata across restarts; add unit tests for these behaviors.
Show a summary per file
| File | Description |
|---|---|
| src/managers/common/nativePythonFinder.ts | Adds refresh measurement builder + RPC-timeout retry helper; updates PET telemetry payloads, timeout attribution, and info fetch retry/persistence. |
| src/common/telemetry/errorClassifier.ts | Updates timeout classification to be method-specific for RpcTimeoutError. |
| src/common/telemetry/constants.ts | Adjusts telemetry property mappings to reflect movement of numeric fields into measurements. |
| src/test/managers/common/nativePythonFinder.telemetry.unit.test.ts | Adds unit tests for refresh measurement building and RPC-timeout retry behavior. |
| src/test/common/telemetry/sender.unit.test.ts | Adds regression test asserting sender passes durations as measurements. |
| src/test/common/telemetry/errorClassifier.unit.test.ts | Updates tests to validate method-specific RPC timeout categorization. |
Review details
- Files reviewed: 6/6 changed files
- Comments generated: 4
- Review effort level: Lite
Isolate PET telemetry helpers, stop metadata retries for superseded connections, clarify GDPR measurement declarations, and map refresh breakdown phases centrally. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Review details
Suppressed comments (4)
src/managers/common/petTelemetry.ts:46
- The conda detection relies on the magic string
'Conda', which can drift from the actualNativePythonEnvironmentKindvalues (e.g., casing changes or enum string values). Consider normalizinginfo.kind(e.g., lowercasing) and comparing against a single canonical value, or accepting both expected representations, socondaEnvCountremains stable if upstream naming changes.
for (const info of input.nativeInfo) {
if (info.tool) {
managerCount++;
} else {
envCount++;
if (info.kind === 'Conda') {
condaEnvCount++;
}
}
}
src/managers/common/nativePythonFinder.ts:777
- With
this.petInfono longer reset onstart(), a newly started PET process can inherit stale build attribution if the PET binary is replaced in-place between restarts (same path, different build) and theinfocall fails (e.g., transient failures). To avoid incorrect attribution, consider invalidatingpetInfowhen you can detect a binary change (e.g., by storing and comparing executable mtime/size, or clearing the cache when the failure indicates an older binary such as a method-not-found response).
connection.listen();
// Stamp PET telemetry with version/buildId/commitSha. Fire-and-forget — must not block refresh.
this.kickoffInfoFetch(connection);
src/managers/common/nativePythonFinder.ts:973
- On refresh failures,
locatorsJson(and any available locator timing context) is not included even whenrefreshPerfmay have partial data. Since the success path includeslocatorsJson, consider also attachinglocatorsJsonin the error path whenrefreshPerfis present to improve failure investigations without changing the numeric measures payload.
{
result: ex instanceof RpcTimeoutError ? 'timeout' : 'error',
errorType,
...this.getPetInfoProperties(),
},
src/managers/common/nativePythonFinder.ts:294
- The new input-validation behavior for
retryRpcTimeout(throwingRangeErrorwhenmaxAttempts < 1or non-integer) isn’t covered by the new unit tests. Add a unit test to assert theRangeErroris thrown for invalid attempt limits (e.g., 0 and 1.5) so the contract stays stable.
if (!Number.isInteger(maxAttempts) || maxAttempts < 1) {
throw new RangeError('maxAttempts must be a positive integer');
}
- Files reviewed: 7/7 changed files
- Comments generated: 0 new
- Review effort level: Lite
Use the canonical Conda kind, invalidate cached build metadata when the PET binary changes, retain partial locator timing on failures, and cover retry input validation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Review details
Suppressed comments (1)
src/common/telemetry/errorClassifier.ts:37
classifyError()now mapsRpcTimeoutErrortorpc_*_timeout/rpc_timeoutinstead ofspawn_timeout. Any callers that inferresult: 'timeout'by checking only'spawn_timeout'will now misclassify PET JSON-RPC timeouts as'error'(e.g.src/internal.api.tsuseserrorType === 'spawn_timeout'when setting ENVIRONMENT_DISCOVERY.result). Update those call sites to treatrpc_timeoutandrpc_*_timeoutas timeouts as well (or centralize that logic in a shared helper).
if (ex instanceof RpcTimeoutError) {
switch (ex.method) {
case 'configure':
return 'rpc_configure_timeout';
case 'refresh':
- Files reviewed: 7/7 changed files
- Comments generated: 0 new
- Review effort level: Lite
Centralize timeout-category detection so environment discovery telemetry treats method-specific PET JSONRPC timeouts as timeouts rather than generic errors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Review details
Suppressed comments (3)
src/managers/common/nativePythonFinder.ts:997
- In this error path,
resultis based onex instanceof RpcTimeoutError, buterrorTypemay be a timeout category even when the exception is notRpcTimeoutError(e.g.spawn_timeout). This risks emittingresult: "error"with a timeouterrorType, which makes timeout slices unreliable. Use the classifiederrorTypeto decide the result.
const errorType = classifyError(ex);
sendTelemetryEvent(
EventNames.PET_REFRESH,
getRefreshTelemetryMeasures({
duration: sw.elapsedTime,
nativeInfo,
condaKind: NativePythonEnvironmentKind.conda,
unresolvedCount,
workspaceDirCount,
searchPathCount,
attempt,
refreshPerformance: refreshPerf,
}),
{
result: ex instanceof RpcTimeoutError ? 'timeout' : 'error',
errorType,
locatorsJson: refreshPerf ? JSON.stringify(refreshPerf.locators) : undefined,
...this.getPetInfoProperties(),
},
src/managers/common/nativePythonFinder.ts:1066
errorTypeis already classified for telemetry, butresultis still derived fromex instanceof RpcTimeoutError. IfclassifyError(ex)returns a timeout category for a non-RpcTimeoutError(e.g.spawn_timeoutfrom message matching), telemetry could reportresult: "error"with a timeouterrorType. Consider computingerrorTypeonce and derivingresultfrom it.
} catch (ex) {
sendTelemetryEvent(
EventNames.PET_CONFIGURE,
{ duration: sw.elapsedTime, workspaceDirCount, envDirCount, retryCount },
{
result: ex instanceof RpcTimeoutError ? 'timeout' : 'error',
errorType: classifyError(ex),
},
src/managers/common/nativePythonFinder.ts:427
- Telemetry 'result' is derived from
ex instanceof RpcTimeoutError, but theerrorTypeused for the same event can represent timeouts that are notRpcTimeoutErrorinstances (e.g.spawn_timeoutvia message matching, or future timeout categories). This can yield inconsistent telemetry likeresult: "error"witherrorType: "spawn_timeout". Consider basingresulton the classifiederrorTypeinstead.
This issue also appears in the following locations of the same file:
- line 979
- line 1059
const errorType = classifyError(ex);
sendTelemetryEvent(
EventNames.PET_RESOLVE,
sw.elapsedTime,
{
result: ex instanceof RpcTimeoutError ? 'timeout' : 'error',
errorType,
...this.getPetInfoProperties(),
},
- Files reviewed: 8/8 changed files
- Comments generated: 0 new
- Review effort level: Lite
Derive resolve, refresh, and configure results from the centralized timeout category so result and errorType cannot disagree. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Summary
Correct PET/setup telemetry so timeout investigations retain numeric context and reliable binary attribution.
infotimeouts while retaining metadata only for an unchanged binaryValidation
npm run lintnpm run compile-testsnpm run unittest(1504 passing, 5 pending)npm run compileFixes microsoft/python-environment-tools#478