-
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
Conversation
src/client/activation/progress.ts
Outdated
@@ -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 comment
The 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 comment
The 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 comment
The 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 comment
The reason will be displayed to describe this comment to others. Learn more.
@MikhailArkhipov please see #2606
Note: Also snuck my PVSC_EXTENSION_ID constant into this PR... |
src/client/activation/downloader.ts
Outdated
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 comment
The reason will be displayed to describe this comment to others. Learn more.
Change second argument to {success:true}
And remove this last argument , no need of callback
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.
Ok, I can do that - but let's discuss a bit first.
What if the TelemetryProperties
instance handed to the decorator doesn't have a success
field within?
What if I want to do other things than just set properties on the telemetry? Or set a field called something other than success
accordingly?
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.
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 comment
The 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.
What if I want to do other things than just set properties on the telemetry? Or set a field called something other than success accordingly?
Like what? We haven't had the need for such things. All we need with telemetry is send data.
Let's not plan for tomorrow and overload the purpose of the decoratotor. Just send telemetry. If you want pre and post method execution, then you need another decorator, not this.
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.
Yep, I buy that, I'll make the change.
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.
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.
src/client/telemetry/index.ts
Outdated
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 comment
The 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.
Remove the 3rd and 4th arguments.
src/client/telemetry/index.ts
Outdated
@@ -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 comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove this if condition and this new code.
src/client/telemetry/index.ts
Outdated
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 comment
The 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 comment
The reason will be displayed to describe this comment to others. Learn more.
I cannot quite understand this exactly.
What happens when properties
is not null, and was not given with a success
member?
For instance, what happens when this comes from the decorator on CodeExecutionManager::executeFileInterTerminal
?
That use of TelemetryProperties
is using the union-ed type CodeExecutionTelemetry
and has no success
field.
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 comment
The 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 comment
The 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 comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I don't think we want to do this. Assuming that success
is always false when an exception is thrown (or Promise rejected) in this one case isn't necessarily going to be the right path for the next TelemetryProperties
type we add with a success
field.
I want to go back to doing something more explicit in the downloader/languageServer themselves instead.
@@ -140,6 +147,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 comment
The reason will be displayed to describe this comment to others. Learn more.
Please do not access this
inside the decoratotor.
Just change second parameter to {success: true}
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.
Is this
not valid in a decorator? Perhaps a static variable then (gross...) or I'll just do your suggestion. Loose typing like this can confuse me at times...
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.
We don't need this
, all we need is a constant object.
Loose typing like this can confuse me at times...
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,
PYTHON_LANGUAGE_SERVER_STARTUP, | ||
this.languageServerStartupTelemetry, | ||
true, | ||
(props?: LanguageServerTelemetry) => props ? props.success = true : undefined |
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 this last two argument, not necessary.
src/client/activation/downloader.ts
Outdated
@@ -28,6 +34,11 @@ export const DownloadLinks = { | |||
}; | |||
|
|||
export class LanguageServerDownloader { | |||
//tslint:disable-next-line:no-unused-variable | |||
private lsDownloadTelemetry: LanguageServerTelemetry = {}; |
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
src/client/activation/downloader.ts
Outdated
//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 comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove
src/client/activation/downloader.ts
Outdated
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 comment
The reason will be displayed to describe this comment to others. Learn more.
Change second argument to {success:true}
And remove this last argument , no need of callback
src/client/activation/progress.ts
Outdated
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 comment
The reason will be displayed to describe this comment to others. Learn more.
Change to setTimeout(this.handleTimeout.bind(this), ....
.
You need to preserve callback scope.
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.
Cool - that callback scope still isn't always clear to me, thanks for catching this.
Fixes microsoft#2461 (alternate fix to PR microsoft#2593) - Capture telemetry surrounds methods, minimizing change - Telemetry type can be altered with less code later. - Add success/fail props modifyer func to `captureTelemetry`
- LanguageServerTelemetry is simpler and succinct enough for this use
- I believe this might fix microsoft#2297 (and related issues) - Calling 'beginProgress' from LS causes progress reporting to not get cleaned up properly. - Related fix for microsoft/python-language-server#94
- versus hard-coding the string in multiple places.
dcf92cc
to
4fd9348
Compare
- to avoid pollute the generic nature of the decorator, it's best to make this solution explicit
src/client/activation/downloader.ts
Outdated
if (localTempFilePath.length > 0) { | ||
await this.fs.deleteFile(localTempFilePath); | ||
} | ||
sendTelemetryEvent('DEREK2', timer.elapsedTime, { success: success === true }); |
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.
DEREK2
?
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.
Apologies - this is a WIP at the moment...
@DonJayamanne please re-review. |
try { | ||
localTempFilePath = await this.downloadFile(downloadUri, 'Downloading Microsoft Python Language Server... '); | ||
} catch (err) { |
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.
Why did we go with this approach instead of the decorator?
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.
As you can see there's some duplication of code..
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.
Because the decorator emits telemetry, and adds timing info. It does not allow for state changes in property values without increasing complexity of the decorator function. I couldn't come up with an appropriate reason for the added complexity.
src/client/activation/progress.ts
Outdated
private progressTimer?: StopWatch; | ||
// tslint:disable-next-line:no-unused-variable | ||
private progressTimeout?: NodeJS.Timer; | ||
private ANALYSIS_TIMEOUT_MS: number = 60000; |
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.
This needs to be a constant, not a member.
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.
Agreed. I will make the change. Thanks for calling this out!
Fixes #2461 (alternate fix to PR #2593)
Capture telemetry using decorators to methods, minimizing change, improving maintainability (thanks @DonJayamanne!)
Add success/fail props modifier func to
captureTelemetry
to better handle failure telemetry emissions.Potential correction of New language server seems to be frozen at analyzing workspace for more than 30 minutes #2297 and related (I believe Progress reporting does not always clear when analysis completes python-language-server#94 is corrected here as well).
Pull request represents a single change (i.e. not fixing disparate/unrelated things in a single PR)
Title summarizes what is changing
Has a news entry file (remember to thank yourself!)
Has unit tests & system/integration tests (needed for progress reporter, telemetry tests are likely unnecessary unless someone tells me differently).We will need to alter the code (makeLanguageClient
andProgress
accessible via DI - not appropriate for this PR).Remove pre-success, pre-error, telemetry emission functions, replace with object member-setter.
Remove 2297 fix from this PR and create new one.
Any new/changed dependencies inpackage.json
are pinned (e.g."1.2.3"
, not"^1.2.3"
for the specified version)package-lock.json
has been regenerated by runningnpm install
(if dependencies have changed)