Skip to content

Commit 2920829

Browse files
Get native finder telemetry (#23646)
Closes #23565 --------- Co-authored-by: Don Jayamanne <[email protected]>
1 parent 54b64c0 commit 2920829

File tree

8 files changed

+324
-127
lines changed

8 files changed

+324
-127
lines changed

src/client/common/vscodeApis/windowApis.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import {
1919
QuickPickItemButtonEvent,
2020
Uri,
2121
TerminalShellExecutionStartEvent,
22+
LogOutputChannel,
23+
OutputChannel,
2224
} from 'vscode';
2325
import { createDeferred, Deferred } from '../utils/async';
2426
import { Resource } from '../types';
@@ -249,3 +251,10 @@ export function getActiveResource(): Resource {
249251
const workspaces = getWorkspaceFolders();
250252
return Array.isArray(workspaces) && workspaces.length > 0 ? workspaces[0].uri : undefined;
251253
}
254+
255+
export function createOutputChannel(name: string, languageId?: string): OutputChannel {
256+
return window.createOutputChannel(name, languageId);
257+
}
258+
export function createLogOutputChannel(name: string, options: { log: true }): LogOutputChannel {
259+
return window.createOutputChannel(name, options);
260+
}

src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts

Lines changed: 68 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4-
import { Disposable, EventEmitter, Event, workspace, window, Uri } from 'vscode';
4+
import { Disposable, EventEmitter, Event, Uri } from 'vscode';
55
import * as ch from 'child_process';
66
import * as path from 'path';
77
import * as rpc from 'vscode-jsonrpc/node';
@@ -12,10 +12,16 @@ import { createDeferred, createDeferredFrom } from '../../../../common/utils/asy
1212
import { DisposableBase, DisposableStore } from '../../../../common/utils/resourceLifecycle';
1313
import { DEFAULT_INTERPRETER_PATH_SETTING_KEY } from '../lowLevel/customWorkspaceLocator';
1414
import { noop } from '../../../../common/utils/misc';
15-
import { getConfiguration } from '../../../../common/vscodeApis/workspaceApis';
15+
import {
16+
getConfiguration,
17+
getWorkspaceFolderPaths,
18+
getWorkspaceFolders,
19+
} from '../../../../common/vscodeApis/workspaceApis';
1620
import { CONDAPATH_SETTING_KEY } from '../../../common/environmentManagers/conda';
1721
import { VENVFOLDERS_SETTING_KEY, VENVPATH_SETTING_KEY } from '../lowLevel/customVirtualEnvLocator';
1822
import { getUserHomeDir } from '../../../../common/utils/platform';
23+
import { createLogOutputChannel } from '../../../../common/vscodeApis/windowApis';
24+
import { PythonEnvKind } from '../../info';
1925

2026
const untildify = require('untildify');
2127

@@ -48,6 +54,7 @@ export interface NativeEnvManagerInfo {
4854
export interface NativeGlobalPythonFinder extends Disposable {
4955
resolve(executable: string): Promise<NativeEnvInfo>;
5056
refresh(): AsyncIterable<NativeEnvInfo>;
57+
categoryToKind(category: string): PythonEnvKind;
5158
}
5259

5360
interface NativeLog {
@@ -60,7 +67,7 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGloba
6067

6168
private firstRefreshResults: undefined | (() => AsyncGenerator<NativeEnvInfo, void, unknown>);
6269

63-
private readonly outputChannel = this._register(window.createOutputChannel('Python Locator', { log: true }));
70+
private readonly outputChannel = this._register(createLogOutputChannel('Python Locator', { log: true }));
6471

6572
constructor() {
6673
super();
@@ -80,6 +87,40 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGloba
8087
return environment;
8188
}
8289

90+
categoryToKind(category: string): PythonEnvKind {
91+
switch (category.toLowerCase()) {
92+
case 'conda':
93+
return PythonEnvKind.Conda;
94+
case 'system':
95+
case 'homebrew':
96+
case 'mac-python-org':
97+
case 'mac-command-line-tools':
98+
case 'windows-registry':
99+
return PythonEnvKind.System;
100+
case 'pyenv':
101+
case 'pyenv-other':
102+
return PythonEnvKind.Pyenv;
103+
case 'pipenv':
104+
return PythonEnvKind.Pipenv;
105+
case 'pyenv-virtualenv':
106+
return PythonEnvKind.VirtualEnv;
107+
case 'venv':
108+
return PythonEnvKind.Venv;
109+
case 'virtualenv':
110+
return PythonEnvKind.VirtualEnv;
111+
case 'virtualenvwrapper':
112+
return PythonEnvKind.VirtualEnvWrapper;
113+
case 'windows-store':
114+
return PythonEnvKind.MicrosoftStore;
115+
case 'unknown':
116+
return PythonEnvKind.Unknown;
117+
default: {
118+
this.outputChannel.info(`Unknown Python Environment category '${category}' from Native Locator.`);
119+
return PythonEnvKind.Unknown;
120+
}
121+
}
122+
}
123+
83124
async *refresh(): AsyncIterable<NativeEnvInfo> {
84125
if (this.firstRefreshResults) {
85126
// If this is the first time we are refreshing,
@@ -154,16 +195,33 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGloba
154195
// eslint-disable-next-line class-methods-use-this
155196
private start(): rpc.MessageConnection {
156197
this.outputChannel.info(`Starting Python Locator ${PYTHON_ENV_TOOLS_PATH} server`);
157-
const proc = ch.spawn(PYTHON_ENV_TOOLS_PATH, ['server'], { env: process.env });
158-
const disposables: Disposable[] = [];
198+
159199
// jsonrpc package cannot handle messages coming through too quickly.
160200
// Lets handle the messages and close the stream only when
161201
// we have got the exit event.
162202
const readable = new PassThrough();
163-
proc.stdout.pipe(readable, { end: false });
164-
proc.stderr.on('data', (data) => this.outputChannel.error(data.toString()));
165203
const writable = new PassThrough();
166-
writable.pipe(proc.stdin, { end: false });
204+
const disposables: Disposable[] = [];
205+
try {
206+
const proc = ch.spawn(PYTHON_ENV_TOOLS_PATH, ['server'], { env: process.env });
207+
proc.stdout.pipe(readable, { end: false });
208+
proc.stderr.on('data', (data) => this.outputChannel.error(data.toString()));
209+
writable.pipe(proc.stdin, { end: false });
210+
211+
disposables.push({
212+
dispose: () => {
213+
try {
214+
if (proc.exitCode === null) {
215+
proc.kill();
216+
}
217+
} catch (ex) {
218+
this.outputChannel.error('Error disposing finder', ex);
219+
}
220+
},
221+
});
222+
} catch (ex) {
223+
this.outputChannel.error(`Error starting Python Finder ${PYTHON_ENV_TOOLS_PATH} server`, ex);
224+
}
167225
const disposeStreams = new Disposable(() => {
168226
readable.end();
169227
writable.end();
@@ -200,17 +258,6 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGloba
200258
connection.onClose(() => {
201259
disposables.forEach((d) => d.dispose());
202260
}),
203-
{
204-
dispose: () => {
205-
try {
206-
if (proc.exitCode === null) {
207-
proc.kill();
208-
}
209-
} catch (ex) {
210-
this.outputChannel.error('Error disposing finder', ex);
211-
}
212-
},
213-
},
214261
);
215262

216263
connection.listen();
@@ -286,7 +333,7 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGloba
286333
}
287334

288335
private sendRefreshRequest() {
289-
const pythonPathSettings = (workspace.workspaceFolders || []).map((w) =>
336+
const pythonPathSettings = (getWorkspaceFolders() || []).map((w) =>
290337
getPythonSettingAndUntildify<string>(DEFAULT_INTERPRETER_PATH_SETTING_KEY, w.uri),
291338
);
292339
pythonPathSettings.push(getPythonSettingAndUntildify<string>(DEFAULT_INTERPRETER_PATH_SETTING_KEY));
@@ -308,7 +355,7 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGloba
308355
{
309356
// This has a special meaning in locator, its lot a low priority
310357
// as we treat this as workspace folders that can contain a large number of files.
311-
search_paths: (workspace.workspaceFolders || []).map((w) => w.uri.fsPath),
358+
search_paths: getWorkspaceFolderPaths(),
312359
// Also send the python paths that are configured in the settings.
313360
python_interpreter_paths: pythonSettings,
314361
// We do not want to mix this with `search_paths`

src/client/pythonEnvironments/base/locators/composite/envsCollectionService.ts

Lines changed: 88 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
import { getQueryFilter } from '../../locatorUtils';
2525
import { PythonEnvCollectionChangedEvent, PythonEnvsWatcher } from '../../watcher';
2626
import { IEnvsCollectionCache } from './envsCollectionCache';
27+
import { createNativeGlobalPythonFinder } from '../common/nativePythonFinder';
2728

2829
/**
2930
* A service which maintains the collection of known environments.
@@ -43,6 +44,8 @@ export class EnvsCollectionService extends PythonEnvsWatcher<PythonEnvCollection
4344

4445
private readonly progress = new EventEmitter<ProgressNotificationEvent>();
4546

47+
private nativeFinder = createNativeGlobalPythonFinder();
48+
4649
public refreshState = ProgressReportStage.discoveryFinished;
4750

4851
public get onProgress(): Event<ProgressNotificationEvent> {
@@ -54,11 +57,7 @@ export class EnvsCollectionService extends PythonEnvsWatcher<PythonEnvCollection
5457
return this.progressPromises.get(stage)?.promise;
5558
}
5659

57-
constructor(
58-
private readonly cache: IEnvsCollectionCache,
59-
private readonly locator: IResolvingLocator,
60-
private readonly usingNativeLocator: boolean,
61-
) {
60+
constructor(private readonly cache: IEnvsCollectionCache, private readonly locator: IResolvingLocator) {
6261
super();
6362
this.locator.onChanged((event) => {
6463
const query: PythonLocatorQuery | undefined = event.providerId
@@ -260,9 +259,21 @@ export class EnvsCollectionService extends PythonEnvsWatcher<PythonEnvCollection
260259
}
261260
}
262261

263-
private sendTelemetry(query: PythonLocatorQuery | undefined, stopWatch: StopWatch) {
262+
private telemetrySentOnce = false;
263+
264+
private async sendTelemetry(query: PythonLocatorQuery | undefined, stopWatch: StopWatch) {
265+
if (this.telemetrySentOnce) {
266+
return;
267+
}
268+
this.telemetrySentOnce = true;
264269
if (!query && !this.hasRefreshFinished(query)) {
265270
const envs = this.cache.getAllEnvs();
271+
272+
const nativeEnvs = [];
273+
for await (const data of this.nativeFinder.refresh()) {
274+
nativeEnvs.push(data);
275+
}
276+
266277
const environmentsWithoutPython = envs.filter(
267278
(e) => getEnvPath(e.executable.filename, e.location).pathType === 'envFolderPath',
268279
).length;
@@ -281,11 +292,65 @@ export class EnvsCollectionService extends PythonEnvsWatcher<PythonEnvCollection
281292
const venvEnvs = envs.filter((e) => e.kind === PythonEnvKind.Venv).length;
282293
const virtualEnvEnvs = envs.filter((e) => e.kind === PythonEnvKind.VirtualEnv).length;
283294
const virtualEnvWrapperEnvs = envs.filter((e) => e.kind === PythonEnvKind.VirtualEnvWrapper).length;
295+
const global = envs.filter(
296+
(e) =>
297+
e.kind === PythonEnvKind.OtherGlobal ||
298+
e.kind === PythonEnvKind.System ||
299+
e.kind === PythonEnvKind.Custom ||
300+
e.kind === PythonEnvKind.OtherVirtual,
301+
).length;
302+
303+
const nativeEnvironmentsWithoutPython = nativeEnvs.filter((e) => e.executable === undefined).length;
304+
const nativeCondaEnvs = nativeEnvs.filter(
305+
(e) => this.nativeFinder.categoryToKind(e.category) === PythonEnvKind.Conda,
306+
).length;
307+
const nativeCustomEnvs = nativeEnvs.filter(
308+
(e) => this.nativeFinder.categoryToKind(e.category) === PythonEnvKind.Custom,
309+
).length;
310+
const nativeMicrosoftStoreEnvs = nativeEnvs.filter(
311+
(e) => this.nativeFinder.categoryToKind(e.category) === PythonEnvKind.MicrosoftStore,
312+
).length;
313+
const nativeOtherGlobalEnvs = nativeEnvs.filter(
314+
(e) => this.nativeFinder.categoryToKind(e.category) === PythonEnvKind.OtherGlobal,
315+
).length;
316+
const nativeOtherVirtualEnvs = nativeEnvs.filter(
317+
(e) => this.nativeFinder.categoryToKind(e.category) === PythonEnvKind.OtherVirtual,
318+
).length;
319+
const nativePipEnvEnvs = nativeEnvs.filter(
320+
(e) => this.nativeFinder.categoryToKind(e.category) === PythonEnvKind.Pipenv,
321+
).length;
322+
const nativePoetryEnvs = nativeEnvs.filter(
323+
(e) => this.nativeFinder.categoryToKind(e.category) === PythonEnvKind.Poetry,
324+
).length;
325+
const nativePyenvEnvs = nativeEnvs.filter(
326+
(e) => this.nativeFinder.categoryToKind(e.category) === PythonEnvKind.Pyenv,
327+
).length;
328+
const nativeSystemEnvs = nativeEnvs.filter(
329+
(e) => this.nativeFinder.categoryToKind(e.category) === PythonEnvKind.System,
330+
).length;
331+
const nativeUnknownEnvs = nativeEnvs.filter(
332+
(e) => this.nativeFinder.categoryToKind(e.category) === PythonEnvKind.Unknown,
333+
).length;
334+
const nativeVenvEnvs = nativeEnvs.filter(
335+
(e) => this.nativeFinder.categoryToKind(e.category) === PythonEnvKind.Venv,
336+
).length;
337+
const nativeVirtualEnvEnvs = nativeEnvs.filter(
338+
(e) => this.nativeFinder.categoryToKind(e.category) === PythonEnvKind.VirtualEnv,
339+
).length;
340+
const nativeVirtualEnvWrapperEnvs = nativeEnvs.filter(
341+
(e) => this.nativeFinder.categoryToKind(e.category) === PythonEnvKind.VirtualEnvWrapper,
342+
).length;
343+
const nativeGlobal = nativeEnvs.filter(
344+
(e) =>
345+
this.nativeFinder.categoryToKind(e.category) === PythonEnvKind.OtherGlobal ||
346+
this.nativeFinder.categoryToKind(e.category) === PythonEnvKind.System ||
347+
this.nativeFinder.categoryToKind(e.category) === PythonEnvKind.Custom ||
348+
this.nativeFinder.categoryToKind(e.category) === PythonEnvKind.OtherVirtual,
349+
).length;
284350

285351
// Intent is to capture time taken for discovery of all envs to complete the first time.
286352
sendTelemetryEvent(EventName.PYTHON_INTERPRETER_DISCOVERY, stopWatch.elapsedTime, {
287353
interpreters: this.cache.getAllEnvs().length,
288-
usingNativeLocator: this.usingNativeLocator,
289354
environmentsWithoutPython,
290355
activeStateEnvs,
291356
condaEnvs,
@@ -302,6 +367,22 @@ export class EnvsCollectionService extends PythonEnvsWatcher<PythonEnvCollection
302367
venvEnvs,
303368
virtualEnvEnvs,
304369
virtualEnvWrapperEnvs,
370+
global,
371+
nativeEnvironmentsWithoutPython,
372+
nativeCondaEnvs,
373+
nativeCustomEnvs,
374+
nativeMicrosoftStoreEnvs,
375+
nativeOtherGlobalEnvs,
376+
nativeOtherVirtualEnvs,
377+
nativePipEnvEnvs,
378+
nativePoetryEnvs,
379+
nativePyenvEnvs,
380+
nativeSystemEnvs,
381+
nativeUnknownEnvs,
382+
nativeVenvEnvs,
383+
nativeVirtualEnvEnvs,
384+
nativeVirtualEnvWrapperEnvs,
385+
nativeGlobal,
305386
});
306387
}
307388
this.hasRefreshFinishedForQuery.set(query, true);

src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts

Lines changed: 2 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Disposable, Event, EventEmitter, Uri } from 'vscode';
55
import { IDisposable } from '../../../../common/types';
66
import { ILocator, BasicEnvInfo, IPythonEnvsIterator } from '../../locator';
77
import { PythonEnvsChangedEvent } from '../../watcher';
8-
import { PythonEnvKind, PythonVersion } from '../../info';
8+
import { PythonVersion } from '../../info';
99
import { Conda } from '../../../common/environmentManagers/conda';
1010
import { traceError } from '../../../../logging';
1111
import type { KnownEnvironmentTools } from '../../../../api/types';
@@ -14,40 +14,6 @@ import { NativeGlobalPythonFinder, createNativeGlobalPythonFinder } from '../com
1414
import { disposeAll } from '../../../../common/utils/resourceLifecycle';
1515
import { Architecture } from '../../../../common/utils/platform';
1616

17-
function categoryToKind(category: string): PythonEnvKind {
18-
switch (category.toLowerCase()) {
19-
case 'conda':
20-
return PythonEnvKind.Conda;
21-
case 'system':
22-
case 'homebrew':
23-
case 'mac-python-org':
24-
case 'mac-command-line-tools':
25-
case 'windows-registry':
26-
return PythonEnvKind.System;
27-
case 'pyenv':
28-
case 'pyenv-other':
29-
return PythonEnvKind.Pyenv;
30-
case 'pipenv':
31-
return PythonEnvKind.Pipenv;
32-
case 'pyenv-virtualenv':
33-
return PythonEnvKind.VirtualEnv;
34-
case 'venv':
35-
return PythonEnvKind.Venv;
36-
case 'virtualenv':
37-
return PythonEnvKind.VirtualEnv;
38-
case 'virtualenvwrapper':
39-
return PythonEnvKind.VirtualEnvWrapper;
40-
case 'windows-store':
41-
return PythonEnvKind.MicrosoftStore;
42-
case 'unknown':
43-
return PythonEnvKind.Unknown;
44-
default: {
45-
traceError(`Unknown Python Environment category '${category}' from Native Locator.`);
46-
return PythonEnvKind.Unknown;
47-
}
48-
}
49-
}
50-
5117
function toolToKnownEnvironmentTool(tool: string): KnownEnvironmentTools {
5218
switch (tool.toLowerCase()) {
5319
case 'conda':
@@ -124,7 +90,7 @@ export class NativeLocator implements ILocator<BasicEnvInfo>, IDisposable {
12490
if (data.executable) {
12591
const arch = (data.arch || '').toLowerCase();
12692
const env: BasicEnvInfo = {
127-
kind: categoryToKind(data.category),
93+
kind: this.finder.categoryToKind(data.category),
12894
executablePath: data.executable ? data.executable : '',
12995
envPath: data.prefix ? data.prefix : undefined,
13096
version: data.version ? parseVersion(data.version) : undefined,

0 commit comments

Comments
 (0)