Skip to content

Commit 9148f5f

Browse files
committed
add more telemetry
1 parent 76e73c9 commit 9148f5f

File tree

6 files changed

+159
-16
lines changed

6 files changed

+159
-16
lines changed

src/common/telemetry/constants.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,22 @@ export enum EventNames {
6666
* - failureStage: string (which phase was in progress when the watchdog fired)
6767
*/
6868
SETUP_HANG_DETECTED = 'SETUP.HANG_DETECTED',
69+
/**
70+
* Telemetry event for when a manager skips registration because its tool was not found.
71+
* This is an expected outcome (not an error) and is distinct from MANAGER_REGISTRATION_FAILED.
72+
* Properties:
73+
* - managerName: string (e.g. 'conda', 'pyenv', 'pipenv', 'poetry')
74+
* - reason: string ('tool_not_found')
75+
*/
76+
MANAGER_REGISTRATION_SKIPPED = 'MANAGER_REGISTRATION.SKIPPED',
77+
/**
78+
* Telemetry event fired after manager registration when PET discovered environments
79+
* of a kind whose corresponding manager did not register.
80+
* Properties:
81+
* - managerName: string (e.g. 'conda', 'pyenv', 'pipenv', 'poetry')
82+
* - petEnvCount: number (how many envs PET found for that kind)
83+
*/
84+
MANAGER_DISCOVERY_MISMATCH = 'MANAGER_DISCOVERY.MISMATCH',
6985
}
7086

7187
// Map all events to their properties
@@ -283,4 +299,26 @@ export interface IEventNamePropertyMapping {
283299
[EventNames.SETUP_HANG_DETECTED]: {
284300
failureStage: string;
285301
};
302+
303+
/* __GDPR__
304+
"manager_registration.skipped": {
305+
"managerName": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" },
306+
"reason": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" }
307+
}
308+
*/
309+
[EventNames.MANAGER_REGISTRATION_SKIPPED]: {
310+
managerName: string;
311+
reason: 'tool_not_found';
312+
};
313+
314+
/* __GDPR__
315+
"manager_discovery.mismatch": {
316+
"managerName": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" },
317+
"petEnvCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "eleanorjboyd" }
318+
}
319+
*/
320+
[EventNames.MANAGER_DISCOVERY_MISMATCH]: {
321+
managerName: string;
322+
petEnvCount: number;
323+
};
286324
}

