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