Skip to content

Commit 9976f85

Browse files
authored
Add telemetry for failed activation and searching kernel (microsoft#9158)
* Add some telemetry * Fix linter * Changes to wait for idle * Remove * Fix linter * Increase timeout
1 parent 149881d commit 9976f85

9 files changed

Lines changed: 74 additions & 14 deletions

File tree

src/client/datascience/constants.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,8 @@ export enum Telemetry {
222222
RegisterInterpreterAsKernel = 'DATASCIENCE.JUPYTER_REGISTER_INTERPRETER_AS_KERNEL',
223223
UserInstalledJupyter = 'DATASCIENCE.USER_INSTALLED_JUPYTER',
224224
UserDidNotInstallJupyter = 'DATASCIENCE.USER_DID_NOT_INSTALL_JUPYTER',
225-
OpenedInteractiveWindow = 'DATASCIENCE.OPENED_INTERACTIVE'
225+
OpenedInteractiveWindow = 'DATASCIENCE.OPENED_INTERACTIVE',
226+
FindKernelForLocalConnection = 'DATASCIENCE.FIND_KERNEL_FOR_LOCAL_CONNECTION'
226227
}
227228

228229
export enum NativeKeyboardCommandTelemetry {

src/client/datascience/jupyter/jupyterSession.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import { traceInfo, traceWarning } from '../../common/logger';
1414
import { sleep, waitForPromise } from '../../common/utils/async';
1515
import * as localize from '../../common/utils/localize';
1616
import { noop } from '../../common/utils/misc';
17+
import { captureTelemetry } from '../../telemetry';
18+
import { Telemetry } from '../constants';
1719
import { IConnection, IJupyterKernelSpec, IJupyterSession } from '../types';
1820
import { JupyterWaitForIdleError } from './jupyterWaitForIdleError';
1921
import { JupyterKernelPromiseFailedError } from './kernels/jupyterKernelPromiseFailedError';
@@ -248,6 +250,7 @@ export class JupyterSession implements IJupyterSession {
248250
}
249251
}
250252

253+
@captureTelemetry(Telemetry.WaitForIdleJupyter, undefined, true)
251254
private async waitForIdleOnSession(session: ISession | undefined, timeout: number): Promise<void> {
252255
if (session && session.kernel) {
253256
traceInfo(`Waiting for idle on (kernel): ${session.kernel.id} -> ${session.kernel.status}`);

src/client/datascience/jupyter/kernels/kernelSelector.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ import { traceError, traceInfo, traceVerbose } from '../../../common/logger';
1212
import { IInstaller, Product } from '../../../common/types';
1313
import * as localize from '../../../common/utils/localize';
1414
import { noop } from '../../../common/utils/misc';
15+
import { StopWatch } from '../../../common/utils/stopWatch';
1516
import { IInterpreterService, PythonInterpreter } from '../../../interpreter/contracts';
16-
import { sendTelemetryEvent } from '../../../telemetry';
17+
import { IEventNamePropertyMapping, sendTelemetryEvent } from '../../../telemetry';
1718
import { Telemetry } from '../../constants';
1819
import { IJupyterKernelSpec, IJupyterSessionManager } from '../../types';
1920
import { KernelSelectionProvider } from './kernelSelections';
@@ -119,6 +120,8 @@ export class KernelSelector {
119120
notebookMetadata?: nbformat.INotebookMetadata,
120121
cancelToken?: CancellationToken
121122
): Promise<KernelSpecInterpreter> {
123+
const stopWatch = new StopWatch();
124+
const telemetryProps: IEventNamePropertyMapping[Telemetry.FindKernelForLocalConnection] = { kernelSpecFound: false, interpreterFound: false, promptedToSelect: false };
122125
// When this method is called, we know we've started a local jupyter server.
123126
// Lets pre-warm the list of local kernels.
124127
this.selectionProvider.getKernelSelectionsForLocalSession(sessionManager, cancelToken).ignoreErrors();
@@ -135,6 +138,7 @@ export class KernelSelector {
135138
if (activeInterpreter) {
136139
selection = await this.useInterpreterAsKernel(activeInterpreter, notebookMetadata.kernelspec.display_name, sessionManager, cancelToken);
137140
} else {
141+
telemetryProps.promptedToSelect = true;
138142
selection = await this.selectLocalKernel(sessionManager, cancelToken);
139143
}
140144
}
@@ -150,6 +154,10 @@ export class KernelSelector {
150154
if (!selection.kernelSpec) {
151155
traceError('Jupyter Kernel Spec not found for a local connection');
152156
}
157+
158+
telemetryProps.kernelSpecFound = !!selection.kernelSpec;
159+
telemetryProps.interpreterFound = !!selection.interpreter;
160+
sendTelemetryEvent(Telemetry.FindKernelForLocalConnection, stopWatch.elapsedTime, telemetryProps);
153161
return selection;
154162
}
155163

src/client/datascience/jupyter/kernels/kernelService.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,8 @@ export class KernelService {
256256
}
257257

258258
let kernel = await this.findMatchingKernelSpec({ display_name: interpreter.displayName, name }, undefined, cancelToken);
259-
for (let counter = 0; counter < 5; counter += 1) {
259+
// Wait for at least 5s. We know launching a python (conda env) process on windows can sometimes take around 4s.
260+
for (let counter = 0; counter < 10; counter += 1) {
260261
if (Cancellation.isCanceled(cancelToken)) {
261262
return;
262263
}

src/client/datascience/jupyter/liveshare/hostJupyterServer.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@ import { IApplicationShell, ILiveShareApi, IWorkspaceService } from '../../../co
1212
import { traceInfo } from '../../../common/logger';
1313
import { IAsyncDisposableRegistry, IConfigurationService, IDisposableRegistry } from '../../../common/types';
1414
import * as localize from '../../../common/utils/localize';
15-
import { StopWatch } from '../../../common/utils/stopWatch';
16-
import { sendTelemetryEvent } from '../../../telemetry';
17-
import { Identifiers, LiveShare, LiveShareCommands, RegExpValues, Telemetry } from '../../constants';
15+
import { Identifiers, LiveShare, LiveShareCommands, RegExpValues } from '../../constants';
1816
import {
1917
IDataScience,
2018
IJupyterSession,
@@ -182,10 +180,8 @@ export class HostJupyterServer
182180

183181
// Wait for it to be ready
184182
traceInfo(`Waiting for idle (session) ${this.id}`);
185-
const stopWatch = new StopWatch();
186183
const idleTimeout = configService.getSettings().datascience.jupyterLaunchTimeout;
187184
await notebook.waitForIdle(idleTimeout);
188-
sendTelemetryEvent(Telemetry.WaitForIdleJupyter, stopWatch.elapsedTime);
189185

190186
// Run initial setup
191187
await notebook.initialize(cancelToken);

src/client/interpreter/activation/service.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
import { OSType } from '../../common/utils/platform';
1919
import { IEnvironmentVariablesProvider } from '../../common/variables/types';
2020
import { EXTENSION_ROOT_DIR } from '../../constants';
21-
import { captureTelemetry } from '../../telemetry';
21+
import { captureTelemetry, sendTelemetryEvent } from '../../telemetry';
2222
import { EventName } from '../../telemetry/constants';
2323
import { PythonInterpreter } from '../contracts';
2424
import { IEnvironmentActivationService } from './types';
@@ -58,14 +58,14 @@ export class EnvironmentActivationService implements IEnvironmentActivationServi
5858
if (!shellInfo) {
5959
return;
6060
}
61-
61+
let isPossiblyCondaEnv = false;
6262
try {
6363
const activationCommands = await this.helper.getEnvironmentActivationShellCommands(resource, shellInfo.shellType, interpreter);
6464
traceVerbose(`Activation Commands received ${activationCommands} for shell ${shellInfo.shell}`);
6565
if (!activationCommands || !Array.isArray(activationCommands) || activationCommands.length === 0) {
6666
return;
6767
}
68-
68+
isPossiblyCondaEnv = activationCommands.join(' ').toLowerCase().includes('conda');
6969
// Run the activate command collect the environment from it.
7070
const activationCommand = this.fixActivationCommands(activationCommands).join(' && ');
7171
const processService = await this.processServiceFactory.create(resource);
@@ -91,6 +91,7 @@ export class EnvironmentActivationService implements IEnvironmentActivationServi
9191
return this.parseEnvironmentOutput(result.stdout);
9292
} catch (e) {
9393
traceError('getActivatedEnvironmentVariables', e);
94+
sendTelemetryEvent(EventName.ACTIVATE_ENV_TO_GET_ENV_VARS_FAILED, undefined, {isPossiblyCondaEnv, terminal: shellInfo.shellType});
9495

9596
// Some callers want this to bubble out, others don't
9697
if (allowExceptions) {

src/client/telemetry/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ export enum EventName {
7878

7979
TERMINAL_CREATE = 'TERMINAL.CREATE',
8080
ACTIVATE_ENV_IN_CURRENT_TERMINAL = 'ACTIVATE_ENV_IN_CURRENT_TERMINAL',
81+
ACTIVATE_ENV_TO_GET_ENV_VARS_FAILED = 'ACTIVATE_ENV_TO_GET_ENV_VARS_FAILED',
8182
PYTHON_LANGUAGE_SERVER_LIST_BLOB_STORE_PACKAGES = 'PYTHON_LANGUAGE_SERVER.LIST_BLOB_PACKAGES',
8283
DIAGNOSTICS_ACTION = 'DIAGNOSTICS.ACTION',
8384
DIAGNOSTICS_MESSAGE = 'DIAGNOSTICS.MESSAGE',

src/client/telemetry/index.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1592,4 +1592,53 @@ export interface IEventNamePropertyMapping {
15921592
hasCustomShell: undefined | boolean;
15931593
hasShellInEnv: undefined | boolean;
15941594
};
1595+
/**
1596+
* Telemetry event sent when getting environment variables for an activated environment has failed.
1597+
*
1598+
* @type {(undefined | never)}
1599+
* @memberof IEventNamePropertyMapping
1600+
*/
1601+
[EventName.ACTIVATE_ENV_TO_GET_ENV_VARS_FAILED]: {
1602+
/**
1603+
* Whether the activation commands contain the name `conda`.
1604+
*
1605+
* @type {boolean}
1606+
*/
1607+
isPossiblyCondaEnv: boolean;
1608+
/**
1609+
* The type of terminal shell created: powershell, cmd, zsh, bash etc.
1610+
*
1611+
* @type {TerminalShellType}
1612+
*/
1613+
terminal: TerminalShellType;
1614+
};
1615+
/**
1616+
* Telemetry event sent once done searching for kernel spec and interpreter for a local connection.
1617+
*
1618+
* @type {{
1619+
* kernelSpecFound: boolean;
1620+
* interpreterFound: boolean;
1621+
* }}
1622+
* @memberof IEventNamePropertyMapping
1623+
*/
1624+
[Telemetry.FindKernelForLocalConnection]: {
1625+
/**
1626+
* Whether a kernel spec was found.
1627+
*
1628+
* @type {boolean}
1629+
*/
1630+
kernelSpecFound: boolean;
1631+
/**
1632+
* Whether an interpreter was found.
1633+
*
1634+
* @type {boolean}
1635+
*/
1636+
interpreterFound: boolean;
1637+
/**
1638+
* Whether user was prompted to select a kernel spec.
1639+
*
1640+
* @type {boolean}
1641+
*/
1642+
promptedToSelect?: boolean;
1643+
};
15951644
}

src/test/datascience/jupyter/kernels/kernelService.unit.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ suite('Data Science - KernelService', () => {
275275
const kernelName = installArgs[3];
276276
assert.deepEqual(installArgs, ['install', '--user', '--name', kernelName, '--display-name', interpreter.displayName]);
277277
await assert.isRejected(promise, `Kernel not created with the name ${kernelName}, display_name ${interpreter.displayName}. Output is `);
278-
}).timeout(5_000);
278+
}).timeout(10_000);
279279
test('If ipykernel is not installed, then prompt to install ipykernel', async () => {
280280
when(execService.execModule('ipykernel', anything(), anything())).thenResolve({ stdout: '' });
281281
when(installer.isInstalled(Product.ipykernel, interpreter)).thenResolve(false);
@@ -291,7 +291,7 @@ suite('Data Science - KernelService', () => {
291291
assert.deepEqual(installArgs, ['install', '--user', '--name', kernelName, '--display-name', interpreter.displayName]);
292292
await assert.isRejected(promise, `Kernel not created with the name ${kernelName}, display_name ${interpreter.displayName}. Output is `);
293293
verify(installer.promptToInstall(anything(), anything(), anything())).once();
294-
}).timeout(5_000);
294+
}).timeout(10_000);
295295
test('If ipykernel is not installed, and ipykerne installation is canclled, then do not reigster kernel', async () => {
296296
when(execService.execModule('ipykernel', anything(), anything())).thenResolve({ stdout: '' });
297297
when(installer.isInstalled(Product.ipykernel, interpreter)).thenResolve(false);
@@ -303,7 +303,7 @@ suite('Data Science - KernelService', () => {
303303
assert.isUndefined(kernel);
304304
verify(execService.execModule('ipykernel', anything(), anything())).never();
305305
verify(installer.promptToInstall(anything(), anything(), anything())).once();
306-
}).timeout(5_000);
306+
}).timeout(10_000);
307307
test('Fail if installed kernel is not an instance of JupyterKernelSpec', async () => {
308308
when(execService.execModule('ipykernel', anything(), anything())).thenResolve({ stdout: '' });
309309
when(installer.isInstalled(Product.ipykernel, interpreter)).thenResolve(true);

0 commit comments

Comments
 (0)