Skip to content

Commit 2c4c0dd

Browse files
committed
Show plots in the collection page
1 parent 15032aa commit 2c4c0dd

File tree

9 files changed

+290
-27
lines changed

9 files changed

+290
-27
lines changed

lib/bench.ml

+46
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,49 @@ let group_by f lst =
1616
| None -> Hashtbl.add tbl key [ item ])
1717
lst;
1818
Hashtbl.fold (fun key group acc -> (key, List.rev group) :: acc) tbl []
19+
20+
module Json = struct
21+
let of_value = function
22+
| Int value -> float_of_int value
23+
| Float value -> value
24+
| Bytes _ -> 0. (* FIXME: Bytes to Float?! *)
25+
26+
let of_result result =
27+
`Assoc
28+
[
29+
("timestamp", `Float result.timestamp);
30+
("value", `Float (of_value result.value));
31+
]
32+
33+
let of_test (test : test) =
34+
`Assoc
35+
[
36+
("name", `String test.name);
37+
( "results",
38+
`List
39+
(test.results
40+
|> List.sort (fun a b -> compare a.timestamp b.timestamp)
41+
|> List.map of_result) );
42+
]
43+
44+
let of_group (group : group) =
45+
`Assoc
46+
[
47+
("name", `String group.name);
48+
("tests", `List (List.map of_test group.tests));
49+
]
50+
51+
let of_collection (collection : collection) =
52+
`Assoc
53+
[
54+
("name", `String collection.name);
55+
("groups", `List (List.map of_group collection.groups));
56+
]
57+
58+
let of_project project =
59+
`Assoc
60+
[
61+
("name", `String project.name);
62+
("groups", `List (List.map of_collection project.collections));
63+
]
64+
end

lib/dune

+9
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,18 @@
1818
(echo "let chart_js = {js|")
1919
(cat html-assets/chart.min.js)
2020
(echo "|js}\n")
21+
(echo "let moment_js = {js|")
22+
(cat html-assets/moment.min.js)
23+
(echo "|js}\n")
24+
(echo "let adapter_moment_js = {js|")
25+
(cat html-assets/chartjs-adapter-moment.min.js)
26+
(echo "|js}\n")
2127
(echo "let alpine_js = {js|")
2228
(cat html-assets/alpine.min.js)
2329
(echo "|js}\n")
30+
(echo "let plot_js = {js|")
31+
(cat html-assets/plot.js)
32+
(echo "|js}\n")
2433
(echo "let logo_with_name_svg = {js|")
2534
(cat html-assets/logo-with-name.svg)
2635
(echo "|js}\n")

lib/html-assets/chartjs-adapter-moment.min.js

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/html-assets/collection.html

+150-12
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,150 @@
1-
{{ collection_name }}
2-
<ul>
3-
{{#groups}}
4-
<li>{{ name }}
5-
<ol>
6-
{{#tests}}
7-
<li>{{ . }}</li>
8-
{{/tests}}
9-
</ol>
10-
</li>
11-
{{/groups}}
12-
</ul>
1+
<!DOCTYPE html>
2+
<html lang="en" class="h-full bg-white">
3+
4+
<head>
5+
<meta charset="UTF-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<title>{{project_name}} -- Caliper Dashboard</title>
8+
<link rel="stylesheet" href="../main.css">
9+
<script src="../chart.min.js"></script>
10+
<script src="../alpine.min.js" defer></script>
11+
<script src="../moment.min.js"></script>
12+
<script src="../chartjs-adapter-moment.min.js"></script>
13+
<script src="../plot.js" defer></script>
14+
</head>
15+
16+
<body class="h-full">
17+
<div class="bg-white">
18+
<div x-data="{ open: false }" @keydown.window.escape="open = false">
19+
20+
<div x-show="open" class="relative z-50 lg:hidden"
21+
x-description="Off-canvas menu for mobile, show/hide based on off-canvas menu state." x-ref="dialog"
22+
aria-modal="true" style="display: none;">
23+
24+
<div x-show="open" x-transition:enter="transition-opacity ease-linear duration-300"
25+
x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100"
26+
x-transition:leave="transition-opacity ease-linear duration-300" x-transition:leave-start="opacity-100"
27+
x-transition:leave-end="opacity-0" class="fixed inset-0 bg-gray-900/80"
28+
x-description="Off-canvas menu backdrop, show/hide based on off-canvas menu state." style="display: none;">
29+
</div>
30+
31+
<div class="fixed inset-0 flex">
32+
33+
<div x-show="open" x-transition:enter="transition ease-in-out duration-300 transform"
34+
x-transition:enter-start="-translate-x-full" x-transition:enter-end="translate-x-0"
35+
x-transition:leave="transition ease-in-out duration-300 transform" x-transition:leave-start="translate-x-0"
36+
x-transition:leave-end="-translate-x-full"
37+
x-description="Off-canvas menu, show/hide based on off-canvas menu state."
38+
class="relative mr-16 flex w-full max-w-xs flex-1" @click.away="open = false" style="display: none;">
39+
40+
<div x-show="open" x-transition:enter="ease-in-out duration-300" x-transition:enter-start="opacity-0"
41+
x-transition:enter-end="opacity-100" x-transition:leave="ease-in-out duration-300"
42+
x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0"
43+
x-description="Close button, show/hide based on off-canvas menu state."
44+
class="absolute left-full top-0 flex w-16 justify-center pt-5" style="display: none;">
45+
<button type="button" class="-m-2.5 p-2.5" @click="open = false">
46+
<span class="sr-only">Close sidebar</span>
47+
<svg class="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
48+
aria-hidden="true">
49+
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"></path>
50+
</svg>
51+
</button>
52+
</div>
53+
54+
<div class="flex grow flex-col gap-y-5 overflow-y-auto bg-white px-6 pb-2">
55+
<div class="flex h-16 shrink-0 items-center">
56+
<a href="../index.html">
57+
<img class="h-8 w-auto" src="../logo-with-name.svg" alt="Caliper">
58+
</a>
59+
</div>
60+
<nav class="flex flex-1 flex-col">
61+
<ul role="list" class="-mx-2 space-y-1">
62+
{{#projects}}
63+
<li>
64+
<a href="{{.}}.html"
65+
class="text-gray-700 hover:text-orange-600 hover:bg-gray-50 group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold"
66+
x-state:on="Current" x-state:off="Default"
67+
x-state-description="Current: &quot;bg-gray-50 text-orange-600&quot;, Default: &quot;text-gray-700 hover:text-orange-600 hover:bg-gray-50&quot;">
68+
<span
69+
class="flex h-6 w-6 shrink-0 items-center justify-center rounded-lg border text-[0.625rem] font-medium bg-white text-gray-400 border-gray-200 group-hover:border-orange-600 group-hover:text-orange-600">H</span>
70+
<span class="truncate">{{.}}</span>
71+
</a>
72+
</li>
73+
{{/projects}}
74+
</ul>
75+
</nav>
76+
</div>
77+
</div>
78+
79+
</div>
80+
</div>
81+
82+
<div class="hidden lg:fixed lg:inset-y-0 lg:z-50 lg:flex lg:w-72 lg:flex-col">
83+
<div class="flex grow flex-col gap-y-5 overflow-y-auto border-r border-gray-200 bg-white px-6">
84+
<div class="flex h-16 shrink-0 items-center">
85+
<a href="../index.html">
86+
<img class="h-8 w-auto" src="../logo-with-name.svg" alt="Caliper">
87+
</a>
88+
</div>
89+
<nav class="flex flex-1 flex-col">
90+
<ul role="list" class="-mx-2 space-y-1">
91+
{{#projects}}
92+
<li>
93+
<a href="{{.}}.html"
94+
class="text-gray-700 hover:text-orange-600 hover:bg-gray-50 group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold"
95+
x-state-description="undefined: &quot;bg-gray-50 text-orange-600&quot;, undefined: &quot;text-gray-700 hover:text-orange-600 hover:bg-gray-50&quot;">
96+
<span
97+
class="flex h-6 w-6 shrink-0 items-center justify-center rounded-lg border text-[0.625rem] font-medium bg-white text-gray-400 border-gray-200 group-hover:border-orange-600 group-hover:text-orange-600">W</span>
98+
<span class="truncate">{{.}}</span>
99+
</a>
100+
</li>
101+
{{/projects}}
102+
</ul>
103+
</nav>
104+
</div>
105+
</div>
106+
107+
<div class="sticky top-0 z-40 flex items-center gap-x-6 bg-white px-4 py-4 shadow-sm sm:px-6 lg:hidden">
108+
<button type="button" class="-m-2.5 p-2.5 text-gray-700 lg:hidden" @click="open = true">
109+
<span class="sr-only">Open sidebar</span>
110+
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
111+
aria-hidden="true">
112+
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5">
113+
</path>
114+
</svg>
115+
</button>
116+
<div class="flex-1 text-sm font-semibold leading-6 text-gray-900">Caliber</div>
117+
</div>
118+
119+
<main class="py-10 lg:pl-72">
120+
<div class="px-4 sm:px-6 lg:px-8">
121+
<div class="mx-auto max-w-auto text-center">
122+
<h2 class="mt-2 text-4xl font-bold tracking-tight text-gray-900 sm:text-6xl">{{project_name}} &ndash; {{collection_name}}</h2>
123+
<div class="max-w-auto mx-auto">
124+
<div class="caliper-collection" data-source="{{project_name}}--{{collection_name}}"/>
125+
126+
{{#groups}}
127+
<div>
128+
<h3 class="mt-2 text-xl font-bold tracking-tight text-gray-900 sm:text-4xl">{{name}}</h3>
129+
<div>
130+
{{#tests}}
131+
<div>
132+
<!-- <h4 class="mt-2 text font-bold tracking-tight text-gray-900 sm:text-2xl">{{.}}</h4> -->
133+
<div class="calipert-chart-container" style="height: 300px; width:80vw">
134+
<canvas class="caliper-plot" data-source="{{project_name}}--{{collection_name}}" data-test-name="{{.}}" data-group-name="{{name}}" />
135+
</div>
136+
</div>
137+
{{/tests}}
138+
</div>
139+
</div>
140+
{{/groups}}
141+
</div>
142+
</div>
143+
</div>
144+
</div>
145+
</main>
146+
</div>
147+
</div>
148+
</body>
149+
150+
</html>

lib/html-assets/moment.min.js

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/html-assets/plot.js

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
const plot = (el) => {
2+
const collectionName = el.dataset.source;
3+
const groupName = el.dataset.groupName;
4+
const testName = el.dataset.testName;
5+
6+
const data = window.dataset[collectionName];
7+
const groupData = data.groups.find((it) => it.name === groupName);
8+
const testData = groupData.tests.find((it) => it.name === testName);
9+
10+
const results = testData.results;
11+
const values = results.map((r) => r.value);
12+
const timestamps = results.map((r) => r.timestamp);
13+
14+
new Chart(el, {
15+
type: "line",
16+
data: {
17+
labels: timestamps.map((ts) => moment.unix(ts).toDate()), // Convert Unix seconds to Date objects
18+
datasets: [{ data: values, label: testName }],
19+
},
20+
options: {
21+
maintainAspectRatio: false,
22+
scales: {
23+
x: {
24+
type: "time",
25+
time: {
26+
displayFormats: {
27+
day: "MMM D, YYYY",
28+
},
29+
},
30+
},
31+
y: {
32+
beginAtZero: true,
33+
},
34+
},
35+
},
36+
});
37+
};
38+
39+
const fetchData = (el) => {
40+
const collectionName = el.dataset.source;
41+
const datasource = `${collectionName}.json`;
42+
return fetch(datasource)
43+
.then((response) => response.json())
44+
.then((data) => {
45+
window.dataset = { ...window.dataset, [collectionName]: data };
46+
})
47+
.catch((error) => console.error("Error fetching data:", error));
48+
};
49+
50+
const promises = Array.from(
51+
document.querySelectorAll(".caliper-collection"),
52+
).map(fetchData);
53+
54+
Promise.all(promises).then(() =>
55+
document.querySelectorAll(".caliper-plot").forEach(plot),
56+
);

lib/html.ml

+17
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,27 @@ let render_collection_html t project collection =
9696
Mustache.render template_collection_html
9797
Collection_mustache.(to_json (of_bench t project collection))
9898

99+
let write_collection_data ~project_name project_dir
100+
(collection : Bench.collection) =
101+
let filename = project_name ^ "--" ^ collection.name ^ ".json" in
102+
let filepath = Filename.concat project_dir filename in
103+
let json = Bench.Json.of_collection collection in
104+
let pp = Yojson.Safe.pretty_to_channel ~std:false in
105+
Out_channel.with_open_text filepath (fun oc -> pp oc json)
106+
99107
let generate root t =
100108
if not (Sys.file_exists root) then Unix.mkdir root 0o755;
101109
Out_channel.with_open_text (Filename.concat root "chart.min.js") (fun oc ->
102110
Out_channel.output_string oc Asset.chart_js);
111+
Out_channel.with_open_text (Filename.concat root "moment.min.js") (fun oc ->
112+
Out_channel.output_string oc Asset.moment_js);
113+
Out_channel.with_open_text
114+
(Filename.concat root "chartjs-adapter-moment.min.js") (fun oc ->
115+
Out_channel.output_string oc Asset.adapter_moment_js);
103116
Out_channel.with_open_text (Filename.concat root "alpine.min.js") (fun oc ->
104117
Out_channel.output_string oc Asset.alpine_js);
118+
Out_channel.with_open_text (Filename.concat root "plot.js") (fun oc ->
119+
Out_channel.output_string oc Asset.plot_js);
105120
Out_channel.with_open_text (Filename.concat root "logo-with-name.svg")
106121
(fun oc -> Out_channel.output_string oc Asset.logo_with_name_svg);
107122
Out_channel.with_open_text (Filename.concat root "main.css") (fun oc ->
@@ -128,6 +143,8 @@ let generate root t =
128143
Filename.concat project_dir_root
129144
(project.name ^ "--" ^ collection.name ^ ".html")
130145
in
146+
write_collection_data ~project_name:project.name project_dir_root
147+
collection;
131148
Out_channel.with_open_text filepath (fun oc ->
132149
Out_channel.output_string oc
133150
(render_collection_html t project collection)))

test/generate-html-dashboard.t/run.t

+3
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ Generate an HTML page with the benchmark history.
55
_
66
alpine.min.js
77
chart.min.js
8+
chartjs-adapter-moment.min.js
89
index.html
910
logo-with-name.svg
1011
main.css
12+
moment.min.js
13+
plot.js
1114
$ cat html-output/index.html
1215
<!DOCTYPE html>
1316
<html lang="en" class="h-full bg-white">

test/parse-cb-json.t/run.t

-15
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,6 @@
11
Parse Current Bench JSON data and generate a cache directory.
22

33
$ caliper parse-cb-json
4-
1706661699.66
5-
1706396887.2
6-
1706385451.14
7-
1706623106.99
8-
1706604852.64
9-
1706380431.55
10-
1706348118.96
11-
1706668348.24
12-
1706469136.66
13-
1706342172.47
14-
1706529029.87
15-
1706416295.31
16-
1706675457.2
17-
1706626080.56
18-
1706507744.62
194
$ ls cache/sample/default | wc -l
205
15
216
$ cat cache/sample/default/1706675457.2

0 commit comments

Comments
 (0)