Skip to content

Commit

Permalink
feat: add profiling substate to sessions
Browse files Browse the repository at this point in the history
  • Loading branch information
connor4312 committed Apr 24, 2020
1 parent 66a0b0e commit d3f7da1
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 12 deletions.
4 changes: 2 additions & 2 deletions src/common/datastructure/observableMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { EventEmitter } from '../events';
*/
export class ObservableMap<K, V> {
private readonly changeEmitter = new EventEmitter<void>();
private readonly addEmitter = new EventEmitter<V>();
private readonly addEmitter = new EventEmitter<[K, V]>();
private readonly removeEmitter = new EventEmitter<[K, V]>();
private readonly targetMap = new Map<K, V>();

Expand Down Expand Up @@ -41,7 +41,7 @@ export class ObservableMap<K, V> {
*/
public add(key: K, target: V) {
this.targetMap.set(key, target);
this.addEmitter.fire(target);
this.addEmitter.fire([key, target]);
this.changeEmitter.fire();
}

Expand Down
13 changes: 13 additions & 0 deletions src/ioc-extras.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import { interfaces } from 'inversify';
import { IDisposable } from './common/disposable';
import { promises as fsPromises } from 'fs';
import { ObservableMap } from './common/datastructure/observableMap';

/**
* The IOC container itself.
Expand Down Expand Up @@ -67,6 +68,18 @@ export const BrowserFinder = Symbol('IBrowserFinder');
*/
export type ExtensionLocation = 'local' | 'remote';

export type SessionSubStates = ObservableMap<string, string>;

/**
* An ObservableMap<string, string> containing custom substates for sessions.
* This is used to add the "profiling" state to session names. Eventually, this
* handling may move to DAP.
*
* @see https://github.com/microsoft/vscode/issues/94812
* @see https://github.com/microsoft/debug-adapter-protocol/issues/108
*/
export const SessionSubStates = Symbol('SessionSubStates');

const toDispose = new WeakMap<interfaces.Container, IDisposable[]>();

/**
Expand Down
3 changes: 3 additions & 0 deletions src/ioc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
BrowserFinder,
ExtensionLocation,
IContainer,
SessionSubStates,
} from './ioc-extras';
import { BrowserAttacher } from './targets/browser/browserAttacher';
import { ChromeLauncher } from './targets/browser/chromeLauncher';
Expand Down Expand Up @@ -72,6 +73,7 @@ import { ResourceProviderState } from './adapter/resourceProvider/resourceProvid
import { StatefulResourceProvider } from './adapter/resourceProvider/statefulResourceProvider';
import { IResourceProvider } from './adapter/resourceProvider';
import { BreakpointManager } from './adapter/breakpoints';
import { ObservableMap } from './common/datastructure/observableMap';

/**
* Contains IOC container factories for the extension. We use Inverisfy, which
Expand Down Expand Up @@ -231,6 +233,7 @@ export const createGlobalContainer = (options: {

container.bind(DelegateLauncherFactory).toSelf().inSingletonScope();

container.bind(SessionSubStates).toConstantValue(new ObservableMap());
container.bind(IDefaultBrowserProvider).to(DefaultBrowserProvider).inSingletonScope();
container.bind(StoragePath).toConstantValue(options.storagePath);
container.bind(IsVSCode).toConstantValue(options.isVsCode);
Expand Down
33 changes: 28 additions & 5 deletions src/sessionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { createTopLevelSessionContainer } from './ioc';
import { IPseudoAttachConfiguration } from './configuration';
import { IDapTransport } from './dap/transport';
import { DebugConfiguration } from 'vscode';
import { SessionSubStates } from './ioc-extras';
import { DisposableList } from './common/disposable';

/**
* Interface for abstracting the details of a particular (e.g. vscode vs VS) debug session
Expand All @@ -40,23 +42,39 @@ export type SessionLauncher<T extends IDebugSessionLike> = (
*/
export class Session<TSessionImpl extends IDebugSessionLike> implements IDisposable {
public readonly connection: DapConnection;
private subscription?: IDisposable;
private readonly subscriptions = new DisposableList();

constructor(
public readonly debugSession: TSessionImpl,
transport: IDapTransport,
public readonly logger: ILogger,
public readonly sessionStates: SessionSubStates,
) {
transport.setLogger(logger);
this.connection = new DapConnection(transport, this.logger);
}

listenToTarget(target: ITarget) {
this.subscription = target.onNameChanged(() => (this.debugSession.name = target.name()));
this.subscriptions.push(
target.onNameChanged(() => this.setName(target)),
this.sessionStates.onAdd(
([sessionId]) => sessionId === this.debugSession.id && this.setName(target),
),
this.sessionStates.onRemove(
([sessionId]) => sessionId === this.debugSession.id && this.setName(target),
),
);

this.setName(target);
}

dispose() {
this.subscription?.dispose();
this.subscriptions.dispose();
}

private setName(target: ITarget) {
const substate = this.sessionStates.get(this.debugSession.id);
this.debugSession.name = substate ? `${target.name()} (${substate})` : target.name();
}
}

Expand All @@ -68,7 +86,7 @@ export class RootSession<TSessionImpl extends IDebugSessionLike> extends Session
transport: IDapTransport,
private readonly services: Container,
) {
super(debugSession, transport, services.get(ILogger));
super(debugSession, transport, services.get(ILogger), services.get(SessionSubStates));
this.connection.attachTelemetry(services.get(ITelemetryReporter));
}

Expand Down Expand Up @@ -128,7 +146,12 @@ export class SessionManager<TSessionImpl extends IDebugSessionLike>
}

