From ed32da66a37acd7dd354ccffbdefc9ccbc03b816 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 11 Feb 2021 14:48:18 -0800 Subject: [PATCH] Cherry pick commits #4742, #4760, #4766 --- .eslintrc.js | 34 +---- src/client/api/pythonApi.ts | 42 +++++- src/client/api/types.ts | 1 + src/client/common/cancellation.ts | 5 +- src/client/common/errors/errorUtils.ts | 52 +++---- src/client/common/errors/errors.ts | 112 +++++++++++++++ src/client/common/errors/index.ts | 87 ++++++++++++ .../common/errors/moduleNotInstalledError.ts | 6 +- src/client/common/errors/types.ts | 79 +++++++++++ src/client/common/process/baseDaemon.ts | 13 +- src/client/common/process/pythonDaemon.ts | 14 +- src/client/common/process/types.ts | 5 +- src/client/common/utils/async.ts | 8 +- src/client/datascience/activation.ts | 2 +- src/client/datascience/baseJupyterSession.ts | 14 +- .../activeEditorContext.ts | 2 +- .../datascience/commands/notebookCommands.ts | 8 +- src/client/datascience/common.ts | 9 -- .../errorClassificationRegistration.ts | 14 -- .../intellisense/intellisenseProvider.ts | 7 +- .../interactive-common/interactiveBase.ts | 20 +-- .../interactive-common/notebookProvider.ts | 2 +- .../notebookServerProvider.ts | 5 +- .../interactive-ipynb/nativeEditor.ts | 2 +- .../interactiveWindowCommandListener.ts | 5 +- .../ipywidgets/ipyWidgetMessageDispatcher.ts | 1 + .../ipywidgets/ipyWidgetScriptSource.ts | 1 + .../jupyter/invalidNotebookFileError.ts | 4 +- .../jupyter/jupyterConnectError.ts | 5 +- .../jupyter/jupyterDataRateLimitError.ts | 5 +- .../jupyterDebuggerNotInstalledError.ts | 5 +- .../jupyterDebuggerPortBlockedError.ts | 4 +- .../jupyterDebuggerPortNotAvailableError.ts | 4 +- .../jupyterDebuggerRemoteNotSupported.ts | 5 +- .../datascience/jupyter/jupyterExecution.ts | 26 ++-- .../jupyter/jupyterInstallError.ts | 5 +- .../jupyter/jupyterInterruptError.ts | 9 -- .../jupyter/jupyterInvalidKernelError.ts | 4 +- .../datascience/jupyter/jupyterNotebook.ts | 4 +- .../jupyter/jupyterNotebookProvider.ts | 2 + .../jupyter/jupyterSelfCertsError.ts | 5 +- .../datascience/jupyter/jupyterServer.ts | 2 +- .../jupyter/jupyterWaitForIdleError.ts | 5 +- .../jupyterZMQBinariesNotFoundError.ts | 6 +- .../jupyter/kernels/cellExecutionQueue.ts | 2 +- .../jupyterKernelPromiseFailedError.ts | 6 +- .../datascience/jupyter/kernels/kernel.ts | 17 ++- .../jupyter/kernels/kernelExecution.ts | 2 +- .../jupyter/kernels/kernelSelector.ts | 66 ++++++--- .../liveshare/guestJupyterExecution.ts | 1 + .../jupyter/liveshare/hostJupyterServer.ts | 1 + .../jupyter/liveshare/serverCache.ts | 1 + .../datascience/jupyter/notebookStarter.ts | 2 +- .../datascience/jupyter/serverPreload.ts | 7 +- .../kernel-launcher/kernelDaemon.ts | 9 +- .../kernel-launcher/kernelLauncher.ts | 2 +- .../kernel-launcher/kernelProcess.ts | 29 ++-- .../datascience/kernel-launcher/types.ts | 25 +++- .../datascience/notebook/kernelProvider.ts | 10 +- .../datascience/notebook/notebookEditor.ts | 13 +- .../notebookStorage/nativeEditorProvider.ts | 2 +- .../liveshare/hostRawNotebookProvider.ts | 6 + .../raw-kernel/rawJupyterSession.ts | 51 ++++--- src/client/datascience/serviceRegistry.ts | 2 +- .../interpreterCountTracker.ts | 0 .../telemetry/interpreterPackageTracker.ts | 79 +++++++++++ .../telemetry/interpreterPackages.ts | 109 +++++++++++++++ .../{context => telemetry}/kernelTelemetry.ts | 0 .../{context => telemetry}/serviceRegistry.ts | 8 +- .../{context => telemetry}/telemetry.ts | 112 ++++++++------- .../{context => telemetry}/types.ts | 4 + .../workspaceInterpreterTracker.ts | 0 src/client/datascience/types.ts | 7 +- src/client/telemetry/helpers.ts | 40 ++++++ src/client/telemetry/index.ts | 131 ++---------------- src/test/datascience/activation.unit.test.ts | 2 +- .../notebookCommands.functional.test.ts | 4 +- .../datascience/dataScienceIocContainer.ts | 7 +- .../dataviewer.functional.test.tsx | 3 +- .../gotocell.functional.test.ts | 2 +- .../notebookProvider.unit.test.ts | 20 ++- .../notebookServerProvider.unit.test.ts | 6 +- .../jupyter/jupyterSession.unit.test.ts | 7 +- .../kernels/kernelSelector.unit.test.ts | 4 +- ...UriProviderRegistration.functional.test.ts | 3 +- src/test/datascience/mockJupyterSession.ts | 7 +- .../nativeEditor.functional.test.tsx | 3 +- .../datascience/notebook.functional.test.ts | 8 +- src/test/datascience/variableTestHelpers.ts | 3 +- src/test/telemetry/index.unit.test.ts | 4 +- 90 files changed, 1013 insertions(+), 464 deletions(-) create mode 100644 src/client/common/errors/errors.ts create mode 100644 src/client/common/errors/index.ts create mode 100644 src/client/common/errors/types.ts rename src/client/datascience/{context => commands}/activeEditorContext.ts (99%) delete mode 100644 src/client/datascience/context/errorClassificationRegistration.ts delete mode 100644 src/client/datascience/jupyter/jupyterInterruptError.ts rename src/client/datascience/{context => telemetry}/interpreterCountTracker.ts (100%) create mode 100644 src/client/datascience/telemetry/interpreterPackageTracker.ts create mode 100644 src/client/datascience/telemetry/interpreterPackages.ts rename src/client/datascience/{context => telemetry}/kernelTelemetry.ts (100%) rename src/client/datascience/{context => telemetry}/serviceRegistry.ts (74%) rename src/client/datascience/{context => telemetry}/telemetry.ts (79%) rename src/client/datascience/{context => telemetry}/types.ts (94%) rename src/client/datascience/{context => telemetry}/workspaceInterpreterTracker.ts (100%) create mode 100644 src/client/telemetry/helpers.ts diff --git a/.eslintrc.js b/.eslintrc.js index d29f944e0e1..f7402fe0b7c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -232,8 +232,7 @@ module.exports = { 'src/test/datascience/mockCode2ProtocolConverter.ts', 'src/test/datascience/mockFileSystem.ts', 'src/test/datascience/interactive-common/trustService.unit.test.ts', - 'src/test/datascience/interactive-common/notebookProvider.unit.test.ts', - 'src/test/datascience/interactive-common/notebookServerProvider.unit.test.ts', + 'src/test/datascience/interactive-common/', 'src/test/datascience/interactive-common/trustCommandHandler.unit.test.ts', 'src/test/datascience/mockStatusProvider.ts', 'src/test/datascience/extensionapi/exampleextension/ms-toolsai-test/webpack.config.js', @@ -253,8 +252,6 @@ module.exports = { 'src/test/datascience/preWarmVariables.unit.test.ts', 'src/test/datascience/remoteTestHelpers.ts', 'src/test/datascience/mockWorkspaceFolder.ts', - 'src/test/datascience/mockJupyterSession.ts', - 'src/test/datascience/jupyterUriProviderRegistration.functional.test.ts', 'src/test/datascience/mockJupyterRequest.ts', 'src/test/datascience/inputHistory.unit.test.ts', 'src/test/datascience/jupyterHelpers.ts', @@ -271,12 +268,10 @@ module.exports = { 'src/test/datascience/mockProtocol2CodeConverter.ts', 'src/test/datascience/editor-integration/helpers.ts', 'src/test/datascience/editor-integration/cellhashprovider.unit.test.ts', - 'src/test/datascience/editor-integration/gotocell.functional.test.ts', 'src/test/datascience/editor-integration/codelensprovider.unit.test.ts', 'src/test/datascience/editor-integration/codewatcher.unit.test.ts', 'src/test/datascience/jupyterPasswordConnect.unit.test.ts', 'src/test/datascience/testHelpers.tsx', - 'src/test/datascience/notebook.functional.test.ts', 'src/test/datascience/mockLanguageClient.ts', 'src/test/datascience/errorHandler.functional.test.tsx', 'src/test/datascience/notebook/notebookStorage.unit.test.ts', @@ -300,8 +295,6 @@ module.exports = { 'src/test/datascience/intellisense.unit.test.ts', 'src/test/datascience/markdownManipulation.unit.test.ts', 'src/test/datascience/interactivePanel.functional.test.tsx', - 'src/test/datascience/variableTestHelpers.ts', - 'src/test/datascience/activation.unit.test.ts', 'src/test/datascience/testPersistentStateFactory.ts', 'src/test/datascience/jupyter/interpreter/jupyterInterpreterSubCommandExecutionService.unit.test.ts', 'src/test/datascience/jupyter/interpreter/jupyterInterpreterDependencyService.unit.test.ts', @@ -759,7 +752,6 @@ module.exports = { 'src/client/common/application/commands/reloadCommand.ts', 'src/client/common/application/terminalManager.ts', 'src/client/common/application/applicationEnvironment.ts', - 'src/client/common/errors/moduleNotInstalledError.ts', 'src/client/common/installer/serviceRegistry.ts', 'src/client/common/installer/productNames.ts', 'src/client/common/installer/condaInstaller.ts', @@ -776,7 +768,6 @@ module.exports = { 'src/client/common/process/currentProcess.ts', 'src/client/common/process/processFactory.ts', 'src/client/common/process/serviceRegistry.ts', - 'src/client/common/process/pythonDaemon.ts', 'src/client/common/process/pythonToolService.ts', 'src/client/common/process/internal/python.ts', 'src/client/common/process/internal/scripts/testing_tools.ts', @@ -784,7 +775,6 @@ module.exports = { 'src/client/common/process/internal/scripts/index.ts', 'src/client/common/process/pythonDaemonPool.ts', 'src/client/common/process/pythonDaemonFactory.ts', - 'src/client/common/process/types.ts', 'src/client/common/process/logger.ts', 'src/client/common/process/constants.ts', 'src/client/common/process/pythonProcess.ts', @@ -905,12 +895,10 @@ module.exports = { 'src/client/datascience/kernel-launcher/kernelDaemonPreWarmer.ts', 'src/client/datascience/ipywidgets/localWidgetScriptSourceProvider.ts', 'src/client/datascience/ipywidgets/ipyWidgetScriptSourceProvider.ts', - 'src/client/datascience/ipywidgets/ipyWidgetScriptSource.ts', 'src/client/datascience/ipywidgets/cdnWidgetScriptSourceProvider.ts', 'src/client/datascience/ipywidgets/types.ts', 'src/client/datascience/ipywidgets/remoteWidgetScriptSourceProvider.ts', 'src/client/datascience/ipywidgets/constants.ts', - 'src/client/datascience/ipywidgets/ipyWidgetMessageDispatcher.ts', 'src/client/datascience/ipywidgets/ipywidgetHandler.ts', 'src/client/datascience/ipywidgets/ipyWidgetMessageDispatcherFactory.ts', 'src/client/datascience/themeFinder.ts', @@ -955,7 +943,6 @@ module.exports = { 'src/client/datascience/notebookStorage/nativeEditorStorage.ts', 'src/client/datascience/notebookStorage/factory.ts', 'src/client/datascience/notebookStorage/types.ts', - 'src/client/datascience/notebookStorage/nativeEditorProvider.ts', 'src/client/datascience/debugLocationTracker.ts', 'src/client/datascience/plotting/plotViewerMessageListener.ts', 'src/client/datascience/plotting/types.ts', @@ -976,7 +963,6 @@ module.exports = { 'src/client/datascience/editor-integration/codelensprovider.ts', 'src/client/datascience/editor-integration/cellhashprovider.ts', 'src/client/datascience/commands/commandLineSelector.ts', - 'src/client/datascience/commands/notebookCommands.ts', 'src/client/datascience/commands/exportCommands.ts', 'src/client/datascience/cellFactory.ts', 'src/client/datascience/notebook/notebookEditorCompatibilitySupport.ts', @@ -1005,10 +991,8 @@ module.exports = { 'src/client/datascience/jupyter/interpreter/jupyterInterpreterSubCommandExecutionService.ts', 'src/client/datascience/jupyter/interpreter/jupyterInterpreterSelector.ts', 'src/client/datascience/jupyter/interpreter/jupyterInterpreterService.ts', - 'src/client/datascience/jupyter/kernels/jupyterKernelPromiseFailedError.ts', 'src/client/datascience/jupyter/kernels/jupyterKernelSpec.ts', 'src/client/datascience/jupyter/jupyterExecutionFactory.ts', - 'src/client/datascience/jupyter/serverPreload.ts', 'src/client/datascience/jupyter/jupyterRequest.ts', 'src/client/datascience/jupyter/commandLineSelector.ts', 'src/client/datascience/jupyter/jupyterDebugger.ts', @@ -1019,29 +1003,13 @@ module.exports = { 'src/client/datascience/jupyter/liveshare/utils.ts', 'src/client/datascience/jupyter/liveshare/guestJupyterSessionManagerFactory.ts', 'src/client/datascience/jupyter/liveshare/types.ts', - 'src/client/datascience/jupyter/liveshare/serverCache.ts', - 'src/client/datascience/jupyter/liveshare/guestJupyterExecution.ts', - 'src/client/datascience/jupyter/jupyterDebuggerPortBlockedError.ts', - 'src/client/datascience/jupyter/jupyterConnectError.ts', 'src/client/datascience/jupyter/debuggerVariableRegistration.ts', - 'src/client/datascience/jupyter/jupyterDebuggerRemoteNotSupported.ts', 'src/client/datascience/jupyter/jupyterConnection.ts', 'src/client/datascience/jupyter/jupyterPasswordConnect.ts', - 'src/client/datascience/jupyter/jupyterDebuggerNotInstalledError.ts', - 'src/client/datascience/jupyter/jupyterSelfCertsError.ts', - 'src/client/datascience/jupyter/jupyterDebuggerPortNotAvailableError.ts', 'src/client/datascience/jupyter/jupyterWebSocket.ts', - 'src/client/datascience/jupyter/jupyterInvalidKernelError.ts', - 'src/client/datascience/jupyter/notebookStarter.ts', - 'src/client/datascience/jupyter/jupyterZMQBinariesNotFoundError.ts', - 'src/client/datascience/jupyter/jupyterUtils.ts', - 'src/client/datascience/jupyter/jupyterDataRateLimitError.ts', 'src/client/datascience/jupyter/variableScriptLoader.ts', 'src/client/datascience/jupyter/jupyterImporter.ts', - 'src/client/datascience/jupyter/jupyterInstallError.ts', 'src/client/datascience/jupyter/oldJupyterVariables.ts', - 'src/client/datascience/jupyter/jupyterInterruptError.ts', - 'src/client/datascience/jupyter/invalidNotebookFileError.ts', 'src/client/datascience/jupyter/jupyterCellOutputMimeTypeTracker.ts', 'src/client/datascience/dataScienceFileSystem.ts', 'src/client/logging/levels.ts', diff --git a/src/client/api/pythonApi.ts b/src/client/api/pythonApi.ts index 50c57c152da..aa766a7fc9b 100644 --- a/src/client/api/pythonApi.ts +++ b/src/client/api/pythonApi.ts @@ -15,7 +15,14 @@ import { inject, injectable } from 'inversify'; import { CancellationToken, Disposable, Event, EventEmitter, Uri } from 'vscode'; import { IApplicationShell, ICommandManager } from '../common/application/types'; import { InterpreterUri } from '../common/installer/types'; -import { IExtensions, InstallerResponse, IPersistentStateFactory, Product, Resource } from '../common/types'; +import { + IDisposableRegistry, + IExtensions, + InstallerResponse, + IPersistentStateFactory, + Product, + Resource +} from '../common/types'; import { createDeferred } from '../common/utils/async'; import * as localize from '../common/utils/localize'; import { noop } from '../common/utils/misc'; @@ -201,6 +208,10 @@ const ProductMapping: { [key in Product]: JupyterProductToInstall } = { /* eslint-disable max-classes-per-file */ @injectable() export class PythonInstaller implements IPythonInstaller { + private readonly _onInstalled = new EventEmitter<{ product: Product; resource?: InterpreterUri }>(); + public get onInstalled(): Event<{ product: Product; resource?: InterpreterUri }> { + return this._onInstalled.event; + } constructor(@inject(IPythonApiProvider) private readonly apiProvider: IPythonApiProvider) {} public install( @@ -208,7 +219,15 @@ export class PythonInstaller implements IPythonInstaller { resource?: InterpreterUri, cancel?: CancellationToken ): Promise { - return this.apiProvider.getApi().then((api) => api.install(ProductMapping[product], resource, cancel)); + return this.apiProvider + .getApi() + .then((api) => api.install(ProductMapping[product], resource, cancel)) + .then((result) => { + if (result === InstallerResponse.Installed) { + this._onInstalled.fire({ product, resource }); + } + return result; + }); } } @@ -240,10 +259,25 @@ export class InterpreterSelector implements IInterpreterSelector { @injectable() export class InterpreterService implements IInterpreterService { private readonly didChangeInterpreter = new EventEmitter(); - - constructor(@inject(IPythonApiProvider) private readonly apiProvider: IPythonApiProvider) {} + private eventHandlerAdded?: boolean; + constructor( + @inject(IPythonApiProvider) private readonly apiProvider: IPythonApiProvider, + @inject(IPythonExtensionChecker) private extensionChecker: IPythonExtensionChecker, + @inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry + ) {} public get onDidChangeInterpreter(): Event { + if (this.extensionChecker.isPythonExtensionInstalled && !this.eventHandlerAdded) { + this.apiProvider + .getApi() + .then((api) => { + if (!this.eventHandlerAdded) { + this.eventHandlerAdded = true; + api.onDidChangeInterpreter(() => this.didChangeInterpreter.fire(), this, this.disposables); + } + }) + .catch(noop); + } return this.didChangeInterpreter.event; } diff --git a/src/client/api/types.ts b/src/client/api/types.ts index 748d26876a9..b9f5c098bf1 100644 --- a/src/client/api/types.ts +++ b/src/client/api/types.ts @@ -110,6 +110,7 @@ export type PythonApi = { export const IPythonInstaller = Symbol('IPythonInstaller'); export interface IPythonInstaller { + readonly onInstalled: Event<{ product: Product; resource?: InterpreterUri }>; install(product: Product, resource?: InterpreterUri, cancel?: CancellationToken): Promise; } diff --git a/src/client/common/cancellation.ts b/src/client/common/cancellation.ts index 9d2f968c0b5..3aebe21a161 100644 --- a/src/client/common/cancellation.ts +++ b/src/client/common/cancellation.ts @@ -3,15 +3,16 @@ 'use strict'; import { CancellationToken, CancellationTokenSource } from 'vscode'; +import { BaseError } from './errors/types'; import { createDeferred } from './utils/async'; import * as localize from './utils/localize'; /** * Error type thrown when canceling. */ -export class CancellationError extends Error { +export class CancellationError extends BaseError { constructor() { - super(localize.Common.canceled()); + super('cancelled', localize.Common.canceled()); } } diff --git a/src/client/common/errors/errorUtils.ts b/src/client/common/errors/errorUtils.ts index 7479359ea02..9eb9f53ea37 100644 --- a/src/client/common/errors/errorUtils.ts +++ b/src/client/common/errors/errorUtils.ts @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { EOL } from 'os'; - // eslint-disable-next-line @typescript-eslint/no-extraneous-class export class ErrorUtils { public static outputHasModuleNotInstalledError(moduleName: string, content?: string): boolean { @@ -14,20 +12,6 @@ export class ErrorUtils { } } -/** - * Wraps an error with a custom error message, retaining the call stack information. - */ -export class WrappedError extends Error { - constructor(message: string, public readonly originalException?: Error) { - super(message); - if (originalException) { - // Retain call stack that trapped the error and rethrows this error. - // Also retain the call stack of the original error. - this.stack = `${new Error('').stack}${EOL}${EOL}${originalException.stack}`; - } - } -} - /** * Given a python traceback, attempt to get the Python error message. * Generally Python error messages are at the bottom of the traceback. @@ -49,14 +33,34 @@ export function getErrorMessageFromPythonTraceback(traceback: string) { return lastLine.match(pythonErrorMessageRegExp) ? lastLine : undefined; } -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type Constructor = { new (...args: any[]): T }; -export function isErrorType(error: Error, expectedType: Constructor) { - if (error instanceof expectedType) { - return true; +export function getLastFrameFromPythonTraceback( + traceback: string +): { fileName: string; folderName: string; packageName: string } | undefined { + if (!traceback) { + return; + } + // File "/Users/donjayamanne/miniconda3/envs/env3/lib/python3.7/site-packages/appnope/_nope.py", line 38, in C + + const lastFrame = traceback + .split('\n') + .map((item) => item.trim().toLowerCase()) + .filter((item) => item.length) + .reverse() + .find( + (line) => + line.startsWith('file ') && line.includes(', line ') && line.includes('.py') && line.includes('.py') + ); + if (!lastFrame) { + return; } - if (error instanceof WrappedError && error.originalException instanceof expectedType) { - return true; + const file = lastFrame.substring(0, lastFrame.lastIndexOf('.py')) + '.py'; + const parts = file.replace(/\\/g, '/').split('/'); + const indexOfSitePackages = parts.indexOf('site-packages'); + let packageName = + indexOfSitePackages >= 0 && parts.length > indexOfSitePackages + 1 ? parts[indexOfSitePackages + 1] : ''; + const reversedParts = parts.reverse(); + if (reversedParts.length < 2) { + return; } - return false; + return { fileName: reversedParts[0], folderName: reversedParts[1], packageName }; } diff --git a/src/client/common/errors/errors.ts b/src/client/common/errors/errors.ts new file mode 100644 index 00000000000..33bce9dc0c8 --- /dev/null +++ b/src/client/common/errors/errors.ts @@ -0,0 +1,112 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const taggers = [tagWithWin32Error, tagWithZmqError, tagWithDllLoadError, tagWithOldIPyKernel, tagWithOldIPython]; +export function getErrorTags(stdErr: string) { + const tags: string[] = []; + stdErr = stdErr.toLowerCase(); + taggers.forEach((tagger) => tagger(stdErr, tags)); + + return Array.from(new Set(tags)).join(','); +} + +function tagWithWin32Error(stdErr: string, tags: string[] = []) { + if (stdErr.includes("ImportError: No module named 'win32api'".toLowerCase())) { + // force re-installing ipykernel worked. + /* + File "C:\Users\\miniconda3\envs\env_zipline\lib\contextlib.py", line 59, in enter + return next(self.gen) + File "C:\Users\\miniconda3\envs\env_zipline\lib\site-packages\jupyter_client\connect.py", line 100, in secure_write + win32_restrict_file_to_user(fname) + File "C:\Users\\miniconda3\envs\env_zipline\lib\site-packages\jupyter_client\connect.py", line 53, in win32_restrict_file_to_user + import win32api + ImportError: No module named 'win32api' + */ + tags.push('win32api'); + } + if (stdErr.includes("ImportError: No module named 'win32api'".toLowerCase())) { + // force re-installing ipykernel worked. + /* + File "C:\Users\\miniconda3\envs\env_zipline\lib\contextlib.py", line 59, in enter + return next(self.gen) + File "C:\Users\\miniconda3\envs\env_zipline\lib\site-packages\jupyter_client\connect.py", line 100, in secure_write + win32_restrict_file_to_user(fname) + File "C:\Users\\miniconda3\envs\env_zipline\lib\site-packages\jupyter_client\connect.py", line 53, in win32_restrict_file_to_user + import win32api + ImportError: No module named 'win32api' + */ + tags.push('win32api'); + } +} +function tagWithZmqError(stdErr: string, tags: string[] = []) { + if ( + stdErr.includes('ImportError: cannot import name'.toLowerCase()) && + stdErr.includes('from partially initialized module'.toLowerCase()) && + stdErr.includes('zmq.backend.cython'.toLowerCase()) + ) { + // force re-installing ipykernel worked. + tags.push('zmq.backend.cython'); + } + if ( + stdErr.includes('zmq'.toLowerCase()) && + stdErr.includes('cython'.toLowerCase()) && + stdErr.includes('__init__.py'.toLowerCase()) + ) { + // force re-installing ipykernel worked. + /* + File "C:\Users\\AppData\Roaming\Python\Python38\site-packages\zmq\backend\cython\__init__.py", line 6, in + from . import (constants, error, message, context, + ImportError: cannot import name 'constants' from partially initialized module 'zmq.backend.cython' (most likely due to a circular import) (C:\Users\\AppData\Roaming\Python\Python38\site-packages\zmq\backend\cython\__init__.py) + */ + tags.push('zmq.cython'); + } +} +function tagWithDllLoadError(stdErr: string, tags: string[] = []) { + if (stdErr.includes('ImportError: DLL load failed'.toLowerCase())) { + // Possibly a conda issue on windows + /* + win32_restrict_file_to_user + import win32api + ImportError: DLL load failed: 找不到指定的程序。 + */ + tags.push('dll.load.failed'); + } +} +function tagWithOldIPython(stdErr: string, tags: string[] = []) { + if (stdErr.includes("AssertionError: Couldn't find Class NSProcessInfo".toLowerCase())) { + // Conda environment with IPython 5.8.0 fails with this message. + // Updating to latest version of ipython fixed it (conda update ipython). + // Possible we might have to update other packages as well (when using `conda update ipython` plenty of other related pacakges got updated, such as zeromq, nbclient, jedi) + /* + Error: Kernel died with exit code 1. Traceback (most recent call last): + File "/Users/donjayamanne/miniconda3/envs/env3/lib/python3.7/site-packages/appnope/_nope.py", line 90, in nope + "Because Reasons" + File "/Users/donjayamanne/miniconda3/envs/env3/lib/python3.7/site-packages/appnope/_nope.py", line 60, in beginActivityWithOptions + NSProcessInfo = C('NSProcessInfo') + File "/Users/donjayamanne/miniconda3/envs/env3/lib/python3.7/site-packages/appnope/_nope.py", line 38, in C + assert ret is not None, "Couldn't find Class %s" % classname + AssertionError: Couldn't find Class NSProcessInfo + */ + tags.push('oldipython'); + } +} +function tagWithOldIPyKernel(stdErr: string, tags: string[] = []) { + if ( + stdErr.includes('NotImplementedError'.toLowerCase()) && + stdErr.includes('asyncio'.toLowerCase()) && + stdErr.includes('events.py'.toLowerCase()) + ) { + /* + "C:\Users\\AppData\Roaming\Python\Python38\site-packages\zmq\eventloop\zmqstream.py", line 127, in __init__ + Info 2020-08-10 12:14:11: Python Daemon (pid: 16976): write to stderr: self._init_io_state() + Info 2020-08-10 12:14:11: Python Daemon (pid: 16976): write to stderr: File "C:\Users\\AppData\Roaming\Python\Python38\site-packages\zmq\eventloop\zmqstream.py", line 546, in _init_io_state + Info 2020-08-10 12:14:11: Python Daemon (pid: 16976): write to stderr: self.io_loop.add_handler(self.socket, self._handle_events, self.io_loop.READ) + Info 2020-08-10 12:14:11: Python Daemon (pid: 16976): write to stderr: File "C:\Users\\AppData\Roaming\Python\Python38\site-packages\tornado\platform\asyncio.py", line 99, in add_handler + Info 2020-08-10 12:14:11: Python Daemon (pid: 16976): write to stderr: self.asyncio_loop.add_reader(fd, self._handle_events, fd, IOLoop.READ) + Info 2020-08-10 12:14:11: Python Daemon (pid: 16976): write to stderr: File "C:\Users\\AppData\Local\Programs\Python\Python38-32\lib\asyncio\events.py", line 501, in add_reader + Info 2020-08-10 12:14:11: Python Daemon (pid: 16976): write to stderr: raise NotImplementedError + Info 2020-08-10 12:14:11: Python Daemon (pid: 16976): write to stderr: NotImplementedError + */ + tags.push('oldipykernel'); + } +} diff --git a/src/client/common/errors/index.ts b/src/client/common/errors/index.ts new file mode 100644 index 00000000000..222a9a8c6c7 --- /dev/null +++ b/src/client/common/errors/index.ts @@ -0,0 +1,87 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { FetchError } from 'node-fetch'; +import * as stackTrace from 'stack-trace'; +import { getTelemetrySafeHashedString } from '../../telemetry/helpers'; +import { getErrorTags } from './errors'; +import { getLastFrameFromPythonTraceback } from './errorUtils'; +import { BaseError, getErrorCategory, TelemetryErrorProperties, WrappedError } from './types'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function populateTelemetryWithErrorInfo(props: Partial, error: Error) { + props.failed = true; + // Don't blow away what we already have. + props.failureCategory = props.failureCategory || getErrorCategory(error); + if (props.failureCategory === 'unknown' && isErrorType(error, FetchError)) { + props.failureCategory = 'fetcherror'; + } + props.stackTrace = serializeStackTrace(error); + const stdErr = error instanceof BaseError ? error.stdErr : ''; + if (!stdErr) { + return; + } + props.failureSubCategory = props.failureSubCategory || getErrorTags(stdErr); + const info = getLastFrameFromPythonTraceback(stdErr); + if (!info) { + return; + } + props.pythonErrorFile = props.pythonErrorFile || getTelemetrySafeHashedString(info.fileName); + props.pythonErrorFolder = props.pythonErrorFolder || getTelemetrySafeHashedString(info.folderName); + props.pythonErrorPackage = props.pythonErrorPackage || getTelemetrySafeHashedString(info.packageName); +} + +function parseStack(ex: Error) { + // Work around bug in stackTrace when ex has an array already + if (ex.stack && Array.isArray(ex.stack)) { + const concatenated = { ...ex, stack: ex.stack.join('\n') }; + return stackTrace.parse(concatenated); + } + return stackTrace.parse(ex); +} + +function serializeStackTrace(ex: Error): string { + // We aren't showing the error message (ex.message) since it might contain PII. + let trace = ''; + for (const frame of parseStack(ex)) { + const filename = frame.getFileName(); + if (filename) { + const lineno = frame.getLineNumber(); + const colno = frame.getColumnNumber(); + trace += `\n\tat ${getCallSite(frame)} ${filename}:${lineno}:${colno}`; + } else { + trace += '\n\tat '; + } + } + // Ensure we always use `/` as path separators. + // This way stack traces (with relative paths) coming from different OS will always look the same. + return trace.trim().replace(/\\/g, '/'); +} + +function getCallSite(frame: stackTrace.StackFrame) { + const parts: string[] = []; + if (typeof frame.getTypeName() === 'string' && frame.getTypeName().length > 0) { + parts.push(frame.getTypeName()); + } + if (typeof frame.getMethodName() === 'string' && frame.getMethodName().length > 0) { + parts.push(frame.getMethodName()); + } + if (typeof frame.getFunctionName() === 'string' && frame.getFunctionName().length > 0) { + if (parts.length !== 2 || parts.join('.') !== frame.getFunctionName()) { + parts.push(frame.getFunctionName()); + } + } + return parts.join('.'); +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type Constructor = { new (...args: any[]): T }; +function isErrorType(error: Error, expectedType: Constructor) { + if (error instanceof expectedType) { + return true; + } + if (error instanceof WrappedError && error.originalException instanceof expectedType) { + return true; + } + return false; +} diff --git a/src/client/common/errors/moduleNotInstalledError.ts b/src/client/common/errors/moduleNotInstalledError.ts index 944f6dfc3e5..92be307e8b0 100644 --- a/src/client/common/errors/moduleNotInstalledError.ts +++ b/src/client/common/errors/moduleNotInstalledError.ts @@ -1,8 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -export class ModuleNotInstalledError extends Error { +import { BaseError } from './types'; + +export class ModuleNotInstalledError extends BaseError { constructor(moduleName: string) { - super(`Module '${moduleName}' not installed.`); + super('notinstalled', `Module '${moduleName}' not installed.`); } } diff --git a/src/client/common/errors/types.ts b/src/client/common/errors/types.ts new file mode 100644 index 00000000000..6f6f6645c88 --- /dev/null +++ b/src/client/common/errors/types.ts @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { EOL } from 'os'; + +export abstract class BaseError extends Error { + public stdErr?: string; + constructor(public readonly category: ErrorCategory, message: string) { + super(message); + } +} + +/** + * Wraps an error with a custom error message, retaining the call stack information. + */ +export class WrappedError extends BaseError { + constructor(message: string, public readonly originalException?: Error) { + super(getErrorCategory(originalException), message); + if (originalException) { + // Retain call stack that trapped the error and rethrows this error. + // Also retain the call stack of the original error. + this.stack = `${new Error('').stack}${EOL}${EOL}${originalException.stack}`; + } + } +} + +export function getErrorCategory(error?: Error): ErrorCategory { + if (!error) { + return 'unknown'; + } + return error instanceof BaseError ? error.category : 'unknown'; +} + +export type ErrorCategory = + | 'cancelled' + | 'timeout' + | 'daemon' + | 'zmq' + | 'debugger' + | 'kerneldied' + | 'kernelpromisetimeout' + | 'jupytersession' + | 'jupyterconnection' + | 'jupyterinstall' + | 'jupyterselfcert' + | 'invalidkernel' + | 'noipykernel' + | 'fetcherror' + | 'notinstalled' + | 'unknown'; + +// If there are errors, then the are added to the telementry properties. +export type TelemetryErrorProperties = { + failed: true; + /** + * Node stacktrace without PII. + */ + stackTrace: string; + /** + * A reason that we generate (e.g. kerneldied, noipykernel, etc), more like a category of the error. + */ + failureCategory?: string; + /** + * Further sub classification of the error. E.g. kernel died due to the fact that zmq is not installed properly. + */ + failureSubCategory?: string; + /** + * Hash of the file name that contains the file in the last frame (from Python stack trace). + */ + pythonErrorFile?: string; + /** + * Hash of the folder that contains the file in the last frame (from Python stack trace). + */ + pythonErrorFolder?: string; + /** + * Hash of the module that contains the file in the last frame (from Python stack trace). + */ + pythonErrorPackage?: string; +}; diff --git a/src/client/common/process/baseDaemon.ts b/src/client/common/process/baseDaemon.ts index 78bce725fe5..c7494fd7ad8 100644 --- a/src/client/common/process/baseDaemon.ts +++ b/src/client/common/process/baseDaemon.ts @@ -9,6 +9,7 @@ import { Subject } from 'rxjs/Subject'; import * as util from 'util'; import { MessageConnection, NotificationType, RequestType, RequestType0 } from 'vscode-jsonrpc'; import { IPlatformService } from '../../common/platform/types'; +import { BaseError } from '../errors/types'; import { traceError, traceInfo, traceVerbose, traceWarning } from '../logger'; import { IDisposable } from '../types'; import { createDeferred, Deferred } from '../utils/async'; @@ -24,15 +25,15 @@ import { export type ErrorResponse = { error?: string }; export type ExecResponse = ErrorResponse & { stdout: string; stderr?: string }; -export class ConnectionClosedError extends Error { - constructor(public readonly message: string) { - super(); +export class ConnectionClosedError extends BaseError { + constructor(message: string) { + super('daemon', message); } } -export class DaemonError extends Error { - constructor(public readonly message: string) { - super(); +export class DaemonError extends BaseError { + constructor(message: string) { + super('daemon', message); } } export abstract class BasePythonDaemon { diff --git a/src/client/common/process/pythonDaemon.ts b/src/client/common/process/pythonDaemon.ts index b588af7f805..6af0de1f963 100644 --- a/src/client/common/process/pythonDaemon.ts +++ b/src/client/common/process/pythonDaemon.ts @@ -8,6 +8,7 @@ import { MessageConnection, RequestType, RequestType0 } from 'vscode-jsonrpc'; import { PythonExecInfo } from '../../pythonEnvironments/exec'; import { InterpreterInformation } from '../../pythonEnvironments/info'; import { extractInterpreterInfo } from '../../pythonEnvironments/info/interpreter'; +import { BaseError } from '../errors/types'; import { traceWarning } from '../logger'; import { IPlatformService } from '../platform/types'; import { BasePythonDaemon } from './baseDaemon'; @@ -21,18 +22,19 @@ import { type ErrorResponse = { error?: string }; -export class ConnectionClosedError extends Error { - constructor(public readonly message: string) { - super(); +export class ConnectionClosedError extends BaseError { + constructor(message: string) { + super('daemon', message); } } -export class DaemonError extends Error { - constructor(public readonly message: string) { - super(); +export class DaemonError extends BaseError { + constructor(message: string) { + super('daemon', message); } } export class PythonDaemonExecutionService extends BasePythonDaemon implements IPythonDaemonExecutionService { + // eslint-disable-next-line @typescript-eslint/no-useless-constructor constructor( pythonExecutionService: IPythonExecutionService, platformService: IPlatformService, diff --git a/src/client/common/process/types.ts b/src/client/common/process/types.ts index 4c8d917e699..7ca6e6e3ebb 100644 --- a/src/client/common/process/types.ts +++ b/src/client/common/process/types.ts @@ -8,6 +8,7 @@ import { CancellationToken, Uri } from 'vscode'; import { Newable } from '../../ioc/types'; import { PythonExecInfo } from '../../pythonEnvironments/exec'; import { InterpreterInformation, PythonEnvironment } from '../../pythonEnvironments/info'; +import { BaseError } from '../errors/types'; import { IDisposable } from '../types'; import { EnvironmentVariables } from '../variables/types'; @@ -183,9 +184,9 @@ export interface IPythonExecutionService { */ export interface IPythonDaemonExecutionService extends IPythonExecutionService, IDisposable {} -export class StdErrError extends Error { +export class StdErrError extends BaseError { constructor(message: string) { - super(message); + super('unknown', message); } } diff --git a/src/client/common/utils/async.ts b/src/client/common/utils/async.ts index ab859dc97c5..cbd44f479e0 100644 --- a/src/client/common/utils/async.ts +++ b/src/client/common/utils/async.ts @@ -3,10 +3,16 @@ 'use strict'; +import { BaseError } from '../errors/types'; + /** * Error type thrown when a timeout occurs */ -export class TimedOutError extends Error {} +export class TimedOutError extends BaseError { + constructor(message: string) { + super('timeout', message); + } +} export async function sleep(timeout: number): Promise { return new Promise((resolve) => { diff --git a/src/client/datascience/activation.ts b/src/client/datascience/activation.ts index 52f9d38f771..a889b849d3d 100644 --- a/src/client/datascience/activation.ts +++ b/src/client/datascience/activation.ts @@ -12,7 +12,7 @@ import { IDisposableRegistry } from '../common/types'; import { debounceAsync, swallowExceptions } from '../common/utils/decorators'; import { sendTelemetryEvent } from '../telemetry'; import { JupyterDaemonModule, Telemetry } from './constants'; -import { ActiveEditorContextService } from './context/activeEditorContext'; +import { ActiveEditorContextService } from './commands/activeEditorContext'; import { JupyterInterpreterService } from './jupyter/interpreter/jupyterInterpreterService'; import { KernelDaemonPreWarmer } from './kernel-launcher/kernelDaemonPreWarmer'; import { INotebookCreationTracker, INotebookEditorProvider } from './types'; diff --git a/src/client/datascience/baseJupyterSession.ts b/src/client/datascience/baseJupyterSession.ts index f980a7d6243..b0262ad89c8 100644 --- a/src/client/datascience/baseJupyterSession.ts +++ b/src/client/datascience/baseJupyterSession.ts @@ -9,8 +9,9 @@ import { ReplaySubject } from 'rxjs/ReplaySubject'; import { Event, EventEmitter } from 'vscode'; import { CancellationToken } from 'vscode-jsonrpc'; import { ServerStatus } from '../../datascience-ui/interactive-common/mainState'; -import { WrappedError } from '../common/errors/errorUtils'; +import { WrappedError } from '../common/errors/types'; import { traceError, traceInfo, traceWarning } from '../common/logger'; +import { Resource } from '../common/types'; import { sleep, waitForPromise } from '../common/utils/async'; import * as localize from '../common/utils/localize'; import { noop } from '../common/utils/misc'; @@ -22,6 +23,7 @@ import { kernelConnectionMetadataHasKernelSpec } from './jupyter/kernels/helpers import { JupyterKernelPromiseFailedError } from './jupyter/kernels/jupyterKernelPromiseFailedError'; import { getKernelConnectionId, KernelConnectionMetadata } from './jupyter/kernels/types'; import { suppressShutdownErrors } from './raw-kernel/rawKernel'; +import { trackKernelResourceInformation } from './telemetry/telemetry'; import { IJupyterSession, ISessionWithSocket, KernelSocketInformation } from './types'; /** @@ -34,7 +36,7 @@ import { IJupyterSession, ISessionWithSocket, KernelSocketInformation } from './ export class JupyterSessionStartError extends WrappedError { constructor(originalException: Error) { super(originalException.message, originalException); - sendTelemetryEvent(Telemetry.StartSessionFailedJupyter); + sendTelemetryEvent(Telemetry.StartSessionFailedJupyter, undefined, undefined, originalException, true); } } @@ -145,9 +147,12 @@ export abstract class BaseJupyterSession implements IJupyterSession { } return this.session.kernel.requestKernelInfo(); } - public async changeKernel(kernelConnection: KernelConnectionMetadata, timeoutMS: number): Promise { + public async changeKernel( + resource: Resource, + kernelConnection: KernelConnectionMetadata, + timeoutMS: number + ): Promise { let newSession: ISessionWithSocket | undefined; - // If we are already using this kernel in an active session just return back const currentKernelSpec = this.kernelConnectionMetadata && kernelConnectionMetadataHasKernelSpec(this.kernelConnectionMetadata) @@ -163,6 +168,7 @@ export abstract class BaseJupyterSession implements IJupyterSession { } } + trackKernelResourceInformation(resource, { kernelConnection }); newSession = await this.createNewKernelSession(kernelConnection, timeoutMS); // This is just like doing a restart, kill the old session (and the old restart session), and start new ones diff --git a/src/client/datascience/context/activeEditorContext.ts b/src/client/datascience/commands/activeEditorContext.ts similarity index 99% rename from src/client/datascience/context/activeEditorContext.ts rename to src/client/datascience/commands/activeEditorContext.ts index c4164890fdf..4a43dc0afee 100644 --- a/src/client/datascience/context/activeEditorContext.ts +++ b/src/client/datascience/commands/activeEditorContext.ts @@ -148,7 +148,7 @@ export class ActiveEditorContextService implements IExtensionSingleActivationSer private updateContextOfActiveNotebookKernel(activeEditor?: INotebookEditor) { if (activeEditor) { this.notebookProvider - .getOrCreateNotebook({ identity: activeEditor.file, getOnly: true }) + .getOrCreateNotebook({ identity: activeEditor.file, resource: activeEditor.file, getOnly: true }) .then((nb) => { if (activeEditor === this.notebookEditorProvider.activeEditor) { const canStart = nb && nb.status !== ServerStatus.NotStarted && activeEditor.model?.isTrusted; diff --git a/src/client/datascience/commands/notebookCommands.ts b/src/client/datascience/commands/notebookCommands.ts index 7c96bd7590a..7367da18ee6 100644 --- a/src/client/datascience/commands/notebookCommands.ts +++ b/src/client/datascience/commands/notebookCommands.ts @@ -74,11 +74,15 @@ export class NotebookCommands implements IDisposable { } if (options.identity) { // Make sure we have a connection or we can't get remote kernels. - const connection = await this.notebookProvider.connect({ getOnly: false, disableUI: false }); + const connection = await this.notebookProvider.connect({ + getOnly: false, + disableUI: false, + resource: options.resource + }); // Select a new kernel using the connection information const kernel = await this.kernelSelector.selectJupyterKernel( - options.identity, + options.resource, connection, connection?.type || this.notebookProvider.type, options.currentKernelDisplayName diff --git a/src/client/datascience/common.ts b/src/client/datascience/common.ts index 981962e23dc..b217babe8f5 100644 --- a/src/client/datascience/common.ts +++ b/src/client/datascience/common.ts @@ -206,12 +206,3 @@ export function sendNotebookOrKernelLanguageTelemetry( } sendTelemetryEvent(telemetryEvent, undefined, { language }); } - -export function getTelemetrySafeLanguage(language: string = 'unknown') { - language = (language || 'unknown').toLowerCase(); - language = KnownKernelLanguageAliases.get(language) || language; - if (!KnownNotebookLanguages.includes(language)) { - language = 'unknown'; - } - return language; -} diff --git a/src/client/datascience/context/errorClassificationRegistration.ts b/src/client/datascience/context/errorClassificationRegistration.ts deleted file mode 100644 index 5a965ee8a03..00000000000 --- a/src/client/datascience/context/errorClassificationRegistration.ts +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { injectable } from 'inversify'; -import { IExtensionSyncActivationService } from '../../activation/types'; -import { registerErrorClassifier } from '../../telemetry'; -import { getErrorClassification } from './telemetry'; - -@injectable() -export class ErrorClassificationRegistration implements IExtensionSyncActivationService { - activate(): void { - registerErrorClassifier(getErrorClassification); - } -} diff --git a/src/client/datascience/interactive-common/intellisense/intellisenseProvider.ts b/src/client/datascience/interactive-common/intellisense/intellisenseProvider.ts index abc4b738c64..af3f0a793ac 100644 --- a/src/client/datascience/interactive-common/intellisense/intellisenseProvider.ts +++ b/src/client/datascience/interactive-common/intellisense/intellisenseProvider.ts @@ -931,7 +931,12 @@ export class IntellisenseProvider implements IInteractiveWindowListener { private async getNotebook(token: CancellationToken): Promise { return this.notebookIdentity - ? this.notebookProvider.getOrCreateNotebook({ identity: this.notebookIdentity, getOnly: true, token }) + ? this.notebookProvider.getOrCreateNotebook({ + identity: this.notebookIdentity, + resource: this.resource, + getOnly: true, + token + }) : undefined; } diff --git a/src/client/datascience/interactive-common/interactiveBase.ts b/src/client/datascience/interactive-common/interactiveBase.ts index 152a7244bfe..8f2d31fee50 100644 --- a/src/client/datascience/interactive-common/interactiveBase.ts +++ b/src/client/datascience/interactive-common/interactiveBase.ts @@ -108,7 +108,7 @@ import { WebviewPanelHost } from '../webviews/webviewPanelHost'; import { DataViewerChecker } from './dataViewerChecker'; import { InteractiveWindowMessageListener } from './interactiveWindowMessageListener'; import { serializeLanguageConfiguration } from './serialization'; -import { getErrorClassification, sendKernelTelemetryEvent, trackKernelResourceInformation } from '../context/telemetry'; +import { sendKernelTelemetryEvent, trackKernelResourceInformation } from '../telemetry/telemetry'; export abstract class InteractiveBase extends WebviewPanelHost implements IInteractiveBase { public get notebook(): INotebook | undefined { @@ -877,10 +877,7 @@ export abstract class InteractiveBase extends WebviewPanelHost { // Check to see if we are already connected to our provider - const providerConnection = await this.notebookProvider.connect({ getOnly: true }); + const providerConnection = await this.notebookProvider.connect({ + getOnly: true, + resource: this.owningResource + }); if (providerConnection) { try { @@ -934,7 +934,7 @@ export abstract class InteractiveBase extends WebviewPanelHost { @@ -230,6 +232,7 @@ export class NotebookServerProvider implements IJupyterServerProvider { return { uri: serverURI, + resource: options.resource, skipUsingDefaultConfig: !useDefaultConfig, purpose: Identifiers.HistoryPurpose, kernelConnection: options.kernelConnection, diff --git a/src/client/datascience/interactive-ipynb/nativeEditor.ts b/src/client/datascience/interactive-ipynb/nativeEditor.ts index 82af8096b54..e5d71da7443 100644 --- a/src/client/datascience/interactive-ipynb/nativeEditor.ts +++ b/src/client/datascience/interactive-ipynb/nativeEditor.ts @@ -84,7 +84,7 @@ import { getCellHashProvider } from '../editor-integration/cellhashprovider'; import { KernelSelector } from '../jupyter/kernels/kernelSelector'; import { KernelConnectionMetadata } from '../jupyter/kernels/types'; import { NativeEditorNotebookModel } from '../notebookStorage/notebookModel'; -import { sendKernelTelemetryEvent } from '../context/telemetry'; +import { sendKernelTelemetryEvent } from '../telemetry/telemetry'; import { noop } from '../../common/utils/misc'; const nativeEditorDir = path.join(EXTENSION_ROOT_DIR, 'out', 'datascience-ui', 'notebook'); diff --git a/src/client/datascience/interactive-window/interactiveWindowCommandListener.ts b/src/client/datascience/interactive-window/interactiveWindowCommandListener.ts index 3ead51962eb..b75b80ddd92 100644 --- a/src/client/datascience/interactive-window/interactiveWindowCommandListener.ts +++ b/src/client/datascience/interactive-window/interactiveWindowCommandListener.ts @@ -304,7 +304,10 @@ export class InteractiveWindowCommandListener implements IDataScienceCommandList try { const settings = this.configuration.getSettings(document.uri); // Create a new notebook - notebook = await this.notebookProvider.getOrCreateNotebook({ identity: createExportInteractiveIdentity() }); + notebook = await this.notebookProvider.getOrCreateNotebook({ + identity: createExportInteractiveIdentity(), + resource: file + }); // If that works, then execute all of the cells. const cells = Array.prototype.concat( ...(await Promise.all( diff --git a/src/client/datascience/ipywidgets/ipyWidgetMessageDispatcher.ts b/src/client/datascience/ipywidgets/ipyWidgetMessageDispatcher.ts index 3ba28e91a47..39f2ff01ec0 100644 --- a/src/client/datascience/ipywidgets/ipyWidgetMessageDispatcher.ts +++ b/src/client/datascience/ipywidgets/ipyWidgetMessageDispatcher.ts @@ -389,6 +389,7 @@ export class IPyWidgetMessageDispatcher implements IIPyWidgetMessageDispatcher { if (this.notebookIdentity && !this.notebook) { this.notebook = await this.notebookProvider.getOrCreateNotebook({ identity: this.notebookIdentity, + resource: this.notebookIdentity, getOnly: true }); } diff --git a/src/client/datascience/ipywidgets/ipyWidgetScriptSource.ts b/src/client/datascience/ipywidgets/ipyWidgetScriptSource.ts index 6c4ee352295..1f05c048ee2 100644 --- a/src/client/datascience/ipywidgets/ipyWidgetScriptSource.ts +++ b/src/client/datascience/ipywidgets/ipyWidgetScriptSource.ts @@ -183,6 +183,7 @@ export class IPyWidgetScriptSource implements ILocalResourceUriConverter { if (!this.notebook) { this.notebook = await this.notebookProvider.getOrCreateNotebook({ identity: this.identity, + resource: this.identity, disableUI: true, getOnly: true }); diff --git a/src/client/datascience/jupyter/invalidNotebookFileError.ts b/src/client/datascience/jupyter/invalidNotebookFileError.ts index eb8989a5540..146f5c12b62 100644 --- a/src/client/datascience/jupyter/invalidNotebookFileError.ts +++ b/src/client/datascience/jupyter/invalidNotebookFileError.ts @@ -1,12 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. 'use strict'; +import { BaseError } from '../../common/errors/types'; import '../../common/extensions'; import * as localize from '../../common/utils/localize'; -export class InvalidNotebookFileError extends Error { +export class InvalidNotebookFileError extends BaseError { constructor(file?: string) { super( + 'unknown', file ? localize.DataScience.invalidNotebookFileErrorFormat().format(file) : localize.DataScience.invalidNotebookFileError() diff --git a/src/client/datascience/jupyter/jupyterConnectError.ts b/src/client/datascience/jupyter/jupyterConnectError.ts index 9cd33eae3a3..c1f8899faa2 100644 --- a/src/client/datascience/jupyter/jupyterConnectError.ts +++ b/src/client/datascience/jupyter/jupyterConnectError.ts @@ -1,10 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. 'use strict'; +import { BaseError } from '../../common/errors/types'; import '../../common/extensions'; -export class JupyterConnectError extends Error { +export class JupyterConnectError extends BaseError { constructor(message: string, stderr?: string) { - super(message + (stderr ? `\n${stderr}` : '')); + super('jupyterconnection', message + (stderr ? `\n${stderr}` : '')); } } diff --git a/src/client/datascience/jupyter/jupyterDataRateLimitError.ts b/src/client/datascience/jupyter/jupyterDataRateLimitError.ts index 7e08688651d..f872d720e32 100644 --- a/src/client/datascience/jupyter/jupyterDataRateLimitError.ts +++ b/src/client/datascience/jupyter/jupyterDataRateLimitError.ts @@ -1,10 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. 'use strict'; +import { BaseError } from '../../common/errors/types'; import * as localize from '../../common/utils/localize'; -export class JupyterDataRateLimitError extends Error { +export class JupyterDataRateLimitError extends BaseError { constructor() { - super(localize.DataScience.jupyterDataRateExceeded()); + super('unknown', localize.DataScience.jupyterDataRateExceeded()); } } diff --git a/src/client/datascience/jupyter/jupyterDebuggerNotInstalledError.ts b/src/client/datascience/jupyter/jupyterDebuggerNotInstalledError.ts index 755d5f208ee..f9330ed8b48 100644 --- a/src/client/datascience/jupyter/jupyterDebuggerNotInstalledError.ts +++ b/src/client/datascience/jupyter/jupyterDebuggerNotInstalledError.ts @@ -1,14 +1,15 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. 'use strict'; +import { BaseError } from '../../common/errors/types'; import '../../common/extensions'; import * as localize from '../../common/utils/localize'; -export class JupyterDebuggerNotInstalledError extends Error { +export class JupyterDebuggerNotInstalledError extends BaseError { constructor(debuggerPkg: string, message?: string) { const errorMessage = message ? message : localize.DataScience.jupyterDebuggerNotInstalledError().format(debuggerPkg); - super(errorMessage); + super('notinstalled', errorMessage); } } diff --git a/src/client/datascience/jupyter/jupyterDebuggerPortBlockedError.ts b/src/client/datascience/jupyter/jupyterDebuggerPortBlockedError.ts index 8ff97f941dd..c9d1824aa66 100644 --- a/src/client/datascience/jupyter/jupyterDebuggerPortBlockedError.ts +++ b/src/client/datascience/jupyter/jupyterDebuggerPortBlockedError.ts @@ -1,12 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. 'use strict'; +import { BaseError } from '../../common/errors/types'; import '../../common/extensions'; import * as localize from '../../common/utils/localize'; -export class JupyterDebuggerPortBlockedError extends Error { +export class JupyterDebuggerPortBlockedError extends BaseError { constructor(portNumber: number, rangeBegin: number, rangeEnd: number) { super( + 'debugger', portNumber === -1 ? localize.DataScience.jupyterDebuggerPortBlockedSearchError().format( rangeBegin.toString(), diff --git a/src/client/datascience/jupyter/jupyterDebuggerPortNotAvailableError.ts b/src/client/datascience/jupyter/jupyterDebuggerPortNotAvailableError.ts index 1a33e9eb00e..4007669a8c0 100644 --- a/src/client/datascience/jupyter/jupyterDebuggerPortNotAvailableError.ts +++ b/src/client/datascience/jupyter/jupyterDebuggerPortNotAvailableError.ts @@ -1,12 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. 'use strict'; +import { BaseError } from '../../common/errors/types'; import '../../common/extensions'; import * as localize from '../../common/utils/localize'; -export class JupyterDebuggerPortNotAvailableError extends Error { +export class JupyterDebuggerPortNotAvailableError extends BaseError { constructor(portNumber: number, rangeBegin: number, rangeEnd: number) { super( + 'debugger', portNumber === -1 ? localize.DataScience.jupyterDebuggerPortNotAvailableSearchError().format( rangeBegin.toString(), diff --git a/src/client/datascience/jupyter/jupyterDebuggerRemoteNotSupported.ts b/src/client/datascience/jupyter/jupyterDebuggerRemoteNotSupported.ts index 03c09ef8d1d..ee3eaa44fcd 100644 --- a/src/client/datascience/jupyter/jupyterDebuggerRemoteNotSupported.ts +++ b/src/client/datascience/jupyter/jupyterDebuggerRemoteNotSupported.ts @@ -1,11 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. 'use strict'; +import { BaseError } from '../../common/errors/types'; import '../../common/extensions'; import * as localize from '../../common/utils/localize'; -export class JupyterDebuggerRemoteNotSupported extends Error { +export class JupyterDebuggerRemoteNotSupported extends BaseError { constructor() { - super(localize.DataScience.remoteDebuggerNotSupported()); + super('debugger', localize.DataScience.remoteDebuggerNotSupported()); } } diff --git a/src/client/datascience/jupyter/jupyterExecution.ts b/src/client/datascience/jupyter/jupyterExecution.ts index 0a78d042a87..4bd5e63cb49 100644 --- a/src/client/datascience/jupyter/jupyterExecution.ts +++ b/src/client/datascience/jupyter/jupyterExecution.ts @@ -7,7 +7,7 @@ import { CancellationToken, CancellationTokenSource, Event, EventEmitter } from import { IApplicationShell, ILiveShareApi, IWorkspaceService } from '../../common/application/types'; import { Cancellation } from '../../common/cancellation'; -import { WrappedError } from '../../common/errors/errorUtils'; +import { WrappedError } from '../../common/errors/types'; import { traceError, traceInfo } from '../../common/logger'; import { IConfigurationService, IDisposableRegistry, IOutputChannel } from '../../common/types'; import * as localize from '../../common/utils/localize'; @@ -19,7 +19,7 @@ import { PythonEnvironment } from '../../pythonEnvironments/info'; import { captureTelemetry, sendTelemetryEvent } from '../../telemetry'; import { JupyterSessionStartError } from '../baseJupyterSession'; import { Commands, Identifiers, Telemetry } from '../constants'; -import { getErrorClassification } from '../context/telemetry'; +import { trackKernelResourceInformation } from '../telemetry/telemetry'; import { IJupyterConnection, IJupyterExecution, @@ -193,7 +193,7 @@ export class JupyterExecutionBase implements IJupyterExecution { const sessionManager = await sessionManagerFactory.create(connection); try { kernelConnectionMetadata = await this.kernelSelector.getPreferredKernelForRemoteConnection( - undefined, + options?.resource, sessionManager, options?.metadata, cancelToken @@ -212,7 +212,12 @@ export class JupyterExecutionBase implements IJupyterExecution { purpose: options ? options.purpose : uuid(), disableUI: !allowUI }; - + // If we were not provided a kernel connection, this means we changed the connection here. + if (!options?.kernelConnection) { + trackKernelResourceInformation(options?.resource, { + kernelConnection: launchInfo.kernelConnectionMetadata + }); + } // eslint-disable-next-line no-constant-condition while (true) { try { @@ -239,7 +244,7 @@ export class JupyterExecutionBase implements IJupyterExecution { const selection = await this.appShell.showErrorMessage(message, selectKernel, cancel); if (selection === selectKernel) { const kernelInterpreter = await this.kernelSelector.selectLocalKernel( - undefined, + options?.resource, 'jupyter', new StopWatch(), cancelToken, @@ -247,6 +252,9 @@ export class JupyterExecutionBase implements IJupyterExecution { ); if (kernelInterpreter) { launchInfo.kernelConnectionMetadata = kernelInterpreter; + trackKernelResourceInformation(options?.resource, { + kernelConnection: launchInfo.kernelConnectionMetadata + }); continue; } } @@ -283,9 +291,7 @@ export class JupyterExecutionBase implements IJupyterExecution { // Something else went wrong if (!isLocalConnection) { - sendTelemetryEvent(Telemetry.ConnectRemoteFailedJupyter, undefined, { - failureReason: getErrorClassification(err) - }); + sendTelemetryEvent(Telemetry.ConnectRemoteFailedJupyter, undefined, undefined, err, true); // Check for the self signed certs error specifically if (err.message.indexOf('reason: self signed certificate') >= 0) { @@ -301,9 +307,7 @@ export class JupyterExecutionBase implements IJupyterExecution { ); } } else { - sendTelemetryEvent(Telemetry.ConnectFailedJupyter, undefined, { - failureReason: getErrorClassification(err) - }); + sendTelemetryEvent(Telemetry.ConnectFailedJupyter, undefined, undefined, err, true); throw new WrappedError( localize.DataScience.jupyterNotebookConnectFailed().format(connection.baseUrl, err), err diff --git a/src/client/datascience/jupyter/jupyterInstallError.ts b/src/client/datascience/jupyter/jupyterInstallError.ts index 6af84538235..77be044c7d9 100644 --- a/src/client/datascience/jupyter/jupyterInstallError.ts +++ b/src/client/datascience/jupyter/jupyterInstallError.ts @@ -1,15 +1,16 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. 'use strict'; +import { BaseError } from '../../common/errors/types'; import '../../common/extensions'; import { HelpLinks } from '../constants'; -export class JupyterInstallError extends Error { +export class JupyterInstallError extends BaseError { public action: string; public actionTitle: string; constructor(message: string, actionFormatString: string) { - super(message); + super('jupyterinstall', message); this.action = HelpLinks.PythonInteractiveHelpLink; this.actionTitle = actionFormatString.format(HelpLinks.PythonInteractiveHelpLink); } diff --git a/src/client/datascience/jupyter/jupyterInterruptError.ts b/src/client/datascience/jupyter/jupyterInterruptError.ts deleted file mode 100644 index 172899250d3..00000000000 --- a/src/client/datascience/jupyter/jupyterInterruptError.ts +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -export class JupyterInterruptError extends Error { - constructor(message: string) { - super(message); - } -} diff --git a/src/client/datascience/jupyter/jupyterInvalidKernelError.ts b/src/client/datascience/jupyter/jupyterInvalidKernelError.ts index 1dc6735a8e9..becfacc9e11 100644 --- a/src/client/datascience/jupyter/jupyterInvalidKernelError.ts +++ b/src/client/datascience/jupyter/jupyterInvalidKernelError.ts @@ -1,15 +1,17 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +import { BaseError } from '../../common/errors/types'; import * as localize from '../../common/utils/localize'; import { sendTelemetryEvent } from '../../telemetry'; import { Telemetry } from '../constants'; import { getDisplayNameOrNameOfKernelConnection } from './kernels/helpers'; import { KernelConnectionMetadata } from './kernels/types'; -export class JupyterInvalidKernelError extends Error { +export class JupyterInvalidKernelError extends BaseError { constructor(public readonly kernelConnectionMetadata: KernelConnectionMetadata | undefined) { super( + 'invalidkernel', localize.DataScience.kernelInvalid().format( getDisplayNameOrNameOfKernelConnection(kernelConnectionMetadata) ) diff --git a/src/client/datascience/jupyter/jupyterNotebook.ts b/src/client/datascience/jupyter/jupyterNotebook.ts index d186f06b58d..7ba5e1062dd 100644 --- a/src/client/datascience/jupyter/jupyterNotebook.ts +++ b/src/client/datascience/jupyter/jupyterNotebook.ts @@ -51,7 +51,7 @@ import { isPythonKernelConnection } from './kernels/helpers'; import { isResourceNativeNotebook } from '../notebook/helpers/helpers'; -import { sendKernelTelemetryEvent } from '../context/telemetry'; +import { sendKernelTelemetryEvent } from '../telemetry/telemetry'; class CellSubscriber { public get startTime(): number { @@ -685,7 +685,7 @@ export class JupyterNotebookBase implements INotebook { this.ranInitialSetup = false; // Change the kernel on the session - await this.session.changeKernel(connectionMetadata, timeoutMS); + await this.session.changeKernel(this.resource, connectionMetadata, timeoutMS); // Change our own kernel spec // Only after session was successfully created. diff --git a/src/client/datascience/jupyter/jupyterNotebookProvider.ts b/src/client/datascience/jupyter/jupyterNotebookProvider.ts index 025877beb4e..76833106210 100644 --- a/src/client/datascience/jupyter/jupyterNotebookProvider.ts +++ b/src/client/datascience/jupyter/jupyterNotebookProvider.ts @@ -35,6 +35,7 @@ export class JupyterNotebookProvider implements IJupyterNotebookProvider { const server = await this.serverProvider.getOrCreateServer({ getOnly: options.getOnly, disableUI: options.disableUI, + resource: options.resource, token: options.token }); @@ -56,6 +57,7 @@ export class JupyterNotebookProvider implements IJupyterNotebookProvider { getOnly: options.getOnly, disableUI: options.disableUI, token: options.token, + resource: options.resource, metadata: options.metadata, kernelConnection: options.kernelConnection }); diff --git a/src/client/datascience/jupyter/jupyterSelfCertsError.ts b/src/client/datascience/jupyter/jupyterSelfCertsError.ts index 0c2ee41a5ae..8c9ebd481d0 100644 --- a/src/client/datascience/jupyter/jupyterSelfCertsError.ts +++ b/src/client/datascience/jupyter/jupyterSelfCertsError.ts @@ -1,10 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. 'use strict'; +import { BaseError } from '../../common/errors/types'; import '../../common/extensions'; -export class JupyterSelfCertsError extends Error { +export class JupyterSelfCertsError extends BaseError { constructor(message: string) { - super(message); + super('jupyterselfcert', message); } } diff --git a/src/client/datascience/jupyter/jupyterServer.ts b/src/client/datascience/jupyter/jupyterServer.ts index 704c4d4623c..41dc0deb953 100644 --- a/src/client/datascience/jupyter/jupyterServer.ts +++ b/src/client/datascience/jupyter/jupyterServer.ts @@ -21,7 +21,7 @@ import { noop } from '../../common/utils/misc'; import { StopWatch } from '../../common/utils/stopWatch'; import { IServiceContainer } from '../../ioc/types'; import { Telemetry } from '../constants'; -import { sendKernelTelemetryEvent } from '../context/telemetry'; +import { sendKernelTelemetryEvent } from '../telemetry/telemetry'; import { IJupyterConnection, IJupyterSession, diff --git a/src/client/datascience/jupyter/jupyterWaitForIdleError.ts b/src/client/datascience/jupyter/jupyterWaitForIdleError.ts index 85441c98b79..095ea32dff6 100644 --- a/src/client/datascience/jupyter/jupyterWaitForIdleError.ts +++ b/src/client/datascience/jupyter/jupyterWaitForIdleError.ts @@ -2,12 +2,13 @@ // Licensed under the MIT License. 'use strict'; +import { BaseError } from '../../common/errors/types'; import { sendTelemetryEvent } from '../../telemetry'; import { Telemetry } from '../constants'; -export class JupyterWaitForIdleError extends Error { +export class JupyterWaitForIdleError extends BaseError { constructor(message: string) { - super(message); + super('timeout', message); sendTelemetryEvent(Telemetry.SessionIdleTimeout); } } diff --git a/src/client/datascience/jupyter/jupyterZMQBinariesNotFoundError.ts b/src/client/datascience/jupyter/jupyterZMQBinariesNotFoundError.ts index 96a5e47107a..141610541ce 100644 --- a/src/client/datascience/jupyter/jupyterZMQBinariesNotFoundError.ts +++ b/src/client/datascience/jupyter/jupyterZMQBinariesNotFoundError.ts @@ -2,8 +2,10 @@ // Licensed under the MIT License. 'use strict'; -export class JupyterZMQBinariesNotFoundError extends Error { +import { BaseError } from '../../common/errors/types'; + +export class JupyterZMQBinariesNotFoundError extends BaseError { constructor(message: string) { - super(message); + super('zmq', message); } } diff --git a/src/client/datascience/jupyter/kernels/cellExecutionQueue.ts b/src/client/datascience/jupyter/kernels/cellExecutionQueue.ts index a4a8f93849d..89b3467f019 100644 --- a/src/client/datascience/jupyter/kernels/cellExecutionQueue.ts +++ b/src/client/datascience/jupyter/kernels/cellExecutionQueue.ts @@ -5,7 +5,7 @@ import { NotebookCell, NotebookCellRunState } from '../../../../../types/vscode- import { traceError, traceInfo } from '../../../common/logger'; import { noop } from '../../../common/utils/misc'; import { Telemetry } from '../../constants'; -import { sendKernelTelemetryEvent } from '../../context/telemetry'; +import { sendKernelTelemetryEvent } from '../../telemetry/telemetry'; import { traceCellMessage } from '../../notebook/helpers/helpers'; import { INotebook } from '../../types'; import { CellExecution, CellExecutionFactory } from './cellExecution'; diff --git a/src/client/datascience/jupyter/kernels/jupyterKernelPromiseFailedError.ts b/src/client/datascience/jupyter/kernels/jupyterKernelPromiseFailedError.ts index 6ba8afa6304..ca7ad4b779c 100644 --- a/src/client/datascience/jupyter/kernels/jupyterKernelPromiseFailedError.ts +++ b/src/client/datascience/jupyter/kernels/jupyterKernelPromiseFailedError.ts @@ -2,8 +2,10 @@ // Licensed under the MIT License. 'use strict'; -export class JupyterKernelPromiseFailedError extends Error { +import { BaseError } from '../../../common/errors/types'; + +export class JupyterKernelPromiseFailedError extends BaseError { constructor(message: string) { - super(message); + super('kernelpromisetimeout', message); } } diff --git a/src/client/datascience/jupyter/kernels/kernel.ts b/src/client/datascience/jupyter/kernels/kernel.ts index 1b700c6e66b..20b02447aa6 100644 --- a/src/client/datascience/jupyter/kernels/kernel.ts +++ b/src/client/datascience/jupyter/kernels/kernel.ts @@ -18,11 +18,7 @@ import { noop } from '../../../common/utils/misc'; import { StopWatch } from '../../../common/utils/stopWatch'; import { sendTelemetryEvent } from '../../../telemetry'; import { CodeSnippets, Telemetry } from '../../constants'; -import { - getErrorClassification, - sendKernelTelemetryEvent, - trackKernelResourceInformation -} from '../../context/telemetry'; +import { sendKernelTelemetryEvent, trackKernelResourceInformation } from '../../telemetry/telemetry'; import { getNotebookMetadata } from '../../notebook/helpers/helpers'; import { IDataScienceErrorHandler, @@ -212,10 +208,13 @@ export class Kernel implements IKernel { } } catch (ex) { traceError('failed to create INotebook in kernel', ex); - sendKernelTelemetryEvent(options.document.uri, Telemetry.NotebookStart, stopWatch.elapsedTime, { - failed: true, - failureReason: getErrorClassification(ex) - }); + sendKernelTelemetryEvent( + options.document.uri, + Telemetry.NotebookStart, + stopWatch.elapsedTime, + undefined, + ex + ); if (!options.disableUI) { this.errorHandler.handleError(ex).ignoreErrors(); // Just a notification, so don't await this } diff --git a/src/client/datascience/jupyter/kernels/kernelExecution.ts b/src/client/datascience/jupyter/kernels/kernelExecution.ts index 29002aedd87..1ab2ba7a74a 100644 --- a/src/client/datascience/jupyter/kernels/kernelExecution.ts +++ b/src/client/datascience/jupyter/kernels/kernelExecution.ts @@ -12,7 +12,7 @@ import { createDeferred, waitForPromise } from '../../../common/utils/async'; import { StopWatch } from '../../../common/utils/stopWatch'; import { captureTelemetry } from '../../../telemetry'; import { Telemetry, VSCodeNativeTelemetry } from '../../constants'; -import { sendKernelTelemetryEvent, trackKernelResourceInformation } from '../../context/telemetry'; +import { sendKernelTelemetryEvent, trackKernelResourceInformation } from '../../telemetry/telemetry'; import { traceCellMessage } from '../../notebook/helpers/helpers'; import { chainWithPendingUpdates } from '../../notebook/helpers/notebookUpdater'; import { diff --git a/src/client/datascience/jupyter/kernels/kernelSelector.ts b/src/client/datascience/jupyter/kernels/kernelSelector.ts index fb2ce4245df..ea7feea0ac8 100644 --- a/src/client/datascience/jupyter/kernels/kernelSelector.ts +++ b/src/client/datascience/jupyter/kernels/kernelSelector.ts @@ -20,8 +20,8 @@ import { PythonEnvironment } from '../../../pythonEnvironments/info'; import { captureTelemetry, IEventNamePropertyMapping, sendTelemetryEvent } from '../../../telemetry'; import { sendNotebookOrKernelLanguageTelemetry } from '../../common'; import { Commands, Telemetry } from '../../constants'; -import { sendKernelListTelemetry } from '../../context/kernelTelemetry'; -import { sendKernelTelemetryEvent, trackKernelResourceInformation } from '../../context/telemetry'; +import { sendKernelListTelemetry } from '../../telemetry/kernelTelemetry'; +import { sendKernelTelemetryEvent } from '../../telemetry/telemetry'; import { IKernelFinder, IpyKernelNotInstalledError } from '../../kernel-launcher/types'; import { isPythonNotebook } from '../../notebook/helpers/helpers'; import { getInterpreterInfoStoredInMetadata } from '../../notebookStorage/baseModel'; @@ -54,6 +54,7 @@ import { LiveKernelConnectionMetadata, PythonKernelConnectionMetadata } from './types'; +import { InterpreterPackages } from '../../telemetry/interpreterPackages'; /** * All KernelConnections returned (as return values of methods) by the KernelSelector can be used in a number of ways. @@ -74,7 +75,8 @@ export class KernelSelector implements IKernelSelectionUsage { @inject(IConfigurationService) private configService: IConfigurationService, @inject(IPythonExtensionChecker) private readonly extensionChecker: IPythonExtensionChecker, @inject(PreferredRemoteKernelIdProvider) - private readonly preferredRemoteKernelIdProvider: PreferredRemoteKernelIdProvider + private readonly preferredRemoteKernelIdProvider: PreferredRemoteKernelIdProvider, + @inject(InterpreterPackages) private readonly interpreterPackages: InterpreterPackages ) {} /** @@ -123,11 +125,18 @@ export class KernelSelector implements IKernelSelectionUsage { cancelToken, currentKernelDisplayName ); + if (selection?.interpreter) { + this.interpreterPackages.trackPackages(selection.interpreter); + } return cloneDeep(selection); } /** * Gets a kernel that needs to be used with a local session. * (will attempt to find the best matching kernel, or prompt user to use current interpreter or select one). + * + * @param {boolean} [ignoreTrackingKernelInformation] + * As a side effect these method tracks the kernel information for telemetry, we should ensure that tracking is disabled for Native Notebooks. Native Notebooks knows exactly what the current kernel information is, webviews/interactive is not the same. + * I.e. whenever these methods are called by webviews/interactive assume the return value is the active kernel. */ @traceDecorators.info('Get preferred local kernel connection') @reportAction(ReportableAction.KernelsGetKernelForLocalConnection) @@ -206,12 +215,20 @@ export class KernelSelector implements IKernelSelectionUsage { } return itemToReturn; } + if (selection?.interpreter) { + this.interpreterPackages.trackPackages(selection.interpreter); + } + return selection; } /** * Gets a kernel that needs to be used with a remote session. * (will attempt to find the best matching kernel, or prompt user to use current interpreter or select one). + * + * @param {boolean} [ignoreTrackingKernelInformation] + * As a side effect these method tracks the kernel information for telemetry, we should ensure that tracking is disabled for Native Notebooks. Native Notebooks knows exactly what the current kernel information is, webviews/interactive is not the same. + * I.e. whenever these methods are called by webviews/interactive assume the return value is the active kernel. */ // eslint-disable-next-line complexity @traceDecorators.info('Get preferred remote kernel connection') @@ -308,8 +325,10 @@ export class KernelSelector implements IKernelSelectionUsage { bestScore = score; } } + + let kernelConnection: KernelConnectionMetadata; if (bestMatch) { - return cloneDeep({ + kernelConnection = cloneDeep({ kernelSpec: bestMatch, interpreter: interpreter, kind: 'startUsingKernelSpec' @@ -318,11 +337,13 @@ export class KernelSelector implements IKernelSelectionUsage { traceError('No preferred kernel, using the default kernel'); // Unlikely scenario, we expect there to be at least one kernel spec. // Either way, return so that we can start using the default kernel. - return cloneDeep({ + kernelConnection = cloneDeep({ interpreter: interpreter, kind: 'startUsingDefaultKernel' }); } + + return kernelConnection; } public async useSelectedKernel( selection: KernelConnectionMetadata, @@ -427,7 +448,14 @@ export class KernelSelector implements IKernelSelectionUsage { return this.selectRemoteKernel(resource, stopWatch, sessionManagerCreator, undefined, currentKernelDisplayName); } - // Get our kernelspec and matching interpreter for a connection to a local jupyter server + /** + * Get our kernelspec and matching interpreter for a connection to a local jupyter server + * + * + * @param {boolean} [ignoreTrackingKernelInformation] + * As a side effect these method tracks the kernel information for telemetry, we should ensure that tracking is disabled for Native Notebooks. Native Notebooks knows exactly what the current kernel information is, webviews/interactive is not the same. + * I.e. whenever these methods are called by webviews/interactive assume the return value is the active kernel. + */ private async getKernelForLocalJupyterConnection( resource: Resource, stopWatch: StopWatch, @@ -467,7 +495,6 @@ export class KernelSelector implements IKernelSelectionUsage { telemetryProps.promptedToSelect = true; kernelConnection = await this.selectLocalKernel(resource, 'jupyter', stopWatch, cancelToken); } - trackKernelResourceInformation(resource, { kernelConnection }); return kernelConnection; } } else if (!cancelToken?.isCancellationRequested) { @@ -486,7 +513,6 @@ export class KernelSelector implements IKernelSelectionUsage { } else { kernelConnection = { kind: 'startUsingDefaultKernel', interpreter: activeInterpreter }; } - trackKernelResourceInformation(resource, { kernelConnection }); return kernelConnection; } } @@ -504,6 +530,10 @@ export class KernelSelector implements IKernelSelectionUsage { } /** * Get our kernelspec and interpreter for a local raw connection + * + * @param {boolean} [ignoreTrackingKernelInformation] + * As a side effect these method tracks the kernel information for telemetry, we should ensure that tracking is disabled for Native Notebooks. Native Notebooks knows exactly what the current kernel information is, webviews/interactive is not the same. + * I.e. whenever these methods are called by webviews/interactive assume the return value is the active kernel. */ @traceDecorators.verbose('Find kernel spec') private async getKernelForLocalRawConnection( @@ -524,7 +554,6 @@ export class KernelSelector implements IKernelSelectionUsage { interpreter: interpreterStoredInKernelSpec }; // Install missing dependencies only if we're dealing with a Python kernel. - trackKernelResourceInformation(resource, { kernelConnection }); if (interpreterStoredInKernelSpec && isPythonKernelConnection(kernelConnection)) { await this.installDependenciesIntoInterpreter( interpreterStoredInKernelSpec, @@ -536,7 +565,13 @@ export class KernelSelector implements IKernelSelectionUsage { } // First use our kernel finder to locate a kernelspec on disk - const kernelSpec = await this.kernelFinder.findKernelSpec(resource, notebookMetadata, cancelToken); + const hasKernelMetadataForPythonNb = + isPythonNotebook(notebookMetadata) && notebookMetadata?.kernelspec ? true : false; + // Don't look for kernel spec for python notebooks if we don't have the kernel metadata. + const kernelSpec = + hasKernelMetadataForPythonNb || !isPythonNotebook(notebookMetadata) + ? await this.kernelFinder.findKernelSpec(resource, notebookMetadata, cancelToken) + : undefined; traceInfoIf( !!process.env.VSC_JUPYTER_FORCE_LOGGING, `Kernel spec found ${JSON.stringify(kernelSpec)}, metadata ${JSON.stringify(notebookMetadata || '')}` @@ -550,7 +585,6 @@ export class KernelSelector implements IKernelSelectionUsage { kind: 'startUsingPythonInterpreter', interpreter: activeInterpreter }; - trackKernelResourceInformation(resource, { kernelConnection }); await this.installDependenciesIntoInterpreter(activeInterpreter, ignoreDependencyCheck, cancelToken); // Return current interpreter. @@ -567,7 +601,6 @@ export class KernelSelector implements IKernelSelectionUsage { kernelSpec, interpreter }; - trackKernelResourceInformation(resource, { kernelConnection }); // Install missing dependencies only if we're dealing with a Python kernel. if (interpreter && isPythonKernelConnection(kernelConnection)) { await this.installDependenciesIntoInterpreter(interpreter, ignoreDependencyCheck, cancelToken); @@ -587,7 +620,6 @@ export class KernelSelector implements IKernelSelectionUsage { kernelSpec: firstPython, interpreter: undefined }; - trackKernelResourceInformation(resource, { kernelConnection }); return kernelConnection; } } @@ -599,7 +631,6 @@ export class KernelSelector implements IKernelSelectionUsage { kernelSpec: kernelSpecs[0], interpreter: undefined }; - trackKernelResourceInformation(resource, { kernelConnection }); return kernelConnection; } } @@ -623,10 +654,9 @@ export class KernelSelector implements IKernelSelectionUsage { if (!selection?.selection) { return; } - trackKernelResourceInformation(resource, { - kernelConnection: selection.selection, - kernelConnectionChanged: true - }); + if (selection.selection.interpreter) { + this.interpreterPackages.trackPackages(selection.selection.interpreter); + } sendKernelTelemetryEvent(resource, Telemetry.SwitchKernel); return (this.useSelectedKernel(selection.selection, resource, type, cancelToken) as unknown) as T | undefined; } diff --git a/src/client/datascience/jupyter/liveshare/guestJupyterExecution.ts b/src/client/datascience/jupyter/liveshare/guestJupyterExecution.ts index b842aa903fa..7eb12b79b2d 100644 --- a/src/client/datascience/jupyter/liveshare/guestJupyterExecution.ts +++ b/src/client/datascience/jupyter/liveshare/guestJupyterExecution.ts @@ -106,6 +106,7 @@ export class GuestJupyterExecution extends LiveShareParticipantGuest( return super.connectToNotebookServer( { uri: newUri, + resource: options?.resource, skipUsingDefaultConfig: options && options.skipUsingDefaultConfig, workingDir: options ? options.workingDir : undefined, purpose, diff --git a/src/client/datascience/jupyter/liveshare/hostJupyterServer.ts b/src/client/datascience/jupyter/liveshare/hostJupyterServer.ts index ccc304b82e5..6d2c15f6add 100644 --- a/src/client/datascience/jupyter/liveshare/hostJupyterServer.ts +++ b/src/client/datascience/jupyter/liveshare/hostJupyterServer.ts @@ -234,6 +234,7 @@ export class HostJupyterServer extends LiveShareParticipantHost(JupyterServerBas // If we switched kernels, try switching the possible session if (changedKernel && possibleSession && info.kernelConnectionMetadata) { await possibleSession.changeKernel( + resource, info.kernelConnectionMetadata, this.configService.getSettings(resource).jupyterLaunchTimeout ); diff --git a/src/client/datascience/jupyter/liveshare/serverCache.ts b/src/client/datascience/jupyter/liveshare/serverCache.ts index 2468e5689c8..972f0ef44dd 100644 --- a/src/client/datascience/jupyter/liveshare/serverCache.ts +++ b/src/client/datascience/jupyter/liveshare/serverCache.ts @@ -126,6 +126,7 @@ export class ServerCache implements IAsyncDisposable { public async generateDefaultOptions(options?: INotebookServerOptions): Promise { return { uri: options ? options.uri : undefined, + resource: options?.resource, skipUsingDefaultConfig: options ? options.skipUsingDefaultConfig : false, // Default for this is false usingDarkTheme: options ? options.usingDarkTheme : undefined, purpose: options ? options.purpose : uuid(), diff --git a/src/client/datascience/jupyter/notebookStarter.ts b/src/client/datascience/jupyter/notebookStarter.ts index 60530aa7e24..a37d35fdf1d 100644 --- a/src/client/datascience/jupyter/notebookStarter.ts +++ b/src/client/datascience/jupyter/notebookStarter.ts @@ -10,7 +10,7 @@ import * as path from 'path'; import * as uuid from 'uuid/v4'; import { CancellationToken, Disposable } from 'vscode'; import { CancellationError, createPromiseFromCancellation } from '../../common/cancellation'; -import { WrappedError } from '../../common/errors/errorUtils'; +import { WrappedError } from '../../common/errors/types'; import { traceInfo } from '../../common/logger'; import { IFileSystem, TemporaryDirectory } from '../../common/platform/types'; import { IDisposable, IOutputChannel } from '../../common/types'; diff --git a/src/client/datascience/jupyter/serverPreload.ts b/src/client/datascience/jupyter/serverPreload.ts index 93efa6c4cc1..5708327b17e 100644 --- a/src/client/datascience/jupyter/serverPreload.ts +++ b/src/client/datascience/jupyter/serverPreload.ts @@ -59,13 +59,18 @@ export class ServerPreload implements IExtensionSingleActivationService { traceInfo(`Attempting to start a server because of preload conditions ...`); // Check if we are already connected - let providerConnection = await this.notebookProvider.connect({ getOnly: true, disableUI: true }); + let providerConnection = await this.notebookProvider.connect({ + getOnly: true, + disableUI: true, + resource: undefined + }); // If it didn't start, attempt for local and if allowed. if (!providerConnection && !this.configService.getSettings(undefined).disableJupyterAutoStart) { // Local case, try creating one providerConnection = await this.notebookProvider.connect({ getOnly: false, + resource: undefined, disableUI: true, localOnly: true }); diff --git a/src/client/datascience/kernel-launcher/kernelDaemon.ts b/src/client/datascience/kernel-launcher/kernelDaemon.ts index 5189a0b7b24..7fcf07f3f1a 100644 --- a/src/client/datascience/kernel-launcher/kernelDaemon.ts +++ b/src/client/datascience/kernel-launcher/kernelDaemon.ts @@ -105,12 +105,13 @@ export class PythonKernelDaemon extends BasePythonDaemon implements IPythonKerne const KernelDiedNotification = new NotificationType<{ exit_code: string; reason?: string }, void>( 'kernel_died' ); - let possibleReasonForProcExit = ''; + let stdErr = ''; this.connection.onNotification(KernelDiedNotification, (output) => { this.subject.error( new PythonKernelDiedError({ exitCode: parseInt(output.exit_code, 10), - reason: output.reason || possibleReasonForProcExit // If we have collected the error then use that (if reason is empty). + reason: output.reason || stdErr, // If we have collected the error then use that (if reason is empty). + stdErr: stdErr || output.reason || '' }) ); }); @@ -123,7 +124,7 @@ export class PythonKernelDaemon extends BasePythonDaemon implements IPythonKerne if (out.source === 'stderr') { // Don't call this.subject.error, as that can only be called once (hence can only be handled once). // Instead log this error & pass this only when the kernel dies. - possibleReasonForProcExit += out.out; + stdErr += out.out; traceWarning(`Kernel ${this.proc.pid} as possibly died, StdErr from Kernel Process ${out.out}`); } else { this.subject.next(out); @@ -134,6 +135,6 @@ export class PythonKernelDaemon extends BasePythonDaemon implements IPythonKerne ); // If the daemon dies, then kernel is also dead. - this.closed.catch((error) => this.subject.error(new PythonKernelDiedError({ error }))); + this.closed.catch((error) => this.subject.error(new PythonKernelDiedError({ error, stdErr }))); } } diff --git a/src/client/datascience/kernel-launcher/kernelLauncher.ts b/src/client/datascience/kernel-launcher/kernelLauncher.ts index 536a01d2aa0..1d452513635 100644 --- a/src/client/datascience/kernel-launcher/kernelLauncher.ts +++ b/src/client/datascience/kernel-launcher/kernelLauncher.ts @@ -27,7 +27,7 @@ import { IKernelConnection, IKernelLauncher, IKernelProcess, IpyKernelNotInstall import * as localize from '../../common/utils/localize'; import { createDeferredFromPromise, Deferred } from '../../common/utils/async'; import { CancellationError } from '../../common/cancellation'; -import { sendKernelTelemetryWhenDone } from '../context/telemetry'; +import { sendKernelTelemetryWhenDone } from '../telemetry/telemetry'; const PortFormatString = `kernelLauncherPortStart_{0}.tmp`; diff --git a/src/client/datascience/kernel-launcher/kernelProcess.ts b/src/client/datascience/kernel-launcher/kernelProcess.ts index fedae8fd9fc..e43ac60b1de 100644 --- a/src/client/datascience/kernel-launcher/kernelProcess.ts +++ b/src/client/datascience/kernel-launcher/kernelProcess.ts @@ -6,15 +6,15 @@ import { ChildProcess } from 'child_process'; import * as fs from 'fs-extra'; import * as tcpPortUsed from 'tcp-port-used'; import * as tmp from 'tmp'; -import { CancellationToken, CancellationTokenSource, Event, EventEmitter } from 'vscode'; +import { CancellationToken, Event, EventEmitter } from 'vscode'; import { IPythonExtensionChecker } from '../../api/types'; -import { createPromiseFromCancellation, wrapCancellationTokens } from '../../common/cancellation'; +import { createPromiseFromCancellation } from '../../common/cancellation'; import { getErrorMessageFromPythonTraceback } from '../../common/errors/errorUtils'; import { traceDecorators, traceError, traceInfo, traceWarning } from '../../common/logger'; import { IFileSystem } from '../../common/platform/types'; import { IProcessServiceFactory, ObservableExecutionResult } from '../../common/process/types'; import { Resource } from '../../common/types'; -import { TimedOutError } from '../../common/utils/async'; +import { createDeferred, TimedOutError } from '../../common/utils/async'; import * as localize from '../../common/utils/localize'; import { noop, swallowExceptions } from '../../common/utils/misc'; import { captureTelemetry } from '../../telemetry'; @@ -34,6 +34,7 @@ import { IKernelProcess, IPythonKernelDaemon, KernelDiedError, + KernelProcessExited, PythonKernelDiedError } from './types'; @@ -95,9 +96,8 @@ export class KernelProcess implements IKernelProcess { let stderr = ''; let stderrProc = ''; let exitEventFired = false; - const cancelWaiting = new CancellationTokenSource(); - const combinedToken = wrapCancellationTokens(cancelWaiting.token, cancelToken); let providedExitCode: number | null; + const deferred = createDeferred(); exeObs.proc!.on('exit', (exitCode) => { exitCode = exitCode || providedExitCode; traceInfo('KernelProcess Exit', `Exit - ${exitCode}`, stderrProc); @@ -111,7 +111,7 @@ export class KernelProcess implements IKernelProcess { }); exitEventFired = true; } - cancelWaiting.cancel(); + deferred.reject(new KernelProcessExited(exitCode || -1)); }); exeObs.proc!.stdout.on('data', (data: Buffer | string) => { @@ -148,9 +148,9 @@ export class KernelProcess implements IKernelProcess { } else { traceError('KernelProcess Exit', `Exit - ${error.exitCode}, ${error.reason}`, error); } - if (!stderrProc && (error.reason || error.message)) { + if (!stderrProc && (error.stdErr || error.reason || error.message)) { // This is used when process exits. - stderrProc = error.reason || error.message; + stderrProc = error.stdErr || error.reason || error.message; } if (!exitEventFired) { let reason = error.reason || error.message; @@ -160,7 +160,7 @@ export class KernelProcess implements IKernelProcess { }); exitEventFired = true; } - cancelWaiting.cancel(); + deferred.reject(error); } }, () => { @@ -172,8 +172,9 @@ export class KernelProcess implements IKernelProcess { try { await Promise.race([ tcpPortUsed.waitUntilUsed(this.connection.hb_port, 200, timeout), + deferred.promise, createPromiseFromCancellation({ - token: combinedToken, + token: cancelToken, cancelAction: 'reject', defaultValue: undefined }) @@ -183,7 +184,11 @@ export class KernelProcess implements IKernelProcess { // Make sure to dispose if we never get a heartbeat this.dispose().ignoreErrors(); - if (cancelWaiting.token.isCancellationRequested) { + if ( + cancelToken?.isCancellationRequested || + e instanceof KernelProcessExited || + e instanceof PythonKernelDiedError + ) { traceError(stderrProc || stderr); // If we have the python error message, display that. const errorMessage = @@ -191,6 +196,8 @@ export class KernelProcess implements IKernelProcess { (stderrProc || stderr).substring(0, 100); throw new KernelDiedError( localize.DataScience.kernelDied().format(Commands.ViewJupyterOutput, errorMessage), + // Include what ever we have as the stderr. + stderrProc + '\n' + stderr + '\n', e ); } else { diff --git a/src/client/datascience/kernel-launcher/types.ts b/src/client/datascience/kernel-launcher/types.ts index c35d2d575fc..b5202a1d7ca 100644 --- a/src/client/datascience/kernel-launcher/types.ts +++ b/src/client/datascience/kernel-launcher/types.ts @@ -5,7 +5,7 @@ import type { nbformat } from '@jupyterlab/coreutils'; import { SpawnOptions } from 'child_process'; import { CancellationToken, Event } from 'vscode'; -import { WrappedError } from '../../common/errors/errorUtils'; +import { BaseError, WrappedError } from '../../common/errors/types'; import { ObservableExecutionResult } from '../../common/process/types'; import { IAsyncDisposable, IDisposable, Resource } from '../../common/types'; import { PythonEnvironment } from '../../pythonEnvironments/info'; @@ -67,17 +67,28 @@ export interface IPythonKernelDaemon extends IDisposable { start(moduleName: string, args: string[], options: SpawnOptions): Promise>; } -export class KernelDiedError extends WrappedError {} +export class KernelDiedError extends WrappedError { + constructor(message: string, public readonly stdErr: string, originalException?: Error) { + super(message, originalException); + } +} + +export class KernelProcessExited extends BaseError { + constructor(public readonly exitCode: number = -1) { + super('kerneldied', 'Kernel process Exited'); + } +} -export class PythonKernelDiedError extends Error { +export class PythonKernelDiedError extends BaseError { public readonly exitCode: number; public readonly reason?: string; - constructor(options: { exitCode: number; reason?: string } | { error: Error }) { + constructor(options: { exitCode: number; reason?: string; stdErr: string } | { error: Error; stdErr: string }) { const message = 'exitCode' in options ? `Kernel died with exit code ${options.exitCode}. ${options.reason}` : `Kernel died ${options.error.message}`; - super(message); + super('kerneldied', message); + this.stdErr = options.stdErr; if ('exitCode' in options) { this.exitCode = options.exitCode; this.reason = options.reason; @@ -90,8 +101,8 @@ export class PythonKernelDiedError extends Error { } } -export class IpyKernelNotInstalledError extends Error { +export class IpyKernelNotInstalledError extends BaseError { constructor(message: string, public reason: KernelInterpreterDependencyResponse) { - super(message); + super('noipykernel', message); } } diff --git a/src/client/datascience/notebook/kernelProvider.ts b/src/client/datascience/notebook/kernelProvider.ts index 8f4dc066081..0b36959c966 100644 --- a/src/client/datascience/notebook/kernelProvider.ts +++ b/src/client/datascience/notebook/kernelProvider.ts @@ -17,8 +17,8 @@ import { StopWatch } from '../../common/utils/stopWatch'; import { captureTelemetry } from '../../telemetry'; import { sendNotebookOrKernelLanguageTelemetry } from '../common'; import { Telemetry } from '../constants'; -import { sendKernelListTelemetry } from '../context/kernelTelemetry'; -import { getErrorClassification, sendKernelTelemetryEvent, trackKernelResourceInformation } from '../context/telemetry'; +import { sendKernelListTelemetry } from '../telemetry/kernelTelemetry'; +import { sendKernelTelemetryEvent, trackKernelResourceInformation } from '../telemetry/telemetry'; import { areKernelConnectionsEqual, isLocalLaunch } from '../jupyter/kernels/helpers'; import { KernelSelectionProvider } from '../jupyter/kernels/kernelSelections'; import { KernelSelector } from '../jupyter/kernels/kernelSelector'; @@ -236,6 +236,7 @@ export class VSCodeKernelPickerProvider implements INotebookKernelProvider { // Make sure we have a connection or we can't get remote kernels. const connection = await this.notebookProvider.connect({ getOnly: false, + resource, disableUI: false, localOnly: false }); @@ -249,10 +250,7 @@ export class VSCodeKernelPickerProvider implements INotebookKernelProvider { } catch (ex) { // This condition is met when remote Uri is invalid. // User cannot even run a cell, as kernel list is invalid (we can't get it). - sendKernelTelemetryEvent(resource, Telemetry.NotebookStart, undefined, { - failed: true, - failureReason: getErrorClassification(ex) - }); + sendKernelTelemetryEvent(resource, Telemetry.NotebookStart, undefined, undefined, ex); throw ex; } } diff --git a/src/client/datascience/notebook/notebookEditor.ts b/src/client/datascience/notebook/notebookEditor.ts index b622b97fe22..739c78344a7 100644 --- a/src/client/datascience/notebook/notebookEditor.ts +++ b/src/client/datascience/notebook/notebookEditor.ts @@ -6,8 +6,6 @@ import { ConfigurationTarget, Event, EventEmitter, ProgressLocation, Uri, WebviewPanel } from 'vscode'; import { NotebookCell, NotebookDocument } from '../../../../types/vscode-proposed'; import { IApplicationShell, ICommandManager, IVSCodeNotebook } from '../../common/application/types'; -import { CancellationError } from '../../common/cancellation'; -import { isErrorType } from '../../common/errors/errorUtils'; import { traceError } from '../../common/logger'; import { IConfigurationService, IDisposable, IDisposableRegistry } from '../../common/types'; import { DataScience } from '../../common/utils/localize'; @@ -15,7 +13,7 @@ import { noop } from '../../common/utils/misc'; import { StopWatch } from '../../common/utils/stopWatch'; import { captureTelemetry, sendTelemetryEvent } from '../../telemetry'; import { Telemetry } from '../constants'; -import { sendKernelTelemetryEvent, trackKernelResourceInformation } from '../context/telemetry'; +import { sendKernelTelemetryEvent, trackKernelResourceInformation } from '../telemetry/telemetry'; import { JupyterKernelPromiseFailedError } from '../jupyter/kernels/jupyterKernelPromiseFailedError'; import { IKernel, IKernelProvider } from '../jupyter/kernels/types'; import { @@ -335,7 +333,7 @@ export class NotebookEditor implements INotebookEditor { if (exc instanceof JupyterKernelPromiseFailedError && kernel) { sendKernelTelemetryEvent(this.document.uri, Telemetry.NotebookRestart, stopWatch.elapsedTime, { failed: true, - failureReason: 'kernelpromisetimeout' + failureCategory: 'kernelpromisetimeout' }); // Old approach (INotebook is not exposed in IKernel, and INotebook will eventually go away). const notebook = await this.notebookProvider.getOrCreateNotebook({ @@ -346,12 +344,9 @@ export class NotebookEditor implements INotebookEditor { if (notebook) { await notebook.dispose(); } - await this.notebookProvider.connect({ getOnly: false, disableUI: false }); + await this.notebookProvider.connect({ getOnly: false, disableUI: false, resource: this.file }); } else { - sendKernelTelemetryEvent(this.document.uri, Telemetry.NotebookRestart, stopWatch.elapsedTime, { - failed: true, - failureReason: isErrorType(exc, CancellationError) ? 'cancelled' : 'unknown' - }); + sendKernelTelemetryEvent(this.document.uri, Telemetry.NotebookRestart, stopWatch.elapsedTime, exc); // Show the error message void this.applicationShell.showErrorMessage(exc); traceError(exc); diff --git a/src/client/datascience/notebookStorage/nativeEditorProvider.ts b/src/client/datascience/notebookStorage/nativeEditorProvider.ts index 2763d48c7d0..b8520e8884a 100644 --- a/src/client/datascience/notebookStorage/nativeEditorProvider.ts +++ b/src/client/datascience/notebookStorage/nativeEditorProvider.ts @@ -320,7 +320,7 @@ export class NativeEditorProvider implements INotebookEditorProvider, CustomEdit private onDisposedModel(model: INotebookModel) { // When model goes away, dispose of the associated notebook (as all of the editors have closed down) this.notebookProvider - .getOrCreateNotebook({ identity: model.file, getOnly: true }) + .getOrCreateNotebook({ identity: model.file, getOnly: true, resource: model.file }) .then((n) => n?.dispose()) .ignoreErrors(); this.models.delete(model); diff --git a/src/client/datascience/raw-kernel/liveshare/hostRawNotebookProvider.ts b/src/client/datascience/raw-kernel/liveshare/hostRawNotebookProvider.ts index 68813ef8b3e..e665c1dd9a0 100644 --- a/src/client/datascience/raw-kernel/liveshare/hostRawNotebookProvider.ts +++ b/src/client/datascience/raw-kernel/liveshare/hostRawNotebookProvider.ts @@ -54,6 +54,7 @@ import { import { calculateWorkingDirectory } from '../../utils'; import { RawJupyterSession } from '../rawJupyterSession'; import { RawNotebookProviderBase } from '../rawNotebookProvider'; +import { trackKernelResourceInformation } from '../../telemetry/telemetry'; // eslint-disable-next-line @typescript-eslint/no-require-imports /* eslint-disable @typescript-eslint/no-explicit-any */ @@ -163,6 +164,7 @@ export class HostRawNotebookProvider traceInfo(`Getting preferred kernel for ${identity.toString()}`); try { + const kernelConnectionProvided = !!kernelConnection; if ( kernelConnection && isPythonKernelConnection(kernelConnection) && @@ -233,6 +235,10 @@ export class HostRawNotebookProvider ) { notebookPromise.reject('Failed to find a kernelspec to use for ipykernel launch'); } else { + // If a kernel connection was not provided, then we set it up here. + if (!kernelConnectionProvided) { + trackKernelResourceInformation(resource, { kernelConnection: kernelConnectionMetadata }); + } traceInfo( `Connecting to raw session for ${identity.toString()} with connection ${JSON.stringify( kernelConnectionMetadata diff --git a/src/client/datascience/raw-kernel/rawJupyterSession.ts b/src/client/datascience/raw-kernel/rawJupyterSession.ts index c4345a9df28..70e7f228aae 100644 --- a/src/client/datascience/raw-kernel/rawJupyterSession.ts +++ b/src/client/datascience/raw-kernel/rawJupyterSession.ts @@ -5,7 +5,7 @@ import type { Kernel } from '@jupyterlab/services'; import type { Slot } from '@phosphor/signaling'; import { CancellationToken } from 'vscode-jsonrpc'; import { CancellationError } from '../../common/cancellation'; -import { WrappedError } from '../../common/errors/errorUtils'; +import { WrappedError } from '../../common/errors/types'; import { traceError, traceInfo } from '../../common/logger'; import { IDisposable, IOutputChannel, Resource } from '../../common/types'; import { TimedOutError } from '../../common/utils/async'; @@ -15,13 +15,13 @@ import { StopWatch } from '../../common/utils/stopWatch'; import { captureTelemetry } from '../../telemetry'; import { BaseJupyterSession } from '../baseJupyterSession'; import { Identifiers, Telemetry } from '../constants'; -import { sendKernelTelemetryEvent, trackKernelResourceInformation } from '../context/telemetry'; import { getDisplayNameOrNameOfKernelConnection } from '../jupyter/kernels/helpers'; import { KernelConnectionMetadata } from '../jupyter/kernels/types'; -import { IKernelLauncher, IpyKernelNotInstalledError, KernelDiedError } from '../kernel-launcher/types'; +import { IKernelLauncher, IpyKernelNotInstalledError } from '../kernel-launcher/types'; import { reportAction } from '../progress/decorator'; import { ReportableAction } from '../progress/types'; import { RawSession } from '../raw-kernel/rawSession'; +import { sendKernelTelemetryEvent, trackKernelResourceInformation } from '../telemetry/telemetry'; import { ISessionWithSocket } from '../types'; // Error thrown when we are unable to start a raw kernel session @@ -116,39 +116,50 @@ export class RawJupyterSession extends BaseJupyterSession { } catch (error) { this.connected = false; if (error instanceof CancellationError) { - sendKernelTelemetryEvent(resource, Telemetry.RawKernelSessionStart, stopWatch.elapsedTime, { - failed: true, - failureReason: 'cancelled' - }); + sendKernelTelemetryEvent( + resource, + Telemetry.RawKernelSessionStart, + stopWatch.elapsedTime, + undefined, + error + ); sendKernelTelemetryEvent(resource, Telemetry.RawKernelSessionStartUserCancel); traceInfo('Starting of raw session cancelled by user'); throw error; } else if (error instanceof TimedOutError) { - sendKernelTelemetryEvent(resource, Telemetry.RawKernelSessionStart, stopWatch.elapsedTime, { - failed: true, - failureReason: 'timeout' - }); + sendKernelTelemetryEvent( + resource, + Telemetry.RawKernelSessionStart, + stopWatch.elapsedTime, + undefined, + error + ); sendKernelTelemetryEvent(resource, Telemetry.RawKernelSessionStartTimeout); traceError('Raw session failed to start in given timeout'); // Translate into original error throw new RawKernelSessionStartError(kernelConnection, error); } else if (error instanceof IpyKernelNotInstalledError) { - sendKernelTelemetryEvent(resource, Telemetry.RawKernelSessionStart, stopWatch.elapsedTime, { - failed: true, - failureReason: 'noipykernel' - }); + sendKernelTelemetryEvent( + resource, + Telemetry.RawKernelSessionStart, + stopWatch.elapsedTime, + undefined, + error + ); sendKernelTelemetryEvent(resource, Telemetry.RawKernelSessionStartNoIpykernel, { reason: error.reason }); traceError('Raw session failed to start because dependencies not installed'); throw error; } else { - const failureReason = error instanceof KernelDiedError ? 'kerneldied' : 'unknown'; // Send our telemetry event with the error included - sendKernelTelemetryEvent(resource, Telemetry.RawKernelSessionStart, stopWatch.elapsedTime, { - failed: true, - failureReason - }); + sendKernelTelemetryEvent( + resource, + Telemetry.RawKernelSessionStart, + stopWatch.elapsedTime, + undefined, + error + ); sendKernelTelemetryEvent( resource, Telemetry.RawKernelSessionStartException, diff --git a/src/client/datascience/serviceRegistry.ts b/src/client/datascience/serviceRegistry.ts index 9f627ca4cd5..63dc4dacb91 100644 --- a/src/client/datascience/serviceRegistry.ts +++ b/src/client/datascience/serviceRegistry.ts @@ -119,7 +119,7 @@ import { NotebookEditorCompatibilitySupport } from './notebook/notebookEditorCom import { NotebookEditorProvider } from './notebook/notebookEditorProvider'; import { NotebookEditorProviderWrapper } from './notebook/notebookEditorProviderWrapper'; import { registerTypes as registerNotebookTypes } from './notebook/serviceRegistry'; -import { registerTypes as registerContextTypes } from './context/serviceRegistry'; +import { registerTypes as registerContextTypes } from './telemetry/serviceRegistry'; import { NotebookCreationTracker } from './notebookAndInteractiveTracker'; import { NotebookExtensibility } from './notebookExtensibility'; import { NotebookModelFactory } from './notebookStorage/factory'; diff --git a/src/client/datascience/context/interpreterCountTracker.ts b/src/client/datascience/telemetry/interpreterCountTracker.ts similarity index 100% rename from src/client/datascience/context/interpreterCountTracker.ts rename to src/client/datascience/telemetry/interpreterCountTracker.ts diff --git a/src/client/datascience/telemetry/interpreterPackageTracker.ts b/src/client/datascience/telemetry/interpreterPackageTracker.ts new file mode 100644 index 00000000000..d681435e4b2 --- /dev/null +++ b/src/client/datascience/telemetry/interpreterPackageTracker.ts @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { inject, injectable } from 'inversify'; +import { IExtensionSingleActivationService } from '../../activation/types'; +import { IPythonInstaller, IPythonExtensionChecker } from '../../api/types'; +import { IVSCodeNotebook } from '../../common/application/types'; +import { InterpreterUri } from '../../common/installer/types'; +import { IExtensions, IDisposableRegistry, Product, IConfigurationService } from '../../common/types'; +import { isResource, noop } from '../../common/utils/misc'; +import { IInterpreterService } from '../../interpreter/contracts'; +import { isLocalLaunch } from '../jupyter/kernels/helpers'; +import { InterpreterPackages } from './interpreterPackages'; +import { NotebookKernel as VSCNotebookKernel } from '../../../../types/vscode-proposed'; +import { isJupyterKernel } from '../notebook/helpers/helpers'; + +@injectable() +export class InterpreterPackageTracker implements IExtensionSingleActivationService { + private activeInterpreterTrackedUponActivation?: boolean; + constructor( + @inject(InterpreterPackages) private readonly packages: InterpreterPackages, + @inject(IPythonInstaller) private readonly installer: IPythonInstaller, + @inject(IExtensions) private readonly extensions: IExtensions, + @inject(IPythonExtensionChecker) private readonly pythonExtensionChecker: IPythonExtensionChecker, + @inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry, + @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, + @inject(IVSCodeNotebook) private readonly notebook: IVSCodeNotebook, + @inject(IConfigurationService) private readonly configurationService: IConfigurationService + ) {} + public async activate(): Promise { + if (!isLocalLaunch(this.configurationService)) { + return; + } + this.notebook.onDidChangeActiveNotebookKernel(this.onDidChangeActiveNotebookKernel, this, this.disposables); + this.interpreterService.onDidChangeInterpreter(this.trackPackagesOfActiveInterpreter, this, this.disposables); + this.installer.onInstalled(this.onDidInstallPackage, this, this.disposables); + this.extensions.onDidChange(this.trackUponActivation, this, this.disposables); + this.trackUponActivation().catch(noop); + } + private async onDidChangeActiveNotebookKernel({ kernel }: { kernel: VSCNotebookKernel | undefined }) { + if (!kernel || !isJupyterKernel(kernel) || !kernel.selection.interpreter) { + return; + } + await this.packages.trackPackages(kernel.selection.interpreter); + } + private async trackUponActivation() { + if (this.activeInterpreterTrackedUponActivation) { + return; + } + if (!this.pythonExtensionChecker.isPythonExtensionInstalled) { + return; + } + this.activeInterpreterTrackedUponActivation = true; + await this.trackPackagesOfActiveInterpreter(); + } + private async trackPackagesOfActiveInterpreter() { + if (!this.pythonExtensionChecker.isPythonExtensionInstalled) { + return; + } + // Get details of active interpreter. + const activeInterpreter = await this.interpreterService.getActiveInterpreter(undefined); + if (!activeInterpreter) { + return; + } + await this.packages.trackPackages(activeInterpreter); + } + private async onDidInstallPackage(args: { product: Product; resource?: InterpreterUri }) { + if (!this.pythonExtensionChecker.isPythonExtensionInstalled) { + return; + } + if (isResource(args.resource)) { + // Get details of active interpreter for the Uri provided. + const activeInterpreter = await this.interpreterService.getActiveInterpreter(args.resource); + await this.packages.trackPackages(activeInterpreter, true); + } else { + await this.packages.trackPackages(args.resource, true); + } + } +} diff --git a/src/client/datascience/telemetry/interpreterPackages.ts b/src/client/datascience/telemetry/interpreterPackages.ts new file mode 100644 index 00000000000..209bed59cb9 --- /dev/null +++ b/src/client/datascience/telemetry/interpreterPackages.ts @@ -0,0 +1,109 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { inject, injectable } from 'inversify'; +import { IPythonExtensionChecker } from '../../api/types'; +import { InterpreterUri } from '../../common/installer/types'; +import { IPythonExecutionFactory } from '../../common/process/types'; +import { isResource, noop } from '../../common/utils/misc'; +import { IInterpreterService } from '../../interpreter/contracts'; +import { PythonEnvironment } from '../../pythonEnvironments/info'; +import { getTelemetrySafeHashedString, getTelemetrySafeVersion } from '../../telemetry/helpers'; + +const interestedPackages = new Set( + [ + 'ipykernel', + 'ipython-genutils', + 'jupyter', + 'jupyter-client', + 'jupyter-core', + 'nbconvert', + 'nbformat', + 'notebook', + 'pyzmq', + 'pyzmq32', + 'tornado', + 'traitlets' + ].map((item) => item.toLowerCase()) +); + +@injectable() +export class InterpreterPackages { + private static interpreterInformation = new Map>(); + private static pendingInterpreterInformation = new Map>(); + constructor( + @inject(IPythonExtensionChecker) private readonly pythonExtensionChecker: IPythonExtensionChecker, + @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, + @inject(IPythonExecutionFactory) private readonly executionFactory: IPythonExecutionFactory + ) {} + public static getPackageVersions(interpreter: PythonEnvironment): Map | undefined { + return InterpreterPackages.interpreterInformation.get(interpreter.path); + } + public trackPackages(interpreterUri: InterpreterUri, ignoreCache?: boolean) { + this.trackPackagesInternal(interpreterUri, ignoreCache).catch(noop); + } + public async trackPackagesInternal(interpreterUri: InterpreterUri, ignoreCache?: boolean) { + if (!this.pythonExtensionChecker.isPythonExtensionInstalled) { + return; + } + let interpreter: PythonEnvironment; + if (isResource(interpreterUri)) { + // Get details of active interpreter for the Uri provided. + const activeInterpreter = await this.interpreterService.getActiveInterpreter(interpreterUri); + if (!activeInterpreter) { + return; + } + interpreter = activeInterpreter; + } else { + interpreter = interpreterUri; + } + this.trackInterpreterPackages(interpreter, ignoreCache).catch(noop); + } + private async trackInterpreterPackages(interpreter: PythonEnvironment, ignoreCache?: boolean) { + const key = interpreter.path; + if (InterpreterPackages.pendingInterpreterInformation.has(key) && !ignoreCache) { + return; + } + + const promise = this.getPackageInformation(interpreter); + promise.finally(() => { + // If this promise was resolved, then remove it from the pending list. + if (InterpreterPackages.pendingInterpreterInformation.get(key) === promise) { + InterpreterPackages.pendingInterpreterInformation.delete(key); + } + }); + InterpreterPackages.pendingInterpreterInformation.set(key, promise); + } + private async getPackageInformation(interpreter: PythonEnvironment) { + const service = await this.executionFactory.createActivatedEnvironment({ + allowEnvironmentFetchExceptions: true, + bypassCondaExecution: true, + interpreter + }); + + // Ignore errors, and merge the two (in case some versions of python write to stderr). + const output = await service.execModule('pip', ['list'], { throwOnStdErr: false, mergeStdOutErr: true }); + const packageAndVersions = new Map(); + // Add defaults. + interestedPackages.forEach((item) => { + packageAndVersions.set(getTelemetrySafeHashedString(item), 'NOT INSTALLED'); + }); + InterpreterPackages.interpreterInformation.set(interpreter.path, packageAndVersions); + output.stdout + .split('\n') + .map((line) => line.trim().toLowerCase()) + .filter((line) => line.length > 0) + .forEach((line) => { + const parts = line.split(' ').filter((item) => item.trim().length); + if (parts.length < 2) { + return; + } + const [packageName, rawVersion] = parts; + if (!interestedPackages.has(packageName.toLowerCase().trim())) { + return; + } + const version = getTelemetrySafeVersion(rawVersion); + packageAndVersions.set(getTelemetrySafeHashedString(packageName), version || ''); + }); + } +} diff --git a/src/client/datascience/context/kernelTelemetry.ts b/src/client/datascience/telemetry/kernelTelemetry.ts similarity index 100% rename from src/client/datascience/context/kernelTelemetry.ts rename to src/client/datascience/telemetry/kernelTelemetry.ts diff --git a/src/client/datascience/context/serviceRegistry.ts b/src/client/datascience/telemetry/serviceRegistry.ts similarity index 74% rename from src/client/datascience/context/serviceRegistry.ts rename to src/client/datascience/telemetry/serviceRegistry.ts index a7dfcc32136..2fc6b1780d1 100644 --- a/src/client/datascience/context/serviceRegistry.ts +++ b/src/client/datascience/telemetry/serviceRegistry.ts @@ -5,20 +5,22 @@ import { IExtensionSingleActivationService, IExtensionSyncActivationService } from '../../activation/types'; import { IServiceManager } from '../../ioc/types'; -import { ActiveEditorContextService } from './activeEditorContext'; -import { ErrorClassificationRegistration } from './errorClassificationRegistration'; +import { ActiveEditorContextService } from '../commands/activeEditorContext'; import { InterpreterCountTracker } from './interpreterCountTracker'; +import { InterpreterPackages } from './interpreterPackages'; +import { InterpreterPackageTracker } from './interpreterPackageTracker'; import { WorkspaceInterpreterTracker } from './workspaceInterpreterTracker'; export function registerTypes(serviceManager: IServiceManager) { serviceManager.addSingleton(ActiveEditorContextService, ActiveEditorContextService); + serviceManager.addSingleton(InterpreterPackages, InterpreterPackages); serviceManager.addSingleton( IExtensionSyncActivationService, WorkspaceInterpreterTracker ); serviceManager.addSingleton( IExtensionSyncActivationService, - ErrorClassificationRegistration + InterpreterPackageTracker ); serviceManager.addSingleton( IExtensionSingleActivationService, diff --git a/src/client/datascience/context/telemetry.ts b/src/client/datascience/telemetry/telemetry.ts similarity index 79% rename from src/client/datascience/context/telemetry.ts rename to src/client/datascience/telemetry/telemetry.ts index 7f870a8aecf..d65f3d15164 100644 --- a/src/client/datascience/context/telemetry.ts +++ b/src/client/datascience/telemetry/telemetry.ts @@ -4,29 +4,20 @@ import { Uri } from 'vscode'; import { getOSType } from '../../common/utils/platform'; import { getKernelConnectionId, KernelConnectionMetadata } from '../jupyter/kernels/types'; -import * as hashjs from 'hash.js'; import { Resource } from '../../common/types'; import { IEventNamePropertyMapping, sendTelemetryEvent, setSharedProperty } from '../../telemetry'; import { StopWatch } from '../../common/utils/stopWatch'; import { ResourceSpecificTelemetryProperties } from './types'; -import { isErrorType } from '../../common/errors/errorUtils'; -import { CancellationError } from '../../common/cancellation'; -import { TimedOutError } from '../../common/utils/async'; -import { JupyterInvalidKernelError } from '../jupyter/jupyterInvalidKernelError'; -import { JupyterWaitForIdleError } from '../jupyter/jupyterWaitForIdleError'; -import { JupyterKernelPromiseFailedError } from '../jupyter/kernels/jupyterKernelPromiseFailedError'; -import { IpyKernelNotInstalledError, KernelDiedError } from '../kernel-launcher/types'; -import { JupyterSessionStartError } from '../baseJupyterSession'; -import { JupyterConnectError } from '../jupyter/jupyterConnectError'; -import { JupyterInstallError } from '../jupyter/jupyterInstallError'; -import { JupyterSelfCertsError } from '../jupyter/jupyterSelfCertsError'; import { Telemetry } from '../constants'; import { WorkspaceInterpreterTracker } from './workspaceInterpreterTracker'; import { InterruptResult } from '../types'; -import { getResourceType, getTelemetrySafeLanguage } from '../common'; +import { getResourceType } from '../common'; import { PYTHON_LANGUAGE } from '../../common/constants'; import { InterpreterCountTracker } from './interpreterCountTracker'; -import { FetchError } from 'node-fetch'; +import { getTelemetrySafeHashedString, getTelemetrySafeLanguage } from '../../telemetry/helpers'; +import { PythonEnvironment } from '../../pythonEnvironments/info'; +import { InterpreterPackages } from './interpreterPackages'; +import { populateTelemetryWithErrorInfo } from '../../common/errors'; type ContextualTelemetryProps = { kernelConnection: KernelConnectionMetadata; @@ -51,37 +42,8 @@ type Context = { }; const trackedInfo = new Map(); const currentOSType = getOSType(); +const pythonEnvironmentsByHash = new Map(); -export function getErrorClassification(error: Error) { - if (error.message.indexOf('reason: self signed certificate') >= 0) { - return 'jupyterselfcert'; - } else if (isErrorType(error, JupyterSelfCertsError)) { - return 'jupyterselfcert'; - } else if (isErrorType(error, JupyterWaitForIdleError)) { - return 'timeout'; - } else if (isErrorType(error, TimedOutError)) { - return 'timeout'; - } else if (isErrorType(error, JupyterInvalidKernelError)) { - return 'invalidkernel'; - } else if (isErrorType(error, JupyterKernelPromiseFailedError)) { - return 'kernelpromisetimeout'; - } else if (isErrorType(error, IpyKernelNotInstalledError)) { - return 'noipykernel'; - } else if (isErrorType(error, CancellationError)) { - return 'cancelled'; - } else if (isErrorType(error, JupyterSessionStartError)) { - return 'jupytersession'; - } else if (isErrorType(error, JupyterConnectError)) { - return 'jupyterconnection'; - } else if (isErrorType(error, JupyterInstallError)) { - return 'jupyterinstall'; - } else if (isErrorType(error, KernelDiedError)) { - return 'kerneldied'; - } else if (isErrorType(error, FetchError)) { - return 'fetcherror'; - } - return 'unknown'; -} export function sendKernelTelemetryEvent

