-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Add telemetry for download, extract, and analyze. #2597
Changes from 8 commits
7deb17e
5334b59
3f04848
44c9396
9ea1c39
fa2376f
2f6cd5f
4fd9348
0dd4175
ec73eb4
c211f0f
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 |
---|---|---|
@@ -0,0 +1 @@ | ||
Add telemetry to download, extract, and analyze, phases of the Python Language Server |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Stop duplicate initializations of Python Language Server progress reporter. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,12 @@ import { ProgressLocation, window } from 'vscode'; | |
import { createDeferred } from '../../utils/async'; | ||
import { IFileSystem } from '../common/platform/types'; | ||
import { IExtensionContext, IOutputChannel } from '../common/types'; | ||
import { captureTelemetry } from '../telemetry'; | ||
import { | ||
PYTHON_LANGUAGE_SERVER_DOWNLOADED, | ||
PYTHON_LANGUAGE_SERVER_EXTRACTED | ||
} from '../telemetry/constants'; | ||
import { LanguageServerTelemetry } from '../telemetry/types'; | ||
import { PlatformData, PlatformName } from './platformData'; | ||
import { IDownloadFileService } from './types'; | ||
|
||
|
@@ -28,6 +34,11 @@ export const DownloadLinks = { | |
}; | ||
|
||
export class LanguageServerDownloader { | ||
//tslint:disable-next-line:no-unused-variable | ||
private lsDownloadTelemetry: LanguageServerTelemetry = {}; | ||
//tslint:disable-next-line:no-unused-variable | ||
private lsExtractTelemetry: LanguageServerTelemetry = {}; | ||
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. Please remove |
||
|
||
constructor( | ||
private readonly output: IOutputChannel, | ||
private readonly fs: IFileSystem, | ||
|
@@ -59,6 +70,12 @@ export class LanguageServerDownloader { | |
} | ||
} | ||
|
||
@captureTelemetry( | ||
PYTHON_LANGUAGE_SERVER_DOWNLOADED, | ||
{}, | ||
true, | ||
(props?: LanguageServerTelemetry) => props ? props.success = true : 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. Change second argument to 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. Ok, I can do that - but let's discuss a bit first. What if the What if I want to do other things than just set properties on the telemetry? Or set a field called something other than 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. Actually, now that you have me thinking, what about instead of a callback that gets a reference to the properties only, the beforeFailedEmit gets both the properties and the error? 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. Adding additional properties is easy. Today you just need to add a type deffinition. You have already done that. I have added type deffinition to the twenty to ensure we don't add anything, this way we know exactly what's sent in the telemetry, else we have to go around looking at the code base to find the telemetry properties.
Like what? We haven't had the need for such things. All we need with telemetry is send data. 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. Yep, I buy that, I'll make the change. 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. Just to pile on here, let's not build things we don't need. Even things we do need we shouldn't build until we need them unless they have major architectural implications. Less is more. |
||
) | ||
private async downloadFile(uri: string, title: string): Promise<string> { | ||
this.output.append(`Downloading ${uri}... `); | ||
const tempFile = await this.fs.createTemporaryFile(downloadFileExtension); | ||
|
@@ -101,6 +118,12 @@ export class LanguageServerDownloader { | |
return tempFile.filePath; | ||
} | ||
|
||
@captureTelemetry( | ||
PYTHON_LANGUAGE_SERVER_EXTRACTED, | ||
this.languageServerStartupTelemetry, | ||
true, | ||
(props?: LanguageServerTelemetry) => props ? props.success = true : 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. Change second argument to |
||
) | ||
private async unpackArchive(extensionPath: string, tempFilePath: string): Promise<void> { | ||
this.output.append('Unpacking archive... '); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,8 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
'use strict'; | ||
|
||
import { inject, injectable } from 'inversify'; | ||
import * as path from 'path'; | ||
import { | ||
|
@@ -28,12 +30,15 @@ import { | |
import { IEnvironmentVariablesProvider } from '../common/variables/types'; | ||
import { IServiceContainer } from '../ioc/types'; | ||
import { LanguageServerSymbolProvider } from '../providers/symbolProvider'; | ||
import { captureTelemetry } from '../telemetry'; | ||
import { | ||
PYTHON_LANGUAGE_SERVER_DOWNLOADED, | ||
PYTHON_LANGUAGE_SERVER_ENABLED, | ||
PYTHON_LANGUAGE_SERVER_ERROR | ||
PYTHON_LANGUAGE_SERVER_ERROR, | ||
PYTHON_LANGUAGE_SERVER_STARTUP | ||
} from '../telemetry/constants'; | ||
import { getTelemetryReporter } from '../telemetry/telemetry'; | ||
import { LanguageServerTelemetry } from '../telemetry/types'; | ||
import { IUnitTestManagementService } from '../unittests/types'; | ||
import { LanguageServerDownloader } from './downloader'; | ||
import { InterpreterData, InterpreterDataService } from './interpreterDataService'; | ||
|
@@ -76,6 +81,8 @@ export class LanguageServerExtensionActivator implements IExtensionActivator { | |
private surveyBanner: IPythonExtensionBanner; | ||
// tslint:disable-next-line:no-unused-variable | ||
private progressReporting: ProgressReporting | undefined; | ||
//tslint:disable-next-line:no-unused-variable | ||
private languageServerStartupTelemetry: LanguageServerTelemetry = {}; | ||
|
||
constructor(@inject(IServiceContainer) private readonly services: IServiceContainer) { | ||
this.context = this.services.get<IExtensionContext>(IExtensionContext); | ||
|
@@ -141,6 +148,12 @@ export class LanguageServerExtensionActivator implements IExtensionActivator { | |
(this.configuration.getSettings() as PythonSettings).removeListener('change', this.onSettingsChanged.bind(this)); | ||
} | ||
|
||
@captureTelemetry( | ||
PYTHON_LANGUAGE_SERVER_STARTUP, | ||
this.languageServerStartupTelemetry, | ||
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. Please do not access 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. Is 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. We don't need
It's not loose typing at all, you can't set the second argument to anything, you'll get to compilation errors, meaning it's strongly typed. Once again it is not at all loosely typed. See the definition for the function, check the second argument: export function captureTelemetry(
eventName: string,
properties?: TelemetryProperties, |
||
true, | ||
(props?: LanguageServerTelemetry) => props ? props.success = true : 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. Please remove this last two argument, not necessary. |
||
) | ||
private async startLanguageServer(clientOptions: LanguageClientOptions): Promise<boolean> { | ||
// Determine if we are running MSIL/Universal via dotnet or self-contained app. | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,11 +4,18 @@ | |
import { Progress, ProgressLocation, window } from 'vscode'; | ||
import { Disposable, LanguageClient } from 'vscode-languageclient'; | ||
import { createDeferred, Deferred } from '../../utils/async'; | ||
import { StopWatch } from '../../utils/stopWatch'; | ||
import { sendTelemetryEvent } from '../telemetry'; | ||
import { PYTHON_LANGUAGE_SERVER_ANALYSISTIME } from '../telemetry/constants'; | ||
import { LanguageServerTelemetry } from '../telemetry/types'; | ||
|
||
export class ProgressReporting { | ||
private statusBarMessage: Disposable | undefined; | ||
private progress: Progress<{ message?: string; increment?: number }> | undefined; | ||
private progressDeferred: Deferred<void> | undefined; | ||
private progressTimer: StopWatch | undefined; | ||
private progressTimeout: NodeJS.Timer | undefined; | ||
private ANALYSIS_TIMEOUT_MS: number = 60000; | ||
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 needs to be a constant, not a member. 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. Agreed. I will make the change. Thanks for calling this out! |
||
|
||
constructor(private readonly languageClient: LanguageClient) { | ||
this.languageClient.onNotification('python/setStatusBarMessage', (m: string) => { | ||
|
@@ -19,7 +26,14 @@ export class ProgressReporting { | |
}); | ||
|
||
this.languageClient.onNotification('python/beginProgress', async _ => { | ||
if (this.progressDeferred) { // if we restarted, no worries as reporting will still funnel to the same place. | ||
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 is the fix for #2297 and related... 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. For #2297 (while most of the problem is not here, restart is rare) you need to track 'stateChange' on the language client. When it gets to 'stopped' LS has terminated. 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. Cool! Thanks for the tip. I'll move this code to another PR that we can review separately and cover all the appropriate cases. 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. @MikhailArkhipov please see #2606 |
||
return; | ||
} | ||
|
||
this.progressDeferred = createDeferred<void>(); | ||
this.progressTimer = new StopWatch(); | ||
this.progressTimeout = setTimeout(this.handleTimeout, this.ANALYSIS_TIMEOUT_MS); | ||
|
||
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. Change to 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. Cool - that callback scope still isn't always clear to me, thanks for catching this. |
||
window.withProgress({ | ||
location: ProgressLocation.Window, | ||
title: '' | ||
|
@@ -40,7 +54,32 @@ export class ProgressReporting { | |
if (this.progressDeferred) { | ||
this.progressDeferred.resolve(); | ||
this.progressDeferred = undefined; | ||
this.completeAnalysisTracking(true); | ||
this.progress = undefined; | ||
} | ||
}); | ||
} | ||
|
||
private completeAnalysisTracking(isSuccess: boolean): void { | ||
if (this.progressTimer) { | ||
const lsAnalysisTelemetry: LanguageServerTelemetry = { | ||
success: isSuccess | ||
}; | ||
sendTelemetryEvent( | ||
PYTHON_LANGUAGE_SERVER_ANALYSISTIME, | ||
this.progressTimer.elapsedTime, | ||
lsAnalysisTelemetry | ||
); | ||
this.progressTimer = undefined; | ||
} | ||
|
||
if (this.progressTimeout) { | ||
this.progressTimeout = undefined; | ||
} | ||
} | ||
|
||
// tslint:disable-next-line:no-any | ||
private handleTimeout(_args: any[]): void { | ||
this.completeAnalysisTracking(false); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,7 +26,15 @@ export function sendTelemetryEvent(eventName: string, durationMs?: number, prope | |
} | ||
|
||
// tslint:disable-next-line:no-any function-name | ||
export function captureTelemetry(eventName: string, properties?: TelemetryProperties, captureDuration: boolean = true) { | ||
export function captureTelemetry( | ||
eventName: string, | ||
properties?: TelemetryProperties, | ||
captureDuration: boolean = true, | ||
// tslint:disable-next-line:no-any | ||
beforeSuccessEmit?: (props?: any) => void, | ||
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. We don't need these two new parameters. Just remove the last two parameters. |
||
// tslint:disable-next-line:no-any | ||
beforeFailEmit?: (props?: any) => void | ||
) { | ||
// tslint:disable-next-line:no-function-expression no-any | ||
return function (target: Object, propertyKey: string, descriptor: TypedPropertyDescriptor<any>) { | ||
const originalMethod = descriptor.value; | ||
|
@@ -48,11 +56,17 @@ export function captureTelemetry(eventName: string, properties?: TelemetryProper | |
// tslint:disable-next-line:prefer-type-cast | ||
(result as Promise<void>) | ||
.then(data => { | ||
if (beforeSuccessEmit) { | ||
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. Please remove this if condition and this new code. |
||
beforeSuccessEmit(properties); | ||
} | ||
sendTelemetryEvent(eventName, stopWatch.elapsedTime, properties); | ||
return data; | ||
}) | ||
// tslint:disable-next-line:promise-function-async | ||
.catch(ex => { | ||
if (beforeFailEmit) { | ||
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. Please remove this if condition and this code. Change as follows: properties = properties ? properties : {};
properties.success = false; 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. I cannot quite understand this exactly. What happens when For instance, what happens when this comes from the decorator on That use of properties = properties ? properties : {};
if ((<LanguageServerTelemetry>properties).success) {
(<LanguageServerTelemetry>properties).success = false;
} Reference: Type Guards and Differentiating Types 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. 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. Just change as follows: properties = (properties ? properties : {}) as any;
if (properties.success !== undefined) {
properties.success = false;
} Check for 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. Yeah, I don't think we want to do this. Assuming that I want to go back to doing something more explicit in the downloader/languageServer themselves instead. |
||
beforeFailEmit(properties); | ||
} | ||
sendTelemetryEvent(eventName, stopWatch.elapsedTime, properties); | ||
return Promise.reject(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.
Please remove