Skip to content

Commit 880e806

Browse files
committed
Rchiodo/kernel telemetry (#10115)
* Add duration to select local/remote kernel * Add notebook language telemetry * Add news entries * #9883 telemetry * News entry * Another spot for kernel spec failure * Add telemetry on product install * Fix install telemetry * Undo launch.json change * Handle other cases * Better way to handle case * Wrong event for jupyter install * Fix unit tests
1 parent ff3f3a4 commit 880e806

16 files changed

Lines changed: 225 additions & 46 deletions

‎news/3 Code Health/10049.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Change select kernel telemetry to track duration till quick pick appears.

‎news/3 Code Health/9819.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add telemetry to track notebook languages

‎news/3 Code Health/9883.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Telemetry around kernels not working and installs not working.

‎src/client/common/installer/productInstaller.ts‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import * as os from 'os';
55
import { CancellationToken, OutputChannel, Uri } from 'vscode';
66
import '../../common/extensions';
77
import * as localize from '../../common/utils/localize';
8+
import { Telemetry } from '../../datascience/constants';
89
import { IInterpreterService } from '../../interpreter/contracts';
910
import { IServiceContainer } from '../../ioc/types';
1011
import { LinterId } from '../../linters/types';
@@ -18,6 +19,7 @@ import { IProcessServiceFactory, IPythonExecutionFactory } from '../process/type
1819
import { ITerminalServiceFactory } from '../terminal/types';
1920
import { IConfigurationService, IInstaller, InstallerResponse, IOutputChannel, IPersistentStateFactory, ModuleNamePurpose, Product, ProductType } from '../types';
2021
import { isResource } from '../utils/misc';
22+
import { StopWatch } from '../utils/stopWatch';
2123
import { ProductNames } from './productNames';
2224
import { IInstallationChannelManager, InterpreterUri, IProductPathService, IProductService } from './types';
2325

@@ -278,7 +280,21 @@ export class DataScienceInstaller extends BaseInstaller {
278280
protected async promptToInstallImplementation(product: Product, resource?: InterpreterUri, cancel?: CancellationToken): Promise<InstallerResponse> {
279281
const productName = ProductNames.get(product)!;
280282
const item = await this.appShell.showErrorMessage(localize.DataScience.libraryNotInstalled().format(productName), 'Yes', 'No');
281-
return item === 'Yes' ? this.install(product, resource, cancel) : InstallerResponse.Ignore;
283+
if (item === 'Yes') {
284+
const stopWatch = new StopWatch();
285+
try {
286+
const response = await this.install(product, resource, cancel);
287+
const event = product === Product.jupyter ? Telemetry.UserInstalledJupyter : Telemetry.UserInstalledModule;
288+
sendTelemetryEvent(event, stopWatch.elapsedTime, { product: productName });
289+
return response;
290+
} catch (e) {
291+
if (product === Product.jupyter) {
292+
sendTelemetryEvent(Telemetry.JupyterInstallFailed);
293+
}
294+
throw e;
295+
}
296+
}
297+
return InstallerResponse.Ignore;
282298
}
283299
}
284300

‎src/client/datascience/constants.ts‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ export const JUPYTER_OUTPUT_CHANNEL = 'JUPYTER_OUTPUT_CHANNEL';
1414
// Python Module to be used when instantiating the Python Daemon.
1515
export const PythonDaemonModule = 'datascience.jupyter_daemon';
1616

17+
// List of 'language' names that we know about. All should be lower case as that's how we compare.
18+
export const KnownNotebookLanguages: string[] = ['python', 'r', 'julia', 'c++', 'c#', 'f#', 'scala', 'haskell', 'bash', 'cling', 'sas'];
19+
1720
export namespace Commands {
1821
export const RunAllCells = 'python.datascience.runallcells';
1922
export const RunAllCellsAbove = 'python.datascience.runallcellsabove';
@@ -147,7 +150,7 @@ export enum Telemetry {
147150
CollapseAll = 'DATASCIENCE.COLLAPSE_ALL',
148151
SelectJupyterURI = 'DATASCIENCE.SELECT_JUPYTER_URI',
149152
SelectLocalJupyterKernel = 'DATASCIENCE.SELECT_LOCAL_JUPYTER_KERNEL',
150-
SelectRemoteJupyuterKernel = 'DATASCIENCE.SELECT_REMOTE_JUPYTER_KERNEL',
153+
SelectRemoteJupyterKernel = 'DATASCIENCE.SELECT_REMOTE_JUPYTER_KERNEL',
151154
SetJupyterURIToLocal = 'DATASCIENCE.SET_JUPYTER_URI_LOCAL',
152155
SetJupyterURIToUserSpecified = 'DATASCIENCE.SET_JUPYTER_URI_USER_SPECIFIED',
153156
Interrupt = 'DATASCIENCE.INTERRUPT',
@@ -239,7 +242,14 @@ export enum Telemetry {
239242
OpenedInteractiveWindow = 'DATASCIENCE.OPENED_INTERACTIVE',
240243
FindKernelForLocalConnection = 'DS_INTERNAL.FIND_KERNEL_FOR_LOCAL_CONNECTION',
241244
CompletionTimeFromLS = 'DS_INTERNAL.COMPLETION_TIME_FROM_LS',
242-
CompletionTimeFromJupyter = 'DS_INTERNAL.COMPLETION_TIME_FROM_JUPYTER'
245+
CompletionTimeFromJupyter = 'DS_INTERNAL.COMPLETION_TIME_FROM_JUPYTER',
246+
NotebookLanguage = 'DATASCIENCE.NOTEBOOK_LANGUAGE',
247+
KernelSpecNotFound = 'DS_INTERNAL.KERNEL_SPEC_NOT_FOUND',
248+
KernelRegisterFailed = 'DS_INTERNAL.KERNEL_REGISTER_FAILED',
249+
KernelEnumeration = 'DS_INTERNAL.KERNEL_ENUMERATION',
250+
JupyterInstallFailed = 'DS_INTERNAL.JUPYTER_INSTALL_FAILED',
251+
UserInstalledModule = 'DATASCIENCE.USER_INSTALLED_MODULE',
252+
JupyterCommandLineNonDefault = 'DS_INTERNAL.JUPYTER_CUSTOM_COMMAND_LINE'
243253
}
244254

245255
export enum NativeKeyboardCommandTelemetry {

‎src/client/datascience/interactive-ipynb/nativeEditor.ts‎

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { StopWatch } from '../../common/utils/stopWatch';
2424
import { EXTENSION_ROOT_DIR } from '../../constants';
2525
import { IInterpreterService } from '../../interpreter/contracts';
2626
import { captureTelemetry, sendTelemetryEvent } from '../../telemetry';
27-
import { EditorContexts, Identifiers, NativeKeyboardCommandTelemetryLookup, NativeMouseCommandTelemetryLookup, Telemetry } from '../constants';
27+
import { EditorContexts, Identifiers, KnownNotebookLanguages, NativeKeyboardCommandTelemetryLookup, NativeMouseCommandTelemetryLookup, Telemetry } from '../constants';
2828
import { InteractiveBase } from '../interactive-common/interactiveBase';
2929
import {
3030
IEditCell,
@@ -398,7 +398,7 @@ export class NativeEditor extends InteractiveBase implements INotebookEditor {
398398
}
399399
}
400400

401-
@captureTelemetry(Telemetry.ExecuteNativeCell, undefined, false)
401+
@captureTelemetry(Telemetry.ExecuteNativeCell, undefined, true)
402402
// tslint:disable-next-line:no-any
403403
protected async reexecuteCell(info: ISubmitNewCell): Promise<void> {
404404
try {
@@ -588,6 +588,9 @@ export class NativeEditor extends InteractiveBase implements INotebookEditor {
588588
// Then save the contents. We'll stick our cells back into this format when we save
589589
if (json) {
590590
this.notebookJson = json;
591+
592+
// Log language or kernel telemetry
593+
this.sendLanguageTelemetry(this.notebookJson);
591594
}
592595
this.contentsLoadedPromise.resolve();
593596

@@ -609,6 +612,27 @@ export class NativeEditor extends InteractiveBase implements INotebookEditor {
609612
);
610613
}
611614

615+
private sendLanguageTelemetry(notebookJson: Partial<nbformat.INotebookContent>) {
616+
try {
617+
// See if we have a language
618+
let language = '';
619+
if (notebookJson.metadata?.language_info?.name) {
620+
language = notebookJson.metadata?.language_info?.name;
621+
} else if (notebookJson.metadata?.kernelspec?.language) {
622+
language = notebookJson.metadata?.kernelspec?.language.toString();
623+
}
624+
if (language && !KnownNotebookLanguages.includes(language.toLowerCase())) {
625+
language = 'unknown';
626+
}
627+
if (language) {
628+
sendTelemetryEvent(Telemetry.NotebookLanguage, undefined, { language });
629+
}
630+
} catch {
631+
// If this fails, doesn't really matter
632+
noop();
633+
}
634+
}
635+
612636
private async loadCells(cells: ICell[], forceDirty: boolean): Promise<void> {
613637
// Make sure cells have at least 1
614638
if (cells.length === 0) {

‎src/client/datascience/jupyter/interpreter/jupyterCommandInterpreterDependencyService.ts‎

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { ProductNames } from '../../../common/installer/productNames';
99
import { IInstallationChannelManager } from '../../../common/installer/types';
1010
import { Product } from '../../../common/types';
1111
import { DataScience } from '../../../common/utils/localize';
12+
import { StopWatch } from '../../../common/utils/stopWatch';
1213
import { sendTelemetryEvent } from '../../../telemetry';
1314
import { Telemetry } from '../../constants';
1415
import { IJupyterInterpreterDependencyManager } from '../../types';
@@ -29,12 +30,28 @@ export class JupyterCommandInterpreterDependencyService implements IJupyterInter
2930
// If Conda is available, always pick it as the user must have a Conda Environment
3031
const installer = installers.find(ins => ins.name === 'Conda');
3132
const product = ProductNames.get(Product.jupyter);
33+
const stopWatch = new StopWatch();
3234

3335
if (installer && product) {
34-
sendTelemetryEvent(Telemetry.UserInstalledJupyter);
35-
installer.installModule(product).catch(e => this.applicationShell.showErrorMessage(e.message, DataScience.pythonInteractiveHelpLink()));
36+
installer
37+
.installModule(product)
38+
.then(() => {
39+
sendTelemetryEvent(Telemetry.UserInstalledJupyter, stopWatch.elapsedTime);
40+
})
41+
.catch(e => {
42+
sendTelemetryEvent(Telemetry.JupyterInstallFailed, undefined, { product });
43+
this.applicationShell.showErrorMessage(e.message, DataScience.pythonInteractiveHelpLink());
44+
});
3645
} else if (installers[0] && product) {
37-
installers[0].installModule(product).catch(e => this.applicationShell.showErrorMessage(e.message, DataScience.pythonInteractiveHelpLink()));
46+
installers[0]
47+
.installModule(product)
48+
.then(() => {
49+
sendTelemetryEvent(Telemetry.UserInstalledJupyter, stopWatch.elapsedTime);
50+
})
51+
.catch(e => {
52+
sendTelemetryEvent(Telemetry.JupyterInstallFailed, undefined, { product });
53+
this.applicationShell.showErrorMessage(e.message, DataScience.pythonInteractiveHelpLink());
54+
});
3855
}
3956
}
4057
} else if (response === DataScience.notebookCheckForImportNo()) {

‎src/client/datascience/jupyter/interpreter/jupyterInterpreterDependencyService.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,10 @@ export class JupyterInterpreterDependencyService {
278278
return execService
279279
.execModule('jupyter', ['kernelspec', '--version'], { throwOnStdErr: true })
280280
.then(() => true)
281-
.catch(() => false);
281+
.catch(() => {
282+
sendTelemetryEvent(Telemetry.KernelSpecNotFound);
283+
return false;
284+
});
282285
}
283286

284287
/**

‎src/client/datascience/jupyter/interpreter/jupyterInterpreterSubCommandExecutionService.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import { DataScience } from '../../../common/utils/localize';
1515
import { noop } from '../../../common/utils/misc';
1616
import { EXTENSION_ROOT_DIR } from '../../../constants';
1717
import { IInterpreterService, PythonInterpreter } from '../../../interpreter/contracts';
18-
import { JUPYTER_OUTPUT_CHANNEL, PythonDaemonModule } from '../../constants';
18+
import { sendTelemetryEvent } from '../../../telemetry';
19+
import { JUPYTER_OUTPUT_CHANNEL, PythonDaemonModule, Telemetry } from '../../constants';
1920
import { IJupyterInterpreterDependencyManager, IJupyterSubCommandExecutionService } from '../../types';
2021
import { JupyterServerInfo } from '../jupyterConnection';
2122
import { JupyterInstallError } from '../jupyterInstallError';
@@ -153,6 +154,7 @@ export class JupyterInterpreterSubCommandExecutionService implements IJupyterSub
153154
.execModule('jupyter', ['kernelspec', 'list', '--json'], spawnOptions)
154155
.then(output => output.stdout)
155156
.catch(daemonEx => {
157+
sendTelemetryEvent(Telemetry.KernelSpecNotFound);
156158
traceError('Failed to list kernels from daemon', daemonEx);
157159
return '';
158160
});

‎src/client/datascience/jupyter/jupyterExecution.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ export class JupyterExecutionBase implements IJupyterExecution {
192192
if (selection === selectKernel) {
193193
const sessionManagerFactory = this.serviceContainer.get<IJupyterSessionManagerFactory>(IJupyterSessionManagerFactory);
194194
const sessionManager = await sessionManagerFactory.create(connection);
195-
const kernelInterpreter = await this.kernelSelector.selectLocalKernel(sessionManager, cancelToken, launchInfo.kernelSpec);
195+
const kernelInterpreter = await this.kernelSelector.selectLocalKernel(new StopWatch(), sessionManager, cancelToken, launchInfo.kernelSpec);
196196
if (Object.keys(kernelInterpreter).length > 0) {
197197
launchInfo.interpreter = kernelInterpreter.interpreter;
198198
launchInfo.kernelSpec = kernelInterpreter.kernelSpec || kernelInterpreter.kernelModel;

0 commit comments

Comments
 (0)