Mercurial > repos > public > sbplib_julia
comparison benchmark/benchmark_utils.jl @ 1361:689978b1ef16
Merge tooling/benchmarks
author | Vidar Stiernström <vidar.stiernstrom@it.uu.se> |
---|---|
date | Sun, 21 May 2023 20:48:46 +0200 |
parents | 42738616422e |
children | 5193e6cd6c6a |
comparison
equal
deleted
inserted
replaced
1354:150313ed2cae | 1361:689978b1ef16 |
---|---|
1 import PkgBenchmark | |
2 import Markdown | |
3 import Mustache | |
4 import Dates | |
5 | |
6 import Sbplib | |
7 | |
8 const sbplib_root = splitpath(pathof(Sbplib))[1:end-2] |> joinpath | |
9 const results_dir = mkpath(joinpath(sbplib_root, "benchmark/results")) | |
10 const template_path = joinpath(sbplib_root, "benchmark/result.tmpl") | |
11 | |
12 """ | |
13 mainmain(;rev=nothing, target=nothing, baseline=nothing , kwargs...) | |
14 | |
15 Calls `run_benchmark(args...; kwargs...)` and writes the results as an HTML | |
16 file in `benchmark/results`. | |
17 | |
18 * If `rev` is set, the benchmarks are run for the given mercurial revision. | |
19 * If only `baseline` is set, the current working directory is compared with | |
20 the revision given in `baseline`. | |
21 * If both `target` and `baseline` is set those revision are compared. | |
22 | |
23 For control over what happens to the benchmark result datastructure see the | |
24 different methods of [`run_benchmark`](@ref) | |
25 """ | |
26 function main(;rev=nothing, target=nothing, baseline=nothing , kwargs...) | |
27 if !isnothing(rev) | |
28 r = run_benchmark(rev; kwargs...) | |
29 elseif !isnothing(baseline) | |
30 if isnothing(target) | |
31 r = compare_benchmarks(baseline; kwargs...) | |
32 else | |
33 r = compare_benchmarks(target, baseline; kwargs...) | |
34 end | |
35 else | |
36 # Neither rev, or baseline were set => Run on current working directory. | |
37 r = run_benchmark(;kwargs...) | |
38 end | |
39 | |
40 file_path = write_result_html(r) | |
41 open_in_default_browser(file_path) | |
42 end | |
43 | |
44 | |
45 """ | |
46 run_benchmark() | |
47 | |
48 Run the benchmark suite for the current working directory and return a | |
49 `PkgBenchmark.BenchmarkResult` | |
50 """ | |
51 function run_benchmark(;kwargs...) | |
52 r = PkgBenchmark.benchmarkpkg(Sbplib; kwargs...) | |
53 | |
54 rev = hg_rev() # Should be changed to hg_id() when the html can handle it. | |
55 | |
56 return add_rev_info(r, rev) | |
57 end | |
58 | |
59 """ | |
60 run_benchmark(rev) | |
61 | |
62 Updates the repository to the given revison and runs the benchmark suite. When | |
63 done, reverts the repository to the original state. `rev` can be any | |
64 identifier compatible with `hg update`. | |
65 | |
66 Returns a `PkgBenchmark.BenchmarkResult` | |
67 """ | |
68 function run_benchmark(rev; kwargs...) | |
69 return hg_at_revision(rev) do | |
70 run_benchmark(; kwargs...) | |
71 end | |
72 end | |
73 | |
74 """ | |
75 compare_benchmarks(target, baseline, f=minimum; judgekwargs=Dict()) | |
76 | |
77 Runs the benchmark at revisions `target` and `baseline` and compares them | |
78 using `PkgBenchmark.judge`. `f` is the function used to compare. `judgekwargs` | |
79 are keyword arguments passed to `judge`. | |
80 | |
81 `target` and `baseline` can be any identifier compatible with `hg update`. | |
82 | |
83 Returns a `PkgBenchmark.BenchmarkJudgement` | |
84 """ | |
85 function compare_benchmarks(target, baseline, f=minimum; judgekwargs=Dict(), kwargs...) | |
86 t = run_benchmark(target; kwargs...) | |
87 b = run_benchmark(baseline; kwargs...) | |
88 | |
89 return PkgBenchmark.judge(t,b,f; judgekwargs...) | |
90 end | |
91 | |
92 """ | |
93 compare_benchmarks(baseline, ...) | |
94 | |
95 Compare the results at the current working directory with the revision | |
96 specified in `baseline`. | |
97 | |
98 Accepts the same arguments as the two revision version. | |
99 """ | |
100 function compare_benchmark(baseline, f=minimum; judgekwargs=Dict(), kwargs...) | |
101 t = run_benchmark(;kwargs...) | |
102 b = run_benchmark(baseline; kwargs...) | |
103 | |
104 return PkgBenchmark.judge(t,b,f; judgekwargs...) | |
105 end | |
106 | |
107 | |
108 function add_rev_info(benchmarkresult, rev) | |
109 if endswith(rev,"+") | |
110 revstr = "+$rev" # Workaround for the bad presentation of BenchmarkResults. | |
111 else | |
112 revstr = rev | |
113 end | |
114 | |
115 return PkgBenchmark.BenchmarkResults( | |
116 benchmarkresult.name, | |
117 revstr, | |
118 benchmarkresult.benchmarkgroup, | |
119 benchmarkresult.date, | |
120 benchmarkresult.julia_commit, | |
121 benchmarkresult.vinfo, | |
122 benchmarkresult.benchmarkconfig, | |
123 ) | |
124 end | |
125 | |
126 | |
127 function write_result_html(io, r) | |
128 iobuffer = IOBuffer() | |
129 PkgBenchmark.export_markdown(iobuffer, r) | |
130 | |
131 parsed_md = Markdown.parse(String(take!(iobuffer))) | |
132 content = Markdown.html(parsed_md) | |
133 | |
134 template = Mustache.load(template_path) | |
135 | |
136 dt = Dates.format(PkgBenchmark.date(r), "yyyy-mm-dd HH:MM:SS") | |
137 Mustache.render(io, template, Dict("title"=>dt, "content"=>content)) | |
138 end | |
139 | |
140 function write_result_html(r) | |
141 dt = Dates.format(PkgBenchmark.date(r), "yyyy-mm-dd HHMMSS") | |
142 file_path = joinpath(results_dir, dt*".html") | |
143 | |
144 open(file_path, "w") do io | |
145 write_result_html(io, r) | |
146 end | |
147 | |
148 return file_path | |
149 end | |
150 | |
151 | |
152 PkgBenchmark.date(j::PkgBenchmark.BenchmarkJudgement) = PkgBenchmark.date(PkgBenchmark.target_result(j)) | |
153 | |
154 | |
155 function hg_id() | |
156 cmd = Cmd(`hg id`, dir=sbplib_root) | |
157 return readchomp(addenv(cmd, "HGPLAIN"=>"")) | |
158 end | |
159 | |
160 function hg_rev() | |
161 cmd = Cmd(`hg id -i`, dir=sbplib_root) | |
162 return readchomp(addenv(cmd, "HGPLAIN"=>"")) | |
163 end | |
164 | |
165 function hg_update(rev) | |
166 cmd = Cmd(`hg update --check -r $rev`, dir=sbplib_root) | |
167 run(addenv(cmd, "HGPLAIN"=>"")) | |
168 | |
169 return nothing | |
170 end | |
171 | |
172 """ | |
173 hg_commit(msg; secret=false) | |
174 | |
175 Make a hg commit with the provided message. If `secret` is true the commit is | |
176 in the secret phase stopping it from being pushed. | |
177 """ | |
178 function hg_commit(msg; secret=false) | |
179 if secret | |
180 cmd = Cmd(`hg commit --verbose --secret --message $msg`, dir=sbplib_root) | |
181 else | |
182 cmd = Cmd(`hg commit --verbose --message $msg`, dir=sbplib_root) | |
183 end | |
184 | |
185 out = readchomp(addenv(cmd, "HGPLAIN"=>"")) | |
186 | |
187 return only(match(r"committed changeset \d+:([0-9a-z]+)", out)) | |
188 end | |
189 | |
190 """ | |
191 hg_strip(rev; keep=false) | |
192 | |
193 Strips the given commit from the repo. If `keep` is true, the changes of the | |
194 commit are kept in the working directory. | |
195 """ | |
196 function hg_strip(rev; keep=false) | |
197 if keep | |
198 cmd = Cmd(`hg --config extensions.strip= strip --keep -r $rev`, dir=sbplib_root) | |
199 else | |
200 cmd = Cmd(`hg --config extensions.strip= strip -r $rev`, dir=sbplib_root) | |
201 end | |
202 | |
203 run(addenv(cmd, "HGPLAIN"=>"")) | |
204 | |
205 return nothing | |
206 end | |
207 | |
208 """ | |
209 hg_is_dirty() | |
210 | |
211 Return true if the repositopry has uncommited changes. | |
212 """ | |
213 function hg_is_dirty() | |
214 cmd = Cmd(`hg identify --id`, dir=sbplib_root) | |
215 out = readchomp(addenv(cmd, "HGPLAIN"=>"")) | |
216 | |
217 return endswith(out, "+") | |
218 end | |
219 | |
220 """ | |
221 hg_at_revision(f, rev) | |
222 | |
223 Update the repository to the given revision and run the function `f`. After | |
224 `f` is run the working directory is restored. If there are uncommited changes | |
225 a temporary commit will be used to save the state of the working directory. | |
226 """ | |
227 function hg_at_revision(f, rev) | |
228 if hg_is_dirty() | |
229 hg_with_temporary_commit() do | |
230 return _hg_at_revision(f, rev) | |
231 end | |
232 else | |
233 return _hg_at_revision(f, rev) | |
234 end | |
235 end | |
236 | |
237 function _hg_at_revision(f, rev) | |
238 @assert !hg_is_dirty() | |
239 | |
240 origin_rev = hg_rev() | |
241 | |
242 hg_update(rev) | |
243 try | |
244 return f() | |
245 finally | |
246 hg_update(origin_rev) | |
247 end | |
248 end | |
249 | |
250 """ | |
251 hg_with_temporary_commit(f) | |
252 | |
253 Run the function `f` after making a temporary commit with the current working | |
254 directory. After `f` has finished the working directory is restored to its | |
255 original state and the temporary commit stripped. | |
256 """ | |
257 function hg_with_temporary_commit(f) | |
258 @assert hg_is_dirty() | |
259 | |
260 origin_rev = hg_commit("[Automatic commit by julia]",secret=true) | |
261 | |
262 try | |
263 return f() | |
264 finally | |
265 hg_update(origin_rev) | |
266 hg_strip(origin_rev; keep=true) | |
267 end | |
268 end | |
269 | |
270 | |
271 # From Pluto.jl/src/webserver/WebServer.jl (2023-01-24) | |
272 function open_in_default_browser(url::AbstractString)::Bool | |
273 try | |
274 if Sys.isapple() | |
275 Base.run(`open $url`) | |
276 true | |
277 elseif Sys.iswindows() || detectwsl() | |
278 Base.run(`powershell.exe Start "'$url'"`) | |
279 true | |
280 elseif Sys.islinux() | |
281 Base.run(`xdg-open $url`) | |
282 true | |
283 else | |
284 false | |
285 end | |
286 catch ex | |
287 false | |
288 end | |
289 end | |
290 | |
291 | |
292 main | |
293 | |
294 # TODO: Better logging of what is happening | |
295 # TODO: Improve the workflow? How? | |
296 | |
297 # TODO: Clean up the HTML output? | |
298 # TODO: Make the codeblocks in the table look nicer | |
299 # TODO: Change width of tables and code blocks so everything is visible | |
300 # TODO: Fix the commit id, it chops off all the important info | |
301 # TODO: Make title less verbose | |
302 # TBD: Do we have to replace export_markdown? Could use a template instead. | |
303 | |
304 # Should be able to run the current benchmark script at a different revision. | |
305 # Should have a way to filter the benchmark suite | |
306 | |
307 # TBD: What parts are PkgBenchmark contributing? Can it be stripped out? | |
308 | |
309 | |
310 ## Catching the exit code and errors from a command can be done with code similar to | |
311 # proc = open(cmd) | |
312 # if success(proc) | |
313 | |
314 # else | |
315 | |
316 # end |