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

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