Skip to content

Commit 6703525

Browse files
authored
feat: add ENVIRONMENT_DISCOVERY telemetry event and update related telemetry (#1265)
extra telemetry to track success and errors for both environment discovery and package installations. Use to help identify if there are trends in error data.
1 parent 1c9eb07 commit 6703525

File tree

2 files changed

+88
-6
lines changed

2 files changed

+88
-6
lines changed

src/common/telemetry/constants.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@ export enum EventNames {
3232
* - projectUnderRoot: number (count of projects nested under workspace roots)
3333
*/
3434
PROJECT_STRUCTURE = 'PROJECT_STRUCTURE',
35+
/**
36+
* Telemetry event for environment discovery per manager.
37+
* Properties:
38+
* - managerId: string (the id of the environment manager)
39+
* - result: 'success' | 'error' | 'timeout'
40+
* - envCount: number (environments found, on success only)
41+
* - errorType: string (error class name, on failure only)
42+
*/
43+
ENVIRONMENT_DISCOVERY = 'ENVIRONMENT_DISCOVERY',
3544
}
3645

3746
// Map all events to their properties
@@ -134,12 +143,17 @@ export interface IEventNamePropertyMapping {
134143
/* __GDPR__
135144
"package_management": {
136145
"managerId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" },
137-
"result": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" }
146+
"result": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" },
147+
"errorType": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" },
148+
"triggerSource": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" },
149+
"<duration>": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "eleanorjboyd" }
138150
}
139151
*/
140152
[EventNames.PACKAGE_MANAGEMENT]: {
141153
managerId: string;
142154
result: 'success' | 'error' | 'cancelled';
155+
errorType?: string;
156+
triggerSource: 'ui' | 'requirements' | 'package' | 'uninstall';
143157
};
144158

145159
/* __GDPR__
@@ -180,4 +194,20 @@ export interface IEventNamePropertyMapping {
180194
uniqueInterpreterCount: number;
181195
projectUnderRoot: number;
182196
};
197+
198+
/* __GDPR__
199+
"environment_discovery": {
200+
"managerId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" },
201+
"result": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" },
202+
"envCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "eleanorjboyd" },
203+
"errorType": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" },
204+
"<duration>": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "eleanorjboyd" }
205+
}
206+
*/
207+
[EventNames.ENVIRONMENT_DISCOVERY]: {
208+
managerId: string;
209+
result: 'success' | 'error' | 'timeout';
210+
envCount?: number;
211+
errorType?: string;
212+
};
183213
}

src/internal.api.ts

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
SetEnvironmentScope,
2929
} from './api';
3030
import { CreateEnvironmentNotSupported, RemoveEnvironmentNotSupported } from './common/errors/NotSupportedError';
31+
import { StopWatch } from './common/stopWatch';
3132
import { EventNames } from './common/telemetry/constants';
3233
import { sendTelemetryEvent } from './common/telemetry/sender';
3334

@@ -201,8 +202,30 @@ export class InternalEnvironmentManager implements EnvironmentManager {
201202
: Promise.reject(new RemoveEnvironmentNotSupported(`Remove Environment not supported by: ${this.id}`));
202203
}
203204

204-
refresh(options: RefreshEnvironmentsScope): Promise<void> {
205-
return this.manager.refresh(options);
205+
async refresh(options: RefreshEnvironmentsScope): Promise<void> {
206+
const sw = new StopWatch();
207+
try {
208+
await this.manager.refresh(options);
209+
const envs = await this.manager.getEnvironments('all').catch(() => []);
210+
sendTelemetryEvent(EventNames.ENVIRONMENT_DISCOVERY, sw.elapsedTime, {
211+
managerId: this.id,
212+
result: 'success',
213+
envCount: envs.length,
214+
});
215+
} catch (ex) {
216+
const isTimeout = ex instanceof Error && ex.message.includes('timed out');
217+
sendTelemetryEvent(
218+
EventNames.ENVIRONMENT_DISCOVERY,
219+
sw.elapsedTime,
220+
{
221+
managerId: this.id,
222+
result: isTimeout ? 'timeout' : 'error',
223+
errorType: ex instanceof Error ? ex.name : 'unknown',
224+
},
225+
ex instanceof Error ? ex : undefined,
226+
);
227+
throw ex;
228+
}
206229
}
207230

208231
getEnvironments(options: GetEnvironmentsScope): Promise<PythonEnvironment[]> {
@@ -245,6 +268,23 @@ export class InternalEnvironmentManager implements EnvironmentManager {
245268
}
246269
}
247270

271+
function inferPackageManagementTrigger(
272+
options: PackageManagementOptions,
273+
): 'ui' | 'requirements' | 'package' | 'uninstall' {
274+
const hasInstall = options.install && options.install.length > 0;
275+
const hasUninstall = options.uninstall && options.uninstall.length > 0;
276+
if (!hasInstall && hasUninstall) {
277+
return 'uninstall';
278+
}
279+
if (!hasInstall) {
280+
return 'ui'; // empty install list opens the package picker UI
281+
}
282+
if (options.install?.some((arg) => arg === '-r' || arg === '--requirement')) {
283+
return 'requirements';
284+
}
285+
return 'package';
286+
}
287+
248288
export class InternalPackageManager implements PackageManager {
249289
public constructor(
250290
public readonly id: string,
@@ -271,18 +311,30 @@ export class InternalPackageManager implements PackageManager {
271311
}
272312

273313
async manage(environment: PythonEnvironment, options: PackageManagementOptions): Promise<void> {
314+
const stopWatch = new StopWatch();
315+
const triggerSource = inferPackageManagementTrigger(options);
274316
try {
275317
await this.manager.manage(environment, options);
276-
sendTelemetryEvent(EventNames.PACKAGE_MANAGEMENT, undefined, { managerId: this.id, result: 'success' });
318+
sendTelemetryEvent(EventNames.PACKAGE_MANAGEMENT, stopWatch.elapsedTime, {
319+
managerId: this.id,
320+
result: 'success',
321+
triggerSource,
322+
});
277323
} catch (error) {
278324
if (error instanceof CancellationError) {
279-
sendTelemetryEvent(EventNames.PACKAGE_MANAGEMENT, undefined, {
325+
sendTelemetryEvent(EventNames.PACKAGE_MANAGEMENT, stopWatch.elapsedTime, {
280326
managerId: this.id,
281327
result: 'cancelled',
328+
triggerSource,
282329
});
283330
throw error;
284331
}
285-
sendTelemetryEvent(EventNames.PACKAGE_MANAGEMENT, undefined, { managerId: this.id, result: 'error' });
332+
sendTelemetryEvent(EventNames.PACKAGE_MANAGEMENT, stopWatch.elapsedTime, {
333+
managerId: this.id,
334+
result: 'error',
335+
errorType: error instanceof Error ? error.name : 'unknown',
336+
triggerSource,
337+
});
286338
throw error;
287339
}
288340
}

0 commit comments

Comments
 (0)