Skip to content

Commit d563d5b

Browse files
committed
add telemetry
1 parent 7dfce4b commit d563d5b

3 files changed

Lines changed: 85 additions & 2 deletions

File tree

src/common/telemetry/constants.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ 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 tool usage at extension startup.
37+
* Fires once per tool that has at least one project using it.
38+
* Use dcount(machineId) by toolName to get unique users per tool.
39+
* Properties:
40+
* - toolName: string (the tool being used: venv, conda, poetry, etc.)
41+
*/
42+
ENVIRONMENT_TOOL_USAGE = 'ENVIRONMENT_TOOL_USAGE',
3543
}
3644

3745
// Map all events to their properties
@@ -180,4 +188,13 @@ export interface IEventNamePropertyMapping {
180188
uniqueInterpreterCount: number;
181189
projectUnderRoot: number;
182190
};
191+
192+
/* __GDPR__
193+
"environment_tool_usage": {
194+
"toolName": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "stellaHuang95" }
195+
}
196+
*/
197+
[EventNames.ENVIRONMENT_TOOL_USAGE]: {
198+
toolName: string;
199+
};
183200
}

src/common/telemetry/helpers.ts

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@ import { getWorkspaceFolders } from '../workspace.apis';
44
import { EventNames } from './constants';
55
import { sendTelemetryEvent } from './sender';
66

7+
/**
8+
* Extracts the base tool name from a manager ID.
9+
* Example: 'ms-python.python:venv' -> 'venv'
10+
* Example: 'ms-python.python:conda' -> 'conda'
11+
*/
12+
function extractToolName(managerId: string): string {
13+
// Manager IDs follow the pattern 'extensionId:toolName'
14+
const parts = managerId.split(':');
15+
return parts.length > 1 ? parts[1].toLowerCase() : managerId.toLowerCase();
16+
}
17+
718
export function sendManagerSelectionTelemetry(pm: PythonProjectManager) {
819
const ems: Set<string> = new Set();
920
const ps: Set<string> = new Set();
@@ -58,7 +69,7 @@ export async function sendProjectStructureTelemetry(
5869
for (const wsFolder of workspaceFolders) {
5970
const workspacePath = wsFolder.uri.fsPath;
6071
const projectPath = project.uri.fsPath;
61-
72+
6273
// Check if project is a subdirectory of workspace folder:
6374
// - Path must start with workspace path
6475
// - Path must not be equal to workspace path
@@ -80,3 +91,53 @@ export async function sendProjectStructureTelemetry(
8091
projectUnderRoot,
8192
});
8293
}
94+
95+
/**
96+
* Sends telemetry about which environment tools are actively used across all projects.
97+
* This tracks ACTUAL USAGE (which environments are set for projects), not just what's installed.
98+
*
99+
* Fires one event per tool that has at least one project using it.
100+
* This allows simple deduplication: dcount(machineId) by toolName gives unique users per tool.
101+
*
102+
* Called once at extension activation to understand user's environment tool usage patterns.
103+
*/
104+
export async function sendEnvironmentToolUsageTelemetry(
105+
pm: PythonProjectManager,
106+
envManagers: EnvironmentManagers,
107+
): Promise<void> {
108+
const projects = pm.getProjects();
109+
110+
// Track which tools are used (Set ensures uniqueness)
111+
const toolsUsed = new Set<string>();
112+
113+
// Check which environment manager is used for each project
114+
for (const project of projects) {
115+
try {
116+
const env = await envManagers.getEnvironment(project.uri);
117+
if (env?.envId?.managerId) {
118+
const toolName = extractToolName(env.envId.managerId);
119+
120+
// Check if this is a UV environment (UV uses venv manager but has 'uv' in description)
121+
const isUv = env.description?.toLowerCase().includes('uv') ?? false;
122+
123+
// Determine the tool name
124+
if (isUv) {
125+
toolsUsed.add('uv');
126+
} else {
127+
// Normalize 'global' to 'system' for consistency
128+
const normalizedTool = toolName === 'global' ? 'system' : toolName;
129+
toolsUsed.add(normalizedTool);
130+
}
131+
}
132+
} catch {
133+
// Ignore errors when getting environment for a project
134+
}
135+
}
136+
137+
// Fire one event per tool used
138+
toolsUsed.forEach((tool) => {
139+
sendTelemetryEvent(EventNames.ENVIRONMENT_TOOL_USAGE, undefined, {
140+
toolName: tool,
141+
});
142+
});
143+
}

src/extension.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ import { clearPersistentState, setPersistentState } from './common/persistentSta
1717
import { newProjectSelection } from './common/pickers/managers';
1818
import { StopWatch } from './common/stopWatch';
1919
import { EventNames } from './common/telemetry/constants';
20-
import { sendManagerSelectionTelemetry, sendProjectStructureTelemetry } from './common/telemetry/helpers';
20+
import {
21+
sendEnvironmentToolUsageTelemetry,
22+
sendManagerSelectionTelemetry,
23+
sendProjectStructureTelemetry,
24+
} from './common/telemetry/helpers';
2125
import { sendTelemetryEvent } from './common/telemetry/sender';
2226
import { createDeferred } from './common/utils/deferred';
2327

@@ -545,6 +549,7 @@ export async function activate(context: ExtensionContext): Promise<PythonEnviron
545549
await terminalManager.initialize(api);
546550
sendManagerSelectionTelemetry(projectManager);
547551
await sendProjectStructureTelemetry(projectManager, envManagers);
552+
await sendEnvironmentToolUsageTelemetry(projectManager, envManagers);
548553
} catch (error) {
549554
traceError('Failed to initialize environment managers:', error);
550555
// Show a user-friendly error message

0 commit comments

Comments
 (0)