OILS / benchmarks / uftrace.sh View on Github | oilshell.org

349 lines, 176 significant
1#!/usr/bin/env bash
2#
3# Usage:
4# benchmarks/uftrace.sh <function name>
5#
6# Examples:
7# benchmarks/uftrace.sh record-oils-cpp
8# benchmarks/uftrace.sh replay-alloc
9# benchmarks/uftrace.sh plugin-allocs
10#
11# TODO:
12# - uftrace dump --chrome # time-based trace
13# - uftrace dump --flame-graph # common stack traces, e.g. for allocation
14
15set -o nounset
16set -o pipefail
17set -o errexit
18
19source benchmarks/common.sh # cmark function. TODO: could use executable
20source build/dev-shell.sh # put uftrace in $PATH, R_LIBS_USER
21source devtools/common.sh # banner
22source test/common.sh # escape-html
23
24readonly BASE_DIR=_tmp/uftrace
25
26download() {
27 wget --no-clobber --directory _cache \
28 https://github.com/namhyung/uftrace/archive/refs/tags/v0.13.tar.gz
29 #https://github.com/namhyung/uftrace/archive/v0.9.3.tar.gz
30
31}
32
33extract() {
34 pushd _cache
35 tar --extract -z < v0.13.tar.gz
36 popd
37}
38
39build() {
40 cd _cache/uftrace-0.13
41 ./configure
42 make
43
44 # It can't find some files unless we do this
45 echo 'Run sudo make install'
46}
47
48ubuntu-hack() {
49 # Annoying: the plugin engine tries to look for the wrong file?
50 # What's 3.6m.so vs 3.6.so ???
51
52 cd /usr/lib/x86_64-linux-gnu
53 ln -s libpython3.6m.so.1.0 libpython3.6.so
54}
55
56# https://github.com/namhyung/uftrace/wiki/Tutorial
57hello-demo() {
58 cat >_tmp/hello.c <<EOF
59#include <stdio.h>
60
61int main(void) {
62 printf("Hello world\n");
63 return 0;
64}
65EOF
66
67 gcc -o _tmp/hello -pg _tmp/hello.c
68
69 uftrace _tmp/hello
70}
71
72record-oils-cpp() {
73 ### Record a trace, but limit to allocations functions, for size
74
75 local out_dir=$1
76 local unfiltered=${2:-}
77 shift 2
78
79 #local flags=(-F process::Process::RunWait -F process::Process::Process)
80
81 local -a flags
82
83 if test -n "$unfiltered"; then
84 out_dir=$out_dir.unfiltered
85
86 # Look for the pattern:
87 # Alloc() {
88 # MarkSweepHeap::Allocate(24)
89 # syntax_asdl::line_span::line_span()
90 # }
91 flags=(
92 -F 'Alloc'
93 -F 'MarkSweepHeap::Allocate' -A 'MarkSweepHeap::Allocate@arg2'
94 -D 2
95 )
96 # If we don't filter at all, then it's huge
97 # flags=()
98
99 else
100 # It's faster to filter just these function calls
101 # Need .* for --demangle full
102
103 flags=(
104 # low level allocation
105 -F 'MarkSweepHeap::Allocate.*' -A 'MarkSweepHeap::Allocate.*@arg2'
106
107 # typed allocation
108 -F 'Alloc<.*' # missing type info
109
110 # Flexible array allocation
111 # arg 1 is str_len
112 -F 'NewStr.*' -A 'NewStr.*@arg1'
113 -F 'OverAllocatedStr.*' -A 'OverAllocatedStr.*@arg1'
114
115 # This constructor doesn't matter. We care about the interface in in
116 # mycpp/gc_alloc.h
117 # -F 'Str::Str.*'
118
119 # arg1 is number of elements of type T
120 -F 'NewSlab<.*' -A 'NewSlab<.*@arg1'
121 # -F 'Slab<.*>::Slab.*'
122
123 # Fixed size header allocation
124 # arg2 is the number of items to reserve
125 # -F 'List<.*>::List.*'
126 -F 'List<.*>::reserve.*' -A 'List<.*>::reserve.*@arg2'
127 # -F 'Dict<.*>::Dict.*' # does not allocate
128 -F 'Dict<.*>::reserve.*' -A 'Dict<.*>::reserve.*@arg2'
129
130 # Common object
131 # -F 'syntax_asdl::Token::Token'
132
133 -D 1
134 )
135
136 # Problem: some of these aren't allocations
137 # -F 'Tuple2::Tuple2'
138 # -F 'Tuple3::Tuple3'
139 # -F 'Tuple4::Tuple4'
140
141 # StrFromC calls NewStr, so we don't need it
142 # -F 'StrFromC' -A 'StrFromC@arg1' -A 'StrFromC@arg2'
143 fi
144
145 soil/cpp-tarball.sh build-like-ninja uftrace
146 local bin=_bin/cxx-uftrace/osh
147
148 mkdir -p $out_dir
149 time uftrace record --demangle full -d $out_dir "${flags[@]}" $bin "$@"
150
151 ls -d $out_dir/
152 ls -l --si $out_dir/
153}
154
155run-tasks() {
156 while read task; do
157 banner "$task: utrace record"
158
159 # TODO: Could share with benchmarks/gc
160 case $task in
161 parse.configure-cpython)
162 data_file='Python-2.7.13/configure'
163 ;;
164 parse.abuild)
165 data_file='benchmarks/testdata/abuild'
166 ;;
167 esac
168
169 # Construct argv for each task
170 local -a argv
171 case $task in
172 parse.*)
173 argv=( --ast-format none -n $data_file )
174 ;;
175
176 ex.compute-fib)
177 argv=( benchmarks/compute/fib.sh 10 44 )
178 ;;
179
180 ex.bashcomp-excerpt)
181 # NOTE: benchmarks/gc.sh uses the larger clang.txt file
182 argv=( benchmarks/parse-help/pure-excerpt.sh parse_help_file
183 benchmarks/parse-help/mypy.txt )
184 ;;
185
186 ex.bin-true)
187 argv=( testdata/osh-runtime/bin_true.sh )
188 ;;
189
190 esac
191
192 local out_dir=$BASE_DIR/raw/$task
193
194 record-oils-cpp $out_dir '' "${argv[@]}"
195 done
196}
197
198print-tasks() {
199 # Same as benchmarks/gc
200 local -a tasks=(
201 # This one is a bit big
202 # parse.configure-cpython
203
204 parse.abuild
205 ex.bashcomp-excerpt
206 ex.compute-fib
207 ex.bin-true
208 )
209
210 for task in "${tasks[@]}"; do
211 echo $task
212 done
213}
214
215measure-all() {
216 print-tasks | run-tasks
217}
218
219frequent-calls() {
220 ### Histogram
221
222 local out_dir=$1
223 uftrace report -d $out_dir -s call --demangle full
224}
225
226call-graph() {
227 ### Time-based trace
228
229 local out_dir=$1
230 uftrace graph -d $out_dir
231}
232
233tsv-plugin() {
234 local task=${1:-ex.compute-fib}
235
236 local dir=$BASE_DIR/raw/$task
237
238 # On the big configure-coreutils script, this takes 10 seconds. That's
239 # acceptable. Gives 2,402,003 allocations.
240
241 local out_dir=_tmp/uftrace/stage1/$task
242 mkdir -p $out_dir
243 time uftrace script --demangle full -d $dir -S benchmarks/uftrace_allocs.py $out_dir
244
245 wc -l $out_dir/*.tsv
246}
247
248report-all() {
249 print-tasks | while read task; do
250 banner "$task: report"
251
252 frequent-calls $BASE_DIR/raw/$task
253
254 echo
255 done
256}
257
258export-all() {
259 if uftrace --version | grep python3; then
260 echo 'uftrace has Python 3 plugin support'
261 else
262 die 'uftrace is MISSING Python 3 plugin support'
263 fi
264
265 # TODO: Join into a single TSV file
266 print-tasks | while read task; do
267 banner "$task: export to TSV with Python3 plugin"
268 time tsv-plugin $task
269 done
270}
271
272html-index() {
273 echo '<body style="margin: 0 auto; width: 40em; font-size: large">'
274
275 cmark << 'EOF'
276# uftrace reports
277
278Workloads:
279EOF
280
281 # Link to text files
282 print-tasks | while read task; do
283 echo "<a href="stage2/$task.txt">$task</a> <br/>"
284 done
285
286 cmark <<< '## Summary'
287
288 echo '<pre>'
289
290 cat $BASE_DIR/stage2/summary.txt | escape-html
291
292 echo '</pre>'
293 echo '</body>'
294}
295
296analyze-all() {
297 local in_dir=$BASE_DIR/stage1/
298 local out_dir=$BASE_DIR/stage2/
299
300 # prepare dirs for R to write to
301 print-tasks | while read task; do
302 mkdir -v -p $out_dir/$task
303 done
304
305 # Writes stage2/summary.txt
306 benchmarks/report.R uftrace $in_dir $out_dir
307
308 html-index > $BASE_DIR/index.html
309 echo "Wrote $BASE_DIR/index.html"
310}
311
312
313# Hm this shows EVERY call stack that produces a list!
314
315# uftrace graph usage shown here
316# https://github.com/namhyung/uftrace/wiki/Tutorial
317
318replay-alloc() {
319 local out_dir=$1
320
321 # call graph
322 #uftrace graph -C 'MarkSweepHeap::Allocate'
323
324 # shows what calls this function
325 #uftrace replay -C 'MarkSweepHeap::Allocate'
326
327 # shows what this function calls
328 #uftrace replay -F 'MarkSweepHeap::Allocate'
329
330 # filters may happen at record or replay time
331
332 # depth of 1
333 #uftrace replay -D 1 -F 'MarkSweepHeap::Allocate'
334
335 uftrace replay -D 1 -F 'MarkSweepHeap::Allocate'
336}
337
338plugin() {
339 # Note this one likes UNFILTERED data
340 uftrace script -S benchmarks/uftrace_plugin.py
341}
342
343soil-run() {
344 measure-all
345 export-all
346 analyze-all
347}
348
349"$@"