Skip to content

Commit df51f05

Browse files
authored
Add runtime benchmarks to benchviz (#75)
* Fix browserify Buffer * Add runtime benchmarks to benchviz
1 parent 548d3f7 commit df51f05

File tree

3 files changed

+130
-74
lines changed

3 files changed

+130
-74
lines changed

src/pages/benchviz/Benchmark.tsx

Lines changed: 112 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,127 +1,168 @@
1+
import { Buffer } from "buffer/";
12
import * as d3 from "d3";
23
import React, { useEffect, useRef } from "react";
34
import * as zlib from "zlib";
4-
import { BenchmarkResult, MemoryBenchmarkCategory } from "./benchmark-types";
5+
import {
6+
BenchmarkKind,
7+
BenchmarkResult,
8+
isMemoryBenchmarkResult,
9+
isRuntimeBenchmarkResult,
10+
MemoryBenchmarkCategory,
11+
MemoryBenchmarkResult,
12+
RuntimeBenchmarkResult,
13+
} from "./benchmark-types";
514
import { joinOnProperty, JoinResult } from "./util";
615
import { barComparisonGraph } from "./visualizations/bar-comparison-graph";
716
import { positiveNegativeBarGraph } from "./visualizations/positive-negative-bar-graph";
817

9-
const garbageCreatedComparisonGraphWidth = 1000;
10-
const garbageCreatedComparisonGraphHeight = 300;
18+
const comparisonGraphWidth = 1000;
19+
const comparisonGraphHeight = 300;
1120

12-
const garbageCreatedChangeGraphWidth = 1000;
13-
const garbageCreatedChangeGraphHeight = 300;
21+
const changeGraphWidth = 1000;
22+
const changeGraphHeight = 300;
1423

1524
// Utility functions
1625
const formatBenchmarkName = (benchmarkName: string) => benchmarkName.replace(".lua", "").split("/").pop()!;
17-
const formatMemory = (value: number) => `${Math.round(value / 10) / 100} Mb`;
18-
19-
const benchmarkGarbage = (bm: BenchmarkResult) => bm.categories[MemoryBenchmarkCategory.Garbage];
20-
const garbagePercentChange = (result: JoinResult<BenchmarkResult>) =>
21-
(benchmarkGarbage(result.right!) - benchmarkGarbage(result.left!)) / benchmarkGarbage(result.left!);
22-
23-
export default function Benchmark() {
24-
let garbageCreatedChangeSvgRef = useRef<SVGSVGElement>(null!);
25-
let garbageCreatedComparisonSvgRef = useRef<SVGSVGElement>(null!);
26-
27-
const benchmarkData = decodeBenchmarkData(window.location.search.split("?d=")[1]);
28-
// Sort by percentage change of garbage created
29-
const benchmarksSortedByPercentDifference = benchmarkData.sort(
30-
(a, b) => garbagePercentChange(a) - garbagePercentChange(b),
26+
const percentChange = (oldValue: number, newValue: number) => ((newValue - oldValue) / oldValue) * 100;
27+
const formatMemory = (value: number) => `${(value / 1000).toFixed(2)} Mb`;
28+
const formatTime = (value: number) => `${(value * 1000).toFixed(2)} ms`;
29+
const formatPercent = (value: number) => `${value.toFixed(2)}%`;
30+
31+
function BenchmarkCategory<T extends BenchmarkResult>({
32+
name,
33+
benchmarks,
34+
extractValue,
35+
formatValue,
36+
}: {
37+
name: string;
38+
benchmarks: JoinResult<T>[];
39+
extractValue: (result: T) => number;
40+
formatValue: (value: number) => string;
41+
}) {
42+
let changeSvgRef = useRef<SVGSVGElement>(null!);
43+
let comparisonSvgRef = useRef<SVGSVGElement>(null!);
44+
45+
const percentChangeForResult = (result: JoinResult<T>) =>
46+
percentChange(extractValue(result.left!), extractValue(result.right!));
47+
48+
// Sort by percentage change
49+
const benchmarksSortedByPercentageDiff = benchmarks.sort(
50+
(a, b) => percentChangeForResult(a) - percentChangeForResult(b),
3151
);
3252

3353
// Populate graph with benchmark results
34-
const benchmarkResultsTable = benchmarksSortedByPercentDifference.map((bm, i) => {
35-
const change = garbagePercentChange(bm);
54+
const memoryBenchmarksResultTable = benchmarksSortedByPercentageDiff.map((bm, i) => {
55+
const change = percentChangeForResult(bm);
3656
const rowColor = change === 0 ? "currentColor" : change > 0 ? "red" : "green";
3757

3858
return (
3959
<tr key={i} style={{ color: rowColor }}>
4060
<td>{bm.left!.benchmarkName}</td>
41-
<td>{formatMemory(benchmarkGarbage(bm.left!))}</td>
42-
<td>{formatMemory(benchmarkGarbage(bm.right!))}</td>
43-
<td>{change}</td>
61+
<td>{formatValue(extractValue(bm.left!))}</td>
62+
<td>{formatValue(extractValue(bm.right!))}</td>
63+
<td>{formatPercent(change)}</td>
4464
</tr>
4565
);
4666
});
4767

48-
// Comparison data master garbage created vs commit garbate created (PERCENTAGE CHANGE)
49-
const generatedGarbageChangeData = benchmarksSortedByPercentDifference.map((bm) => {
50-
const oldValue = bm.left!.categories[MemoryBenchmarkCategory.Garbage]!;
51-
const newValue = bm.right!.categories[MemoryBenchmarkCategory.Garbage]!;
52-
53-
return {
54-
name: formatBenchmarkName(bm.left!.benchmarkName || bm.right!.benchmarkName!),
55-
value: (100 * (newValue - oldValue)) / oldValue,
56-
};
57-
});
68+
// Comparison data master vs commit (PERCENTAGE CHANGE)
69+
const changeData = benchmarksSortedByPercentageDiff.map((bm) => ({
70+
name: formatBenchmarkName(bm.left!.benchmarkName || bm.right!.benchmarkName!),
71+
value: percentChangeForResult(bm),
72+
}));
5873

59-
// Comparison data master garbage created vs commit garbate created (ABSOLUTE)
60-
const generatedGarbageData = benchmarksSortedByPercentDifference.map((bm) => ({
74+
// Comparison data master vs commit (ABSOLUTE)
75+
const absoluteData = benchmarksSortedByPercentageDiff.map((bm) => ({
6176
name: formatBenchmarkName(bm.left!.benchmarkName || bm.right!.benchmarkName!),
62-
oldValue: bm.left!.categories[MemoryBenchmarkCategory.Garbage] || 0,
63-
newValue: bm.right!.categories[MemoryBenchmarkCategory.Garbage] || 0,
77+
oldValue: extractValue(bm.left!) || 0,
78+
newValue: extractValue(bm.right!) || 0,
6479
}));
6580

6681
useEffect(() => {
67-
// Populate graph showing percentual change in garbage created
68-
positiveNegativeBarGraph(
69-
d3.select(garbageCreatedChangeSvgRef.current),
70-
generatedGarbageChangeData,
71-
garbageCreatedChangeGraphWidth,
72-
garbageCreatedChangeGraphHeight,
73-
);
82+
// Populate graph showing percent change
83+
positiveNegativeBarGraph(d3.select(changeSvgRef.current), changeData, changeGraphWidth, changeGraphHeight);
7484

75-
// Populate graph showing absolute garbage created numbers
85+
// Populate graph showing absolute numbers
7686
barComparisonGraph(
77-
d3.select(garbageCreatedComparisonSvgRef.current),
78-
generatedGarbageData,
79-
garbageCreatedComparisonGraphWidth,
80-
garbageCreatedComparisonGraphHeight,
87+
d3.select(comparisonSvgRef.current),
88+
absoluteData,
89+
comparisonGraphWidth,
90+
comparisonGraphHeight,
8191
);
8292
});
8393

8494
return (
8595
<>
86-
<h2>Benchmark results</h2>
96+
<h1>{name} results</h1>
8797
{/* Results table */}
8898
<table>
8999
<thead>
90100
<tr style={{ fontWeight: "bold" }}>
91101
<td>Benchmark</td>
92-
<td>Garbage Master</td>
93-
<td>Garbage Commit</td>
102+
<td>Master</td>
103+
<td>Commit</td>
94104
<td>% Change</td>
95105
</tr>
96106
</thead>
97-
<tbody>{benchmarkResultsTable}</tbody>
107+
<tbody>{memoryBenchmarksResultTable}</tbody>
98108
</table>
99109

100-
<h2>Garbage created change</h2>
101-
{/* [% Delta] Gerbage created */}
102-
<svg
103-
ref={garbageCreatedChangeSvgRef}
104-
width={garbageCreatedChangeGraphWidth}
105-
height={garbageCreatedChangeGraphHeight}
106-
></svg>
107-
108-
<h2>Garbage created</h2>
109-
{/* [Absolute] Garbage created comparison */}
110-
<svg
111-
ref={garbageCreatedComparisonSvgRef}
112-
width={garbageCreatedComparisonGraphWidth}
113-
height={garbageCreatedComparisonGraphHeight}
114-
></svg>
110+
<h2>{name} change</h2>
111+
{/* [% Delta] */}
112+
<svg ref={changeSvgRef} width={changeGraphWidth} height={changeGraphHeight} />
113+
114+
<h2>{name}</h2>
115+
{/* [Absolute] comparison */}
116+
<svg ref={comparisonSvgRef} width={comparisonGraphWidth} height={comparisonGraphHeight} />
117+
</>
118+
);
119+
}
120+
121+
export default function Benchmark() {
122+
const benchmarkData = decodeBenchmarkData(window.location.search.split("?d=")[1]);
123+
return (
124+
<>
125+
{/* Results table */}
126+
<BenchmarkCategory
127+
name={"Garbage created"}
128+
benchmarks={benchmarkData.memory}
129+
extractValue={(bm) => bm.categories[MemoryBenchmarkCategory.Garbage]}
130+
formatValue={formatMemory}
131+
/>
132+
133+
<BenchmarkCategory
134+
name={"Runtime"}
135+
benchmarks={benchmarkData.runtime}
136+
extractValue={(bm) => bm.time}
137+
formatValue={formatTime}
138+
/>
115139
</>
116140
);
117141
}
118142

119-
function decodeBenchmarkData(encodedData: string) {
143+
interface BenchmarkData {
144+
[BenchmarkKind.Memory]: JoinResult<MemoryBenchmarkResult>[];
145+
[BenchmarkKind.Runtime]: JoinResult<RuntimeBenchmarkResult>[];
146+
}
147+
148+
function isMemoryBenchmarkJoinResult(r: JoinResult<BenchmarkResult>): r is JoinResult<MemoryBenchmarkResult> {
149+
return isMemoryBenchmarkResult(r.left || r.right!);
150+
}
151+
152+
function isRuntimeBenchmarkJoinResult(r: JoinResult<BenchmarkResult>): r is JoinResult<RuntimeBenchmarkResult> {
153+
return isRuntimeBenchmarkResult(r.left || r.right!);
154+
}
155+
156+
function decodeBenchmarkData(encodedData: string): BenchmarkData {
120157
const results = JSON.parse(zlib.inflateSync(Buffer.from(encodedData, "base64")).toString());
121158

122159
const dataMaster = results.old as BenchmarkResult[];
123160
const dataCommit = results.new as BenchmarkResult[];
124161

125162
// Match old/new results by name
126-
return joinOnProperty(dataMaster, dataCommit, (bm) => bm.benchmarkName);
163+
const joined = joinOnProperty(dataMaster, dataCommit, (bm) => bm.benchmarkName);
164+
return {
165+
[BenchmarkKind.Memory]: joined.filter(isMemoryBenchmarkJoinResult),
166+
[BenchmarkKind.Runtime]: joined.filter(isRuntimeBenchmarkJoinResult),
167+
};
127168
}
Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,31 @@
11
export enum BenchmarkKind {
22
Memory = "memory",
3+
Runtime = "runtime",
34
}
45

5-
export type BenchmarkResult = MemoryBenchmarkResult;
6+
export type BenchmarkResult = MemoryBenchmarkResult | RuntimeBenchmarkResult;
67

78
export enum MemoryBenchmarkCategory {
89
TotalMemory = "totalMemory",
910
Garbage = "garbage",
1011
}
1112

1213
export interface MemoryBenchmarkResult {
13-
kind: string;
14+
kind: BenchmarkKind.Memory;
1415
categories: Record<MemoryBenchmarkCategory, number>;
1516
benchmarkName: string;
1617
}
18+
19+
export function isMemoryBenchmarkResult(result: BenchmarkResult): result is MemoryBenchmarkResult {
20+
return result.kind === BenchmarkKind.Memory;
21+
}
22+
23+
export interface RuntimeBenchmarkResult {
24+
kind: BenchmarkKind.Runtime;
25+
time: number;
26+
benchmarkName: string;
27+
}
28+
29+
export function isRuntimeBenchmarkResult(result: BenchmarkResult): result is RuntimeBenchmarkResult {
30+
return result.kind === BenchmarkKind.Runtime;
31+
}

src/pages/benchviz/visualizations/positive-negative-bar-graph.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export function positiveNegativeBarGraph(
5353
//.style("stroke", "currentColor");
5454

5555
bars.append("text")
56-
.text((d) => `${d.value > 0 ? "+" : ""}${Math.round(d.value * 100) / 100}%`)
56+
.text((d) => `${d.value > 0 ? "+" : ""}${d.value.toFixed(2)}%`)
5757
.attr("x", (d) => xScale(d.name)! + 0.5 * bandWidth)
5858
.attr("y", (d) => (d.value > 0 ? height / 2 + barSize(d.value) + 30 : height / 2 - barSize(d.value)))
5959
.style("fill", "currentColor")

0 commit comments

Comments
 (0)