Skip to content
1 change: 1 addition & 0 deletions news/3 Code Health/10049.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Change select kernel telemetry to track duration till quick pick appears.
1 change: 1 addition & 0 deletions news/3 Code Health/9819.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add telemetry to track notebook languages
1 change: 1 addition & 0 deletions news/3 Code Health/9883.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Telemetry around kernels not working and installs not working.
19 changes: 18 additions & 1 deletion src/client/common/installer/productInstaller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as os from 'os';
import { CancellationToken, OutputChannel, Uri } from 'vscode';
import '../../common/extensions';
import * as localize from '../../common/utils/localize';
import { Telemetry } from '../../datascience/constants';
import { IInterpreterService } from '../../interpreter/contracts';
import { IServiceContainer } from '../../ioc/types';
import { LinterId } from '../../linters/types';
Expand All @@ -27,6 +28,7 @@ import {
ProductType
} from '../types';
import { isResource } from '../utils/misc';
import { StopWatch } from '../utils/stopWatch';
import { ProductNames } from './productNames';
import { IInstallationChannelManager, InterpreterUri, IProductPathService, IProductService } from './types';

Expand Down Expand Up @@ -355,7 +357,22 @@ export class DataScienceInstaller extends BaseInstaller {
'Yes',
'No'
);
return item === 'Yes' ? this.install(product, resource, cancel) : InstallerResponse.Ignore;
if (item === 'Yes') {
const stopWatch = new StopWatch();
try {
const response = await this.install(product, resource, cancel);
const event =
product === Product.jupyter ? Telemetry.UserInstalledJupyter : Telemetry.UserInstalledModule;
sendTelemetryEvent(event, stopWatch.elapsedTime, { product: productName });
return response;
} catch (e) {
if (product === Product.jupyter) {
sendTelemetryEvent(Telemetry.JupyterInstallFailed);

@IanMatthewHuff IanMatthewHuff Feb 13, 2020

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not relevant in particular to our team. But might be worthwhile just for the extension in general to log telemetry on UserModuleInstallFailed. #WontFix

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dan/Savannah can ask for this if they want it I think. Not sure we should add telemetry unless they want it.


In reply to: 379120268 [](ancestors = 379120268)

}
throw e;
}
}
return InstallerResponse.Ignore;
}
}

Expand Down
23 changes: 22 additions & 1 deletion src/client/datascience/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,21 @@ export const JUPYTER_OUTPUT_CHANNEL = 'JUPYTER_OUTPUT_CHANNEL';
// Python Module to be used when instantiating the Python Daemon.
export const PythonDaemonModule = 'datascience.jupyter_daemon';

// List of 'language' names that we know about. All should be lower case as that's how we compare.
export const KnownNotebookLanguages: string[] = [

@IanMatthewHuff IanMatthewHuff Feb 13, 2020

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wondering where did these values come from. Didn't think that nbformat specified language specific language_info name values. #Resolved

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked them up in a bunch of notebooks on github.


In reply to: 379122367 [](ancestors = 379122367)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually some of them are the wrong case though. I should check case insensitive.


In reply to: 379122816 [](ancestors = 379122816,379122367)

'python',
'r',
'julia',
'c++',
'c#',
'f#',
'scala',
'haskell',
'bash',
'cling',
'sas'
];