const { target, parent } = pending;
session = new Session<TSessionImpl>(debugSession, transport, parent.logger);
session = new Session<TSessionImpl>(
debugSession,
transport,
parent.logger,
parent.sessionStates,
);

this._pendingTarget.delete(pendingTargetId);
session.debugSession.name = target.name();
Expand Down
2 changes: 1 addition & 1 deletion src/targets/delegate/delegateLauncher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export class DelegateLauncher implements ILauncher {
public readonly onTargetListChanged = this.targets.onChanged;

constructor(private readonly parentList: ObservableMap<number, IDelegateRef>) {
parentList.onAdd(ref => {
parentList.onAdd(([, ref]) => {
// we don't need to recurse upwards for the parents, since we know we
// will have previously seen and `add()`ed its direct parent.
if (ref.parent && this.targets.get(ref.parent.id)) {
Expand Down
40 changes: 37 additions & 3 deletions src/test/profiling/profiling.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,11 @@ describe('profiling', () => {
acceptQuickPick.fire();
};

const terminate = async (session: vscode.DebugSession) => {
session.customRequest('disconnect', {});
await new Promise(resolve => vscode.debug.onDidTerminateDebugSession(resolve));
};

it('allows picking breakpoints', async () => {
vscode.debug.addBreakpoints([
new vscode.SourceBreakpoint(
Expand Down Expand Up @@ -286,11 +291,40 @@ describe('profiling', () => {
await new Promise(resolve => vscode.debug.onDidTerminateDebugSession(resolve));
});

it('sets substate correctly', async () => {
const disposable = new DisposableList();
disposable.push(vscode.commands.registerCommand('js-debug.test.callback', () => undefined));

vscode.debug.startDebugging(undefined, {
type: DebugType.Node,
request: 'launch',
name: 'test',
program: script,
});

const session = await new Promise<vscode.DebugSession>(resolve =>
vscode.debug.onDidStartDebugSession(s =>
'__pendingTargetId' in s.configuration ? resolve(s) : undefined,
),
);

await runCommand(vscode.commands, Commands.StartProfile, {
sessionId: session.id,
type: 'cpu',
termination: { type: 'manual' },
});

await eventuallyOk(() => expect(session.name).to.contain('Profiling'), 2000);
await runCommand(vscode.commands, Commands.StopProfile, session.id);
await eventuallyOk(() => expect(session.name).to.not.contain('Profiling'), 2000);
await terminate(session);
disposable.dispose();
});

it('works with pure command API', async () => {
const callback = stub();
const disposable = new DisposableList();
disposable.push(vscode.commands.registerCommand('js-debug.test.callback', callback));
after(() => disposable.dispose());

vscode.debug.startDebugging(undefined, {
type: DebugType.Node,
Expand Down Expand Up @@ -324,8 +358,8 @@ describe('profiling', () => {
expect(() => JSON.parse(args.contents)).to.not.throw;
expect(args.basename).to.match(/\.cpuprofile$/);

session.customRequest('disconnect', {});
await new Promise(resolve => vscode.debug.onDidTerminateDebugSession(resolve));
await terminate(session);
disposable.dispose();
});
});
});
5 changes: 4 additions & 1 deletion src/ui/profiling/uiProfileManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { AnyLaunchConfiguration } from '../../configuration';
import { UiProfileSession } from './uiProfileSession';
import { Commands } from '../../common/contributionUtils';
import { basename } from 'path';
import { FS, FsPromises } from '../../ioc-extras';
import { FS, FsPromises, SessionSubStates } from '../../ioc-extras';
import { IDisposable } from '../../common/disposable';
import { ITerminationConditionFactory } from './terminationCondition';

Expand Down Expand Up @@ -77,6 +77,7 @@ export class UiProfileManager implements IDisposable {
constructor(
@inject(DebugSessionTracker) private readonly tracker: DebugSessionTracker,
@inject(FS) private readonly fs: FsPromises,
@inject(SessionSubStates) private readonly sessionStates: SessionSubStates,
@multiInject(ITerminationConditionFactory)
private readonly terminationConditions: ReadonlyArray<ITerminationConditionFactory>,
) {}
Expand Down Expand Up @@ -121,6 +122,7 @@ export class UiProfileManager implements IDisposable {
}

this.activeSessions.add(uiSession);
this.sessionStates.add(session.id, localize('profile.sessionState', 'Profiling'));
uiSession.onStatusChange(() => this.updateStatusBar());
uiSession.onStop(file => {
if (file) {
Expand Down Expand Up @@ -153,6 +155,7 @@ export class UiProfileManager implements IDisposable {
return;
}

this.sessionStates.remove(session.id);
await uiSession.stop();
}

Expand Down

0 comments on commit d3f7da1

Please sign in to comment.