@@ -180,6 +180,27 @@ class CallStatsTracker {
180
180
}
181
181
}
182
182
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
+
183
204
type CallType = 'EmptyCall' | 'UnaryCall' ;
184
205
185
206
interface ClientConfiguration {
@@ -246,27 +267,34 @@ const callTimeHistogram: {[callType: string]: {[status: number]: number[]}} = {
246
267
EmptyCall : { }
247
268
}
248
269
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 ) {
250
277
const callEnumName = callTypeEnumMapReverse [ type ] ;
251
278
addAccumulatedCallStarted ( callEnumName ) ;
252
279
const notifier = callStatsTracker . startCall ( ) ;
253
280
let gotMetadata : boolean = false ;
254
281
let hostname : string | null = null ;
255
282
let completed : boolean = false ;
256
283
let completedWithError : boolean = false ;
257
- const startTime = process . hrtime ( ) ;
284
+ const startTime = process . hrtime . bigint ( ) ;
258
285
const deadline = new Date ( ) ;
259
286
deadline . setSeconds ( deadline . getSeconds ( ) + currentConfig . timeoutSec ) ;
260
287
const callback = ( error : grpc . ServiceError | undefined , value : Empty__Output | undefined ) => {
261
288
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 ;
263
291
if ( ! callTimeHistogram [ type ] [ statusCode ] ) {
264
292
callTimeHistogram [ type ] [ statusCode ] = [ ] ;
265
293
}
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 ;
268
296
} else {
269
- callTimeHistogram [ type ] [ statusCode ] [ duration [ 0 ] ] = 1 ;
297
+ callTimeHistogram [ type ] [ statusCode ] [ durationSeconds ] = 1 ;
270
298
}
271
299
addAccumulatedCallEnded ( callEnumName , statusCode ) ;
272
300
if ( error ) {
@@ -301,13 +329,28 @@ function makeSingleRequest(client: TestServiceClient, type: CallType, failOnFail
301
329
}
302
330
}
303
331
} ) ;
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
+ }
305
344
}
306
345
307
346
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
+ }
308
351
setInterval ( ( ) => {
309
352
for ( const callType of currentConfig . callTypes ) {
310
- makeSingleRequest ( client , callType , failOnFailedRpcs , callStatsTracker ) ;
353
+ makeSingleRequest ( client , callType , failOnFailedRpcs , callStatsTracker , callStartTimestampsTrackers [ callType ] ) ;
311
354
}
312
355
} , 1000 / qps ) ;
313
356
setInterval ( ( ) => {
0 commit comments