@@ -12,6 +12,7 @@ import {
12
12
MaxRequestsInDurationBenchmark ,
13
13
MultiStageBenchmark ,
14
14
RequestsPerSecondBenchmark ,
15
+ HistBucket ,
15
16
} from '../base/types'
16
17
17
18
import {
@@ -213,14 +214,20 @@ export class K6Executor extends BenchmarkExecutor {
213
214
crlfDelay : Infinity ,
214
215
} )
215
216
216
- // Create a histogram, record each HTTP Request duration in it
217
- const histogram = hdr . build ( )
217
+ // We'll build an hdr histogram of HTTP Request durations
218
+ const hdrHistogram = hdr . build ( )
219
+ // ...and record raw durations for processing:
220
+ var reqDurations : number [ ] = [ ]
221
+
218
222
for await ( const line of rl ) {
219
223
const stat : K6Metric | K6Point = JSON . parse ( line )
224
+ // filter for just service time of successful queries:
220
225
if ( stat . type != 'Point' ) continue
221
226
if ( stat . metric != 'http_req_duration' ) continue
222
227
if ( Number ( stat . data . tags . status ) < 200 ) continue
223
- histogram . recordValue ( stat . data . value )
228
+ hdrHistogram . recordValue ( stat . data . value )
229
+
230
+ reqDurations . push ( stat . data . value )
224
231
}
225
232
226
233
// Remove the temp config file with the K6 run parameters, and logging stats
@@ -231,7 +238,8 @@ export class K6Executor extends BenchmarkExecutor {
231
238
const jsonStats : K6Summary = fs . readJSONSync ( outPath )
232
239
const metrics = makeBenchmarkMetrics ( {
233
240
name : metadata . queryName ,
234
- histogram,
241
+ histogram : hdrHistogram ,
242
+ basicHistogram : histogram ( 200 , reqDurations ) ,
235
243
time : {
236
244
start : benchmarkStart . toISOString ( ) ,
237
245
end : benchmarkEnd . toISOString ( ) ,
@@ -244,8 +252,68 @@ export class K6Executor extends BenchmarkExecutor {
244
252
totalBytes : jsonStats . metrics . data_received . count ,
245
253
bytesPerSecond : jsonStats . metrics . data_received . rate ,
246
254
} ,
255
+ geoMean : geoMean ( reqDurations )
247
256
} )
248
257
249
258
return metrics
250
259
}
251
260
}
261
+
262
+ // geometric mean, with exponent distributed over product so we don't overflow
263
+ function geoMean ( xs : number [ ] ) : number {
264
+ return xs . map ( x => Math . pow ( x , 1 / xs . length ) ) . reduce ( ( acc , x ) => acc * x )
265
+ }
266
+
267
+ // NOTE: To save space and aid readability we’ll filter out any buckets with a
268
+ // count of 0 that follow a bucket with a count of 0. This can still be graphed
269
+ // fine without extra accommodations using a stepped line plot, as we plan
270
+ function histogram ( numBuckets : number , xs : number [ ] ) : HistBucket [ ] {
271
+ if ( numBuckets < 1 || xs . length < 2 ) { throw "We need at least one bucket and xs.length > 1" }
272
+
273
+ var xsSorted = new Float64Array ( xs ) // so sort works properly
274
+ xsSorted . sort ( )
275
+
276
+ const bucketWidth = ( xsSorted [ xsSorted . length - 1 ] - xsSorted [ 0 ] ) / numBuckets
277
+
278
+ var buckets = [ ]
279
+ for ( let gte = xsSorted [ 0 ] ; true ; gte += bucketWidth ) {
280
+ // Last bucket; add remaining and stop
281
+ if ( buckets . length === ( numBuckets - 1 ) ) {
282
+ buckets . push ( { gte, count : xsSorted . length } )
283
+ break
284
+ }
285
+ var count = 0
286
+ var ixNext
287
+ // this should always consume as least one value:
288
+ xsSorted . some ( ( x , ix ) => {
289
+ if ( x < ( gte + bucketWidth ) ) {
290
+ count ++
291
+ return false // i.e. keep looping
292
+ } else {
293
+ ixNext = ix
294
+ return true
295
+ }
296
+ } )
297
+ if ( ixNext === undefined ) { throw "Bugs in histogram!" }
298
+ xsSorted = xsSorted . slice ( ixNext )
299
+ buckets . push ( { gte, count} )
300
+ }
301
+ // having at most one 0 bucket in a row, i.e. `{gte: n, count: 0}` means
302
+ // "This and all higher buckets are empty"
303
+ var bucketsSparse = [ ]
304
+ var inAZeroSpan = false
305
+ buckets . forEach ( b => {
306
+ if ( inAZeroSpan && b . count === 0 ) {
307
+ // drop this bucket
308
+ } else if ( ! inAZeroSpan && b . count === 0 ) {
309
+ // include this zero buckets but not subsequent zero buckets
310
+ bucketsSparse . push ( b )
311
+ inAZeroSpan = true
312
+ } else {
313
+ inAZeroSpan = false
314
+ bucketsSparse . push ( b )
315
+ }
316
+ } )
317
+
318
+ return bucketsSparse
319
+ }
0 commit comments