@@ -11,6 +11,7 @@ import { getExtension } from '../../common/extension.apis';
1111import { traceError , traceVerbose , traceWarn } from '../../common/logging' ;
1212import { StopWatch } from '../../common/stopWatch' ;
1313import { EventNames } from '../../common/telemetry/constants' ;
14+ import { classifyError } from '../../common/telemetry/errorClassifier' ;
1415import { sendTelemetryEvent } from '../../common/telemetry/sender' ;
1516import { untildify , untildifyArray } from '../../common/utils/pathUtils' ;
1617import { isWindows } from '../../common/utils/platformUtils' ;
@@ -253,6 +254,7 @@ class NativePythonFinderImpl implements NativePythonFinder {
253254 }
254255
255256 public async resolve ( executable : string ) : Promise < NativeEnvInfo > {
257+ const sw = new StopWatch ( ) ;
256258 try {
257259 await this . ensureProcessRunning ( ) ;
258260 try {
@@ -267,6 +269,7 @@ class NativePythonFinderImpl implements NativePythonFinder {
267269 this . outputChannel . info ( `Resolved Python Environment ${ environment . executable } ` ) ;
268270 // Reset restart attempts on successful request
269271 this . restartAttempts = 0 ;
272+ sendTelemetryEvent ( EventNames . PET_RESOLVE , sw . elapsedTime , { result : 'success' } ) ;
270273 return environment ;
271274 } catch ( ex ) {
272275 // On resolve timeout or connection error (not configure — configure handles its own timeout),
@@ -280,6 +283,16 @@ class NativePythonFinderImpl implements NativePythonFinder {
280283 throw ex ;
281284 }
282285 } catch ( ex ) {
286+ const errorType = classifyError ( ex ) ;
287+ sendTelemetryEvent (
288+ EventNames . PET_RESOLVE ,
289+ sw . elapsedTime ,
290+ {
291+ result : errorType === 'spawn_timeout' ? 'timeout' : 'error' ,
292+ errorType,
293+ } ,
294+ ex instanceof Error ? ex : undefined ,
295+ ) ;
283296 // If the server mode is fully exhausted, fall back to the CLI JSON mode
284297 if ( this . isServerExhausted ( ) ) {
285298 this . outputChannel . warn ( '[pet] Server mode exhausted, falling back to JSON CLI for resolve' ) ;
@@ -325,13 +338,15 @@ class NativePythonFinderImpl implements NativePythonFinder {
325338 private async restart ( ) : Promise < void > {
326339 this . isRestarting = true ;
327340 this . restartAttempts ++ ;
341+ const attempt = this . restartAttempts ;
328342
329343 const backoffMs = RESTART_BACKOFF_BASE_MS * Math . pow ( 2 , this . restartAttempts - 1 ) ;
330344 this . outputChannel . warn (
331345 `[pet] Restarting Python Environment Tools (attempt ${ this . restartAttempts } /${ MAX_RESTART_ATTEMPTS } , ` +
332346 `waiting ${ backoffMs } ms)` ,
333347 ) ;
334348
349+ const sw = new StopWatch ( ) ;
335350 try {
336351 // Kill existing process if still running
337352 this . killProcess ( ) ;
@@ -353,10 +368,17 @@ class NativePythonFinderImpl implements NativePythonFinder {
353368 this . connection = this . start ( ) ;
354369
355370 this . outputChannel . info ( '[pet] Python Environment Tools restarted successfully' ) ;
371+ sendTelemetryEvent ( EventNames . PET_PROCESS_RESTART , sw . elapsedTime , { attempt, result : 'success' } ) ;
356372
357373 // Reset restart attempts on successful start (process didn't immediately fail)
358374 // We'll reset this only after a successful request completes
359375 } catch ( ex ) {
376+ sendTelemetryEvent (
377+ EventNames . PET_PROCESS_RESTART ,
378+ sw . elapsedTime ,
379+ { attempt, result : 'error' , errorType : classifyError ( ex ) } ,
380+ ex instanceof Error ? ex : undefined ,
381+ ) ;
360382 this . outputChannel . error ( '[pet] Failed to restart Python Environment Tools:' , ex ) ;
361383 this . outputChannel . error (
362384 '[pet] To debug, run "Python Environments: Run Python Environment Tool (PET) in Terminal" from the Command Palette.' ,
@@ -634,13 +656,18 @@ class NativePythonFinderImpl implements NativePythonFinder {
634656 const disposables : Disposable [ ] = [ ] ;
635657 const unresolved : Promise < void > [ ] = [ ] ;
636658 const nativeInfo : NativeInfo [ ] = [ ] ;
659+ const sw = new StopWatch ( ) ;
660+ let unresolvedCount = 0 ;
637661 try {
638662 await this . configure ( ) ;
639663 const refreshOptions = this . getRefreshOptions ( options ) ;
664+ const workspaceDirCount = this . lastConfiguration ?. workspaceDirectories . length ?? 0 ;
665+ const searchPathCount = this . lastConfiguration ?. environmentDirectories . length ?? 0 ;
640666 disposables . push (
641667 this . connection . onNotification ( 'environment' , ( data : NativeEnvInfo ) => {
642668 this . outputChannel . info ( `Discovered env: ${ data . executable || data . prefix } ` ) ;
643669 if ( data . executable && ( ! data . version || ! data . prefix ) ) {
670+ unresolvedCount ++ ;
644671 unresolved . push (
645672 sendRequestWithTimeout < NativeEnvInfo > (
646673 this . connection ,
@@ -680,7 +707,29 @@ class NativePythonFinderImpl implements NativePythonFinder {
680707 if ( attempt > 0 ) {
681708 this . outputChannel . info ( `[pet] Refresh succeeded on retry attempt ${ attempt + 1 } ` ) ;
682709 }
710+
711+ sendTelemetryEvent ( EventNames . PET_REFRESH , sw . elapsedTime , {
712+ result : 'success' ,
713+ envCount : nativeInfo . filter ( ( e ) => isNativeEnvInfo ( e ) ) . length ,
714+ unresolvedCount,
715+ workspaceDirCount,
716+ searchPathCount,
717+ attempt,
718+ } ) ;
683719 } catch ( ex ) {
720+ const errorType = classifyError ( ex ) ;
721+ sendTelemetryEvent (
722+ EventNames . PET_REFRESH ,
723+ sw . elapsedTime ,
724+ {
725+ result : errorType === 'spawn_timeout' ? 'timeout' : 'error' ,
726+ envCount : nativeInfo . filter ( ( e ) => isNativeEnvInfo ( e ) ) . length ,
727+ unresolvedCount,
728+ attempt,
729+ errorType,
730+ } ,
731+ ex instanceof Error ? ex : undefined ,
732+ ) ;
684733 // On refresh timeout or connection error (not configure — configure handles its own timeout),
685734 // kill the hung process so next request triggers restart
686735 if ( ( ex instanceof RpcTimeoutError && ex . method !== 'configure' ) || ex instanceof rpc . ConnectionError ) {
@@ -709,6 +758,7 @@ class NativePythonFinderImpl implements NativePythonFinder {
709758 // No need to send a configuration request if there are no changes.
710759 if ( this . lastConfiguration && this . configurationEquals ( options , this . lastConfiguration ) ) {
711760 this . outputChannel . debug ( '[pet] configure: No changes detected, skipping configuration update.' ) ;
761+ sendTelemetryEvent ( EventNames . PET_CONFIGURE , 0 , { result : 'skipped' , retryCount : 0 } ) ;
712762 return ;
713763 }
714764 this . outputChannel . info ( '[pet] configure: Sending configuration update:' , JSON . stringify ( options ) ) ;
@@ -719,12 +769,33 @@ class NativePythonFinderImpl implements NativePythonFinder {
719769 `[pet] configure: Using extended timeout of ${ timeoutMs } ms (retry ${ this . configureRetry . timeoutCount } )` ,
720770 ) ;
721771 }
772+ const sw = new StopWatch ( ) ;
773+ const retryCount = this . configureRetry . timeoutCount ;
774+ const workspaceDirCount = options . workspaceDirectories . length ;
775+ const envDirCount = options . environmentDirectories . length ;
722776 try {
723777 await sendRequestWithTimeout ( this . connection , 'configure' , options , timeoutMs ) ;
724778 // Only cache after success so failed/timed-out calls will retry
725779 this . lastConfiguration = options ;
726780 this . configureRetry . onSuccess ( ) ;
781+ sendTelemetryEvent ( EventNames . PET_CONFIGURE , sw . elapsedTime , {
782+ result : 'success' ,
783+ workspaceDirCount,
784+ envDirCount,
785+ retryCount,
786+ } ) ;
727787 } catch ( ex ) {
788+ sendTelemetryEvent (
789+ EventNames . PET_CONFIGURE ,
790+ sw . elapsedTime ,
791+ {
792+ result : ex instanceof RpcTimeoutError ? 'timeout' : 'error' ,
793+ workspaceDirCount,
794+ envDirCount,
795+ retryCount,
796+ } ,
797+ ex instanceof Error ? ex : undefined ,
798+ ) ;
728799 // Clear cached config so the next call retries instead of short-circuiting via configurationEquals
729800 this . lastConfiguration = undefined ;
730801 if ( ex instanceof RpcTimeoutError ) {
0 commit comments