grpc-js-xds: interop client: correct for setInterval variance

This commit is contained in:
Michael Lumish 2022-11-04 15:15:54 -07:00
parent e80d9cfc44
commit f392d4d8c5
1 changed files with 51 additions and 8 deletions

View File

@ -180,6 +180,27 @@ class CallStatsTracker {
} }
} }
class RecentTimestampList {
private timeList: bigint[] = [];
private nextIndex = 0;
constructor(private readonly size: number) {}
isFull() {
return this.timeList.length === this.size;
}
insertTimestamp(timestamp: bigint) {
this.timeList[this.nextIndex] = timestamp;
this.nextIndex = (this.nextIndex + 1) % this.size;
}
getSpan(): bigint {
const lastIndex = (this.nextIndex + this.size - 1) % this.size;
return this.timeList[lastIndex] - this.timeList[this.nextIndex];
}
}
type CallType = 'EmptyCall' | 'UnaryCall'; type CallType = 'EmptyCall' | 'UnaryCall';
interface ClientConfiguration { interface ClientConfiguration {
@ -246,7 +267,13 @@ const callTimeHistogram: {[callType: string]: {[status: number]: number[]}} = {
EmptyCall: {} EmptyCall: {}
} }
function makeSingleRequest(client: TestServiceClient, type: CallType, failOnFailedRpcs: boolean, callStatsTracker: CallStatsTracker) { /**
* Timestamps output by process.hrtime.bigint() are a bigint number of
* nanoseconds. This is the representation of 1 second in that context.
*/
const TIMESTAMP_ONE_SECOND = BigInt(1e9);
function makeSingleRequest(client: TestServiceClient, type: CallType, failOnFailedRpcs: boolean, callStatsTracker: CallStatsTracker, callStartTimestamps: RecentTimestampList) {
const callEnumName = callTypeEnumMapReverse[type]; const callEnumName = callTypeEnumMapReverse[type];
addAccumulatedCallStarted(callEnumName); addAccumulatedCallStarted(callEnumName);
const notifier = callStatsTracker.startCall(); const notifier = callStatsTracker.startCall();
@ -254,19 +281,20 @@ function makeSingleRequest(client: TestServiceClient, type: CallType, failOnFail
let hostname: string | null = null; let hostname: string | null = null;
let completed: boolean = false; let completed: boolean = false;
let completedWithError: boolean = false; let completedWithError: boolean = false;
const startTime = process.hrtime(); const startTime = process.hrtime.bigint();
const deadline = new Date(); const deadline = new Date();
deadline.setSeconds(deadline.getSeconds() + currentConfig.timeoutSec); deadline.setSeconds(deadline.getSeconds() + currentConfig.timeoutSec);
const callback = (error: grpc.ServiceError | undefined, value: Empty__Output | undefined) => { const callback = (error: grpc.ServiceError | undefined, value: Empty__Output | undefined) => {
const statusCode = error?.code ?? grpc.status.OK; const statusCode = error?.code ?? grpc.status.OK;
const duration = process.hrtime(startTime); const duration = process.hrtime.bigint() - startTime;
const durationSeconds = Number(duration / TIMESTAMP_ONE_SECOND) | 0;
if (!callTimeHistogram[type][statusCode]) { if (!callTimeHistogram[type][statusCode]) {
callTimeHistogram[type][statusCode] = []; callTimeHistogram[type][statusCode] = [];
} }
if (callTimeHistogram[type][statusCode][duration[0]]) { if (callTimeHistogram[type][statusCode][durationSeconds]) {
callTimeHistogram[type][statusCode][duration[0]] += 1; callTimeHistogram[type][statusCode][durationSeconds] += 1;
} else { } else {
callTimeHistogram[type][statusCode][duration[0]] = 1; callTimeHistogram[type][statusCode][durationSeconds] = 1;
} }
addAccumulatedCallEnded(callEnumName, statusCode); addAccumulatedCallEnded(callEnumName, statusCode);
if (error) { if (error) {
@ -301,13 +329,28 @@ function makeSingleRequest(client: TestServiceClient, type: CallType, failOnFail
} }
} }
}); });
/* callStartTimestamps tracks the last N timestamps of started calls, where N
* is the target QPS. If the measured span of time between the first and last
* of those N calls is greater than 1 second, we make another call
* ~immediately to correct for that. */
callStartTimestamps.insertTimestamp(startTime);
if (callStartTimestamps.isFull()) {
if (callStartTimestamps.getSpan() > TIMESTAMP_ONE_SECOND) {
setImmediate(() => {
makeSingleRequest(client, type, failOnFailedRpcs, callStatsTracker, callStartTimestamps);
});
}
}
} }
function sendConstantQps(client: TestServiceClient, qps: number, failOnFailedRpcs: boolean, callStatsTracker: CallStatsTracker) { function sendConstantQps(client: TestServiceClient, qps: number, failOnFailedRpcs: boolean, callStatsTracker: CallStatsTracker) {
const callStartTimestampsTrackers: {[callType: string]: RecentTimestampList} = {};
for (const callType of currentConfig.callTypes) {
callStartTimestampsTrackers[callType] = new RecentTimestampList(qps);
}
setInterval(() => { setInterval(() => {
for (const callType of currentConfig.callTypes) { for (const callType of currentConfig.callTypes) {
makeSingleRequest(client, callType, failOnFailedRpcs, callStatsTracker); makeSingleRequest(client, callType, failOnFailedRpcs, callStatsTracker, callStartTimestampsTrackers[callType]);
} }
}, 1000/qps); }, 1000/qps);
setInterval(() => { setInterval(() => {