Skip to content

Commit f392d4d

Browse files
committed
grpc-js-xds: interop client: correct for setInterval variance
1 parent e80d9cf commit f392d4d

File tree

1 file changed

+51
-8
lines changed

1 file changed

+51
-8
lines changed

packages/grpc-js-xds/interop/xds-interop-client.ts

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,27 @@ class CallStatsTracker {
180180
}
181181
}
182182

183+
class RecentTimestampList {
184+
private timeList: bigint[] = [];
185+
private nextIndex = 0;
186+
187+
constructor(private readonly size: number) {}
188+
189+
isFull() {
190+
return this.timeList.length === this.size;
191+
}
192+
193+
insertTimestamp(timestamp: bigint) {
194+
this.timeList[this.nextIndex] = timestamp;
195+
this.nextIndex = (this.nextIndex + 1) % this.size;
196+
}
197+
198+
getSpan(): bigint {
199+
const lastIndex = (this.nextIndex + this.size - 1) % this.size;
200+
return this.timeList[lastIndex] - this.timeList[this.nextIndex];
201+
}
202+
}
203+
183204
type CallType = 'EmptyCall' | 'UnaryCall';
184205

185206
interface ClientConfiguration {
@@ -246,27 +267,34 @@ const callTimeHistogram: {[callType: string]: {[status: number]: number[]}} = {
246267
EmptyCall: {}
247268
}
248269

249-
function makeSingleRequest(client: TestServiceClient, type: CallType, failOnFailedRpcs: boolean, callStatsTracker: CallStatsTracker) {
270+
/**
271+
* Timestamps output by process.hrtime.bigint() are a bigint number of
272+
* nanoseconds. This is the representation of 1 second in that context.
273+
*/
274+
const TIMESTAMP_ONE_SECOND = BigInt(1e9);
275+
276+
function makeSingleRequest(client: TestServiceClient, type: CallType, failOnFailedRpcs: boolean, callStatsTracker: CallStatsTracker, callStartTimestamps: RecentTimestampList) {
250277
const callEnumName = callTypeEnumMapReverse[type];
251278
addAccumulatedCallStarted(callEnumName);
252279
const notifier = callStatsTracker.startCall();
253280
let gotMetadata: boolean = false;
254281
let hostname: string | null = null;
255282
let completed: boolean = false;
256283
let completedWithError: boolean = false;
257-
const startTime = process.hrtime();
284+
const startTime = process.hrtime.bigint();
258285
const deadline = new Date();
259286
deadline.setSeconds(deadline.getSeconds() + currentConfig.timeoutSec);
260287
const callback = (error: grpc.ServiceError | undefined, value: Empty__Output | undefined) => {
261288
const statusCode = error?.code ?? grpc.status.OK;
262-
const duration = process.hrtime(startTime);
289+
const duration = process.hrtime.bigint() - startTime;
290+
const durationSeconds = Number(duration / TIMESTAMP_ONE_SECOND) | 0;
263291
if (!callTimeHistogram[type][statusCode]) {
264292
callTimeHistogram[type][statusCode] = [];
265293
}
266-
if (callTimeHistogram[type][statusCode][duration[0]]) {
267-
callTimeHistogram[type][statusCode][duration[0]] += 1;
294+
if (callTimeHistogram[type][statusCode][durationSeconds]) {
295+
callTimeHistogram[type][statusCode][durationSeconds] += 1;
268296
} else {
269-
callTimeHistogram[type][statusCode][duration[0]] = 1;
297+
callTimeHistogram[type][statusCode][durationSeconds] = 1;
270298
}
271299
addAccumulatedCallEnded(callEnumName, statusCode);
272300
if (error) {
@@ -301,13 +329,28 @@ function makeSingleRequest(client: TestServiceClient, type: CallType, failOnFail
301329
}
302330
}
303331
});
304-
332+
/* callStartTimestamps tracks the last N timestamps of started calls, where N
333+
* is the target QPS. If the measured span of time between the first and last
334+
* of those N calls is greater than 1 second, we make another call
335+
* ~immediately to correct for that. */
336+
callStartTimestamps.insertTimestamp(startTime);
337+
if (callStartTimestamps.isFull()) {
338+
if (callStartTimestamps.getSpan() > TIMESTAMP_ONE_SECOND) {
339+
setImmediate(() => {
340+
makeSingleRequest(client, type, failOnFailedRpcs, callStatsTracker, callStartTimestamps);
341+
});
342+
}
343+
}
305344
}
306345

307346
function sendConstantQps(client: TestServiceClient, qps: number, failOnFailedRpcs: boolean, callStatsTracker: CallStatsTracker) {
347+
const callStartTimestampsTrackers: {[callType: string]: RecentTimestampList} = {};
348+
for (const callType of currentConfig.callTypes) {
349+
callStartTimestampsTrackers[callType] = new RecentTimestampList(qps);
350+
}
308351
setInterval(() => {
309352
for (const callType of currentConfig.callTypes) {
310-
makeSingleRequest(client, callType, failOnFailedRpcs, callStatsTracker);
353+
makeSingleRequest(client, callType, failOnFailedRpcs, callStatsTracker, callStartTimestampsTrackers[callType]);
311354
}
312355
}, 1000/qps);
313356
setInterval(() => {

0 commit comments

Comments
 (0)