@@ -9,6 +9,10 @@ import { spawnProcess } from '../../common/childProcess.apis';
99import { ENVS_EXTENSION_ID , PYTHON_EXTENSION_ID } from '../../common/constants' ;
1010import { getExtension } from '../../common/extension.apis' ;
1111import { traceError , traceVerbose , traceWarn } from '../../common/logging' ;
12+ import { StopWatch } from '../../common/stopWatch' ;
13+ import { EventNames } from '../../common/telemetry/constants' ;
14+ import { classifyError } from '../../common/telemetry/errorClassifier' ;
15+ import { sendTelemetryEvent } from '../../common/telemetry/sender' ;
1216import { untildify , untildifyArray } from '../../common/utils/pathUtils' ;
1317import { isWindows } from '../../common/utils/platformUtils' ;
1418import { createRunningWorkerPool , WorkerPool } from '../../common/utils/workerPool' ;
@@ -246,6 +250,7 @@ class NativePythonFinderImpl implements NativePythonFinder {
246250
247251 public async resolve ( executable : string ) : Promise < NativeEnvInfo > {
248252 await this . ensureProcessRunning ( ) ;
253+ const sw = new StopWatch ( ) ;
249254 try {
250255 await this . configure ( ) ;
251256 const environment = await sendRequestWithTimeout < NativeEnvInfo > (
@@ -258,8 +263,19 @@ class NativePythonFinderImpl implements NativePythonFinder {
258263 this . outputChannel . info ( `Resolved Python Environment ${ environment . executable } ` ) ;
259264 // Reset restart attempts on successful request
260265 this . restartAttempts = 0 ;
266+ sendTelemetryEvent ( EventNames . PET_RESOLVE , sw . elapsedTime , { result : 'success' } ) ;
261267 return environment ;
262268 } catch ( ex ) {
269+ const errorType = classifyError ( ex ) ;
270+ sendTelemetryEvent (
271+ EventNames . PET_RESOLVE ,
272+ sw . elapsedTime ,
273+ {
274+ result : errorType === 'spawn_timeout' ? 'timeout' : 'error' ,
275+ errorType,
276+ } ,
277+ ex instanceof Error ? ex : undefined ,
278+ ) ;
263279 // On resolve timeout or connection error (not configure — configure handles its own timeout),
264280 // kill the hung process so next request triggers restart
265281 if ( ( ex instanceof RpcTimeoutError && ex . method !== 'configure' ) || ex instanceof rpc . ConnectionError ) {
@@ -308,13 +324,15 @@ class NativePythonFinderImpl implements NativePythonFinder {
308324 private async restart ( ) : Promise < void > {
309325 this . isRestarting = true ;
310326 this . restartAttempts ++ ;
327+ const attempt = this . restartAttempts ;
311328
312329 const backoffMs = RESTART_BACKOFF_BASE_MS * Math . pow ( 2 , this . restartAttempts - 1 ) ;
313330 this . outputChannel . warn (
314331 `[pet] Restarting Python Environment Tools (attempt ${ this . restartAttempts } /${ MAX_RESTART_ATTEMPTS } , ` +
315332 `waiting ${ backoffMs } ms)` ,
316333 ) ;
317334
335+ const sw = new StopWatch ( ) ;
318336 try {
319337 // Kill existing process if still running
320338 this . killProcess ( ) ;
@@ -336,10 +354,17 @@ class NativePythonFinderImpl implements NativePythonFinder {
336354 this . connection = this . start ( ) ;
337355
338356 this . outputChannel . info ( '[pet] Python Environment Tools restarted successfully' ) ;
357+ sendTelemetryEvent ( EventNames . PET_PROCESS_RESTART , sw . elapsedTime , { attempt, result : 'success' } ) ;
339358
340359 // Reset restart attempts on successful start (process didn't immediately fail)
341360 // We'll reset this only after a successful request completes
342361 } catch ( ex ) {
362+ sendTelemetryEvent (
363+ EventNames . PET_PROCESS_RESTART ,
364+ sw . elapsedTime ,
365+ { attempt, result : 'error' , errorType : classifyError ( ex ) } ,
366+ ex instanceof Error ? ex : undefined ,
367+ ) ;
343368 this . outputChannel . error ( '[pet] Failed to restart Python Environment Tools:' , ex ) ;
344369 this . outputChannel . error (
345370 '[pet] To debug, run "Python Environments: Run Python Environment Tool (PET) in Terminal" from the Command Palette.' ,
@@ -609,13 +634,18 @@ class NativePythonFinderImpl implements NativePythonFinder {
609634 const disposables : Disposable [ ] = [ ] ;
610635 const unresolved : Promise < void > [ ] = [ ] ;
611636 const nativeInfo : NativeInfo [ ] = [ ] ;
637+ const sw = new StopWatch ( ) ;
638+ let unresolvedCount = 0 ;
612639 try {
613640 await this . configure ( ) ;
614641 const refreshOptions = this . getRefreshOptions ( options ) ;
642+ const workspaceDirCount = this . lastConfiguration ?. workspaceDirectories . length ?? 0 ;
643+ const searchPathCount = this . lastConfiguration ?. environmentDirectories . length ?? 0 ;
615644 disposables . push (
616645 this . connection . onNotification ( 'environment' , ( data : NativeEnvInfo ) => {
617646 this . outputChannel . info ( `Discovered env: ${ data . executable || data . prefix } ` ) ;
618647 if ( data . executable && ( ! data . version || ! data . prefix ) ) {
648+ unresolvedCount ++ ;
619649 unresolved . push (
620650 sendRequestWithTimeout < NativeEnvInfo > (
621651 this . connection ,
@@ -655,7 +685,29 @@ class NativePythonFinderImpl implements NativePythonFinder {
655685 if ( attempt > 0 ) {
656686 this . outputChannel . info ( `[pet] Refresh succeeded on retry attempt ${ attempt + 1 } ` ) ;
657687 }
688+
689+ sendTelemetryEvent ( EventNames . PET_REFRESH , sw . elapsedTime , {
690+ result : 'success' ,
691+ envCount : nativeInfo . filter ( ( e ) => isNativeEnvInfo ( e ) ) . length ,
692+ unresolvedCount,
693+ workspaceDirCount,
694+ searchPathCount,
695+ attempt,
696+ } ) ;
658697 } catch ( ex ) {
698+ const errorType = classifyError ( ex ) ;
699+ sendTelemetryEvent (
700+ EventNames . PET_REFRESH ,
701+ sw . elapsedTime ,
702+ {
703+ result : errorType === 'spawn_timeout' ? 'timeout' : 'error' ,
704+ envCount : nativeInfo . filter ( ( e ) => isNativeEnvInfo ( e ) ) . length ,
705+ unresolvedCount,
706+ attempt,
707+ errorType,
708+ } ,
709+ ex instanceof Error ? ex : undefined ,
710+ ) ;
659711 // On refresh timeout or connection error (not configure — configure handles its own timeout),
660712 // kill the hung process so next request triggers restart
661713 if ( ( ex instanceof RpcTimeoutError && ex . method !== 'configure' ) || ex instanceof rpc . ConnectionError ) {
@@ -694,6 +746,7 @@ class NativePythonFinderImpl implements NativePythonFinder {
694746 // No need to send a configuration request if there are no changes.
695747 if ( this . lastConfiguration && this . configurationEquals ( options , this . lastConfiguration ) ) {
696748 this . outputChannel . debug ( '[pet] configure: No changes detected, skipping configuration update.' ) ;
749+ sendTelemetryEvent ( EventNames . PET_CONFIGURE , 0 , { result : 'skipped' , retryCount : 0 } ) ;
697750 return ;
698751 }
699752 this . outputChannel . info ( '[pet] configure: Sending configuration update:' , JSON . stringify ( options ) ) ;
@@ -704,12 +757,33 @@ class NativePythonFinderImpl implements NativePythonFinder {
704757 `[pet] configure: Using extended timeout of ${ timeoutMs } ms (retry ${ this . configureRetry . timeoutCount } )` ,
705758 ) ;
706759 }
760+ const sw = new StopWatch ( ) ;
761+ const retryCount = this . configureRetry . timeoutCount ;
762+ const workspaceDirCount = options . workspaceDirectories . length ;
763+ const envDirCount = options . environmentDirectories . length ;
707764 try {
708765 await sendRequestWithTimeout ( this . connection , 'configure' , options , timeoutMs ) ;
709766 // Only cache after success so failed/timed-out calls will retry
710767 this . lastConfiguration = options ;
711768 this . configureRetry . onSuccess ( ) ;
769+ sendTelemetryEvent ( EventNames . PET_CONFIGURE , sw . elapsedTime , {
770+ result : 'success' ,
771+ workspaceDirCount,
772+ envDirCount,
773+ retryCount,
774+ } ) ;
712775 } catch ( ex ) {
776+ sendTelemetryEvent (
777+ EventNames . PET_CONFIGURE ,
778+ sw . elapsedTime ,
779+ {
780+ result : ex instanceof RpcTimeoutError ? 'timeout' : 'error' ,
781+ workspaceDirCount,
782+ envDirCount,
783+ retryCount,
784+ } ,
785+ ex instanceof Error ? ex : undefined ,
786+ ) ;
713787 // Clear cached config so the next call retries instead of short-circuiting via configurationEquals
714788 this . lastConfiguration = undefined ;
715789 if ( ex instanceof RpcTimeoutError ) {
0 commit comments