src/extension.ts

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,13 +96,87 @@ import { collectEnvironmentInfo, getEnvManagerAndPackageManagerConfigLevels, run
9696
import { EnvironmentManagers, ProjectCreators, PythonProjectManager } from './internal.api';
9797
import { registerSystemPythonFeatures } from './managers/builtin/main';
9898
import { SysPythonManager } from './managers/builtin/sysPythonManager';
99-
import { createNativePythonFinder, NativePythonFinder } from './managers/common/nativePythonFinder';
99+
import {
100+
createNativePythonFinder,
101+
NativePythonEnvironmentKind,
102+
NativePythonFinder,
103+
} from './managers/common/nativePythonFinder';
100104
import { IDisposable } from './managers/common/types';
101105
import { registerCondaFeatures } from './managers/conda/main';
102106
import { registerPipenvFeatures } from './managers/pipenv/main';
103107
import { registerPoetryFeatures } from './managers/poetry/main';
104108
import { registerPyenvFeatures } from './managers/pyenv/main';
105109

110+
/**
111+
* Map from PET NativePythonEnvironmentKind to the manager name used in registration.
112+
* Only includes kinds that have a dedicated manager (not system/venv/etc.).
113+
*/
114+
const PET_KIND_TO_MANAGER: ReadonlyMap<NativePythonEnvironmentKind, string> = new Map([
115+
[NativePythonEnvironmentKind.conda, 'conda'],
116+
[NativePythonEnvironmentKind.pyenv, 'pyenv'],
117+
[NativePythonEnvironmentKind.pyenvVirtualEnv, 'pyenv'],
118+
[NativePythonEnvironmentKind.pipenv, 'pipenv'],
119+
[NativePythonEnvironmentKind.poetry, 'poetry'],
120+
]);
121+
122+
/**
123+
* Checks whether PET discovered environments whose corresponding manager did not register.
124+
* Uses a soft (cached) refresh so it doesn't trigger additional PET work.
125+
*/
126+
async function checkPetManagerMismatch(
127+
nativeFinder: NativePythonFinder,
128+
envManagers: EnvironmentManagers,
129+
): Promise<void> {
130+
const registeredIds = new Set(envManagers.managers.map((m) => m.id));
131+
132+
// Map manager names to their expected managerId prefix
133+
const managerIdPrefixes: ReadonlyMap<string, string> = new Map([
134+
['conda', 'ms-python.python:conda'],
135+
['pyenv', 'ms-python.python:pyenv'],
136+
['pipenv', 'ms-python.python:pipenv'],
137+
['poetry', 'ms-python.python:poetry'],
138+
]);
139+
140+
// Group PET kinds by manager name to avoid double-counting (e.g. pyenv + pyenvVirtualEnv)
141+
const kindsByManager = new Map<string, NativePythonEnvironmentKind[]>();
142+
for (const [kind, managerName] of PET_KIND_TO_MANAGER) {
143+
const existing = kindsByManager.get(managerName) ?? [];
144+
existing.push(kind);
145+
kindsByManager.set(managerName, existing);
146+
}
147+
148+
for (const [managerName, kinds] of kindsByManager) {
149+
const expectedPrefix = managerIdPrefixes.get(managerName);
150+
if (!expectedPrefix) {
151+
continue;
152+
}
153+
154+
// Check if the corresponding manager registered
155+
const isRegistered = Array.from(registeredIds).some((id) => id.startsWith(expectedPrefix));
156+
if (isRegistered) {
157+
continue;
158+
}
159+
160+
// Manager not registered — check if PET found environments of any related kind
161+
let totalPetEnvs = 0;
162+
for (const kind of kinds) {
163+
try {
164+
const petEnvs = await nativeFinder.refresh(false, kind);
165+
totalPetEnvs += petEnvs.length;
166+
} catch {
167+
// PET query failed — don't block post-init; skip this kind
168+
}
169+
}
170+
171+
if (totalPetEnvs > 0) {
172+
sendTelemetryEvent(EventNames.MANAGER_DISCOVERY_MISMATCH, undefined, {
173+
managerName,
174+
petEnvCount: totalPetEnvs,
175+
});
176+
}
177+
}
178+
}
179+
106180
export async function activate(context: ExtensionContext): Promise<PythonEnvironmentApi | undefined> {
107181
// Only skip activation if user explicitly set useEnvironmentsExtension to false.
108182
// When disabled, the main Python extension handles environments instead (legacy mode).
@@ -594,6 +668,9 @@ export async function activate(context: ExtensionContext): Promise<PythonEnviron
594668

595669
// Log discovery summary to help users troubleshoot environment detection issues
596670
await logDiscoverySummary(envManagers);
671+
672+
// Check for PET-vs-manager mismatches (PET found envs but manager didn't register)
673+
await checkPetManagerMismatch(nativeFinder, envManagers);
597674
} catch (postInitError) {
598675
traceError('Post-initialization tasks failed:', postInitError);
599676
}

src/managers/conda/main.ts

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { Disposable, LogOutputChannel } from 'vscode';
22
import { PythonEnvironmentApi } from '../../api';
33
import { traceInfo } from '../../common/logging';
4+
import { EventNames } from '../../common/telemetry/constants';
5+
import { sendTelemetryEvent } from '../../common/telemetry/sender';
46
import { getPythonApi } from '../../features/pythonApi';
57
import { PythonProjectManager } from '../../internal.api';
68
import { NativePythonFinder } from '../common/nativePythonFinder';
@@ -18,25 +20,33 @@ export async function registerCondaFeatures(
1820
): Promise<void> {
1921
const api: PythonEnvironmentApi = await getPythonApi();
2022

23+
let condaPath: string | undefined;
2124
try {
2225
// get Conda will return only ONE conda manager, that correlates to a single conda install
23-
const condaPath: string = await getConda(nativeFinder);
24-
const sourcingStatus: CondaSourcingStatus = await constructCondaSourcingStatus(condaPath);
25-
traceInfo(sourcingStatus.toString());
26-
27-
const envManager = new CondaEnvManager(nativeFinder, api, log);
28-
const packageManager = new CondaPackageManager(api, log);
29-
30-
envManager.sourcingInformation = sourcingStatus;
31-
32-
disposables.push(
33-
envManager,
34-
packageManager,
35-
api.registerEnvironmentManager(envManager),
36-
api.registerPackageManager(packageManager),
37-
);
26+
condaPath = await getConda(nativeFinder);
3827
} catch (ex) {
3928
traceInfo('Conda not found, turning off conda features.', ex);
29+
sendTelemetryEvent(EventNames.MANAGER_REGISTRATION_SKIPPED, undefined, {
30+
managerName: 'conda',
31+
reason: 'tool_not_found',
32+
});
4033
await notifyMissingManagerIfDefault('ms-python.python:conda', projectManager, api);
34+
return;
4135
}
36+
37+
// Conda was found — errors below are real registration failures (let safeRegister handle them)
38+
const sourcingStatus: CondaSourcingStatus = await constructCondaSourcingStatus(condaPath);
39+
traceInfo(sourcingStatus.toString());
40+
41+
const envManager = new CondaEnvManager(nativeFinder, api, log);
42+
const packageManager = new CondaPackageManager(api, log);
43+
44+
envManager.sourcingInformation = sourcingStatus;
45+
46+
disposables.push(
47+
envManager,
48+
packageManager,
49+
api.registerEnvironmentManager(envManager),
50+
api.registerPackageManager(packageManager),
51+
);
4252
}

src/managers/pipenv/main.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { Disposable } from 'vscode';
22
import { PythonEnvironmentApi } from '../../api';
33
import { traceInfo } from '../../common/logging';
4+
import { EventNames } from '../../common/telemetry/constants';
5+
import { sendTelemetryEvent } from '../../common/telemetry/sender';
46
import { getPythonApi } from '../../features/pythonApi';
57
import { PythonProjectManager } from '../../internal.api';
68
import { NativePythonFinder } from '../common/nativePythonFinder';
@@ -35,6 +37,10 @@ export async function registerPipenvFeatures(
3537
traceInfo(
3638
'Pipenv not found, turning off pipenv features. If you have pipenv installed in a non-standard location, set the "python.pipenvPath" setting.',
3739
);
40+
sendTelemetryEvent(EventNames.MANAGER_REGISTRATION_SKIPPED, undefined, {
41+
managerName: 'pipenv',
42+
reason: 'tool_not_found',
43+
});
3844
await notifyMissingManagerIfDefault('ms-python.python:pipenv', projectManager, api);
3945
}
4046
} catch (ex) {

src/managers/poetry/main.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { Disposable, LogOutputChannel } from 'vscode';
22
import { PythonEnvironmentApi } from '../../api';
33
import { traceInfo } from '../../common/logging';
4+
import { EventNames } from '../../common/telemetry/constants';
5+
import { sendTelemetryEvent } from '../../common/telemetry/sender';
46
import { getPythonApi } from '../../features/pythonApi';
57
import { PythonProjectManager } from '../../internal.api';
68
import { NativePythonFinder } from '../common/nativePythonFinder';
@@ -36,6 +38,10 @@ export async function registerPoetryFeatures(
3638
);
3739
} else {
3840
traceInfo('Poetry not found, turning off poetry features.');
41+
sendTelemetryEvent(EventNames.MANAGER_REGISTRATION_SKIPPED, undefined, {
42+
managerName: 'poetry',
43+
reason: 'tool_not_found',
44+
});
3945
await notifyMissingManagerIfDefault('ms-python.python:poetry', projectManager, api);
4046
}
4147
} catch (ex) {

src/managers/pyenv/main.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { Disposable } from 'vscode';
22
import { PythonEnvironmentApi } from '../../api';
33
import { traceInfo } from '../../common/logging';
4+
import { EventNames } from '../../common/telemetry/constants';
5+
import { sendTelemetryEvent } from '../../common/telemetry/sender';
46
import { getPythonApi } from '../../features/pythonApi';
57
import { PythonProjectManager } from '../../internal.api';
68
import { NativePythonFinder } from '../common/nativePythonFinder';
@@ -23,6 +25,10 @@ export async function registerPyenvFeatures(
2325
disposables.push(mgr, api.registerEnvironmentManager(mgr));
2426
} else {
2527
traceInfo('Pyenv not found, turning off pyenv features.');
28+
sendTelemetryEvent(EventNames.MANAGER_REGISTRATION_SKIPPED, undefined, {
29+
managerName: 'pyenv',
30+
reason: 'tool_not_found',
31+
});
2632
await notifyMissingManagerIfDefault('ms-python.python:pyenv', projectManager, api);
2733
}
2834
} catch (ex) {

0 commit comments

Comments
 (0)