Skip to content

Commit 87b568b

Browse files
authored
Refactor the debugAdapterTracker we use to handle multiple sessions (#7589)
* Separate out the debug location tracker and the session telemetry * Rework the singleton nature of the DS location tracker * Review changes
1 parent f57eada commit 87b568b

13 files changed

Lines changed: 176 additions & 89 deletions

news/3 Code Health/7352.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Decouple the DS location tracker from the debug session telemetry.

package-lock.json

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
'use strict';
4+
import { inject, injectable } from 'inversify';
5+
import { DebugAdapterTracker, DebugAdapterTrackerFactory, DebugSession, ProviderResult } from 'vscode';
6+
import { DebugProtocol } from 'vscode-debugprotocol';
7+
8+
import { IExtensionSingleActivationService } from '../../activation/types';
9+
import { AttachRequestArguments, ConsoleType, LaunchRequestArguments, TriggerType } from '../../debugger/types';
10+
import { sendTelemetryEvent } from '../../telemetry';
11+
import { EventName } from '../../telemetry/constants';
12+
import { IDisposableRegistry } from '../types';
13+
import { StopWatch } from '../utils/stopWatch';
14+
import { IDebugService } from './types';
15+
16+
class TelemetryTracker implements DebugAdapterTracker {
17+
private timer = new StopWatch();
18+
private readonly trigger: TriggerType = 'launch';
19+
private readonly console: ConsoleType | undefined;
20+
21+
constructor(session: DebugSession) {
22+
this.trigger = session.configuration.type as TriggerType;
23+
const debugConfiguration = session.configuration as Partial<LaunchRequestArguments & AttachRequestArguments>;
24+
this.console = debugConfiguration.console;
25+
}
26+
27+
public onWillStartSession() {
28+
this.sendTelemetry(EventName.DEBUG_SESSION_START);
29+
}
30+
31+
// tslint:disable-next-line:no-any
32+
public onDidSendMessage(message: DebugProtocol.ProtocolMessage) {
33+
if (message.type === 'response') {
34+
const response = message as DebugProtocol.Response;
35+
if (response.command === 'configurationDone') {
36+
// "configurationDone" response is sent immediately after user code starts running.
37+
this.sendTelemetry(EventName.DEBUG_SESSION_USER_CODE_RUNNING);
38+
}
39+
}
40+
}
41+
42+
public onWillStopSession() {
43+
this.sendTelemetry(EventName.DEBUG_SESSION_STOP);
44+
}
45+
46+
public onError?(_error: Error) {
47+
this.sendTelemetry(EventName.DEBUG_SESSION_ERROR);
48+
}
49+
50+
private sendTelemetry(eventName: EventName) {
51+
if (eventName === EventName.DEBUG_SESSION_START) {
52+
this.timer.reset();
53+
}
54+
const telemetryProps = {
55+
trigger: this.trigger,
56+
console: this.console
57+
};
58+
sendTelemetryEvent(eventName, this.timer.elapsedTime, telemetryProps);
59+
}
60+
}
61+
62+
@injectable()
63+
export class DebugSessionTelemetry implements DebugAdapterTrackerFactory, IExtensionSingleActivationService {
64+
constructor(
65+
@inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry,
66+
@inject(IDebugService) debugService: IDebugService
67+
) {
68+
disposableRegistry.push(debugService.registerDebugAdapterTrackerFactory('python', this));
69+
}
70+
71+
public async activate(): Promise<void> {
72+
// We actually register in the constructor. Not necessary to do it here
73+
}
74+
75+
public createDebugAdapterTracker(session: DebugSession): ProviderResult<DebugAdapterTracker> {
76+
return new TelemetryTracker(session);
77+
}
78+
}

src/client/common/serviceRegistry.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { ApplicationEnvironment } from './application/applicationEnvironment';
99
import { ApplicationShell } from './application/applicationShell';
1010
import { CommandManager } from './application/commandManager';
1111
import { DebugService } from './application/debugService';
12+
import { DebugSessionTelemetry } from './application/debugSessionTelemetry';
1213
import { DocumentManager } from './application/documentManager';
1314
import { Extensions } from './application/extensions';
1415
import { LanguageService } from './application/languageService';
@@ -31,11 +32,20 @@ import { CryptoUtils } from './crypto';
3132
import { EditorUtils } from './editor';
3233
import { ExperimentsManager } from './experiments';
3334
import { FeatureDeprecationManager } from './featureDeprecationManager';
34-
import { ExtensionInsidersDailyChannelRule, ExtensionInsidersOffChannelRule, ExtensionInsidersWeeklyChannelRule } from './insidersBuild/downloadChannelRules';
35+
import {
36+
ExtensionInsidersDailyChannelRule,
37+
ExtensionInsidersOffChannelRule,
38+
ExtensionInsidersWeeklyChannelRule
39+
} from './insidersBuild/downloadChannelRules';
3540
import { ExtensionChannelService } from './insidersBuild/downloadChannelService';
3641
import { InsidersExtensionPrompt } from './insidersBuild/insidersExtensionPrompt';
3742
import { InsidersExtensionService } from './insidersBuild/insidersExtensionService';
38-
import { ExtensionChannel, IExtensionChannelRule, IExtensionChannelService, IInsiderExtensionPrompt } from './insidersBuild/types';
43+
import {
44+
ExtensionChannel,
45+
IExtensionChannelRule,
46+
IExtensionChannelService,
47+
IInsiderExtensionPrompt
48+
} from './insidersBuild/types';
3949
import { ProductInstaller } from './installer/productInstaller';
4050
import { LiveShareApi } from './liveshare/liveshare';
4151
import { Logger } from './logger';
@@ -150,4 +160,5 @@ export function registerTypes(serviceManager: IServiceManager) {
150160
serviceManager.addSingleton<IExtensionChannelRule>(IExtensionChannelRule, ExtensionInsidersOffChannelRule, ExtensionChannel.off);
151161
serviceManager.addSingleton<IExtensionChannelRule>(IExtensionChannelRule, ExtensionInsidersDailyChannelRule, ExtensionChannel.daily);
152162
serviceManager.addSingleton<IExtensionChannelRule>(IExtensionChannelRule, ExtensionInsidersWeeklyChannelRule, ExtensionChannel.weekly);
163+
serviceManager.addSingleton<IExtensionSingleActivationService>(IExtensionSingleActivationService, DebugSessionTelemetry);
153164
}

src/client/datascience/debugLocationTracker.ts

Lines changed: 14 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,28 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33
'use strict';
4-
import { injectable } from 'inversify';
5-
import { DebugSession, Event, EventEmitter } from 'vscode';
4+
import { DebugAdapterTracker, Event, EventEmitter } from 'vscode';
65
import { DebugProtocol } from 'vscode-debugprotocol';
76

8-
import { StopWatch } from '../common/utils/stopWatch';
9-
import { AttachRequestArguments, ConsoleType, LaunchRequestArguments, TriggerType } from '../debugger/types';
10-
import { sendTelemetryEvent } from '../telemetry';
11-
import { EventName } from '../telemetry/constants';
12-
import { IDebugLocation, IDebugLocationTracker } from './types';
7+
import { IDebugLocation } from './types';
138

149
// When a python debugging session is active keep track of the current debug location
15-
@injectable()
16-
export class DebugLocationTracker implements IDebugLocationTracker {
10+
export class DebugLocationTracker implements DebugAdapterTracker {
1711
private waitingForStackTrace: boolean = false;
1812
private _debugLocation: IDebugLocation | undefined;
1913
private debugLocationUpdatedEvent: EventEmitter<void> = new EventEmitter<void>();
20-
private trigger: TriggerType = 'launch';
21-
private console: ConsoleType | undefined;
22-
private timer = new StopWatch();
14+
private sessionEndedEmitter: EventEmitter<DebugLocationTracker> = new EventEmitter<DebugLocationTracker>();
2315

24-
public setDebugSession(targetSession: DebugSession) {
16+
constructor(private _sessionId: string) {
2517
this.DebugLocation = undefined;
26-
this.waitingForStackTrace = false;
27-
this.trigger = targetSession.configuration.type as TriggerType;
28-
const debugConfiguration = targetSession.configuration as Partial<LaunchRequestArguments & AttachRequestArguments>;
29-
this.console = debugConfiguration.console;
18+
}
19+
20+
public get sessionId() {
21+
return this._sessionId;
22+
}
23+
24+
public get sessionEnded(): Event<DebugLocationTracker> {
25+
return this.sessionEndedEmitter.event;
3026
}
3127

3228
public get debugLocationUpdated(): Event<void> {
@@ -37,20 +33,8 @@ export class DebugLocationTracker implements IDebugLocationTracker {
3733
return this._debugLocation;
3834
}
3935

40-
public onWillStartSession() {
41-
this.sendTelemetry(EventName.DEBUG_SESSION_START);
42-
}
43-
4436
// tslint:disable-next-line:no-any
4537
public onDidSendMessage(message: DebugProtocol.ProtocolMessage) {
46-
if (message.type === 'response') {
47-
const response = message as DebugProtocol.Response;
48-
if (response.command === 'configurationDone') {
49-
// "configurationDone" response is sent immediately after user code starts running.
50-
this.sendTelemetry(EventName.DEBUG_SESSION_USER_CODE_RUNNING);
51-
}
52-
}
53-
5438
if (this.isStopEvent(message)) {
5539
// Some type of stop, wait to see our next stack trace to find our location
5640
this.waitingForStackTrace = true;
@@ -70,15 +54,10 @@ export class DebugLocationTracker implements IDebugLocationTracker {
7054
this.waitingForStackTrace = false;
7155
}
7256
}
73-
7457
}
7558

7659
public onWillStopSession() {
77-
this.sendTelemetry(EventName.DEBUG_SESSION_STOP);
78-
}
79-
80-
public onError?(_error: Error) {
81-
this.sendTelemetry(EventName.DEBUG_SESSION_ERROR);
60+
this.sessionEndedEmitter.fire(this);
8261
}
8362

8463
// Set our new location and fire our debug event
@@ -146,15 +125,4 @@ export class DebugLocationTracker implements IDebugLocationTracker {
146125

147126
return false;
148127
}
149-
150-
private sendTelemetry(eventName: EventName) {
151-
if (eventName === EventName.DEBUG_SESSION_START) {
152-
this.timer.reset();
153-
}
154-
const telemetryProps = {
155-
trigger: this.trigger,
156-
console: this.console
157-
};
158-
sendTelemetryEvent(eventName, this.timer.elapsedTime, telemetryProps);
159-
}
160128
}

src/client/datascience/debugLocationTrackerFactory.ts

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,52 @@
22
// Licensed under the MIT License.
33
'use strict';
44
import { inject, injectable } from 'inversify';
5-
import { DebugAdapterTracker, DebugSession, ProviderResult } from 'vscode';
5+
import { DebugAdapterTracker, DebugAdapterTrackerFactory, DebugSession, Event, EventEmitter, ProviderResult } from 'vscode';
66

77
import { IDebugService } from '../common/application/types';
88
import { IDisposableRegistry } from '../common/types';
9-
import { IDebugLocationTracker, IDebugLocationTrackerFactory } from './types';
9+
import { DebugLocationTracker } from './debugLocationTracker';
10+
import { IDebugLocationTracker } from './types';
1011

1112
// Hook up our IDebugLocationTracker to python debugging sessions
1213
@injectable()
13-
export class DebugLocationTrackerFactory implements IDebugLocationTrackerFactory {
14+
export class DebugLocationTrackerFactory implements IDebugLocationTracker, DebugAdapterTrackerFactory {
15+
16+
private activeTrackers: Map<string, DebugLocationTracker> = new Map<string, DebugLocationTracker>();
17+
private updatedEmitter: EventEmitter<void> = new EventEmitter<void>();
18+
1419
constructor(
15-
@inject(IDebugLocationTracker) private locationTracker: IDebugLocationTracker,
1620
@inject(IDebugService) debugService: IDebugService,
1721
@inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry
1822
) {
1923
disposableRegistry.push(debugService.registerDebugAdapterTrackerFactory('python', this));
2024
}
2125

2226
public createDebugAdapterTracker(session: DebugSession): ProviderResult<DebugAdapterTracker> {
23-
this.locationTracker.setDebugSession(session);
24-
return this.locationTracker;
27+
const result = new DebugLocationTracker(session.id);
28+
this.activeTrackers.set(session.id, result);
29+
result.sessionEnded(this.onSessionEnd.bind(this));
30+
result.debugLocationUpdated(this.onLocationUpdated.bind(this));
31+
this.onLocationUpdated();
32+
return result;
33+
}
34+
35+
public get updated(): Event<void> {
36+
return this.updatedEmitter.event;
37+
}
38+
39+
public getLocation(session: DebugSession) {
40+
const tracker = this.activeTrackers.get(session.id);
41+
if (tracker) {
42+
return tracker.debugLocation;
43+
}
44+
}
45+
46+
private onSessionEnd(locationTracker: DebugLocationTracker) {
47+
this.activeTrackers.delete(locationTracker.sessionId);
48+
}
49+
50+
private onLocationUpdated() {
51+
this.updatedEmitter.fire();
2552
}
2653
}

src/client/datascience/editor-integration/codelensprovider.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export class DataScienceCodeLensProvider implements IDataScienceCodeLensProvider
3232
disposableRegistry.push(this);
3333
disposableRegistry.push(this.debugService.onDidChangeActiveDebugSession(this.onChangeDebugSession.bind(this)));
3434
disposableRegistry.push(this.documentManager.onDidCloseTextDocument(this.onDidCloseTextDocument.bind(this)));
35-
disposableRegistry.push(this.debugLocationTracker.debugLocationUpdated(this.onDebugLocationUpdated.bind(this)));
35+
disposableRegistry.push(this.debugLocationTracker.updated(this.onDebugLocationUpdated.bind(this)));
3636
}
3737

3838
public dispose() {
@@ -104,7 +104,7 @@ export class DataScienceCodeLensProvider implements IDataScienceCodeLensProvider
104104
const debugCellList = CodeLensCommands.DebuggerCommands;
105105

106106
if (this.debugService.activeDebugSession) {
107-
const debugLocation = this.debugLocationTracker.debugLocation;
107+
const debugLocation = this.debugLocationTracker.getLocation(this.debugService.activeDebugSession);
108108

109109
if (debugLocation && this.fileSystem.arePathsSame(debugLocation.fileName, document.uri.fsPath)) {
110110
// We are in the given debug file, so only return the code lens that contains the given line

src/client/datascience/serviceRegistry.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import { Telemetry } from './constants';
1111
import { DataViewer } from './data-viewing/dataViewer';
1212
import { DataViewerProvider } from './data-viewing/dataViewerProvider';
1313
import { DataScience } from './datascience';
14-
import { DebugLocationTracker } from './debugLocationTracker';
1514
import { DebugLocationTrackerFactory } from './debugLocationTrackerFactory';
1615
import { CellHashProvider } from './editor-integration/cellhashprovider';
1716
import { CodeLensFactory } from './editor-integration/codeLensFactory';
@@ -58,7 +57,6 @@ import {
5857
IDataViewer,
5958
IDataViewerProvider,
6059
IDebugLocationTracker,
61-
IDebugLocationTrackerFactory,
6260
IGatherExecution,
6361
IInteractiveWindow,
6462
IInteractiveWindowListener,
@@ -140,6 +138,5 @@ export function registerTypes(serviceManager: IServiceManager) {
140138
serviceManager.addSingleton<IDataScienceCommandListener>(IDataScienceCommandListener, wrapType(NativeEditorCommandListener));
141139
serviceManager.addBinding(IGatherExecution, INotebookExecutionLogger);
142140
serviceManager.addBinding(ICodeLensFactory, IInteractiveWindowListener);
143-
serviceManager.addSingleton<IDebugLocationTrackerFactory>(IDebugLocationTrackerFactory, wrapType(DebugLocationTrackerFactory));
144-
serviceManager.addSingleton<IDebugLocationTracker>(IDebugLocationTracker, wrapType(DebugLocationTracker));
141+
serviceManager.addSingleton<IDebugLocationTracker>(IDebugLocationTracker, wrapType(DebugLocationTrackerFactory));
145142
}

0 commit comments

Comments
 (0)