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

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