export namespace Commands {
export const RunAllCells = 'python.datascience.runallcells';
export const RunAllCellsAbove = 'python.datascience.runallcellsabove';
Expand Down Expand Up @@ -153,7 +168,7 @@ export enum Telemetry {
CollapseAll = 'DATASCIENCE.COLLAPSE_ALL',
SelectJupyterURI = 'DATASCIENCE.SELECT_JUPYTER_URI',
SelectLocalJupyterKernel = 'DATASCIENCE.SELECT_LOCAL_JUPYTER_KERNEL',
SelectRemoteJupyuterKernel = 'DATASCIENCE.SELECT_REMOTE_JUPYTER_KERNEL',
SelectRemoteJupyterKernel = 'DATASCIENCE.SELECT_REMOTE_JUPYTER_KERNEL',
SetJupyterURIToLocal = 'DATASCIENCE.SET_JUPYTER_URI_LOCAL',
SetJupyterURIToUserSpecified = 'DATASCIENCE.SET_JUPYTER_URI_USER_SPECIFIED',
Interrupt = 'DATASCIENCE.INTERRUPT',
Expand Down Expand Up @@ -246,6 +261,12 @@ export enum Telemetry {
FindKernelForLocalConnection = 'DS_INTERNAL.FIND_KERNEL_FOR_LOCAL_CONNECTION',
CompletionTimeFromLS = 'DS_INTERNAL.COMPLETION_TIME_FROM_LS',
CompletionTimeFromJupyter = 'DS_INTERNAL.COMPLETION_TIME_FROM_JUPYTER',
NotebookLanguage = 'DATASCIENCE.NOTEBOOK_LANGUAGE',
KernelSpecNotFound = 'DS_INTERNAL.KERNEL_SPEC_NOT_FOUND',
KernelRegisterFailed = 'DS_INTERNAL.KERNEL_REGISTER_FAILED',
KernelEnumeration = 'DS_INTERNAL.KERNEL_ENUMERATION',
JupyterInstallFailed = 'DS_INTERNAL.JUPYTER_INSTALL_FAILED',
UserInstalledModule = 'DATASCIENCE.USER_INSTALLED_MODULE',
JupyterCommandLineNonDefault = 'DS_INTERNAL.JUPYTER_CUSTOM_COMMAND_LINE'
}

Expand Down
27 changes: 26 additions & 1 deletion src/client/datascience/interactive-ipynb/nativeEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import { captureTelemetry, sendTelemetryEvent } from '../../telemetry';
import {
EditorContexts,
Identifiers,
KnownNotebookLanguages,
NativeKeyboardCommandTelemetryLookup,
NativeMouseCommandTelemetryLookup,
Telemetry
Expand Down Expand Up @@ -432,7 +433,7 @@ export class NativeEditor extends InteractiveBase implements INotebookEditor {
}
}

@captureTelemetry(Telemetry.ExecuteNativeCell, undefined, false)
@captureTelemetry(Telemetry.ExecuteNativeCell, undefined, true)
// tslint:disable-next-line:no-any
protected async reexecuteCell(info: ISubmitNewCell): Promise<void> {
try {
Expand Down Expand Up @@ -627,6 +628,9 @@ export class NativeEditor extends InteractiveBase implements INotebookEditor {
// Then save the contents. We'll stick our cells back into this format when we save
if (json) {
this.notebookJson = json;

// Log language or kernel telemetry
this.sendLanguageTelemetry(this.notebookJson);
}
this.contentsLoadedPromise.resolve();

Expand All @@ -650,6 +654,27 @@ export class NativeEditor extends InteractiveBase implements INotebookEditor {
);
}

private sendLanguageTelemetry(notebookJson: Partial<nbformat.INotebookContent>) {
try {
// See if we have a language
let language = '';
if (notebookJson.metadata?.language_info?.name) {
language = notebookJson.metadata?.language_info?.name;
} else if (notebookJson.metadata?.kernelspec?.language) {
language = notebookJson.metadata?.kernelspec?.language.toString();
}
if (language && !KnownNotebookLanguages.includes(language.toLowerCase())) {
language = 'unknown';
}
if (language) {
sendTelemetryEvent(Telemetry.NotebookLanguage, undefined, { language });
}
} catch {
// If this fails, doesn't really matter
noop();
}
}

private async loadCells(cells: ICell[], forceDirty: boolean): Promise<void> {
// Make sure cells have at least 1
if (cells.length === 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ProductNames } from '../../../common/installer/productNames';
import { IInstallationChannelManager } from '../../../common/installer/types';
import { Product } from '../../../common/types';
import { DataScience } from '../../../common/utils/localize';
import { StopWatch } from '../../../common/utils/stopWatch';
import { sendTelemetryEvent } from '../../../telemetry';
import { Telemetry } from '../../constants';
import { IJupyterInterpreterDependencyManager } from '../../types';
Expand Down Expand Up @@ -37,20 +38,28 @@ export class JupyterCommandInterpreterDependencyService implements IJupyterInter
// If Conda is available, always pick it as the user must have a Conda Environment
const installer = installers.find(ins => ins.name === 'Conda');
const product = ProductNames.get(Product.jupyter);
const stopWatch = new StopWatch();

if (installer && product) {
sendTelemetryEvent(Telemetry.UserInstalledJupyter);
installer
.installModule(product)
.catch(e =>
this.applicationShell.showErrorMessage(e.message, DataScience.pythonInteractiveHelpLink())
);
.then(() => {
sendTelemetryEvent(Telemetry.UserInstalledJupyter, stopWatch.elapsedTime);
})
.catch(e => {
sendTelemetryEvent(Telemetry.JupyterInstallFailed, undefined, { product });
this.applicationShell.showErrorMessage(e.message, DataScience.pythonInteractiveHelpLink());
});
} else if (installers[0] && product) {
installers[0]
.installModule(product)
.catch(e =>
this.applicationShell.showErrorMessage(e.message, DataScience.pythonInteractiveHelpLink())
);
.then(() => {
sendTelemetryEvent(Telemetry.UserInstalledJupyter, stopWatch.elapsedTime);
})
.catch(e => {
sendTelemetryEvent(Telemetry.JupyterInstallFailed, undefined, { product });
this.applicationShell.showErrorMessage(e.message, DataScience.pythonInteractiveHelpLink());
});
}
}
} else if (response === DataScience.notebookCheckForImportNo()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,10 @@ export class JupyterInterpreterDependencyService {
return execService
.execModule('jupyter', ['kernelspec', '--version'], { throwOnStdErr: true })
.then(() => true)
.catch(() => false);
.catch(() => {
sendTelemetryEvent(Telemetry.KernelSpecNotFound);
return false;
});
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import { DataScience } from '../../../common/utils/localize';
import { noop } from '../../../common/utils/misc';
import { EXTENSION_ROOT_DIR } from '../../../constants';
import { IInterpreterService, PythonInterpreter } from '../../../interpreter/contracts';
import { JUPYTER_OUTPUT_CHANNEL, PythonDaemonModule } from '../../constants';
import { sendTelemetryEvent } from '../../../telemetry';
import { JUPYTER_OUTPUT_CHANNEL, PythonDaemonModule, Telemetry } from '../../constants';
import { IJupyterInterpreterDependencyManager, IJupyterSubCommandExecutionService } from '../../types';
import { JupyterServerInfo } from '../jupyterConnection';
import { JupyterInstallError } from '../jupyterInstallError';
Expand Down Expand Up @@ -192,6 +193,7 @@ export class JupyterInterpreterSubCommandExecutionService
.execModule('jupyter', ['kernelspec', 'list', '--json'], spawnOptions)
.then(output => output.stdout)
.catch(daemonEx => {
sendTelemetryEvent(Telemetry.KernelSpecNotFound);
traceError('Failed to list kernels from daemon', daemonEx);
return '';
});
Expand Down
1 change: 1 addition & 0 deletions src/client/datascience/jupyter/jupyterExecution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ export class JupyterExecutionBase implements IJupyterExecution {
>(IJupyterSessionManagerFactory);
const sessionManager = await sessionManagerFactory.create(connection);
const kernelInterpreter = await this.kernelSelector.selectLocalKernel(
new StopWatch(),
sessionManager,
cancelToken,
launchInfo.kernelSpec
Expand Down
49 changes: 42 additions & 7 deletions src/client/datascience/jupyter/kernels/kernelSelector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { noop } from '../../../common/utils/misc';
import { StopWatch } from '../../../common/utils/stopWatch';
import { IInterpreterService, PythonInterpreter } from '../../../interpreter/contracts';
import { IEventNamePropertyMapping, sendTelemetryEvent } from '../../../telemetry';
import { Telemetry } from '../../constants';
import { KnownNotebookLanguages, Telemetry } from '../../constants';
import { reportAction } from '../../progress/decorator';
import { ReportableAction } from '../../progress/types';
import { IJupyterKernelSpec, IJupyterSessionManager } from '../../types';
Expand Down Expand Up @@ -90,13 +90,21 @@ export class KernelSelector {
* @memberof KernelSelector
*/
public async selectRemoteKernel(
stopWatch: StopWatch,
session: IJupyterSessionManager,
cancelToken?: CancellationToken,
currentKernel?: IJupyterKernelSpec | LiveKernelModel
): Promise<KernelSpecInterpreter> {
let suggestions = await this.selectionProvider.getKernelSelectionsForRemoteSession(session, cancelToken);
suggestions = suggestions.filter(item => !this.kernelIdsToHide.has(item.selection.kernelModel?.id || ''));
return this.selectKernel(suggestions, session, cancelToken, currentKernel);
return this.selectKernel(
stopWatch,
Telemetry.SelectRemoteJupyterKernel,
suggestions,
session,
cancelToken,
currentKernel
);
}
/**
* Select a kernel from a local session.
Expand All @@ -107,13 +115,21 @@ export class KernelSelector {
* @memberof KernelSelector
*/
public async selectLocalKernel(
stopWatch: StopWatch,
session?: IJupyterSessionManager,
cancelToken?: CancellationToken,
currentKernel?: IJupyterKernelSpec | LiveKernelModel
): Promise<KernelSpecInterpreter> {
let suggestions = await this.selectionProvider.getKernelSelectionsForLocalSession(session, cancelToken);
suggestions = suggestions.filter(item => !this.kernelIdsToHide.has(item.selection.kernelModel?.id || ''));
return this.selectKernel(suggestions, session, cancelToken, currentKernel);
return this.selectKernel(
stopWatch,
Telemetry.SelectLocalJupyterKernel,
suggestions,
session,
cancelToken,
currentKernel
);
}
/**
* Gets a kernel that needs to be used with a local session.
Expand Down Expand Up @@ -168,7 +184,7 @@ export class KernelSelector {
);
} else {
telemetryProps.promptedToSelect = true;
selection = await this.selectLocalKernel(sessionManager, cancelToken);
selection = await this.selectLocalKernel(stopWatch, sessionManager, cancelToken);
}
}
} else {
Expand Down Expand Up @@ -263,6 +279,8 @@ export class KernelSelector {
};
}
private async selectKernel(
stopWatch: StopWatch,
telemetryEvent: Telemetry,
suggestions: IKernelSpecQuickPickItem[],
session?: IJupyterSessionManager,
cancelToken?: CancellationToken,
Expand All @@ -271,6 +289,7 @@ export class KernelSelector {
const placeHolder =
localize.DataScience.selectKernel() +
(currentKernel ? ` (current: ${currentKernel.display_name || currentKernel.name})` : '');
sendTelemetryEvent(telemetryEvent, stopWatch.elapsedTime);
const selection = await this.applicationShell.showQuickPick(suggestions, { placeHolder }, cancelToken);
if (!selection?.selection) {
return {};
Expand All @@ -280,7 +299,9 @@ export class KernelSelector {
sendTelemetryEvent(Telemetry.SwitchToInterpreterAsKernel);
return this.useInterpreterAsKernel(selection.selection.interpreter, undefined, session, false, cancelToken);
} else if (selection.selection.kernelModel) {
sendTelemetryEvent(Telemetry.SwitchToExistingKernel);
sendTelemetryEvent(Telemetry.SwitchToExistingKernel, undefined, {
language: this.computeLanguage(selection.selection.kernelModel.language)
});
// tslint:disable-next-line: no-any
const interpreter = selection.selection.kernelModel
? await this.kernelService.findMatchingInterpreter(selection.selection.kernelModel, cancelToken)
Expand All @@ -291,7 +312,9 @@ export class KernelSelector {
kernelModel: selection.selection.kernelModel
};
} else if (selection.selection.kernelSpec) {
sendTelemetryEvent(Telemetry.SwitchToExistingKernel);
sendTelemetryEvent(Telemetry.SwitchToExistingKernel, undefined, {
language: this.computeLanguage(selection.selection.kernelSpec.language)
});
const interpreter = selection.selection.kernelSpec
? await this.kernelService.findMatchingInterpreter(selection.selection.kernelSpec, cancelToken)
: undefined;
Expand Down Expand Up @@ -348,7 +371,12 @@ export class KernelSelector {
}

// Try an install this interpreter as a kernel.
kernelSpec = await this.kernelService.registerKernel(interpreter, disableUI, cancelToken);
try {
kernelSpec = await this.kernelService.registerKernel(interpreter, disableUI, cancelToken);
} catch (e) {
sendTelemetryEvent(Telemetry.KernelRegisterFailed);
throw e;
}

// If we have a display name of a kernel that could not be found,
// then notify user that we're using current interpreter instead.
Expand All @@ -368,4 +396,11 @@ export class KernelSelector {

return { kernelSpec, interpreter };
}

private computeLanguage(language: string | undefined): string {
if (language && KnownNotebookLanguages.includes(language.toLowerCase())) {
return language;
}
return 'unknown';
}
}
12 changes: 11 additions & 1 deletion src/client/datascience/jupyter/kernels/kernelService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,17 @@ export class KernelService {
return [];
}
const specs: IJupyterKernelSpec[] = await enumerator;
return specs.filter(item => !!item);
const result = specs.filter(item => !!item);

// Send telemetry on this enumeration.
const anyPython = result.find(k => k.language === 'python') !== undefined;
sendTelemetryEvent(Telemetry.KernelEnumeration, undefined, {
count: result.length,
isPython: anyPython,
source: sessionManager ? 'connection' : 'cli'
});

return result;
}
/**
* Not all characters are allowed in a kernel name.
Expand Down
Loading