Skip to content

Commit 0098bef

Browse files
authored
Merge pull request #43 from jberryman/dev
Add new flag, hdr-histogram fixes, add 'min'
2 parents a8ef99e + ae49488 commit 0098bef

File tree

9 files changed

+109
-15
lines changed

9 files changed

+109
-15
lines changed

app/cli/src/commands/query.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ export default class Query extends Command {
3737
multiple: false,
3838
description: 'URL to direct graphql queries; may override \'url\' from the YAML config, which is optional if this flag is passed',
3939
}),
40+
query: flags.string({
41+
required: false,
42+
multiple: false,
43+
description: 'A specific named query to run from the config; if omitted, all queries will be run',
44+
}),
4045
}
4146

4247
async run() {
@@ -49,7 +54,7 @@ export default class Query extends Command {
4954
config.url = flags.url
5055
}
5156
const executor = new BenchmarkRunner(config)
52-
const results = await executor.runBenchmarks()
57+
const results = await executor.runBenchmarks(flags.query)
5358

5459
if (flags.outfile) {
5560
const pathToOutfile = path.join(process.cwd(), flags.outfile)
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import * as hdr from 'hdr-histogram-js'
2+
import { parseHdrHistogramText } from './executors/base/index'
3+
import { HDRHistogramParsedStats } from './executors/base/types'
4+
5+
/* A wrapper for Histogram that gets us more precision. See
6+
* https://github.com/HdrHistogram/HdrHistogramJS/issues/35
7+
*
8+
* Values inserted will be truncated to `logBase 10 scalingFactor` (i.e. 3)
9+
* decimal places.
10+
*/
11+
export class PreciseHdrHistogram {
12+
// We'll need to multiply by scalingFactor anything we insert, and divide by scalingFactor
13+
// anything we output from here:
14+
private _histogramDirty: hdr.Histogram
15+
static scalingFactor: number = 1000
16+
17+
constructor(
18+
request: hdr.BuildRequest
19+
) {
20+
this._histogramDirty = hdr.build(request)
21+
}
22+
23+
//// On inputs we multiply...
24+
public recordValue(value: number) {
25+
this._histogramDirty.recordValue(value*PreciseHdrHistogram.scalingFactor)
26+
}
27+
28+
public recordValueWithCount(value: number, count: number): void {
29+
this._histogramDirty.recordValueWithCount(value*PreciseHdrHistogram.scalingFactor, count)
30+
}
31+
32+
//// ...and on outputs we divide:
33+
public toJSON(): hdr.HistogramSummary {
34+
let summary = this._histogramDirty.summary
35+
for (let key in summary) {
36+
// scale mean and percentiles (but not counts) back down:
37+
if (key == "totalCount") continue
38+
summary[key] /= PreciseHdrHistogram.scalingFactor
39+
}
40+
41+
return summary
42+
}
43+
get mean(): number {
44+
return (this._histogramDirty.mean / PreciseHdrHistogram.scalingFactor)
45+
}
46+
get min(): number {
47+
// NOTE: 'minNonZeroValue' is already just 'min' since 0 can't be recorded
48+
return (this._histogramDirty.minNonZeroValue / PreciseHdrHistogram.scalingFactor)
49+
}
50+
get stdDeviation(): number {
51+
return (this._histogramDirty.stdDeviation / PreciseHdrHistogram.scalingFactor)
52+
}
53+
// This is our own helper, where formerly we called:
54+
// parseHdrHistogramText(histogram.outputPercentileDistribution())
55+
get parsedStats(): HDRHistogramParsedStats[] {
56+
let parsedDirty = parseHdrHistogramText(
57+
this._histogramDirty.outputPercentileDistribution())
58+
59+
// scale mean and percentiles (but not counts) back down:
60+
parsedDirty.forEach(function (line) {
61+
// i.e. line.value /= PreciseHdrHistogram.scalingFactor
62+
line.value = String((Number(line.value) / PreciseHdrHistogram.scalingFactor))
63+
})
64+
return parsedDirty
65+
}
66+
67+
// Don't leak implementation in debugging output, which might be confusing:
68+
[Symbol.for("nodejs.util.inspect.custom")]() {
69+
return JSON.stringify(this.toJSON(), null, 2)
70+
}
71+
}
72+
73+
// Copy-pasted from 'hdr-histogram-js', since this isn't exported
74+
export const defaultRequest: hdr.BuildRequest = {
75+
bitBucketSize: 32,
76+
autoResize: true,
77+
lowestDiscernibleValue: 1,
78+
highestTrackableValue: 2,
79+
numberOfSignificantValueDigits: 3,
80+
useWebAssembly: false,
81+
}
82+
83+
export const build = (request = defaultRequest): PreciseHdrHistogram => {
84+
return new PreciseHdrHistogram(request)
85+
}

app/queries/src/executors/autocannon/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { Options as AutocannonOptions } from 'autocannon'
2424

2525
import fs from 'fs-extra'
2626
import path from 'path'
27-
import * as hdr from 'hdr-histogram-js'
27+
import * as hdr from '../../PreciseHdrHistogram'
2828

2929
export class AutocannonExecutor extends BenchmarkExecutor {
3030
public tool = BenchmarkTool.AUTOCANNON

app/queries/src/executors/base/index.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ export function parseHdrHistogramText(text: string): HDRHistogramParsedStats[] {
2121
const lines = text.split('\n')
2222
for (let line of lines) {
2323
let entries = line.trim().split(/\s+/)
24-
let valid = entries.length == 4 && entries.every(Number)
24+
// Careful of truthiness when parsing zero here:
25+
let valid = entries.length == 4 && entries.every((x) => !(isNaN(Number(x))))
2526
if (!valid) continue
2627
let [value, percentile, totalCount, ofOnePercentile] = entries
2728
results.push({ value, percentile, totalCount, ofOnePercentile })
@@ -42,12 +43,10 @@ export function makeBenchmarkMetrics(
4243
json: {
4344
...histogram.toJSON(),
4445
mean: histogram.mean,
46+
min: histogram.min,
4547
stdDeviation: histogram.stdDeviation,
4648
},
47-
text: histogram.outputPercentileDistribution(),
48-
parsedStats: parseHdrHistogramText(
49-
histogram.outputPercentileDistribution()
50-
),
49+
parsedStats: histogram.parsedStats,
5150
},
5251
}
5352
}

app/queries/src/executors/base/types.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type * as stream from 'stream'
22

33
import type Histogram from 'hdr-histogram-js/src/Histogram'
44
import type { HistogramSummary } from 'hdr-histogram-js/src/Histogram'
5+
import * as precise_hdr from '../../PreciseHdrHistogram'
56

67
import type { K6Options } from '../k6/types'
78
import type { Stage as K6Stage } from 'k6/options'
@@ -122,15 +123,17 @@ export type Benchmark =
122123
* ========================
123124
*/
124125

126+
// see 'parseHdrHistogramText()'
125127
export interface HDRHistogramParsedStats {
126128
value: string
127129
percentile: string
128130
totalCount: string
129131
ofOnePercentile: string
130132
}
131133

132-
export interface HistogramSummaryWithMeanAndStdDev extends HistogramSummary {
134+
export interface HistogramSummaryWithMeanMinAndStdDev extends HistogramSummary {
133135
mean: number
136+
min: number
134137
stdDeviation: number
135138
}
136139

@@ -149,15 +152,14 @@ export interface BenchmarkMetrics {
149152
bytesPerSecond: number
150153
}
151154
histogram: {
152-
json: HistogramSummaryWithMeanAndStdDev
153-
text: string
155+
json: HistogramSummaryWithMeanMinAndStdDev
154156
parsedStats: HDRHistogramParsedStats[]
155157
}
156158
}
157159

158160
export interface BenchmarkMetricParams {
159161
name: string
160-
histogram: Histogram
162+
histogram: precise_hdr.PreciseHdrHistogram
161163
time: {
162164
start: Date | string
163165
end: Date | string

app/queries/src/executors/k6/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as fs from 'fs-extra'
22
import * as path from 'path'
33
import * as cp from 'child_process'
4-
import * as hdr from 'hdr-histogram-js'
4+
import * as hdr from '../../PreciseHdrHistogram'
55
import readline from 'readline'
66

77
import {

app/queries/src/executors/wrk2/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,12 @@ export class Wrk2Executor extends BenchmarkExecutor {
5252
end: end.toISOString(),
5353
},
5454
histogram: {
55-
text: hdrHistogramStdout,
5655
parsedStats: parseHdrHistogramText(hdrHistogramStdout),
5756
json: {
5857
totalCount: stats.requests,
5958
max: stats.latency_aggregate.max,
6059
mean: stats.latency_aggregate.mean,
60+
min: stats.latency_aggregate.min,
6161
stdDeviation: stats.latency_aggregate.stdev,
6262
p50: findPercentile(stats, 50).latency_in_milliseconds,
6363
p75: findPercentile(stats, 75).latency_in_milliseconds,

app/queries/src/main.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,13 @@ import {
1111
export class BenchmarkRunner {
1212
constructor(public config: GlobalConfig) {}
1313

14-
public async runBenchmarks() {
14+
public async runBenchmarks(only_query?: string) {
1515
let results: BenchmarkMetrics[] = []
1616

1717
for (let query of this.config.queries) {
18+
// Maybe run just a single requested benchmark from the config:
19+
if (only_query && query.name != only_query) continue
20+
1821
for (let tool of query.tools) {
1922
switch (tool) {
2023
case BenchmarkTool.AUTOCANNON: {

app/queries/src/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as fs from 'fs'
22
import readline from 'readline'
33

4-
import * as hdr from 'hdr-histogram-js'
4+
import * as hdr from './PreciseHdrHistogram'
55

66
async function* parseNDJSON(filepath: string) {
77
const filestream = fs.createReadStream(filepath)

0 commit comments

Comments
 (0)