Skip to content

Commit 90fb9d7

Browse files
committed
feat(observability): spin up micro-benchmarking
This requires you to add to your environment SPANNER_RUN_BENCHMARKS=true and then it can be run by npm run observability-test whose results look like: ```shell Benchmarking Total Runs: 10000 databaseRunSelect1AsyncAwait RAM Untraced(206.453kB) vs Traced (240.953kB): increase by (34.500kB) or 16.71% RAM Untraced(206.453kB) vs Traced+OTEL(243.391kB): increase by (36.938kB) or 17.89% Time Untraced(911.057µs) vs Traced (1.014ms): increase by (102.749µs) or 11.28% Time Untraced(911.057µs) vs Traced+OTEL(1.017ms): increase by (106.116µs) or 11.65% databaseRunSelect1Callback RAM Untraced(210.219kB) vs Traced (238.797kB): increase by (28.578kB) or 13.59% RAM Untraced(210.219kB) vs Traced+OTEL(242.914kB): increase by (32.695kB) or 15.55% Time Untraced(890.877µs) vs Traced (997.541µs): increase by (106.664µs) or 11.97% Time Untraced(890.877µs) vs Traced+OTEL(1.019ms): increase by (127.997µs) or 14.37% databaseRunTransactionAsyncTxRunUpdate RAM Untraced(308.898kB) vs Traced (325.313kB): increase by (16.414kB) or 5.31% RAM Untraced(308.898kB) vs Traced+OTEL(339.836kB): increase by (30.938kB) or 10.02% Time Untraced(1.510ms) vs Traced (1.652ms): increase by (141.330µs) or 9.36% Time Untraced(1.510ms) vs Traced+OTEL(1.668ms): increase by (157.466µs) or 10.43% databaseRunTransactionAsyncTxRun RAM Untraced(298.977kB) vs Traced (326.227kB): increase by (27.250kB) or 9.11% RAM Untraced(298.977kB) vs Traced+OTEL(315.164kB): increase by (16.188kB) or 5.41% Time Untraced(1.450ms) vs Traced (1.581ms): increase by (130.870µs) or 9.03% Time Untraced(1.450ms) vs Traced+OTEL(1.615ms): increase by (165.426µs) or 11.41% ```
1 parent cbc86fa commit 90fb9d7

File tree

4 files changed

+474
-1
lines changed

4 files changed

+474
-1
lines changed

observability-test/benchmark.ts

+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/*!
2+
* Copyright 2024 Google LLC. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
const lessComparator = (a, b) => {
18+
if (a < b) return -1;
19+
if (a > b) return 1;
20+
return 0;
21+
};
22+
23+
/*
24+
* runBenchmarks runs each of the functions in runners ${nRuns} times
25+
* each time collecting RAM usage and time spent and then produces
26+
* a map of functionNames to the percentiles of RAM usage and time spent.
27+
*/
28+
export async function runBenchmarks(runners: Function[], done: Function) {
29+
const nRuns = 10000;
30+
const benchmarkValues = {_totalRuns: nRuns};
31+
32+
let k = 0;
33+
for (k = 0; k < runners.length; k++) {
34+
const fn = runners[k];
35+
const functionName = fn.name;
36+
const timeSpentL: bigint[] = [];
37+
const ramL: number[] = [];
38+
let i = 0;
39+
40+
// Warm up runs to ensure stable behavior.
41+
for (i = 0; i < 8; i++) {
42+
const value = await fn();
43+
}
44+
45+
for (i = 0; i < nRuns; i++) {
46+
const startTime: bigint = process.hrtime.bigint();
47+
const startHeapUsedBytes: number = process.memoryUsage().heapUsed;
48+
const value = await fn();
49+
timeSpentL.push(process.hrtime.bigint() - startTime);
50+
ramL.push(process.memoryUsage().heapUsed - startHeapUsedBytes);
51+
}
52+
53+
timeSpentL.sort(lessComparator);
54+
ramL.sort(lessComparator);
55+
56+
benchmarkValues[functionName] = {
57+
ram: percentiles(functionName, ramL, 'bytes'),
58+
timeSpent: percentiles(functionName, timeSpentL, 'time'),
59+
};
60+
}
61+
62+
done(benchmarkValues);
63+
}
64+
65+
function percentiles(method, sortedValues, kind) {
66+
const n = sortedValues.length;
67+
const p50 = sortedValues[Math.floor(n * 0.5)];
68+
const p75 = sortedValues[Math.floor(n * 0.75)];
69+
const p90 = sortedValues[Math.floor(n * 0.9)];
70+
const p95 = sortedValues[Math.floor(n * 0.95)];
71+
const p99 = sortedValues[Math.floor(n * 0.99)];
72+
73+
return {
74+
p50: p50,
75+
p75: p75,
76+
p90: p90,
77+
p95: p95,
78+
p99: p99,
79+
p50_s: humanize(p50, kind),
80+
p75_s: humanize(p75, kind),
81+
p90_s: humanize(p90, kind),
82+
p95_s: humanize(p95, kind),
83+
p99_s: humanize(p99, kind),
84+
};
85+
}
86+
87+
function humanize(values, kind) {
88+
let converterFn = humanizeTime;
89+
if (kind === 'bytes') {
90+
converterFn = humanizeBytes;
91+
}
92+
return converterFn(values);
93+
}
94+
95+
const secondUnits = ['ns', 'µs', 'ms', 's'];
96+
interface unitDivisor {
97+
unit: string;
98+
divisor: number;
99+
}
100+
const pastSecondUnits: unitDivisor[] = [
101+
{unit: 'min', divisor: 60},
102+
{unit: 'hr', divisor: 60},
103+
{unit: 'day', divisor: 24},
104+
{unit: 'week', divisor: 7},
105+
{unit: 'month', divisor: 30},
106+
];
107+
function humanizeTime(ns) {
108+
const sign: number = ns < 0 ? -1 : +1;
109+
let value = Math.abs(Number(ns));
110+
for (const unit of secondUnits) {
111+
if (value < 1000) {
112+
return `${(sign * value).toFixed(3)}${unit}`;
113+
}
114+
value /= 1000;
115+
}
116+
117+
let i = 0;
118+
for (i = 0; i < pastSecondUnits.length; i++) {
119+
const unitPlusValue = pastSecondUnits[i];
120+
const unitName = unitPlusValue.unit;
121+
const divisor = unitPlusValue.divisor;
122+
if (value < divisor) {
123+
return `${sign * value}${unitName}`;
124+
}
125+
value = value / divisor;
126+
}
127+
return `${(sign * value).toFixed(3)}${pastSecondUnits[pastSecondUnits.length - 1][0]}`;
128+
}
129+
130+
const bytesUnits = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'ExB'];
131+
function humanizeBytes(b) {
132+
const sign: number = b < 0 ? -1 : +1;
133+
let value = Math.abs(b);
134+
for (const unit of bytesUnits) {
135+
if (value < 1024) {
136+
return `${(sign * value).toFixed(3)}${unit}`;
137+
}
138+
value = value / 1024;
139+
}
140+
141+
return `${(sign * value).toFixed(3)}${bytesUnits[bytesUnits.length - 1]}`;
142+
}
143+
export {humanizeTime, humanizeBytes};

0 commit comments

Comments
 (0)