11import { getDefaultEnvManagerSetting , getDefaultPkgManagerSetting } from '../../features/settings/settingHelpers' ;
22import { EnvironmentManagers , PythonProjectManager } from '../../internal.api' ;
3+ import { getUvEnvironments } from '../../managers/builtin/uvEnvironments' ;
4+ import { traceVerbose } from '../logging' ;
35import { getWorkspaceFolders } from '../workspace.apis' ;
46import { EventNames } from './constants' ;
57import { sendTelemetryEvent } from './sender' ;
68
9+ /**
10+ * Extracts the base tool name from a manager ID.
11+ * Example: 'ms-python.python:venv' -> 'venv'
12+ * Example: 'ms-python.python:conda' -> 'conda'
13+ */
14+ function extractToolName ( managerId : string ) : string {
15+ // Manager IDs follow the pattern 'extensionId:toolName'
16+ const parts = managerId . split ( ':' ) ;
17+ return parts . length > 1 ? parts [ 1 ] . toLowerCase ( ) : managerId . toLowerCase ( ) ;
18+ }
19+
720export function sendManagerSelectionTelemetry ( pm : PythonProjectManager ) {
821 const ems : Set < string > = new Set ( ) ;
922 const ps : Set < string > = new Set ( ) ;
@@ -58,7 +71,7 @@ export async function sendProjectStructureTelemetry(
5871 for ( const wsFolder of workspaceFolders ) {
5972 const workspacePath = wsFolder . uri . fsPath ;
6073 const projectPath = project . uri . fsPath ;
61-
74+
6275 // Check if project is a subdirectory of workspace folder:
6376 // - Path must start with workspace path
6477 // - Path must not be equal to workspace path
@@ -80,3 +93,64 @@ export async function sendProjectStructureTelemetry(
8093 projectUnderRoot,
8194 } ) ;
8295}
96+
97+ /**
98+ * Sends telemetry about which environment tools are actively used across all projects.
99+ * This tracks ACTUAL USAGE (which environments are set for projects), not just what's installed.
100+ *
101+ * Fires one event per tool that has at least one project using it.
102+ * This allows simple deduplication: dcount(machineId) by toolName gives unique users per tool.
103+ *
104+ * Called once at extension activation to understand user's environment tool usage patterns.
105+ */
106+ export async function sendEnvironmentToolUsageTelemetry (
107+ pm : PythonProjectManager ,
108+ envManagers : EnvironmentManagers ,
109+ ) : Promise < void > {
110+ try {
111+ const projects = pm . getProjects ( ) ;
112+
113+ // Track which tools are used (Set ensures uniqueness)
114+ const toolsUsed = new Set < string > ( ) ;
115+
116+ // Lazily loaded once when a venv environment is first encountered
117+ let uvEnvPaths : string [ ] | undefined ;
118+
119+ // Check which environment manager is used for each project
120+ for ( const project of projects ) {
121+ try {
122+ const env = await envManagers . getEnvironment ( project . uri ) ;
123+ if ( env ?. envId ?. managerId ) {
124+ let toolName = extractToolName ( env . envId . managerId ) ;
125+
126+ // UV environments share the venv manager. Check the persistent UV env list instead
127+ if ( toolName === 'venv' && env . environmentPath ) {
128+ uvEnvPaths ??= await getUvEnvironments ( ) ;
129+ if ( uvEnvPaths . includes ( env . environmentPath . fsPath ) ) {
130+ toolName = 'uv' ;
131+ }
132+ }
133+
134+ // Normalize 'global' to 'system' for consistency
135+ if ( toolName === 'global' ) {
136+ toolName = 'system' ;
137+ }
138+
139+ toolsUsed . add ( toolName ) ;
140+ }
141+ } catch {
142+ // Ignore errors when getting environment for a project
143+ }
144+ }
145+
146+ // Fire one event per tool used
147+ toolsUsed . forEach ( ( tool ) => {
148+ sendTelemetryEvent ( EventNames . ENVIRONMENT_TOOL_USAGE , undefined , {
149+ toolName : tool ,
150+ } ) ;
151+ } ) ;
152+ } catch ( error ) {
153+ // Telemetry failures must never disrupt extension activation
154+ traceVerbose ( 'Failed to send environment tool usage telemetry:' , error ) ;
155+ }
156+ }
0 commit comments