-
Notifications
You must be signed in to change notification settings - Fork 300
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Few Perf Improvements for kernel startup & more logging #8317
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -70,14 +70,14 @@ export type TraceInfo = | |
export function tracing<T>(log: (t: TraceInfo) => void, run: () => T, logBeforeCall?: boolean): T { | ||
const timer = new StopWatch(); | ||
try { | ||
if (logBeforeCall) { | ||
log(undefined); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug fix, we were logging just after the method was called (which was incorrect) |
||
// eslint-disable-next-line no-invalid-this, @typescript-eslint/no-use-before-define, | ||
const result = run(); | ||
|
||
// If method being wrapped returns a promise then wait for it. | ||
if (isPromise(result)) { | ||
if (logBeforeCall) { | ||
log(undefined); | ||
} | ||
// eslint-disable-next-line | ||
(result as Promise<void>) | ||
.then((data) => { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -439,6 +439,21 @@ export function isLocalLaunch(configuration: IConfigurationService) { | |
return false; | ||
} | ||
|
||
export function getInterpreterHashInMetdata( | ||
notebookMetadata: nbformat.INotebookMetadata | undefined | ||
): string | undefined { | ||
// If the user has kernelspec in metadata & the interpreter hash is stored in metadata, then its a perfect match. | ||
// This is the preferred approach https://github.com/microsoft/vscode-jupyter/issues/5612 | ||
if ( | ||
typeof notebookMetadata === 'object' && | ||
'interpreter' in notebookMetadata && | ||
(notebookMetadata as any).interpreter && | ||
typeof (notebookMetadata as any).interpreter === 'object' && | ||
'hash' in (notebookMetadata as any).interpreter | ||
) { | ||
return (notebookMetadata as any).interpreter.hash; | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. An unnecessary change, but will be required later. |
||
export function findPreferredKernel( | ||
kernels: KernelConnectionMetadata[], | ||
resource: Resource, | ||
|
@@ -604,15 +619,11 @@ export function findPreferredKernel( | |
|
||
// If the user has kernelspec in metadata & the interpreter hash is stored in metadata, then its a perfect match. | ||
// This is the preferred approach https://github.com/microsoft/vscode-jupyter/issues/5612 | ||
const interpreterHashInMetadata = getInterpreterHashInMetdata(notebookMetadata); | ||
if ( | ||
typeof notebookMetadata === 'object' && | ||
'interpreter' in notebookMetadata && | ||
(notebookMetadata as any).interpreter && | ||
typeof (notebookMetadata as any).interpreter === 'object' && | ||
'hash' in (notebookMetadata as any).interpreter && | ||
(metadata.kind === 'startUsingKernelSpec' || metadata.kind === 'startUsingPythonInterpreter') && | ||
metadata.interpreter && | ||
getInterpreterHash(metadata.interpreter) === (notebookMetadata as any).interpreter.hash | ||
getInterpreterHash(metadata.interpreter) === interpreterHashInMetadata | ||
) { | ||
// This is a perfect match. | ||
traceInfoIfCI('Increased score by +100 for matching interpreter in notbeook metadata'); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,11 +2,11 @@ | |
// Licensed under the MIT License. | ||
'use strict'; | ||
|
||
import { inject, injectable } from 'inversify'; | ||
import { inject, injectable, named } from 'inversify'; | ||
import * as path from 'path'; | ||
import { CancellationToken } from 'vscode'; | ||
import { CancellationToken, Memento } from 'vscode'; | ||
import { IPlatformService } from '../../common/platform/types'; | ||
import { IPathUtils } from '../../common/types'; | ||
import { GLOBAL_MEMENTO, IDisposableRegistry, IMemento, IPathUtils } from '../../common/types'; | ||
import { IEnvironmentVariablesProvider } from '../../common/variables/types'; | ||
import { traceDecorators } from '../../logging'; | ||
import { tryGetRealPath } from '../common'; | ||
|
@@ -15,29 +15,57 @@ const winJupyterPath = path.join('AppData', 'Roaming', 'jupyter', 'kernels'); | |
const linuxJupyterPath = path.join('.local', 'share', 'jupyter', 'kernels'); | ||
const macJupyterPath = path.join('Library', 'Jupyter', 'kernels'); | ||
export const baseKernelPath = path.join('share', 'jupyter', 'kernels'); | ||
const CACHE_KEY_FOR_JUPYTER_KERNELSPEC_ROOT_PATH = 'CACHE_KEY_FOR_JUPYTER_KERNELSPEC_ROOT_PATH'; | ||
const CACHE_KEY_FOR_JUPYTER_PATHS = 'CACHE_KEY_FOR_JUPYTER_PATHS'; | ||
|
||
@injectable() | ||
export class JupyterPaths { | ||
private cachedKernelSpecRootPath?: Promise<string | undefined>; | ||
private cachedJupyterPaths?: Promise<string[]>; | ||
constructor( | ||
@inject(IPlatformService) private platformService: IPlatformService, | ||
@inject(IPathUtils) private readonly pathUtils: IPathUtils, | ||
@inject(IEnvironmentVariablesProvider) private readonly envVarsProvider: IEnvironmentVariablesProvider | ||
) {} | ||
@inject(IEnvironmentVariablesProvider) private readonly envVarsProvider: IEnvironmentVariablesProvider, | ||
@inject(IDisposableRegistry) disposables: IDisposableRegistry, | ||
@inject(IMemento) @named(GLOBAL_MEMENTO) private readonly globalState: Memento | ||
) { | ||
this.envVarsProvider.onDidEnvironmentVariablesChange( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Clear the cache if user updates their env variables |
||
() => { | ||
this.cachedJupyterPaths = undefined; | ||
}, | ||
this, | ||
disposables | ||
); | ||
} | ||
|
||
/** | ||
* This should return a WRITABLE place that jupyter will look for a kernel as documented | ||
* here: https://jupyter-client.readthedocs.io/en/stable/kernels.html#kernel-specs | ||
*/ | ||
@traceDecorators.verbose('Getting Jupyter KernelSpec Root Path') | ||
public async getKernelSpecRootPath(): Promise<string | undefined> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. THis logging was very useful, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Didn't you have some decorator way to cache results from a function? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh yes, yo'ure right, let me check There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The existing decorator will not , because that uses a memory based cache store, i.e. when user reloads VS Code the cache is empty. I'd need to create a decorator that uses the global memento? Would definitely make the code cleaner & improve readability. |
||
if (this.platformService.isWindows) { | ||
// On windows the path is not correct if we combine those variables. | ||
// It won't point to a path that you can actually read from. | ||
return tryGetRealPath(path.join(this.pathUtils.home, winJupyterPath)); | ||
} else if (this.platformService.isMac) { | ||
return path.join(this.pathUtils.home, macJupyterPath); | ||
} else { | ||
return path.join(this.pathUtils.home, linuxJupyterPath); | ||
this.cachedKernelSpecRootPath = | ||
this.cachedKernelSpecRootPath || | ||
(async () => { | ||
if (this.platformService.isWindows) { | ||
// On windows the path is not correct if we combine those variables. | ||
// It won't point to a path that you can actually read from. | ||
return tryGetRealPath(path.join(this.pathUtils.home, winJupyterPath)); | ||
} else if (this.platformService.isMac) { | ||
return path.join(this.pathUtils.home, macJupyterPath); | ||
} else { | ||
return path.join(this.pathUtils.home, linuxJupyterPath); | ||
} | ||
})(); | ||
void this.cachedKernelSpecRootPath.then((value) => { | ||
if (value) { | ||
void this.globalState.update(CACHE_KEY_FOR_JUPYTER_KERNELSPEC_ROOT_PATH, value); | ||
} | ||
}); | ||
if (this.globalState.get(CACHE_KEY_FOR_JUPYTER_KERNELSPEC_ROOT_PATH)) { | ||
return this.globalState.get(CACHE_KEY_FOR_JUPYTER_KERNELSPEC_ROOT_PATH); | ||
} | ||
return this.cachedKernelSpecRootPath; | ||
} | ||
/** | ||
* This list comes from the docs here: | ||
|
@@ -76,27 +104,41 @@ export class JupyterPaths { | |
* We need to look at the 'kernels' sub-directory and these paths are supposed to come first in the searching | ||
* https://jupyter.readthedocs.io/en/latest/projects/jupyter-directories.html#envvar-JUPYTER_PATH | ||
*/ | ||
@traceDecorators.verbose('Get Jupyter Paths') | ||
private async getJupyterPathPaths(cancelToken?: CancellationToken): Promise<string[]> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The logging and caching was very useful, see comments earlier. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same comment on this one. I swear you had something that would cache results for a function call into a memento. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes now I remember, we had code to cache in memento, I think its in the Python extension, & it may have been removed from this repo as it wasn't used. |
||
const paths: string[] = []; | ||
const vars = await this.envVarsProvider.getEnvironmentVariables(); | ||
if (cancelToken?.isCancellationRequested) { | ||
return []; | ||
} | ||
const jupyterPathVars = vars.JUPYTER_PATH | ||
? vars.JUPYTER_PATH.split(path.delimiter).map((jupyterPath) => { | ||
return path.join(jupyterPath, 'kernels'); | ||
}) | ||
: []; | ||
this.cachedJupyterPaths = | ||
this.cachedJupyterPaths || | ||
(async () => { | ||
const paths: string[] = []; | ||
const vars = await this.envVarsProvider.getEnvironmentVariables(); | ||
if (cancelToken?.isCancellationRequested) { | ||
return []; | ||
} | ||
const jupyterPathVars = vars.JUPYTER_PATH | ||
? vars.JUPYTER_PATH.split(path.delimiter).map((jupyterPath) => { | ||
return path.join(jupyterPath, 'kernels'); | ||
}) | ||
: []; | ||
|
||
if (jupyterPathVars.length > 0) { | ||
jupyterPathVars.forEach(async (jupyterPath) => { | ||
const realPath = await tryGetRealPath(jupyterPath); | ||
if (realPath) { | ||
paths.push(realPath); | ||
if (jupyterPathVars.length > 0) { | ||
jupyterPathVars.forEach(async (jupyterPath) => { | ||
const realPath = await tryGetRealPath(jupyterPath); | ||
if (realPath) { | ||
paths.push(realPath); | ||
} | ||
}); | ||
} | ||
}); | ||
} | ||
|
||
return paths; | ||
return paths; | ||
})(); | ||
void this.cachedJupyterPaths.then((value) => { | ||
if (value.length > 0) { | ||
void this.globalState.update(CACHE_KEY_FOR_JUPYTER_PATHS, value); | ||
} | ||
}); | ||
if (this.globalState.get<string[]>(CACHE_KEY_FOR_JUPYTER_PATHS, []).length > 0) { | ||
return this.globalState.get<string[]>(CACHE_KEY_FOR_JUPYTER_PATHS, []); | ||
} | ||
return this.cachedJupyterPaths; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,7 +12,6 @@ import { IPythonExecutionFactory, ObservableExecutionResult } from '../../common | |
import { IDisposable, Resource } from '../../common/types'; | ||
import { noop } from '../../common/utils/misc'; | ||
import { traceDecorators } from '../../logging'; | ||
import { TraceOptions } from '../../logging/trace'; | ||
import { logValue } from '../../logging/trace'; | ||
import { PythonEnvironment } from '../../pythonEnvironments/info'; | ||
import { IJupyterKernelSpec } from '../types'; | ||
|
@@ -34,7 +33,7 @@ export class PythonKernelLauncherDaemon implements IDisposable { | |
@inject(KernelEnvironmentVariablesService) | ||
private readonly kernelEnvVarsService: KernelEnvironmentVariablesService | ||
) {} | ||
@traceDecorators.verbose('Launching kernel daemon', TraceOptions.BeforeCall) | ||
@traceDecorators.verbose('Launching kernel daemon') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
public async launch( | ||
resource: Resource, | ||
workingDirectory: string, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -391,7 +391,7 @@ export class NotebookControllerManager implements INotebookControllerManager, IE | |
|
||
// Prep so that we can track the selected controller for this document | ||
traceInfoIfCI(`Clear controller mapping for ${getDisplayPath(document.uri)}`); | ||
const loadControllersPromise = this.loadNotebookControllers(); | ||
void this.loadNotebookControllers(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Its ok if the controllers haven't loaded, This way, we don't have to wait for all 10-20 or 50 controllers to get loaded. Result, we can now run button will be available even earlier. Got more changes that will imporve this scenario & make this change all the more useful (separate PR). |
||
|
||
if ( | ||
isPythonNotebook(getNotebookMetadata(document)) && | ||
|
@@ -454,9 +454,6 @@ export class NotebookControllerManager implements INotebookControllerManager, IE | |
)}` | ||
); | ||
} | ||
// Wait for our controllers to be loaded before we try to set a preferred on | ||
// can happen if a document is opened quick and we have not yet loaded our controllers | ||
await loadControllersPromise; | ||
const targetController = Array.from(this.registeredControllers.values()).find( | ||
(value) => preferredConnection?.id === value.connection.id | ||
); | ||
|
@@ -470,11 +467,15 @@ export class NotebookControllerManager implements INotebookControllerManager, IE | |
// Save in our map so we can find it in test code. | ||
this.preferredControllers.set(document, targetController); | ||
} else { | ||
traceInfoIfCI( | ||
`TargetController not found ID: ${preferredConnection?.id} for document ${getDisplayPath( | ||
document.uri | ||
)}` | ||
); | ||
// Possible the kernel discovery hasn't completed yet. | ||
if (preferredConnection) { | ||
this.createNotebookControllers([preferredConnection]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If not available, then just create it. |
||
traceInfoIfCI( | ||
`TargetController not found ID: ${preferredConnection?.id} for document ${getDisplayPath( | ||
document.uri | ||
)}` | ||
); | ||
} | ||
} | ||
} catch (ex) { | ||
traceError('Failed to find & set preferred controllers', ex); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added some logging, these were useful