Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/services/extensions/common/extensionHostManager.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { IntervalTimer } from '../../../../base/common/async.js';
7
import { VSBuffer } from '../../../../base/common/buffer.js';
8
import * as errors from '../../../../base/common/errors.js';
9
import { Emitter, Event } from '../../../../base/common/event.js';
10
import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js';
11
import { StopWatch } from '../../../../base/common/stopwatch.js';
12
import { URI } from '../../../../base/common/uri.js';
13
import { IMessagePassingProtocol } from '../../../../base/parts/ipc/common/ipc.js';
14
import * as nls from '../../../../nls.js';
15
import { Categories } from '../../../../platform/action/common/actionCommonCategories.js';
16
import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js';
17
import { ExtensionIdentifier, IExtensionDescription } from '../../../../platform/extensions/common/extensions.js';
18
import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
19
import { ILogService } from '../../../../platform/log/common/log.js';
20
import { RemoteAuthorityResolverErrorCode, getRemoteAuthorityPrefix } from '../../../../platform/remote/common/remoteAuthorityResolver.js';
21
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
22
import { IEditorService } from '../../editor/common/editorService.js';
23
import { IWorkbenchEnvironmentService } from '../../environment/common/environmentService.js';
24
import { ExtHostCustomersRegistry, IInternalExtHostContext } from './extHostCustomers.js';
25
import { ExtensionHostKind, extensionHostKindToString } from './extensionHostKind.js';
26
import { IExtensionHostManager } from './extensionHostManagers.js';
27
import { IExtensionDescriptionDelta } from './extensionHostProtocol.js';
28
import { IExtensionHostProxy, IResolveAuthorityResult } from './extensionHostProxy.js';
29
import { ExtensionRunningLocation } from './extensionRunningLocation.js';
30
import { ActivationKind, ExtensionActivationReason, ExtensionHostStartup, IExtensionHost, IExtensionInspectInfo, IInternalExtensionService } from './extensions.js';
31
import { Proxied, ProxyIdentifier } from './proxyIdentifier.js';
32
import { IRPCProtocolLogger, RPCProtocol, RequestInitiator, ResponsiveState } from './rpcProtocol.js';
33
34
// Enable to see detailed message communication between window and extension host
35
const LOG_EXTENSION_HOST_COMMUNICATION = false;
36
const LOG_USE_COLORS = true;
37
38
type ExtensionHostStartupClassification = {
39
owner: 'alexdima';
40
comment: 'The startup state of the extension host';
41
time: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The time reported by Date.now().' };
42
action: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The action: starting, success or error.' };
43
kind: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The extension host kind: LocalProcess, LocalWebWorker or Remote.' };
44
errorName?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The error name.' };
45
errorMessage?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The error message.' };
46
errorStack?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The error stack.' };
47
};
48
49
type ExtensionHostStartupEvent = {
50
time: number;
51
action: 'starting' | 'success' | 'error';
52
kind: string;
53
errorName?: string;
54
errorMessage?: string;
55
errorStack?: string;
56
};
57
58
export class ExtensionHostManager extends Disposable implements IExtensionHostManager {
59
60
public readonly onDidExit: Event<[number, string | null]>;
61
62
private readonly _onDidChangeResponsiveState: Emitter<ResponsiveState> = this._register(new Emitter<ResponsiveState>());
63
public readonly onDidChangeResponsiveState: Event<ResponsiveState> = this._onDidChangeResponsiveState.event;
64
65
/**
66
* A map of already requested activation events to speed things up if the same activation event is triggered multiple times.
67
*/
68
private readonly _cachedActivationEvents: Map<string, Promise<void>>;
69
private readonly _resolvedActivationEvents: Set<string>;
70
private _rpcProtocol: RPCProtocol | null;
71
private readonly _customers: IDisposable[];
72
private readonly _extensionHost: IExtensionHost;
73
private _proxy: Promise<IExtensionHostProxy | null> | null;
74
private _hasStarted = false;
75
76
public get pid(): number | null {
77
return this._extensionHost.pid;
78
}
79
80
public get kind(): ExtensionHostKind {
81
return this._extensionHost.runningLocation.kind;
82
}
83
84
public get startup(): ExtensionHostStartup {
85
return this._extensionHost.startup;
86
}
87
88
public get friendyName(): string {
89
return friendlyExtHostName(this.kind, this.pid);
90
}
91
92
constructor(
93
extensionHost: IExtensionHost,
94
initialActivationEvents: string[],
95
private readonly _internalExtensionService: IInternalExtensionService,
96
@IInstantiationService private readonly _instantiationService: IInstantiationService,
97
@IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService,
98
@ITelemetryService private readonly _telemetryService: ITelemetryService,
99
@ILogService private readonly _logService: ILogService,
100
) {
101
super();
102
this._cachedActivationEvents = new Map<string, Promise<void>>();
103
this._resolvedActivationEvents = new Set<string>();
104
this._rpcProtocol = null;
105
this._customers = [];
106
107
this._extensionHost = extensionHost;
108
this.onDidExit = this._extensionHost.onExit;
109
110
const startingTelemetryEvent: ExtensionHostStartupEvent = {
111
time: Date.now(),
112
action: 'starting',
113
kind: extensionHostKindToString(this.kind)
114
};
115
this._telemetryService.publicLog2<ExtensionHostStartupEvent, ExtensionHostStartupClassification>('extensionHostStartup', startingTelemetryEvent);
116
117
this._proxy = this._extensionHost.start().then(
118
(protocol) => {
119
this._hasStarted = true;
120
121
// Track healthy extension host startup
122
const successTelemetryEvent: ExtensionHostStartupEvent = {
123
time: Date.now(),
124
action: 'success',
125
kind: extensionHostKindToString(this.kind)
126
};
127
this._telemetryService.publicLog2<ExtensionHostStartupEvent, ExtensionHostStartupClassification>('extensionHostStartup', successTelemetryEvent);
128
129
return this._createExtensionHostCustomers(this.kind, protocol);
130
},
131
(err) => {
132
this._logService.error(`Error received from starting extension host (kind: ${extensionHostKindToString(this.kind)})`);
133
this._logService.error(err);
134
135
// Track errors during extension host startup
136
const failureTelemetryEvent: ExtensionHostStartupEvent = {
137
time: Date.now(),
138
action: 'error',
139
kind: extensionHostKindToString(this.kind)
140
};
141
142
if (err && err.name) {
143
failureTelemetryEvent.errorName = err.name;
144
}
145
if (err && err.message) {
146
failureTelemetryEvent.errorMessage = err.message;
147
}
148
if (err && err.stack) {
149
failureTelemetryEvent.errorStack = err.stack;
150
}
151
this._telemetryService.publicLog2<ExtensionHostStartupEvent, ExtensionHostStartupClassification>('extensionHostStartup', failureTelemetryEvent);
152
153
return null;
154
}
155
);
156
this._proxy.then(() => {
157
initialActivationEvents.forEach((activationEvent) => this.activateByEvent(activationEvent, ActivationKind.Normal));
158
this._register(registerLatencyTestProvider({
159
measure: () => this.measure()
160
}));
161
});
162
}
163
164
public async disconnect(): Promise<void> {
165
await this._extensionHost?.disconnect?.();
166
}
167
168
public override dispose(): void {
169
this._extensionHost?.dispose();
170
this._rpcProtocol?.dispose();
171
172
for (let i = 0, len = this._customers.length; i < len; i++) {
173
const customer = this._customers[i];
174
try {
175
customer.dispose();
176
} catch (err) {
177
errors.onUnexpectedError(err);
178
}
179
}
180
this._proxy = null;
181
182
super.dispose();
183
}
184
185
private async measure(): Promise<ExtHostLatencyResult | null> {
186
const proxy = await this._proxy;
187
if (!proxy) {
188
return null;
189
}
190
const latency = await this._measureLatency(proxy);
191
const down = await this._measureDown(proxy);
192
const up = await this._measureUp(proxy);
193
return {
194
remoteAuthority: this._extensionHost.remoteAuthority,
195
latency,
196
down,
197
up
198
};
199
}
200
201
public async ready(): Promise<void> {
202
await this._proxy;
203
}
204
205
private async _measureLatency(proxy: IExtensionHostProxy): Promise<number> {
206
const COUNT = 10;
207
208
let sum = 0;
209
for (let i = 0; i < COUNT; i++) {
210
const sw = StopWatch.create();
211
await proxy.test_latency(i);
212
sw.stop();
213
sum += sw.elapsed();
214
}
215
return (sum / COUNT);
216
}
217
218
private static _convert(byteCount: number, elapsedMillis: number): number {
219
return (byteCount * 1000 * 8) / elapsedMillis;
220
}
221
222
private async _measureUp(proxy: IExtensionHostProxy): Promise<number> {
223
const SIZE = 10 * 1024 * 1024; // 10MB
224
225
const buff = VSBuffer.alloc(SIZE);
226
const value = Math.ceil(Math.random() * 256);
227
for (let i = 0; i < buff.byteLength; i++) {
228
buff.writeUInt8(i, value);
229
}
230
const sw = StopWatch.create();
231
await proxy.test_up(buff);
232
sw.stop();
233
return ExtensionHostManager._convert(SIZE, sw.elapsed());
234
}
235
236
private async _measureDown(proxy: IExtensionHostProxy): Promise<number> {
237
const SIZE = 10 * 1024 * 1024; // 10MB
238
239
const sw = StopWatch.create();
240
await proxy.test_down(SIZE);
241
sw.stop();
242
return ExtensionHostManager._convert(SIZE, sw.elapsed());
243
}
244
245
private _createExtensionHostCustomers(kind: ExtensionHostKind, protocol: IMessagePassingProtocol): IExtensionHostProxy {
246
247
let logger: IRPCProtocolLogger | null = null;
248
if (LOG_EXTENSION_HOST_COMMUNICATION || this._environmentService.logExtensionHostCommunication) {
249
logger = new RPCLogger(kind);
250
} else if (TelemetryRPCLogger.isEnabled()) {
251
logger = new TelemetryRPCLogger(this._telemetryService);
252
}
253
254
this._rpcProtocol = new RPCProtocol(protocol, logger);
255
this._register(this._rpcProtocol.onDidChangeResponsiveState((responsiveState: ResponsiveState) => this._onDidChangeResponsiveState.fire(responsiveState)));
256
let extensionHostProxy: IExtensionHostProxy | null = null as IExtensionHostProxy | null;
257
let mainProxyIdentifiers: ProxyIdentifier<any>[] = [];
258
const extHostContext: IInternalExtHostContext = {
259
remoteAuthority: this._extensionHost.remoteAuthority,
260
extensionHostKind: this.kind,
261
getProxy: <T>(identifier: ProxyIdentifier<T>): Proxied<T> => this._rpcProtocol!.getProxy(identifier),
262
set: <T, R extends T>(identifier: ProxyIdentifier<T>, instance: R): R => this._rpcProtocol!.set(identifier, instance),
263
dispose: (): void => this._rpcProtocol!.dispose(),
264
assertRegistered: (identifiers: ProxyIdentifier<any>[]): void => this._rpcProtocol!.assertRegistered(identifiers),
265
drain: (): Promise<void> => this._rpcProtocol!.drain(),
266
267
//#region internal
268
internalExtensionService: this._internalExtensionService,
269
_setExtensionHostProxy: (value: IExtensionHostProxy): void => {
270
extensionHostProxy = value;
271
},
272
_setAllMainProxyIdentifiers: (value: ProxyIdentifier<any>[]): void => {
273
mainProxyIdentifiers = value;
274
},
275
//#endregion
276
};
277
278
// Named customers
279
const namedCustomers = ExtHostCustomersRegistry.getNamedCustomers();
280
for (let i = 0, len = namedCustomers.length; i < len; i++) {
281
const [id, ctor] = namedCustomers[i];
282
try {
283
const instance = this._instantiationService.createInstance(ctor, extHostContext);
284
this._customers.push(instance);
285
this._rpcProtocol.set(id, instance);
286
} catch (err) {
287
this._logService.error(`Cannot instantiate named customer: '${id.sid}'`);
288
this._logService.error(err);
289
errors.onUnexpectedError(err);
290
}
291
}
292
293
// Customers
294
const customers = ExtHostCustomersRegistry.getCustomers();
295
for (const ctor of customers) {
296
try {
297
const instance = this._instantiationService.createInstance(ctor, extHostContext);
298
this._customers.push(instance);
299
} catch (err) {
300
this._logService.error(err);
301
errors.onUnexpectedError(err);
302
}
303
}
304
305
if (!extensionHostProxy) {
306
throw new Error(`Missing IExtensionHostProxy!`);
307
}
308
309
// Check that no named customers are missing
310
this._rpcProtocol.assertRegistered(mainProxyIdentifiers);
311
312
return extensionHostProxy;
313
}
314
315
public async activate(extension: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<boolean> {
316
const proxy = await this._proxy;
317
if (!proxy) {
318
return false;
319
}
320
return proxy.activate(extension, reason);
321
}
322
323
public activateByEvent(activationEvent: string, activationKind: ActivationKind): Promise<void> {
324
if (activationKind === ActivationKind.Immediate && !this._hasStarted) {
325
return Promise.resolve();
326
}
327
328
if (!this._cachedActivationEvents.has(activationEvent)) {
329
this._cachedActivationEvents.set(activationEvent, this._activateByEvent(activationEvent, activationKind));
330
}
331
return this._cachedActivationEvents.get(activationEvent)!;
332
}
333
334
public activationEventIsDone(activationEvent: string): boolean {
335
return this._resolvedActivationEvents.has(activationEvent);
336
}
337
338
private async _activateByEvent(activationEvent: string, activationKind: ActivationKind): Promise<void> {
339
if (!this._proxy) {
340
return;
341
}
342
const proxy = await this._proxy;
343
if (!proxy) {
344
// this case is already covered above and logged.
345
// i.e. the extension host could not be started
346
return;
347
}
348
349
if (!this._extensionHost.extensions!.containsActivationEvent(activationEvent)) {
350
this._resolvedActivationEvents.add(activationEvent);
351
return;
352
}
353
354
await proxy.activateByEvent(activationEvent, activationKind);
355
this._resolvedActivationEvents.add(activationEvent);
356
}
357
358
public async getInspectPort(tryEnableInspector: boolean): Promise<IExtensionInspectInfo | undefined> {
359
if (this._extensionHost) {
360
if (tryEnableInspector) {
361
await this._extensionHost.enableInspectPort();
362
}
363
const port = this._extensionHost.getInspectPort();
364
if (port) {
365
return port;
366
}
367
}
368
369
return undefined;
370
}
371
372
public async resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise<IResolveAuthorityResult> {
373
const sw = StopWatch.create(false);
374
const prefix = () => `[${extensionHostKindToString(this._extensionHost.runningLocation.kind)}${this._extensionHost.runningLocation.affinity}][resolveAuthority(${getRemoteAuthorityPrefix(remoteAuthority)},${resolveAttempt})][${sw.elapsed()}ms] `;
375
const logInfo = (msg: string) => this._logService.info(`${prefix()}${msg}`);
376
const logError = (msg: string, err: any = undefined) => this._logService.error(`${prefix()}${msg}`, err);
377
378
logInfo(`obtaining proxy...`);
379
const proxy = await this._proxy;
380
if (!proxy) {
381
logError(`no proxy`);
382
return {
383
type: 'error',
384
error: {
385
message: `Cannot resolve authority`,
386
code: RemoteAuthorityResolverErrorCode.Unknown,
387
detail: undefined
388
}
389
};
390
}
391
logInfo(`invoking...`);
392
const intervalLogger = new IntervalTimer();
393
try {
394
intervalLogger.cancelAndSet(() => logInfo('waiting...'), 1000);
395
const resolverResult = await proxy.resolveAuthority(remoteAuthority, resolveAttempt);
396
intervalLogger.dispose();
397
if (resolverResult.type === 'ok') {
398
logInfo(`returned ${resolverResult.value.authority.connectTo}`);
399
} else {
400
logError(`returned an error`, resolverResult.error);
401
}
402
return resolverResult;
403
} catch (err) {
404
intervalLogger.dispose();
405
logError(`returned an error`, err);
406
return {
407
type: 'error',
408
error: {
409
message: err.message,
410
code: RemoteAuthorityResolverErrorCode.Unknown,
411
detail: err
412
}
413
};
414
}
415
}
416
417
public async getCanonicalURI(remoteAuthority: string, uri: URI): Promise<URI | null> {
418
const proxy = await this._proxy;
419
if (!proxy) {
420
throw new Error(`Cannot resolve canonical URI`);
421
}
422
return proxy.getCanonicalURI(remoteAuthority, uri);
423
}
424
425
public async start(extensionRegistryVersionId: number, allExtensions: IExtensionDescription[], myExtensions: ExtensionIdentifier[]): Promise<void> {
426
const proxy = await this._proxy;
427
if (!proxy) {
428
return;
429
}
430
const deltaExtensions = this._extensionHost.extensions!.set(extensionRegistryVersionId, allExtensions, myExtensions);
431
return proxy.startExtensionHost(deltaExtensions);
432
}
433
434
public async extensionTestsExecute(): Promise<number> {
435
const proxy = await this._proxy;
436
if (!proxy) {
437
throw new Error('Could not obtain Extension Host Proxy');
438
}
439
return proxy.extensionTestsExecute();
440
}
441
442
public representsRunningLocation(runningLocation: ExtensionRunningLocation): boolean {
443
return this._extensionHost.runningLocation.equals(runningLocation);
444
}
445
446
public async deltaExtensions(incomingExtensionsDelta: IExtensionDescriptionDelta): Promise<void> {
447
const proxy = await this._proxy;
448
if (!proxy) {
449
return;
450
}
451
const outgoingExtensionsDelta = this._extensionHost.extensions!.delta(incomingExtensionsDelta);
452
if (!outgoingExtensionsDelta) {
453
// The extension host already has this version of the extensions.
454
return;
455
}
456
return proxy.deltaExtensions(outgoingExtensionsDelta);
457
}
458
459
public containsExtension(extensionId: ExtensionIdentifier): boolean {
460
return this._extensionHost.extensions?.containsExtension(extensionId) ?? false;
461
}
462
463
public async setRemoteEnvironment(env: { [key: string]: string | null }): Promise<void> {
464
const proxy = await this._proxy;
465
if (!proxy) {
466
return;
467
}
468
469
return proxy.setRemoteEnvironment(env);
470
}
471
}
472
473
export function friendlyExtHostName(kind: ExtensionHostKind, pid: number | null) {
474
if (pid) {
475
return `${extensionHostKindToString(kind)} pid: ${pid}`;
476
}
477
return `${extensionHostKindToString(kind)}`;
478
}
479
480
const colorTables = [
481
['#2977B1', '#FC802D', '#34A13A', '#D3282F', '#9366BA'],
482
['#8B564C', '#E177C0', '#7F7F7F', '#BBBE3D', '#2EBECD']
483
];
484
485
function prettyWithoutArrays(data: any): any {
486
if (Array.isArray(data)) {
487
return data;
488
}
489
if (data && typeof data === 'object' && typeof data.toString === 'function') {
490
const result = data.toString();
491
if (result !== '[object Object]') {
492
return result;
493
}
494
}
495
return data;
496
}
497
498
function pretty(data: any): any {
499
if (Array.isArray(data)) {
500
return data.map(prettyWithoutArrays);
501
}
502
return prettyWithoutArrays(data);
503
}
504
505
class RPCLogger implements IRPCProtocolLogger {
506
507
private _totalIncoming = 0;
508
private _totalOutgoing = 0;
509
510
constructor(
511
private readonly _kind: ExtensionHostKind
512
) { }
513
514
private _log(direction: string, totalLength: number, msgLength: number, req: number, initiator: RequestInitiator, str: string, data: any): void {
515
data = pretty(data);
516
517
const colorTable = colorTables[initiator];
518
const color = LOG_USE_COLORS ? colorTable[req % colorTable.length] : '#000000';
519
let args = [`%c[${extensionHostKindToString(this._kind)}][${direction}]%c[${String(totalLength).padStart(7)}]%c[len: ${String(msgLength).padStart(5)}]%c${String(req).padStart(5)} - ${str}`, 'color: darkgreen', 'color: grey', 'color: grey', `color: ${color}`];
520
if (/\($/.test(str)) {
521
args = args.concat(data);
522
args.push(')');
523
} else {
524
args.push(data);
525
}
526
console.log.apply(console, args as [string, ...string[]]);
527
}
528
529
logIncoming(msgLength: number, req: number, initiator: RequestInitiator, str: string, data?: any): void {
530
this._totalIncoming += msgLength;
531
this._log('Ext \u2192 Win', this._totalIncoming, msgLength, req, initiator, str, data);
532
}
533
534
logOutgoing(msgLength: number, req: number, initiator: RequestInitiator, str: string, data?: any): void {
535
this._totalOutgoing += msgLength;
536
this._log('Win \u2192 Ext', this._totalOutgoing, msgLength, req, initiator, str, data);
537
}
538
}
539
540
interface RPCTelemetryData {
541
type: string;
542
length: number;
543
}
544
545
type RPCTelemetryDataClassification = {
546
owner: 'jrieken';
547
comment: 'Insights about RPC message sizes';
548
type: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The type of the RPC message' };
549
length: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The byte-length of the RPC message' };
550
};
551
552
class TelemetryRPCLogger implements IRPCProtocolLogger {
553
554
static isEnabled(): boolean {
555
return Math.random() < 0.0001; // 0.01% of users
556
}
557
558
private readonly _pendingRequests = new Map<number, string>();
559
560
constructor(@ITelemetryService private readonly _telemetryService: ITelemetryService) { }
561
562
logIncoming(msgLength: number, req: number, initiator: RequestInitiator, str: string): void {
563
564
if (initiator === RequestInitiator.LocalSide && /^receiveReply(Err)?:/.test(str)) {
565
// log the size of reply messages
566
const requestStr = this._pendingRequests.get(req) ?? 'unknown_reply';
567
this._pendingRequests.delete(req);
568
this._telemetryService.publicLog2<RPCTelemetryData, RPCTelemetryDataClassification>('extensionhost.incoming', {
569
type: `${str} ${requestStr}`,
570
length: msgLength
571
});
572
}
573
574
if (initiator === RequestInitiator.OtherSide && /^receiveRequest /.test(str)) {
575
// incoming request
576
this._telemetryService.publicLog2<RPCTelemetryData, RPCTelemetryDataClassification>('extensionhost.incoming', {
577
type: `${str}`,
578
length: msgLength
579
});
580
}
581
}
582
583
logOutgoing(msgLength: number, req: number, initiator: RequestInitiator, str: string): void {
584
585
if (initiator === RequestInitiator.LocalSide && str.startsWith('request: ')) {
586
this._pendingRequests.set(req, str);
587
this._telemetryService.publicLog2<RPCTelemetryData, RPCTelemetryDataClassification>('extensionhost.outgoing', {
588
type: str,
589
length: msgLength
590
});
591
}
592
}
593
}
594
595
interface ExtHostLatencyResult {
596
remoteAuthority: string | null;
597
up: number;
598
down: number;
599
latency: number;
600
}
601
602
interface ExtHostLatencyProvider {
603
measure(): Promise<ExtHostLatencyResult | null>;
604
}
605
606
const providers: ExtHostLatencyProvider[] = [];
607
function registerLatencyTestProvider(provider: ExtHostLatencyProvider): IDisposable {
608
providers.push(provider);
609
return {
610
dispose: () => {
611
for (let i = 0; i < providers.length; i++) {
612
if (providers[i] === provider) {
613
providers.splice(i, 1);
614
return;
615
}
616
}
617
}
618
};
619
}
620
621
function getLatencyTestProviders(): ExtHostLatencyProvider[] {
622
return providers.slice(0);
623
}
624
625
registerAction2(class MeasureExtHostLatencyAction extends Action2 {
626
627
constructor() {
628
super({
629
id: 'editor.action.measureExtHostLatency',
630
title: nls.localize2('measureExtHostLatency', "Measure Extension Host Latency"),
631
category: Categories.Developer,
632
f1: true
633
});
634
}
635
636
async run(accessor: ServicesAccessor) {
637
638
const editorService = accessor.get(IEditorService);
639
640
const measurements = await Promise.all(getLatencyTestProviders().map(provider => provider.measure()));
641
editorService.openEditor({ resource: undefined, contents: measurements.map(MeasureExtHostLatencyAction._print).join('\n\n'), options: { pinned: true } });
642
}
643
644
private static _print(m: ExtHostLatencyResult | null): string {
645
if (!m) {
646
return '';
647
}
648
return `${m.remoteAuthority ? `Authority: ${m.remoteAuthority}\n` : ``}Roundtrip latency: ${m.latency.toFixed(3)}ms\nUp: ${MeasureExtHostLatencyAction._printSpeed(m.up)}\nDown: ${MeasureExtHostLatencyAction._printSpeed(m.down)}\n`;
649
}
650
651
private static _printSpeed(n: number): string {
652
if (n <= 1024) {
653
return `${n} bps`;
654
}
655
if (n < 1024 * 1024) {
656
return `${(n / 1024).toFixed(1)} kbps`;
657
}
658
return `${(n / 1024 / 1024).toFixed(1)} Mbps`;
659
}
660
});
661
662