( resource: Resource, eventName: E, @@ -94,20 +56,16 @@ export function sendKernelTelemetryEvent

( @@ -139,8 +97,7 @@ export function sendKernelTelemetryWhenDone

{ const addOnTelemetry = getContextualPropsForTelemetry(resource); Object.assign(props, addOnTelemetry); - props.failed = true; - props.failureReason = getErrorClassification(ex); + populateTelemetryWithErrorInfo(props, ex); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-explicit-any sendTelemetryEvent(eventName as any, stopWatch!.elapsedTime, props as any, ex, true); // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -224,21 +181,58 @@ export function trackKernelResourceInformation(resource: Resource, information: interpreter ); currentData.pythonEnvironmentType = interpreter.envType; - currentData.pythonEnvironmentPath = hashjs.sha256().update(interpreter.path).digest('hex'); + currentData.pythonEnvironmentPath = getTelemetrySafeHashedString(interpreter.path); + pythonEnvironmentsByHash.set(currentData.pythonEnvironmentPath, interpreter); if (interpreter.version) { const { major, minor, patch } = interpreter.version; currentData.pythonEnvironmentVersion = `${major}.${minor}.${patch}`; } else { currentData.pythonEnvironmentVersion = undefined; } + + currentData.pythonEnvironmentPackages = getPythonEnvironmentPackages({ interpreter }); } currentData.kernelConnectionType = currentData.kernelConnectionType || kernelConnection?.kind; } else { context.previouslySelectedKernelConnectionId = ''; } + trackedInfo.set(key, [currentData, context]); } + +/** + * The python package information is fetch asynchronously. + * Its possible the information is available at a later time. + * Use this to update with the latest information (if available) + */ +function updatePythonPackages(currentData: ResourceSpecificTelemetryProperties) { + // Possible the Python package information is now available, update the properties accordingly. + if (currentData.pythonEnvironmentPath) { + currentData.pythonEnvironmentPackages = + getPythonEnvironmentPackages({ interpreterHash: currentData.pythonEnvironmentPath }) || + currentData.pythonEnvironmentPackages; + } +} +/** + * Gets a JSON with hashed keys of some python packages along with their versions. + */ +function getPythonEnvironmentPackages(options: { interpreter: PythonEnvironment } | { interpreterHash: string }) { + let interpreter: PythonEnvironment | undefined; + if ('interpreter' in options) { + interpreter = options.interpreter; + } else { + interpreter = pythonEnvironmentsByHash.get(options.interpreterHash); + } + if (!interpreter) { + return '{}'; + } + const packages = InterpreterPackages.getPackageVersions(interpreter); + if (!packages || packages.size === 0) { + return '{}'; + } + return JSON.stringify(Object.fromEntries(packages)); +} export function deleteTrackedInformation(resource: Uri) { trackedInfo.delete(getUriKey(resource)); } @@ -258,6 +252,10 @@ function getContextualPropsForTelemetry(resource: Resource): ResourceSpecificTel resourceType }; } + if (data) { + // Possible the Python package information is now available, update the properties accordingly. + updatePythonPackages(data[0]); + } return data ? data[0] : undefined; } /** diff --git a/src/client/datascience/context/types.ts b/src/client/datascience/telemetry/types.ts similarity index 94% rename from src/client/datascience/context/types.ts rename to src/client/datascience/telemetry/types.ts index ce1a466d141..59b4a4dad55 100644 --- a/src/client/datascience/context/types.ts +++ b/src/client/datascience/telemetry/types.ts @@ -27,6 +27,10 @@ export type ResourceSpecificTelemetryProperties = Partial<{ * Total number of python environments. */ pythonEnvironmentCount?: number; + /** + * Comma delimited list of hashed packages & their versions. + */ + pythonEnvironmentPackages?: string; /** * Whether kernel was started using kernel spec, interpreter, etc. */ diff --git a/src/client/datascience/context/workspaceInterpreterTracker.ts b/src/client/datascience/telemetry/workspaceInterpreterTracker.ts similarity index 100% rename from src/client/datascience/context/workspaceInterpreterTracker.ts rename to src/client/datascience/telemetry/workspaceInterpreterTracker.ts diff --git a/src/client/datascience/types.ts b/src/client/datascience/types.ts index bf3f94aedf5..d92a48aaa11 100644 --- a/src/client/datascience/types.ts +++ b/src/client/datascience/types.ts @@ -248,11 +248,13 @@ export type ConnectNotebookProviderOptions = { disableUI?: boolean; localOnly?: boolean; token?: CancellationToken; + resource: Resource; onConnectionMade?(): void; // Optional callback for when the first connection is made }; export interface INotebookServerOptions { uri?: string; + resource: Resource; usingDarkTheme?: boolean; skipUsingDefaultConfig?: boolean; workingDir?: string; @@ -332,7 +334,7 @@ export interface IJupyterSession extends IAsyncDisposable { content: KernelMessage.IInspectRequestMsg['content'] ): Promise; sendInputReply(content: string): void; - changeKernel(kernelConnection: KernelConnectionMetadata, timeoutMS: number): Promise; + changeKernel(resource: Resource, kernelConnection: KernelConnectionMetadata, timeoutMS: number): Promise; registerCommTarget( targetName: string, callback: (comm: Kernel.IComm, msg: KernelMessage.ICommOpenMsg) => void | PromiseLike @@ -1179,6 +1181,7 @@ export type GetServerOptions = { disableUI?: boolean; localOnly?: boolean; token?: CancellationToken; + resource: Resource; metadata?: nbformat.INotebookMetadata; kernelConnection?: KernelConnectionMetadata; onConnectionMade?(): void; // Optional callback for when the first connection is made @@ -1188,7 +1191,7 @@ export type GetServerOptions = { * Options for getting a notebook */ export type GetNotebookOptions = { - resource?: Uri; + resource: Resource; identity: Uri; getOnly?: boolean; disableUI?: boolean; diff --git a/src/client/telemetry/helpers.ts b/src/client/telemetry/helpers.ts new file mode 100644 index 00000000000..143db59a401 --- /dev/null +++ b/src/client/telemetry/helpers.ts @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as hashjs from 'hash.js'; +import { traceError } from '../common/logger'; +import { KnownKernelLanguageAliases, KnownNotebookLanguages } from '../datascience/constants'; + +export function getTelemetrySafeLanguage(language: string = 'unknown') { + language = (language || 'unknown').toLowerCase(); + language = KnownKernelLanguageAliases.get(language) || language; + if (!KnownNotebookLanguages.includes(language)) { + language = 'unknown'; + } + return language; +} + +export function getTelemetrySafeVersion(version: string): string | undefined { + try { + // Split by `.` & take only the first 3 numbers. + // Suffix with '.', so we know we'll always have 3 items in the array. + const [major, minor, patch] = `${version.trim()}...`.split('.').map((item) => parseInt(item, 10)); + if (isNaN(major)) { + return; + } else if (isNaN(minor)) { + return major.toString(); + } else if (isNaN(patch)) { + return `${major}.${minor}`; + } + return `${major}.${minor}.${patch}`; + } catch (ex) { + traceError(`Failed to parse version ${version}`, ex); + } +} + +/** + * Safe way to send data in telemetry (obfuscate PII). + */ +export function getTelemetrySafeHashedString(data: string) { + return hashjs.sha256().update(data).digest('hex'); +} diff --git a/src/client/telemetry/index.ts b/src/client/telemetry/index.ts index f8c2d907751..87a57dbef0d 100644 --- a/src/client/telemetry/index.ts +++ b/src/client/telemetry/index.ts @@ -2,7 +2,6 @@ // Licensed under the MIT License. import type { JSONObject } from '@phosphor/coreutils'; -import * as stackTrace from 'stack-trace'; // eslint-disable-next-line import TelemetryReporter from 'vscode-extension-telemetry/lib/telemetryReporter'; @@ -17,10 +16,12 @@ import { Telemetry, VSCodeNativeTelemetry } from '../datascience/constants'; -import { ResourceSpecificTelemetryProperties } from '../datascience/context/types'; +import { ResourceSpecificTelemetryProperties } from '../datascience/telemetry/types'; import { ExportFormat } from '../datascience/export/types'; import { InterruptResult } from '../datascience/types'; import { EventName, PlatformErrors } from './constants'; +import { populateTelemetryWithErrorInfo } from '../common/errors'; +import { ErrorCategory, TelemetryErrorProperties } from '../common/errors/types'; /* eslint-disable @typescript-eslint/no-explicit-any */ @@ -42,20 +43,6 @@ function isTelemetrySupported(): boolean { } } -type ErrorClassifier = (error: Error) => string | undefined; -const errorClassifiers: ErrorClassifier[] = []; -export function registerErrorClassifier(classifier: ErrorClassifier) { - errorClassifiers.push(classifier); -} -function getErrorClassification(error: Error): string { - for (const classifier of errorClassifiers) { - const classification = classifier(error); - if (classification && classification !== 'unknown') { - return classification; - } - } - return 'unknown'; -} /** * Checks if the telemetry is disabled in user settings * @returns {boolean} @@ -156,26 +143,21 @@ export function sendTelemetryEvent

0) { - parts.push(frame.getTypeName()); - } - if (typeof frame.getMethodName() === 'string' && frame.getMethodName().length > 0) { - parts.push(frame.getMethodName()); - } - if (typeof frame.getFunctionName() === 'string' && frame.getFunctionName().length > 0) { - if (parts.length !== 2 || parts.join('.') !== frame.getFunctionName()) { - parts.push(frame.getFunctionName()); - } - } - return parts.join('.'); -} - /** * Map all shared properties to their data types. */ @@ -400,9 +339,6 @@ export interface ISharedPropertyMapping { ['isPythonExtensionInstalled']: 'true' | 'false'; } -// If there are errors, then the are added to the telementry properties. -export type TelemetryErrorProperties = { failed: true; stackTrace: string }; - // Map all events to their properties export interface IEventNamePropertyMapping { /** @@ -565,44 +501,14 @@ export interface IEventNamePropertyMapping { [Telemetry.AddCellBelow]: never | undefined; [Telemetry.CodeLensAverageAcquisitionTime]: never | undefined; [Telemetry.CollapseAll]: never | undefined; - [Telemetry.ConnectFailedJupyter]: { - failureReason: - | 'cancelled' - | 'timeout' - | 'kerneldied' - | 'kerneldied' - | 'kernelpromisetimeout' - | 'jupytersession' - | 'jupyterconnection' - | 'jupyterinstall' - | 'jupyterselfcert' - | 'invalidkernel' - | 'noipykernel' - | 'fetcherror' - | 'unknown'; - }; + [Telemetry.ConnectFailedJupyter]: TelemetryErrorProperties; [Telemetry.ConnectLocalJupyter]: never | undefined; [Telemetry.ConnectRemoteJupyter]: never | undefined; /** * Connecting to an existing Jupyter server, but connecting to localhost. */ [Telemetry.ConnectRemoteJupyterViaLocalHost]: never | undefined; - [Telemetry.ConnectRemoteFailedJupyter]: { - failureReason: - | 'cancelled' - | 'timeout' - | 'kerneldied' - | 'kerneldied' - | 'kernelpromisetimeout' - | 'jupytersession' - | 'jupyterconnection' - | 'jupyterinstall' - | 'jupyterselfcert' - | 'invalidkernel' - | 'noipykernel' - | 'fetcherror' - | 'unknown'; - }; + [Telemetry.ConnectRemoteFailedJupyter]: TelemetryErrorProperties; [Telemetry.ConnectRemoteSelfCertFailedJupyter]: never | undefined; [Telemetry.RegisterAndUseInterpreterAsKernel]: never | undefined; [Telemetry.UseInterpreterAsKernel]: never | undefined; @@ -1113,20 +1019,7 @@ export interface IEventNamePropertyMapping { | ResourceSpecificTelemetryProperties // If successful. | ({ failed: true; - failureReason: - | 'cancelled' - | 'timeout' - | 'kerneldied' - | 'kerneldied' - | 'kernelpromisetimeout' - | 'jupytersession' - | 'jupyterconnection' - | 'jupyterinstall' - | 'jupyterselfcert' - | 'invalidkernel' - | 'noipykernel' - | 'fetcherror' - | 'unknown'; + failureCategory: ErrorCategory; } & ResourceSpecificTelemetryProperties) | (ResourceSpecificTelemetryProperties & TelemetryErrorProperties); // If there any any unhandled exceptions. [Telemetry.SwitchKernel]: ResourceSpecificTelemetryProperties; // If there are unhandled exceptions; @@ -1136,7 +1029,7 @@ export interface IEventNamePropertyMapping { [Telemetry.NotebookRestart]: | ({ failed: true; - failureReason: 'cancelled' | 'kernelpromisetimeout' | 'unknown'; + failureCategory: ErrorCategory; } & ResourceSpecificTelemetryProperties) | (ResourceSpecificTelemetryProperties & TelemetryErrorProperties); // If there are unhandled exceptions; @@ -1145,7 +1038,7 @@ export interface IEventNamePropertyMapping { | ResourceSpecificTelemetryProperties | ({ failed: true; - failureReason: 'cancelled' | 'timeout' | 'noipykernel' | 'kerneldied' | 'unknown'; + failureCategory: ErrorCategory; } & ResourceSpecificTelemetryProperties) | (ResourceSpecificTelemetryProperties & TelemetryErrorProperties); // If there are unhandled exceptions; [Telemetry.RawKernelSessionStartSuccess]: never | undefined; diff --git a/src/test/datascience/activation.unit.test.ts b/src/test/datascience/activation.unit.test.ts index ee5993aa3e1..b5b4276871d 100644 --- a/src/test/datascience/activation.unit.test.ts +++ b/src/test/datascience/activation.unit.test.ts @@ -12,7 +12,7 @@ import { IPythonExecutionFactory } from '../../client/common/process/types'; import { sleep } from '../../client/common/utils/async'; import { Activation } from '../../client/datascience/activation'; import { JupyterDaemonModule } from '../../client/datascience/constants'; -import { ActiveEditorContextService } from '../../client/datascience/context/activeEditorContext'; +import { ActiveEditorContextService } from '../../client/datascience/commands/activeEditorContext'; import { NativeEditor } from '../../client/datascience/interactive-ipynb/nativeEditor'; import { JupyterInterpreterService } from '../../client/datascience/jupyter/interpreter/jupyterInterpreterService'; import { KernelDaemonPreWarmer } from '../../client/datascience/kernel-launcher/kernelDaemonPreWarmer'; diff --git a/src/test/datascience/commands/notebookCommands.functional.test.ts b/src/test/datascience/commands/notebookCommands.functional.test.ts index b35b5c9eb3e..edeb489bf71 100644 --- a/src/test/datascience/commands/notebookCommands.functional.test.ts +++ b/src/test/datascience/commands/notebookCommands.functional.test.ts @@ -29,6 +29,7 @@ import { import { IKernelFinder } from '../../../client/datascience/kernel-launcher/types'; import { NativeEditorProvider } from '../../../client/datascience/notebookStorage/nativeEditorProvider'; import { PreferredRemoteKernelIdProvider } from '../../../client/datascience/notebookStorage/preferredRemoteKernelIdProvider'; +import { InterpreterPackages } from '../../../client/datascience/telemetry/interpreterPackages'; import { IInteractiveWindowProvider, INotebookEditorProvider } from '../../../client/datascience/types'; import { IInterpreterService } from '../../../client/interpreter/contracts'; @@ -146,7 +147,8 @@ suite('DataScience - Notebook Commands', () => { instance(jupyterSessionManagerFactory), instance(configService), instance(extensionChecker), - instance(preferredKernelIdProvider) + instance(preferredKernelIdProvider), + instance(mock(InterpreterPackages)) ); const kernelSwitcher = new KernelSwitcher( diff --git a/src/test/datascience/dataScienceIocContainer.ts b/src/test/datascience/dataScienceIocContainer.ts index df07d9a4760..479e92821bb 100644 --- a/src/test/datascience/dataScienceIocContainer.ts +++ b/src/test/datascience/dataScienceIocContainer.ts @@ -114,7 +114,7 @@ import { ExportCommands } from '../../client/datascience/commands/exportCommands import { NotebookCommands } from '../../client/datascience/commands/notebookCommands'; import { JupyterServerSelectorCommand } from '../../client/datascience/commands/serverSelector'; import { DataScienceStartupTime, Identifiers, JUPYTER_OUTPUT_CHANNEL } from '../../client/datascience/constants'; -import { ActiveEditorContextService } from '../../client/datascience/context/activeEditorContext'; +import { ActiveEditorContextService } from '../../client/datascience/commands/activeEditorContext'; import { DataViewer } from '../../client/datascience/data-viewing/dataViewer'; import { DataViewerDependencyService } from '../../client/datascience/data-viewing/dataViewerDependencyService'; import { DataViewerFactory } from '../../client/datascience/data-viewing/dataViewerFactory'; @@ -311,6 +311,7 @@ import { SystemPseudoRandomNumberGenerator } from '../../client/datascience/inte import { KernelEnvironmentVariablesService } from '../../client/datascience/kernel-launcher/kernelEnvVarsService'; import { PreferredRemoteKernelIdProvider } from '../../client/datascience/notebookStorage/preferredRemoteKernelIdProvider'; import { NotebookWatcher } from '../../client/datascience/variablesView/notebookWatcher'; +import { InterpreterPackages } from '../../client/datascience/telemetry/interpreterPackages'; export class DataScienceIocContainer extends UnitTestIocContainer { public get workingInterpreter() { @@ -496,6 +497,10 @@ export class DataScienceIocContainer extends UnitTestIocContainer { NbConvertExportToPythonService ); + this.serviceManager.addSingletonInstance( + InterpreterPackages, + instance(mock(InterpreterPackages)) + ); this.serviceManager.addSingleton(INotebookModelFactory, NotebookModelFactory); this.serviceManager.addSingleton(IMountedWebViewFactory, MountedWebViewFactory); this.serviceManager.addSingletonInstance(IFileSystem, new MockFileSystem()); diff --git a/src/test/datascience/dataviewer.functional.test.tsx b/src/test/datascience/dataviewer.functional.test.tsx index ca350fdf5d6..b0834e82666 100644 --- a/src/test/datascience/dataviewer.functional.test.tsx +++ b/src/test/datascience/dataviewer.functional.test.tsx @@ -129,7 +129,8 @@ suite('DataScience DataViewer tests', () => { async function injectCode(code: string): Promise { const notebookProvider = ioc.get(INotebookProvider); notebook = await notebookProvider.getOrCreateNotebook({ - identity: getDefaultInteractiveIdentity() + identity: getDefaultInteractiveIdentity(), + resource: undefined }); if (notebook) { const cells = await notebook.execute(code, Identifiers.EmptyFileName, 0, uuid()); diff --git a/src/test/datascience/editor-integration/gotocell.functional.test.ts b/src/test/datascience/editor-integration/gotocell.functional.test.ts index d657aa2c633..9b2f701a78d 100644 --- a/src/test/datascience/editor-integration/gotocell.functional.test.ts +++ b/src/test/datascience/editor-integration/gotocell.functional.test.ts @@ -78,7 +78,7 @@ suite('DataScience gotocell tests', () => { // Catch exceptions. Throw a specific assertion if the promise fails try { const uri = getDefaultInteractiveIdentity(); - const nb = await notebookProvider.getOrCreateNotebook({ identity: uri }); + const nb = await notebookProvider.getOrCreateNotebook({ identity: uri, resource: uri }); const listener = (codeLensFactory as any) as IInteractiveWindowListener; listener.onMessage(InteractiveWindowMessages.NotebookIdentity, { resource: uri, diff --git a/src/test/datascience/interactive-common/notebookProvider.unit.test.ts b/src/test/datascience/interactive-common/notebookProvider.unit.test.ts index b6f93725c93..e453f613b2e 100644 --- a/src/test/datascience/interactive-common/notebookProvider.unit.test.ts +++ b/src/test/datascience/interactive-common/notebookProvider.unit.test.ts @@ -64,7 +64,10 @@ suite('DataScience - NotebookProvider', () => { const notebookMock = createTypeMoq('jupyter notebook'); when(jupyterNotebookProvider.getNotebook(anything())).thenResolve(notebookMock.object); - const notebook = await notebookProvider.getOrCreateNotebook({ identity: Uri('C:\\\\foo.py') }); + const notebook = await notebookProvider.getOrCreateNotebook({ + identity: Uri('C:\\\\foo.py'), + resource: Uri('C:\\\\foo.py') + }); expect(notebook).to.not.equal(undefined, 'Provider should return a notebook'); }); @@ -74,7 +77,10 @@ suite('DataScience - NotebookProvider', () => { when(jupyterNotebookProvider.createNotebook(anything())).thenResolve(notebookMock.object); when(jupyterNotebookProvider.connect(anything())).thenResolve({} as any); - const notebook = await notebookProvider.getOrCreateNotebook({ identity: Uri('C:\\\\foo.py') }); + const notebook = await notebookProvider.getOrCreateNotebook({ + identity: Uri('C:\\\\foo.py'), + resource: Uri('C:\\\\foo.py') + }); expect(notebook).to.not.equal(undefined, 'Provider should return a notebook'); }); @@ -84,10 +90,16 @@ suite('DataScience - NotebookProvider', () => { when(jupyterNotebookProvider.createNotebook(anything())).thenResolve(notebookMock.object); when(jupyterNotebookProvider.connect(anything())).thenResolve({} as any); - const notebook = await notebookProvider.getOrCreateNotebook({ identity: Uri('C:\\\\foo.py') }); + const notebook = await notebookProvider.getOrCreateNotebook({ + identity: Uri('C:\\\\foo.py'), + resource: Uri('C:\\\\foo.py') + }); expect(notebook).to.not.equal(undefined, 'Server should return a notebook'); - const notebook2 = await notebookProvider.getOrCreateNotebook({ identity: Uri('C:\\\\foo.py') }); + const notebook2 = await notebookProvider.getOrCreateNotebook({ + identity: Uri('C:\\\\foo.py'), + resource: Uri('C:\\\\foo.py') + }); expect(notebook2).to.equal(notebook); }); }); diff --git a/src/test/datascience/interactive-common/notebookServerProvider.unit.test.ts b/src/test/datascience/interactive-common/notebookServerProvider.unit.test.ts index ff6a3a628dc..a66341845ec 100644 --- a/src/test/datascience/interactive-common/notebookServerProvider.unit.test.ts +++ b/src/test/datascience/interactive-common/notebookServerProvider.unit.test.ts @@ -73,7 +73,7 @@ suite('DataScience - NotebookServerProvider', () => { test('NotebookServerProvider - Get Only - no server', async () => { when(jupyterExecution.getServer(anything())).thenResolve(undefined); - const server = await serverProvider.getOrCreateServer({ getOnly: true }); + const server = await serverProvider.getOrCreateServer({ getOnly: true, resource: undefined }); expect(server).to.equal(undefined, 'Server expected to be undefined'); verify(jupyterExecution.getServer(anything())).once(); }); @@ -83,7 +83,7 @@ suite('DataScience - NotebookServerProvider', () => { when((notebookServer as any).then).thenReturn(undefined); when(jupyterExecution.getServer(anything())).thenResolve(instance(notebookServer)); - const server = await serverProvider.getOrCreateServer({ getOnly: true }); + const server = await serverProvider.getOrCreateServer({ getOnly: true, resource: undefined }); expect(server).to.not.equal(undefined, 'Server expected to be defined'); verify(jupyterExecution.getServer(anything())).once(); }); @@ -94,7 +94,7 @@ suite('DataScience - NotebookServerProvider', () => { when(jupyterExecution.connectToNotebookServer(anything(), anything())).thenResolve(notebookServer.object); // Disable UI just lets us skip mocking the progress reporter - const server = await serverProvider.getOrCreateServer({ getOnly: false, disableUI: true }); + const server = await serverProvider.getOrCreateServer({ getOnly: false, disableUI: true, resource: undefined }); expect(server).to.not.equal(undefined, 'Server expected to be defined'); }); }); diff --git a/src/test/datascience/jupyter/jupyterSession.unit.test.ts b/src/test/datascience/jupyter/jupyterSession.unit.test.ts index 1e888c012f3..d857eb87765 100644 --- a/src/test/datascience/jupyter/jupyterSession.unit.test.ts +++ b/src/test/datascience/jupyter/jupyterSession.unit.test.ts @@ -261,6 +261,7 @@ suite('DataScience - JupyterSession', () => { assert.isFalse(remoteSessionInstance.isRemoteSession); await jupyterSession.changeKernel( + undefined, { kernelModel: newActiveRemoteKernel, kind: 'connectToLiveKernel' }, 10000 ); @@ -336,7 +337,11 @@ suite('DataScience - JupyterSession', () => { env: undefined }; - await jupyterSession.changeKernel({ kernelSpec: newKernel, kind: 'startUsingKernelSpec' }, 10000); + await jupyterSession.changeKernel( + undefined, + { kernelSpec: newKernel, kind: 'startUsingKernelSpec' }, + 10000 + ); // Wait untill a new session has been started. await newSessionCreated.promise; diff --git a/src/test/datascience/jupyter/kernels/kernelSelector.unit.test.ts b/src/test/datascience/jupyter/kernels/kernelSelector.unit.test.ts index 3b4e7ea24a9..d9e5c02c64c 100644 --- a/src/test/datascience/jupyter/kernels/kernelSelector.unit.test.ts +++ b/src/test/datascience/jupyter/kernels/kernelSelector.unit.test.ts @@ -42,6 +42,7 @@ import { } from '../../../../client/datascience/jupyter/kernels/providers/activeJupyterSessionKernelProvider'; import { InstalledJupyterKernelSelectionListProvider } from '../../../../client/datascience/jupyter/kernels/providers/installJupyterKernelProvider'; import { disposeAllDisposables } from '../../../../client/common/helpers'; +import { InterpreterPackages } from '../../../../client/datascience/telemetry/interpreterPackages'; /* eslint-disable , @typescript-eslint/no-unused-expressions, @typescript-eslint/no-explicit-any */ @@ -103,7 +104,8 @@ suite('DataScience - KernelSelector', () => { instance(jupyterSessionManagerFactory), instance(configService), instance(extensionChecker), - instance(preferredKernelIdProvider) + instance(preferredKernelIdProvider), + instance(mock(InterpreterPackages)) ); }); teardown(() => { diff --git a/src/test/datascience/jupyterUriProviderRegistration.functional.test.ts b/src/test/datascience/jupyterUriProviderRegistration.functional.test.ts index b83f26ac16e..c036031b435 100644 --- a/src/test/datascience/jupyterUriProviderRegistration.functional.test.ts +++ b/src/test/datascience/jupyterUriProviderRegistration.functional.test.ts @@ -115,7 +115,8 @@ suite(`DataScience JupyterServerUriProvider tests`, () => { const server = await jupyterExecution.connectToNotebookServer({ uri, purpose: 'history', - allowUI: () => false + allowUI: () => false, + resource: undefined }); // Verify URI is our expected one diff --git a/src/test/datascience/mockJupyterSession.ts b/src/test/datascience/mockJupyterSession.ts index e64b9baf9b3..94375fd37f6 100644 --- a/src/test/datascience/mockJupyterSession.ts +++ b/src/test/datascience/mockJupyterSession.ts @@ -15,6 +15,7 @@ import { ICell, IJupyterSession, KernelSocketInformation } from '../../client/da import { ServerStatus } from '../../datascience-ui/interactive-common/mainState'; import { sleep } from '../core'; import { MockJupyterRequest } from './mockJupyterRequest'; +import { Resource } from '../../client/common/types'; const LineFeedRegEx = /(\r\n|\n)/g; @@ -210,7 +211,11 @@ export class MockJupyterSession implements IJupyterSession { this.completionTimeout = timeout; } - public changeKernel(kernelConnection: KernelConnectionMetadata, _timeoutMS: number): Promise { + public changeKernel( + _resource: Resource, + kernelConnection: KernelConnectionMetadata, + _timeoutMS: number + ): Promise { if (this.pendingKernelChangeFailure) { this.pendingKernelChangeFailure = false; return Promise.reject(new JupyterInvalidKernelError(kernelConnection)); diff --git a/src/test/datascience/nativeEditor.functional.test.tsx b/src/test/datascience/nativeEditor.functional.test.tsx index 0199fa9f0cb..232e982d20c 100644 --- a/src/test/datascience/nativeEditor.functional.test.tsx +++ b/src/test/datascience/nativeEditor.functional.test.tsx @@ -1093,7 +1093,8 @@ df.head()`; const jupyterExecution = ioc.serviceManager.get(IJupyterExecution); const server = await jupyterExecution.getServer({ allowUI: () => false, - purpose: Identifiers.HistoryPurpose + purpose: Identifiers.HistoryPurpose, + resource: undefined }); assert.ok(server, 'Server was destroyed on notebook shutdown'); diff --git a/src/test/datascience/notebook.functional.test.ts b/src/test/datascience/notebook.functional.test.ts index 5e336daa3a8..da0edacff3b 100644 --- a/src/test/datascience/notebook.functional.test.ts +++ b/src/test/datascience/notebook.functional.test.ts @@ -285,11 +285,12 @@ suite('DataScience notebook tests', () => { // Catch exceptions. Throw a specific assertion if the promise fails try { if (uri) { - ioc.setServerUri(uri); + ioc.setServerUri(uri).catch(noop); } launchingFile = launchingFile || path.join(srcDirectory(), 'foo.py'); const notebook = await notebookProvider.getOrCreateNotebook({ - identity: getDefaultInteractiveIdentity() + identity: getDefaultInteractiveIdentity(), + resource: undefined }); if (notebook) { @@ -1366,7 +1367,8 @@ plt.show()`, usingDarkTheme: false, workingDir: testDir, purpose: '1', - allowUI: () => false + allowUI: () => false, + resource: undefined }); } catch (e) { threw = true; diff --git a/src/test/datascience/variableTestHelpers.ts b/src/test/datascience/variableTestHelpers.ts index 0f2a1407fd0..ddc147bbc8a 100644 --- a/src/test/datascience/variableTestHelpers.ts +++ b/src/test/datascience/variableTestHelpers.ts @@ -137,7 +137,8 @@ export async function verifyCanFetchData( const notebookProvider = ioc.get(INotebookProvider); const notebook = await notebookProvider.getOrCreateNotebook({ getOnly: true, - identity: getDefaultInteractiveIdentity() + identity: getDefaultInteractiveIdentity(), + resource: undefined }); expect(notebook).to.not.be.undefined; const variableList = await variableFetcher.getVariables( diff --git a/src/test/telemetry/index.unit.test.ts b/src/test/telemetry/index.unit.test.ts index 059b1c7b068..dd755218069 100644 --- a/src/test/telemetry/index.unit.test.ts +++ b/src/test/telemetry/index.unit.test.ts @@ -169,7 +169,7 @@ suite('Telemetry', () => { const expectedErrorProperties = { failed: 'true', - failureReason: 'unknown', + failureCategory: 'unknown', originalEventName: eventName }; @@ -215,7 +215,7 @@ suite('Telemetry', () => { const expectedErrorProperties = { failed: 'true', - failureReason: 'unknown', + failureCategory: 'unknown', originalEventName: eventName };