OILS / test / syscall.sh View on Github | oilshell.org

475 lines, 205 significant
1#!/usr/bin/env bash
2#
3# Measure the number of syscalls that shells use.
4#
5# Usage:
6# test/syscall.sh <function name>
7
8: ${LIB_OSH=stdlib/osh}
9source $LIB_OSH/bash-strict.sh
10source $LIB_OSH/task-five.sh
11
12source build/dev-shell.sh
13
14OSH=${OSH:-osh}
15YSH=${YSH:-ysh}
16
17# Compare bash 4 vs. bash 5
18#readonly -a SHELLS=(dash bash-4.4 bash $OSH)
19#readonly -a SHELLS=(dash bash-4.4 bash-5.2.21 mksh zsh ash yash $OSH)
20
21# Remove yash since functions are over-optimized - by-code.wrapped
22readonly -a SHELLS=(dash bash-4.4 bash-5.2.21 mksh zsh ash $OSH)
23
24readonly BASE_DIR='_tmp/syscall' # What we'll publish
25readonly RAW_DIR='_tmp/syscall-raw' # Raw data
26
27# Run it against the dev version of OSH
28REPO_ROOT=$(cd "$(dirname $0)/.."; pwd)
29
30count-procs() {
31 local out_prefix=$1
32 local sh=$2
33 shift 2
34
35 case $sh in
36 # avoid the extra processes that bin/osh starts!
37 # relies on word splitting
38 #(X) # to compare against osh 0.8.pre3 installed
39 osh)
40 sh="env PYTHONPATH=$REPO_ROOT:$REPO_ROOT/vendor $REPO_ROOT/bin/oils_for_unix.py osh"
41 ;;
42 ysh)
43 sh="env PYTHONPATH=$REPO_ROOT:$REPO_ROOT/vendor $REPO_ROOT/bin/oils_for_unix.py ysh"
44 ;;
45 osh-cpp)
46 sh=_bin/cxx-dbg/osh
47 ;;
48 ysh-cpp)
49 sh=_bin/cxx-dbg/ysh
50 ;;
51 esac
52
53 # Ignore failure, because we are just counting
54 strace -ff -o $out_prefix -- $sh "$@" || true
55}
56
57run-case() {
58 ### Run a test case with many shells
59
60 local num=$1
61 local code_str=$2
62 local func_wrap=${3:-}
63
64 if test -n "$func_wrap"; then
65 code_str="wrapper() { $code_str; }; wrapper"
66 fi
67
68 for sh in "${SHELLS[@]}"; do
69 local out_prefix=$RAW_DIR/${sh}__${num}
70 echo "--- $sh"
71 count-procs $out_prefix $sh -c "$code_str"
72 done
73}
74
75run-case-file() {
76 ### Like the above, but the shell reads from a file
77
78 local num=$1
79 local code_str=$2
80
81 echo -n "$code_str" > _tmp/$num.sh
82
83 for sh in "${SHELLS[@]}"; do
84 local out_prefix=$RAW_DIR/${sh}__${num}
85 echo "--- $sh"
86 count-procs $out_prefix $sh _tmp/$num.sh
87 done
88}
89
90run-case-stdin() {
91 ### Like the above, but read from a pipe
92
93 local num=$1
94 local code_str=$2
95
96 for sh in "${SHELLS[@]}"; do
97 local out_prefix=$RAW_DIR/${sh}__${num}
98 echo "--- $sh"
99 echo -n "$code_str" | count-procs $out_prefix $sh
100 done
101}
102
103print-cases() {
104 # format: number, whitespace, then an arbitrary code string
105 egrep -v '^[[:space:]]*(#|$)' <<EOF
106
107# builtin
108echo hi
109
110# external command
111date
112
113# OSH calls this "sentence"
114date ;
115
116# trap - bash has special logic for this
117trap 'echo mytrap' EXIT; date
118
119# external then builtin
120date; echo hi
121
122# builtin then external
123echo hi; date
124
125# two external commands
126date; date
127
128# does a brace group make a difference?
129{ date; date; }
130
131# singleton brace group
132date; { date; }
133
134# does it behave differently if sourced?
135. _tmp/sourced.sh
136
137# dash and zsh somehow optimize this to 1
138(echo hi)
139
140(date)
141
142( ( date ) )
143
144( ( date ) ); echo hi
145
146echo hi; (date)
147
148# Sentence in Oil
149(date;) > /tmp/out.txt
150
151(date; echo hi)
152
153# command sub
154echo \$(date)
155
156# command sub with builtin
157echo \$(echo hi)
158
159# command sub with useless subshell (some scripts use this)
160echo \$( ( date ) )
161
162# command sub with other subshell
163echo \$( ( date ); echo hi )
164
165# 2 processes for all shells
166( echo hi ); echo done
167
168# simple pipeline
169date | wc -l
170
171# negated
172! date | wc -l
173
174# every shell does 3
175echo a | wc -l
176
177# every shell does 3
178command echo a | wc -l
179
180# bash does 4 here!
181command date | wc -l
182
183# negated
184! command date | wc -l
185
186# 3 processes for all?
187# osh gives FIVE??? But others give 3. That's bad.
188( date ) | wc -l
189
190# 3 processes for all shells except zsh and osh, which have shopt -s lastpipe!
191date | read x
192
193# osh has 3, but should be 2 like zsh?
194# hm how can zsh do 2 here? That seems impossible.
195# oh it's lastpipe turns the shell process into wc -l ??? wow.
196{ echo a; echo b; } | wc -l
197
198# zsh behaves normally here. That is a crazy optimization. I guess it's
199# nice when you have SH -c 'mypipeline | wc-l'
200{ echo a; echo b; } | wc -l; echo done
201
202# this is all over the map too. 3 4 4 2.
203{ echo a; date; } | wc -l
204
205# osh does 4 when others do 3. So every shell optimizes this extra pipeline.
206( echo a; echo b ) | wc -l
207
208# osh does 5 when others do 3.
209( echo a; echo b ) | ( wc -l )
210
211echo hi & wait
212
213date & wait
214
215echo hi | wc -l & wait
216
217date | wc -l & wait
218EOF
219
220# Discarded because they're identical
221# pipeline with redirect last
222#date | wc -l > /tmp/out.txt
223
224# pipeline with redirect first
225#date 2>&1 | wc -l
226
227}
228
229number-cases() {
230 # Right justified, leading zeros, with 2
231 # Wish this was %02d
232 print-cases | nl --number-format rz --number-width 2
233}
234
235by-input() {
236 ### Run cases that vary by input reader
237 if ! strace true; then
238 echo "Aborting because we couldn't run strace"
239 return
240 fi
241
242 local suite='by-input'
243
244 rm -r -f -v $RAW_DIR
245 mkdir -p $RAW_DIR $BASE_DIR
246
247 # Wow this newline makes a difference in shells!
248
249 # This means that Id.Eof_Real is different than Id.Op_Newline?
250 # Should we create a Sentence for it too then?
251 # That is possible in _ParseCommandLine
252
253 zero=$'date; date'
254 one=$'date; date\n'
255 two=$'date; date\n#comment\n'
256 comment=$'# comment\ndate;date'
257 newline=$'date\n\ndate'
258 newline2=$'date\n\ndate\n#comment'
259
260 # zsh is the only shell to optimize all 6 cases! 2 processes instead of 3.
261 run-case 30 "$zero"
262 run-case 31 "$one"
263 run-case 32 "$two"
264 run-case 33 "$comment"
265 run-case 34 "$newline"
266 run-case 35 "$newline2"
267
268 run-case-file 40 "$zero"
269 run-case-file 41 "$one"
270 run-case-file 42 "$two"
271 run-case-file 43 "$comment"
272 run-case-file 44 "$newline2"
273 run-case-file 45 "$newline2"
274
275 # yash is the only shell to optimize the stdin case at all!
276 # it looks for a lack of trailing newline.
277 run-case-stdin 50 "$zero"
278 run-case-stdin 51 "$one"
279 run-case-stdin 52 "$two"
280 run-case-stdin 53 "$comment"
281 run-case-stdin 54 "$newline2"
282 run-case-stdin 55 "$newline2"
283
284 # This is identical for all shells
285 #run-case 32 $'date; date\n#comment\n'
286
287 cat >$BASE_DIR/cases.${suite}.txt <<EOF
28830 -c: zero lines
28931 -c: one line
29032 -c: one line and comment
29133 -c: comment first
29234 -c: newline
29335 -c: newline2
29440 file: zero lines
29541 file: one line
29642 file: one line and comment
29743 file: comment first
29844 file: newline
29945 file: newline2
30050 stdin: zero lines
30151 stdin: one line
30252 stdin: one line and comment
30353 stdin: comment first
30454 stdin: newline
30555 stdin: newline2
306EOF
307
308 count-lines $suite
309 summarize $suite 3 0
310}
311
312# Quick hack: every shell uses 2 processes for this... doesn't illuminate much.
313weird-command-sub() {
314 shopt -s nullglob
315 rm -r -f -v $RAW_DIR/*
316
317 local tmp=_tmp/cs
318 echo FOO > $tmp
319 run-case 60 "echo $(< $tmp)"
320 run-case 61 "echo $(< $tmp; echo hi)"
321
322 local suite=weird-command-sub
323
324 cat >$BASE_DIR/cases.${suite}.txt <<EOF
32560 \$(< file)
32661 \$(< file; echo hi)
327EOF
328
329 count-lines $suite
330 summarize $suite 0 0
331}
332
333readonly MAX_CASES=100
334#readonly MAX_CASES=3
335
336by-code() {
337 ### Run cases that vary by code snippet
338 local func_wrap=${1:-}
339
340 if ! strace true; then
341 echo "Aborting because we couldn't run strace"
342 return
343 fi
344
345 local max_cases=${1:-$MAX_CASES}
346
347 rm -r -f -v $RAW_DIR
348 mkdir -p $RAW_DIR $BASE_DIR
349
350 write-sourced
351
352 local suite
353 if test -n "$func_wrap"; then
354 suite='by-code-wrapped'
355 else
356 suite='by-code'
357 fi
358
359 local cases=$BASE_DIR/cases.${suite}.txt
360
361 number-cases > $cases
362 head -n $max_cases $cases | while read -r num code_str; do
363 echo
364 echo '==='
365 echo "$num $code_str"
366 echo
367
368 run-case $num "$code_str" "$func_wrap"
369 done
370
371 # omit total line
372 count-lines $suite
373 summarize $suite 3 0
374}
375
376by-code-cpp() {
377 ninja _bin/cxx-dbg/{osh,ysh}
378 OSH=osh-cpp YSH=ysh-cpp $0 by-code "$@"
379}
380
381by-input-cpp() {
382 ninja _bin/cxx-dbg/{osh,ysh}
383 OSH=osh-cpp YSH=ysh-cpp $0 by-input "$@"
384}
385
386syscall-py() {
387 PYTHONPATH=. test/syscall.py "$@"
388}
389
390write-sourced() {
391 echo -n 'date; date' > _tmp/sourced.sh
392}
393
394count-lines() {
395 local suite=${1:-by-code}
396 ( cd $RAW_DIR && wc -l * ) | head -n -1 > $BASE_DIR/wc.${suite}.txt
397}
398
399summarize() {
400 local suite=${1:-by-code}
401 local not_minimum=${2:-0}
402 local more_than_bash=${3:-0}
403
404 set +o errexit
405 cat $BASE_DIR/wc.${suite}.txt \
406 | syscall-py \
407 --not-minimum $not_minimum \
408 --more-than-bash $more_than_bash \
409 --suite $suite \
410 $BASE_DIR/cases.${suite}.txt \
411 $BASE_DIR
412 local status=$?
413 set -o errexit
414
415 if test $status -eq 0; then
416 echo 'OK'
417 else
418 echo 'FAIL'
419 fi
420}
421
422soil-run() {
423 # Invoked as one of the "other" tests. Soil runs by-code and by-input
424 # separately.
425
426 # Note: Only $BASE_DIR/*.txt is included in the release/$VERSION/other.wwz
427 by-code
428
429 # wrapped
430 by-code T
431
432 by-input
433
434 echo 'OK'
435}
436
437run-for-release() {
438 ### Run the two syscall suites
439
440 soil-run
441}
442
443#
444# Real World
445#
446# $ ls|grep dash|wc -l
447# 6098
448# $ ls|grep bash|wc -l
449# 6102
450# $ ls|grep osh|wc -l
451# 6098
452#
453# So Oil is already at dash level for CPython's configure, and bash isn't
454# far off. So autoconf-generated scripts probably already use constructs
455# that are already "optimal" in most shells.
456
457readonly PY27_DIR=$PWD/Python-2.7.13
458
459cpython-configure() {
460 local raw_dir=$PWD/$RAW_DIR/real
461 mkdir -p $raw_dir
462
463 pushd $PY27_DIR
464 #for sh in "${SHELLS[@]}"; do
465 for sh in bash dash osh; do
466 local out_prefix=$raw_dir/cpython-$sh
467 echo "--- $sh"
468
469 # TODO: Use a different dir
470 count-procs $out_prefix $sh -c './configure'
471 done
472 popd
473}
474
475task-five "$@"