Skip to content

Commit a8a4f70

Browse files
eleanorjboydCopilot
andauthored
Add telemetry properties for environment resolution and package installation (#25927)
Co-authored-by: Copilot <copilot@github.com> 1. Add `duration` to `INVOKE_TOOL` 2. Add `resolveOutcome` to `configure_python_environment` 3. Add `envType` to all tools that resolve an environment 4. Add `packageCount` and `installerType` to `install_python_packages` 5. Add `responsePackageCount` to `get_python_environment_details` --------- Co-authored-by: Copilot <copilot@github.com>
1 parent 957444f commit a8a4f70

7 files changed

Lines changed: 69 additions & 4 deletions

File tree

src/client/chat/baseTool.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ import { IResourceReference, isCancellationError, resolveFilePath } from './util
1616
import { ErrorWithTelemetrySafeReason } from '../common/errors/errorUtils';
1717
import { sendTelemetryEvent } from '../telemetry';
1818
import { EventName } from '../telemetry/constants';
19+
import { StopWatch } from '../common/utils/stopWatch';
1920

2021
export abstract class BaseTool<T extends IResourceReference> implements LanguageModelTool<T> {
22+
protected extraTelemetryProperties: Record<string, string> = {};
2123
constructor(private readonly toolName: string) {}
2224

2325
async invoke(
@@ -29,8 +31,10 @@ export abstract class BaseTool<T extends IResourceReference> implements Language
2931
new LanguageModelTextPart('Cannot use this tool in an untrusted workspace.'),
3032
]);
3133
}
34+
this.extraTelemetryProperties = {};
3235
let error: Error | undefined;
3336
const resource = resolveFilePath(options.input.resourcePath);
37+
const stopWatch = new StopWatch();
3438
try {
3539
return await this.invokeImpl(options, resource, token);
3640
} catch (ex) {
@@ -46,10 +50,11 @@ export abstract class BaseTool<T extends IResourceReference> implements Language
4650
? error.telemetrySafeReason
4751
: 'error'
4852
: undefined;
49-
sendTelemetryEvent(EventName.INVOKE_TOOL, undefined, {
53+
sendTelemetryEvent(EventName.INVOKE_TOOL, stopWatch.elapsedTime, {
5054
toolName: this.toolName,
5155
failed,
5256
failureCategory,
57+
...this.extraTelemetryProperties,
5358
});
5459
}
5560
}

src/client/chat/configurePythonEnvTool.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { ICodeExecutionService } from '../terminals/types';
1818
import { TerminalCodeExecutionProvider } from '../terminals/codeExecution/terminalCodeExecution';
1919
import {
2020
getEnvDetailsForResponse,
21+
getEnvTypeForTelemetry,
2122
getToolResponseIfNotebook,
2223
IResourceReference,
2324
isCancellationError,
@@ -58,6 +59,7 @@ export class ConfigurePythonEnvTool extends BaseTool<IResourceReference>
5859
): Promise<LanguageModelToolResult> {
5960
const notebookResponse = getToolResponseIfNotebook(resource);
6061
if (notebookResponse) {
62+
this.extraTelemetryProperties.resolveOutcome = 'notebook';
6163
return notebookResponse;
6264
}
6365

@@ -67,6 +69,8 @@ export class ConfigurePythonEnvTool extends BaseTool<IResourceReference>
6769
);
6870

6971
if (workspaceSpecificEnv) {
72+
this.extraTelemetryProperties.resolveOutcome = 'existingWorkspaceEnv';
73+
this.extraTelemetryProperties.envType = getEnvTypeForTelemetry(workspaceSpecificEnv);
7074
return getEnvDetailsForResponse(
7175
workspaceSpecificEnv,
7276
this.api,
@@ -79,14 +83,17 @@ export class ConfigurePythonEnvTool extends BaseTool<IResourceReference>
7983

8084
if (await this.createEnvTool.shouldCreateNewVirtualEnv(resource, token)) {
8185
try {
82-
return await lm.invokeTool(CreateVirtualEnvTool.toolName, options, token);
86+
const result = await lm.invokeTool(CreateVirtualEnvTool.toolName, options, token);
87+
this.extraTelemetryProperties.resolveOutcome = 'createdVirtualEnv';
88+
return result;
8389
} catch (ex) {
8490
if (isCancellationError(ex)) {
8591
const input: ISelectPythonEnvToolArguments = {
8692
...options.input,
8793
reason: 'cancelled',
8894
};
8995
// If the user cancelled the tool, then we should invoke the select env tool.
96+
this.extraTelemetryProperties.resolveOutcome = 'selectedEnvAfterCancelledCreate';
9097
return lm.invokeTool(SelectPythonEnvTool.toolName, { ...options, input }, token);
9198
}
9299
throw ex;
@@ -95,6 +102,7 @@ export class ConfigurePythonEnvTool extends BaseTool<IResourceReference>
95102
const input: ISelectPythonEnvToolArguments = {
96103
...options.input,
97104
};
105+
this.extraTelemetryProperties.resolveOutcome = 'selectedEnv';
98106
return lm.invokeTool(SelectPythonEnvTool.toolName, { ...options, input }, token);
99107
}
100108
}

src/client/chat/getExecutableTool.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { TerminalCodeExecutionProvider } from '../terminals/codeExecution/termin
1919
import {
2020
getEnvDisplayName,
2121
getEnvironmentDetails,
22+
getEnvTypeForTelemetry,
2223
getToolResponseIfNotebook,
2324
IResourceReference,
2425
raceCancellationError,
@@ -53,6 +54,12 @@ export class GetExecutableTool extends BaseTool<IResourceReference> implements L
5354
return notebookResponse;
5455
}
5556

57+
const envPath = this.api.getActiveEnvironmentPath(resourcePath);
58+
const environment = await raceCancellationError(this.api.resolveEnvironment(envPath), token);
59+
if (environment) {
60+
this.extraTelemetryProperties.envType = getEnvTypeForTelemetry(environment);
61+
}
62+
5663
const message = await getEnvironmentDetails(
5764
resourcePath,
5865
this.api,

src/client/chat/getPythonEnvTool.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,13 @@ import { IServiceContainer } from '../ioc/types';
1717
import { ICodeExecutionService } from '../terminals/types';
1818
import { TerminalCodeExecutionProvider } from '../terminals/codeExecution/terminalCodeExecution';
1919
import { IProcessServiceFactory, IPythonExecutionFactory } from '../common/process/types';
20-
import { getEnvironmentDetails, getToolResponseIfNotebook, IResourceReference, raceCancellationError } from './utils';
20+
import {
21+
getEnvironmentDetails,
22+
getEnvTypeForTelemetry,
23+
getToolResponseIfNotebook,
24+
IResourceReference,
25+
raceCancellationError,
26+
} from './utils';
2127
import { getPythonPackagesResponse } from './listPackagesTool';
2228
import { ITerminalHelper } from '../common/terminal/types';
2329
import { getEnvExtApi, useEnvExtension } from '../envExt/api.internal';
@@ -64,13 +70,16 @@ export class GetEnvironmentInfoTool extends BaseTool<IResourceReference>
6470
'noEnvFound',
6571
);
6672
}
73+
this.extraTelemetryProperties.envType = getEnvTypeForTelemetry(environment);
6774

6875
let packages = '';
76+
let responsePackageCount = 0;
6977
if (useEnvExtension()) {
7078
const api = await getEnvExtApi();
7179
const env = await api.getEnvironment(resourcePath);
7280
const pkgs = env ? await api.getPackages(env) : [];
7381
if (pkgs && pkgs.length > 0) {
82+
responsePackageCount = pkgs.length;
7483
// Installed Python packages, each in the format <name> or <name> (<version>). The version may be omitted if unknown. Returns an empty array if no packages are installed.
7584
const response = [
7685
'Below is a list of the Python packages, each in the format <name> or <name> (<version>). The version may be omitted if unknown: ',
@@ -90,7 +99,10 @@ export class GetEnvironmentInfoTool extends BaseTool<IResourceReference>
9099
resourcePath,
91100
token,
92101
);
102+
// Count lines starting with '- ' to get the number of packages
103+
responsePackageCount = (packages.match(/^- /gm) || []).length;
93104
}
105+
this.extraTelemetryProperties.responsePackageCount = String(responsePackageCount);
94106
const message = await getEnvironmentDetails(
95107
resourcePath,
96108
this.api,

src/client/chat/installPackagesTool.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { PythonExtension } from '../api/types';
1616
import { IServiceContainer } from '../ioc/types';
1717
import {
1818
getEnvDisplayName,
19+
getEnvTypeForTelemetry,
1920
getToolResponseIfNotebook,
2021
IResourceReference,
2122
isCancellationError,
@@ -51,6 +52,7 @@ export class InstallPackagesTool extends BaseTool<IInstallPackageArgs>
5152
): Promise<LanguageModelToolResult> {
5253
const packageCount = options.input.packageList.length;
5354
const packagePlurality = packageCount === 1 ? 'package' : 'packages';
55+
this.extraTelemetryProperties.packageCount = String(packageCount);
5456
const notebookResponse = getToolResponseIfNotebook(resourcePath);
5557
if (notebookResponse) {
5658
return notebookResponse;
@@ -84,9 +86,11 @@ export class InstallPackagesTool extends BaseTool<IInstallPackageArgs>
8486
'noEnvFound',
8587
);
8688
}
89+
this.extraTelemetryProperties.envType = getEnvTypeForTelemetry(environment);
8790
const isConda = isCondaEnv(environment);
8891
const installers = this.serviceContainer.getAll<IModuleInstaller>(IModuleInstaller);
8992
const installerType = isConda ? ModuleInstallerType.Conda : ModuleInstallerType.Pip;
93+
this.extraTelemetryProperties.installerType = isConda ? 'conda' : 'pip';
9094
const installer = installers.find((i) => i.type === installerType);
9195
if (!installer) {
9296
throw new ErrorWithTelemetrySafeReason(

src/client/chat/utils.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ export function isCondaEnv(env: ResolvedEnvironment) {
7676
return (env.environment?.type || '').toLowerCase() === 'conda';
7777
}
7878

79+
export function getEnvTypeForTelemetry(env: ResolvedEnvironment): string {
80+
return (env.environment?.type || 'unknown').toLowerCase();
81+
}
82+
7983
export async function getEnvironmentDetails(
8084
resourcePath: Uri | undefined,
8185
api: PythonExtension['environments'],

src/client/telemetry/index.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1992,7 +1992,12 @@ export interface IEventNamePropertyMapping {
19921992
"duration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" },
19931993
"toolName" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" },
19941994
"failed": {"classification":"SystemMetaData","purpose":"FeatureInsight","comment":"Whether there was a failure. Common to most of the events.", "owner": "donjayamanne" },
1995-
"failureCategory": {"classification":"SystemMetaData","purpose":"FeatureInsight","comment":"A reason that we generate (e.g. kerneldied, noipykernel, etc), more like a category of the error. Common to most of the events.", "owner": "donjayamanne" }
1995+
"failureCategory": {"classification":"SystemMetaData","purpose":"FeatureInsight","comment":"A reason that we generate (e.g. kerneldied, noipykernel, etc), more like a category of the error. Common to most of the events.", "owner": "donjayamanne" },
1996+
"resolveOutcome": {"classification":"SystemMetaData","purpose":"FeatureInsight","comment":"Which code path resolved the environment in configure_python_environment.", "owner": "donjayamanne" },
1997+
"envType": {"classification":"SystemMetaData","purpose":"FeatureInsight","comment":"The type of Python environment (e.g. venv, conda, system).", "owner": "donjayamanne" },
1998+
"packageCount": {"classification":"SystemMetaData","purpose":"FeatureInsight","comment":"Number of packages requested for installation (install_python_packages only).", "owner": "donjayamanne" },
1999+
"installerType": {"classification":"SystemMetaData","purpose":"FeatureInsight","comment":"Which installer was used: pip or conda (install_python_packages only).", "owner": "donjayamanne" },
2000+
"responsePackageCount": {"classification":"SystemMetaData","purpose":"FeatureInsight","comment":"Number of packages in the environment response (get_python_environment_details only).", "owner": "donjayamanne" }
19962001
}
19972002
*/
19982003
[EventName.INVOKE_TOOL]: {
@@ -2009,6 +2014,26 @@ export interface IEventNamePropertyMapping {
20092014
* A reason the error was thrown.
20102015
*/
20112016
failureCategory?: string;
2017+
/**
2018+
* Which code path resolved the environment (configure_python_environment only).
2019+
*/
2020+
resolveOutcome?: string;
2021+
/**
2022+
* The type of Python environment (e.g. venv, conda, system).
2023+
*/
2024+
envType?: string;
2025+
/**
2026+
* Number of packages requested for installation (install_python_packages only).
2027+
*/
2028+
packageCount?: string;
2029+
/**
2030+
* Which installer was used: pip or conda (install_python_packages only).
2031+
*/
2032+
installerType?: string;
2033+
/**
2034+
* Number of packages in the environment response (get_python_environment_details only).
2035+
*/
2036+
responsePackageCount?: string;
20122037
};
20132038
/**
20142039
* Telemetry event sent if and when user configure tests command. This command can be trigerred from multiple places in the extension. (Command palette, prompt etc.)

0 commit comments

Comments
 (0)