Skip to content

Commit 70daab7

Browse files
authored
remote - merge two latency measurements into one (microsoft#184566)
* remote - remove original latency measurement * remote - log high latency events to logger and telemetry
1 parent e686621 commit 70daab7

File tree

5 files changed

+44
-234
lines changed

5 files changed

+44
-234
lines changed

src/vs/base/parts/ipc/common/ipc.net.ts

Lines changed: 1 addition & 190 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { IntervalTimer } from 'vs/base/common/async';
76
import { VSBuffer } from 'vs/base/common/buffer';
87
import { Emitter, Event } from 'vs/base/common/event';
98
import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
@@ -264,9 +263,7 @@ const enum ProtocolMessageType {
264263
ReplayRequest = 6,
265264
Pause = 7,
266265
Resume = 8,
267-
KeepAlive = 9,
268-
LatencyMeasurementRequest = 10,
269-
LatencyMeasurementResponse = 11,
266+
KeepAlive = 9
270267
}
271268

272269
function protocolMessageTypeToString(messageType: ProtocolMessageType) {
@@ -280,8 +277,6 @@ function protocolMessageTypeToString(messageType: ProtocolMessageType) {
280277
case ProtocolMessageType.Pause: return 'PauseWriting';
281278
case ProtocolMessageType.Resume: return 'ResumeWriting';
282279
case ProtocolMessageType.KeepAlive: return 'KeepAlive';
283-
case ProtocolMessageType.LatencyMeasurementRequest: return 'LatencyMeasurementRequest';
284-
case ProtocolMessageType.LatencyMeasurementResponse: return 'LatencyMeasurementResponse';
285280
}
286281
}
287282

@@ -309,22 +304,6 @@ export const enum ProtocolConstants {
309304
* Send a message every 5 seconds to avoid that the connection is closed by the OS.
310305
*/
311306
KeepAliveSendTime = 5000, // 5 seconds
312-
/**
313-
* Measure the latency every 1 minute.
314-
*/
315-
LatencySampleTime = 1 * 60 * 1000, // 1 minute
316-
/**
317-
* Keep the last 5 samples for latency measurement.
318-
*/
319-
LatencySampleCount = 5,
320-
/**
321-
* A latency over 1s will be considered high.
322-
*/
323-
HighLatencyTimeThreshold = 1000,
324-
/**
325-
* Having 3 or more samples with high latency will trigger a high latency event.
326-
*/
327-
HighLatencySampleThreshold = 3,
328307
}
329308

330309
class ProtocolMessage {
@@ -803,52 +782,6 @@ export interface ILoadEstimator {
803782
hasHighLoad(): boolean;
804783
}
805784

806-
export const enum ConnectionHealth {
807-
/**
808-
* The connection health is considered good when a certain number of recent round trip time measurements are below a certain threshold.
809-
* @see ProtocolConstants.HighLatencyTimeThreshold @see ProtocolConstants.HighLatencySampleThreshold
810-
*/
811-
Good,
812-
/**
813-
* The connection health is considered poor when a certain number of recent round trip time measurements are above a certain threshold.
814-
* @see ProtocolConstants.HighLatencyTimeThreshold @see ProtocolConstants.HighLatencySampleThreshold
815-
*/
816-
Poor
817-
}
818-
819-
export function connectionHealthToString(connectionHealth: ConnectionHealth): 'good' | 'poor' {
820-
switch (connectionHealth) {
821-
case ConnectionHealth.Good: return 'good';
822-
case ConnectionHealth.Poor: return 'poor';
823-
}
824-
}
825-
826-
/**
827-
* An event describing that the connection health has changed.
828-
*/
829-
export class ConnectionHealthChangedEvent {
830-
constructor(
831-
public readonly connectionHealth: ConnectionHealth
832-
) { }
833-
}
834-
835-
/**
836-
* An event describing that a round trip time measurement was above a certain threshold.
837-
*/
838-
export class HighRoundTripTimeEvent {
839-
constructor(
840-
/**
841-
* The round trip time in milliseconds.
842-
*/
843-
public readonly roundTripTime: number,
844-
/**
845-
* The number of recent round trip time measurements that were above the threshold.
846-
* @see ProtocolConstants.HighLatencyTimeThreshold @see ProtocolConstants.HighLatencySampleThreshold
847-
*/
848-
public readonly recentHighRoundTripCount: number
849-
) { }
850-
}
851-
852785
export interface PersistentProtocolOptions {
853786
/**
854787
* The socket to use.
@@ -862,10 +795,6 @@ export interface PersistentProtocolOptions {
862795
* The CPU load estimator to use.
863796
*/
864797
loadEstimator?: ILoadEstimator;
865-
/**
866-
* Whether to measure round trip time. Defaults to false.
867-
*/
868-
measureRoundTripTime?: boolean;
869798
/**
870799
* Whether to send keep alive messages. Defaults to true.
871800
*/
@@ -898,11 +827,9 @@ export class PersistentProtocol implements IMessagePassingProtocol {
898827
private _socket: ISocket;
899828
private _socketWriter: ProtocolWriter;
900829
private _socketReader: ProtocolReader;
901-
private _socketLatencyMonitor: LatencyMonitor;
902830
private _socketDisposables: DisposableStore;
903831

904832
private readonly _loadEstimator: ILoadEstimator;
905-
private readonly _measureRoundTripTime: boolean;
906833
private readonly _shouldSendKeepAlive: boolean;
907834

908835
private readonly _onControlMessage = new BufferedEmitter<VSBuffer>();
@@ -920,19 +847,12 @@ export class PersistentProtocol implements IMessagePassingProtocol {
920847
private readonly _onSocketTimeout = new BufferedEmitter<SocketTimeoutEvent>();
921848
readonly onSocketTimeout: Event<SocketTimeoutEvent> = this._onSocketTimeout.event;
922849

923-
private readonly _onHighRoundTripTime = new BufferedEmitter<HighRoundTripTimeEvent>();
924-
readonly onHighRoundTripTime = this._onHighRoundTripTime.event;
925-
926-
private readonly _onDidChangeConnectionHealth = new BufferedEmitter<ConnectionHealth>();
927-
readonly onDidChangeConnectionHealth = this._onDidChangeConnectionHealth.event;
928-
929850
public get unacknowledgedCount(): number {
930851
return this._outgoingMsgId - this._outgoingAckId;
931852
}
932853

933854
constructor(opts: PersistentProtocolOptions) {
934855
this._loadEstimator = opts.loadEstimator ?? LoadEstimator.getInstance();
935-
this._measureRoundTripTime = opts.measureRoundTripTime ?? false;
936856
this._shouldSendKeepAlive = opts.sendKeepAlive ?? true;
937857
this._isReconnecting = false;
938858
this._outgoingUnackMsg = new Queue<ProtocolMessage>();
@@ -954,13 +874,6 @@ export class PersistentProtocol implements IMessagePassingProtocol {
954874
this._socketReader = this._socketDisposables.add(new ProtocolReader(this._socket));
955875
this._socketDisposables.add(this._socketReader.onMessage(msg => this._receiveMessage(msg)));
956876
this._socketDisposables.add(this._socket.onClose(e => this._onSocketClose.fire(e)));
957-
this._socketLatencyMonitor = this._socketDisposables.add(new LatencyMonitor()); // is started immediately
958-
this._socketDisposables.add(this._socketLatencyMonitor.onSendLatencyRequest(buffer => this._sendLatencyMeasurementRequest(buffer)));
959-
this._socketDisposables.add(this._socketLatencyMonitor.onHighRoundTripTime(e => this._onHighRoundTripTime.fire(e)));
960-
this._socketDisposables.add(this._socketLatencyMonitor.onDidChangeConnectionHealth(e => this._onDidChangeConnectionHealth.fire(e)));
961-
if (this._measureRoundTripTime) {
962-
this._socketLatencyMonitor.start();
963-
}
964877

965878
if (opts.initialChunk) {
966879
this._socketReader.acceptChunk(opts.initialChunk);
@@ -1041,19 +954,12 @@ export class PersistentProtocol implements IMessagePassingProtocol {
1041954
this._socketReader = this._socketDisposables.add(new ProtocolReader(this._socket));
1042955
this._socketDisposables.add(this._socketReader.onMessage(msg => this._receiveMessage(msg)));
1043956
this._socketDisposables.add(this._socket.onClose(e => this._onSocketClose.fire(e)));
1044-
this._socketLatencyMonitor = this._socketDisposables.add(new LatencyMonitor()); // will be started later
1045-
this._socketDisposables.add(this._socketLatencyMonitor.onSendLatencyRequest(buffer => this._sendLatencyMeasurementRequest(buffer)));
1046-
this._socketDisposables.add(this._socketLatencyMonitor.onHighRoundTripTime(e => this._onHighRoundTripTime.fire(e)));
1047-
this._socketDisposables.add(this._socketLatencyMonitor.onDidChangeConnectionHealth(e => this._onDidChangeConnectionHealth.fire(e)));
1048957

1049958
this._socketReader.acceptChunk(initialDataChunk);
1050959
}
1051960

1052961
public endAcceptReconnection(): void {
1053962
this._isReconnecting = false;
1054-
if (this._measureRoundTripTime) {
1055-
this._socketLatencyMonitor.start();
1056-
}
1057963

1058964
// After a reconnection, let the other party know (again) which messages have been received.
1059965
// (perhaps the other party didn't receive a previous ACK)
@@ -1144,15 +1050,6 @@ export class PersistentProtocol implements IMessagePassingProtocol {
11441050
// nothing to do
11451051
break;
11461052
}
1147-
case ProtocolMessageType.LatencyMeasurementRequest: {
1148-
// we just send the data back
1149-
this._sendLatencyMeasurementResponse(msg.data);
1150-
break;
1151-
}
1152-
case ProtocolMessageType.LatencyMeasurementResponse: {
1153-
this._socketLatencyMonitor.handleResponse(msg.data);
1154-
break;
1155-
}
11561053
}
11571054
}
11581055

@@ -1282,92 +1179,6 @@ export class PersistentProtocol implements IMessagePassingProtocol {
12821179
const msg = new ProtocolMessage(ProtocolMessageType.KeepAlive, 0, this._incomingAckId, getEmptyBuffer());
12831180
this._socketWriter.write(msg);
12841181
}
1285-
1286-
private _sendLatencyMeasurementRequest(buffer: VSBuffer): void {
1287-
this._incomingAckId = this._incomingMsgId;
1288-
const msg = new ProtocolMessage(ProtocolMessageType.LatencyMeasurementRequest, 0, this._incomingAckId, buffer);
1289-
this._socketWriter.write(msg);
1290-
}
1291-
1292-
private _sendLatencyMeasurementResponse(buffer: VSBuffer): void {
1293-
this._incomingAckId = this._incomingMsgId;
1294-
const msg = new ProtocolMessage(ProtocolMessageType.LatencyMeasurementResponse, 0, this._incomingAckId, buffer);
1295-
this._socketWriter.write(msg);
1296-
}
1297-
}
1298-
1299-
class LatencyMonitor extends Disposable {
1300-
1301-
private readonly _onSendLatencyRequest = this._register(new Emitter<VSBuffer>());
1302-
readonly onSendLatencyRequest: Event<VSBuffer> = this._onSendLatencyRequest.event;
1303-
1304-
private readonly _onHighRoundTripTime = this._register(new Emitter<HighRoundTripTimeEvent>());
1305-
public readonly onHighRoundTripTime = this._onHighRoundTripTime.event;
1306-
1307-
private readonly _onDidChangeConnectionHealth = this._register(new Emitter<ConnectionHealth>());
1308-
public readonly onDidChangeConnectionHealth = this._onDidChangeConnectionHealth.event;
1309-
1310-
private readonly _measureLatencyTimer = this._register(new IntervalTimer());
1311-
1312-
/**
1313-
* Timestamp of our last latency request message sent to the other host.
1314-
*/
1315-
private _lastLatencyMeasurementSent: number = -1;
1316-
1317-
/**
1318-
* ID separate from the regular message IDs. Used to match up latency
1319-
* requests with responses so we know we're timing the right message
1320-
* even if a reconnection occurs.
1321-
*/
1322-
private _lastLatencyMeasurementId: number = 0;
1323-
1324-
/**
1325-
* Circular buffer of latency measurements
1326-
*/
1327-
private _latencySamples: number[] = Array.from({ length: ProtocolConstants.LatencySampleCount }, (_) => 0);
1328-
private _latencySampleIndex: number = 0;
1329-
private _connectionHealth = ConnectionHealth.Good;
1330-
1331-
constructor() {
1332-
super();
1333-
}
1334-
1335-
public start(): void {
1336-
this._measureLatencyTimer.cancelAndSet(() => {
1337-
this._lastLatencyMeasurementSent = Date.now();
1338-
const measurementId = ++this._lastLatencyMeasurementId;
1339-
const buffer = VSBuffer.alloc(4);
1340-
buffer.writeUInt32BE(measurementId, 0);
1341-
this._onSendLatencyRequest.fire(buffer);
1342-
}, ProtocolConstants.LatencySampleTime);
1343-
}
1344-
1345-
public handleResponse(buffer: VSBuffer): void {
1346-
if (buffer.byteLength !== 4) {
1347-
// invalid measurementId
1348-
return;
1349-
}
1350-
const measurementId = buffer.readUInt32BE(0);
1351-
if (this._lastLatencyMeasurementSent <= 0 || measurementId !== this._lastLatencyMeasurementId) {
1352-
// invalid measurementId
1353-
return;
1354-
}
1355-
1356-
const roundtripTime = Date.now() - this._lastLatencyMeasurementSent;
1357-
const sampleIndex = this._latencySampleIndex++;
1358-
this._latencySamples[sampleIndex % this._latencySamples.length] = roundtripTime;
1359-
1360-
const previousConnectionHealth = this._connectionHealth;
1361-
const highLatencySampleCount = this._latencySamples.filter(s => s >= ProtocolConstants.HighLatencyTimeThreshold).length;
1362-
this._connectionHealth = (highLatencySampleCount >= ProtocolConstants.HighLatencySampleThreshold ? ConnectionHealth.Poor : ConnectionHealth.Good);
1363-
1364-
if (roundtripTime > ProtocolConstants.HighLatencyTimeThreshold) {
1365-
this._onHighRoundTripTime.fire(new HighRoundTripTimeEvent(roundtripTime, highLatencySampleCount));
1366-
}
1367-
if (previousConnectionHealth !== this._connectionHealth) {
1368-
this._onDidChangeConnectionHealth.fire(this._connectionHealth);
1369-
}
1370-
}
13711182
}
13721183

13731184
// (() => {

src/vs/platform/remote/common/remoteAgentConnection.ts

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import * as performance from 'vs/base/common/performance';
1313
import { StopWatch } from 'vs/base/common/stopwatch';
1414
import { generateUuid } from 'vs/base/common/uuid';
1515
import { IIPCLogger } from 'vs/base/parts/ipc/common/ipc';
16-
import { Client, ConnectionHealth, ISocket, PersistentProtocol, ProtocolConstants, SocketCloseEventType } from 'vs/base/parts/ipc/common/ipc.net';
16+
import { Client, ISocket, PersistentProtocol, SocketCloseEventType } from 'vs/base/parts/ipc/common/ipc.net';
1717
import { ILogService } from 'vs/platform/log/common/log';
1818
import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment';
1919
import { RemoteAuthorityResolverError, RemoteConnection } from 'vs/platform/remote/common/remoteAuthorityResolver';
@@ -250,7 +250,7 @@ async function connectToRemoteExtensionHostAgent<T extends RemoteConnection>(opt
250250
protocol = options.reconnectionProtocol;
251251
ownsProtocol = false;
252252
} else {
253-
protocol = new PersistentProtocol({ socket, measureRoundTripTime: true });
253+
protocol = new PersistentProtocol({ socket });
254254
ownsProtocol = true;
255255
}
256256

@@ -482,8 +482,7 @@ export const enum PersistentConnectionEventType {
482482
ReconnectionWait,
483483
ReconnectionRunning,
484484
ReconnectionPermanentFailure,
485-
ConnectionGain,
486-
ConnectionHealthChanged
485+
ConnectionGain
487486
}
488487
export class ConnectionLostEvent {
489488
public readonly type = PersistentConnectionEventType.ConnectionLost;
@@ -521,13 +520,6 @@ export class ConnectionGainEvent {
521520
public readonly attempt: number
522521
) { }
523522
}
524-
export class ConnectionHealthChangedEvent {
525-
public readonly type = PersistentConnectionEventType.ConnectionHealthChanged;
526-
constructor(
527-
public readonly reconnectionToken: string,
528-
public readonly connectionHealth: ConnectionHealth
529-
) { }
530-
}
531523
export class ReconnectionPermanentFailureEvent {
532524
public readonly type = PersistentConnectionEventType.ReconnectionPermanentFailure;
533525
constructor(
@@ -537,7 +529,7 @@ export class ReconnectionPermanentFailureEvent {
537529
public readonly handled: boolean
538530
) { }
539531
}
540-
export type PersistentConnectionEvent = ConnectionGainEvent | ConnectionHealthChangedEvent | ConnectionLostEvent | ReconnectionWaitEvent | ReconnectionRunningEvent | ReconnectionPermanentFailureEvent;
532+
export type PersistentConnectionEvent = ConnectionGainEvent | ConnectionLostEvent | ReconnectionWaitEvent | ReconnectionRunningEvent | ReconnectionPermanentFailureEvent;
541533

542534
export abstract class PersistentConnection extends Disposable {
543535

@@ -607,13 +599,6 @@ export abstract class PersistentConnection extends Disposable {
607599
this._options.logService.info(`${logPrefix} received socket timeout event (unacknowledgedMsgCount: ${e.unacknowledgedMsgCount}, timeSinceOldestUnacknowledgedMsg: ${e.timeSinceOldestUnacknowledgedMsg}, timeSinceLastReceivedSomeData: ${e.timeSinceLastReceivedSomeData}).`);
608600
this._beginReconnecting();
609601
}));
610-
this._register(protocol.onHighRoundTripTime((e) => {
611-
const logPrefix = _commonLogPrefix(this._connectionType, this.reconnectionToken);
612-
this._options.logService.info(`${logPrefix} high roundtrip time: ${e.roundTripTime}ms (${e.recentHighRoundTripCount} of ${ProtocolConstants.LatencySampleCount} recent samples)`);
613-
}));
614-
this._register(protocol.onDidChangeConnectionHealth((connectionHealth) => {
615-
this._onDidStateChange.fire(new ConnectionHealthChangedEvent(this.reconnectionToken, connectionHealth));
616-
}));
617602

618603
PersistentConnection._instances.push(this);
619604
this._register(toDisposable(() => {

src/vs/workbench/contrib/remote/browser/remote.ts

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ import { ILogService } from 'vs/platform/log/common/log';
5353
import { ITimerService } from 'vs/workbench/services/timer/browser/timerService';
5454
import { getRemoteName } from 'vs/platform/remote/common/remoteHosts';
5555
import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
56-
import { connectionHealthToString } from 'vs/base/parts/ipc/common/ipc.net';
5756
import { getVirtualWorkspaceLocation } from 'vs/platform/workspace/common/virtualWorkspace';
5857
import { IJSONSchema } from 'vs/base/common/jsonSchema';
5958
import { IWalkthroughsService } from 'vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService';
@@ -1090,26 +1089,6 @@ export class RemoteAgentConnectionStatusListener extends Disposable implements I
10901089

10911090
hideProgress();
10921091
break;
1093-
1094-
case PersistentConnectionEventType.ConnectionHealthChanged:
1095-
type RemoteConnectionHealthClassification = {
1096-
owner: 'alexdima';
1097-
comment: 'The remote connection health has changed (round trip time)';
1098-
remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The name of the resolver.' };
1099-
reconnectionToken: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The identifier of the connection.' };
1100-
connectionHealth: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The health of the connection: good or poor.' };
1101-
};
1102-
type RemoteConnectionHealthEvent = {
1103-
remoteName: string | undefined;
1104-
reconnectionToken: string;
1105-
connectionHealth: 'good' | 'poor';
1106-
};
1107-
telemetryService.publicLog2<RemoteConnectionHealthEvent, RemoteConnectionHealthClassification>('remoteConnectionHealth', {
1108-
remoteName: getRemoteName(environmentService.remoteAuthority),
1109-
reconnectionToken: e.reconnectionToken,
1110-
connectionHealth: connectionHealthToString(e.connectionHealth)
1111-
});
1112-
break;
11131092
}
11141093
});
11151094
}

0 commit comments

Comments
 (0)