@@ -0,0 +1,665 @@
3+# Note that this script is used in the "streamly" package as well as
4+# in "streaming-benchmarks" package. Any changes to the script should be
5+# generic enough so that it works in both the cases.
7+#------------------------------------------------------------------------------
11+SCRIPT_DIR=$(cd `dirname $0`; pwd)
14+source $SCRIPT_DIR/build-lib.sh
18+ echo " [--benchmarks <"bench1 bench2 ..." | help>]"
19+ echo " [--prefix <benchmark name prefix to match>"
20+ echo " [--fields <"field1 field2 ..." | help>]"
21+ echo " [--sort-by-name]"
24+ echo " [--no-measure]"
30+ echo " [--dev-build]"
32+ echo " [--with-compiler <compiler exe name>]"
33+ echo " [--cabal-build-options <options>]"
34+ echo " [--rtsopts <opts>]"
35+ echo " [--commit-compare] [--base <commit>] [--candidate <commit>]"
36+ #echo " -- <gauge options or benchmarks>"
38+ echo "--benchmarks: benchmarks to run, use 'help' for list of benchmarks"
39+ echo "--compare: compare the specified benchmarks with each other"
40+ echo "--fields: measurement fields to report, use 'help' for a list"
41+ echo "--graphs: Generate graphical reports"
42+ echo "--no-measure: Don't run benchmarks, run reports from previous results"
43+ echo "--append: Don't overwrite previous results, append for comparison"
44+ echo "--long: Use much longer stream size for infinite stream benchmarks"
45+ echo "--slow: Slightly more accurate results at the expense of speed"
46+ echo "--quick: Faster results, useful for longer benchmarks"
47+ echo "--raw: Run the benchmarks but don't report them. This is useful when"
48+ echo " you only want to work with the csv files generated."
49+ echo "--cabal-build-options: Pass any cabal build options to be used for build"
50+ echo " e.g. --cabal-build-options \"--flag dev\""
52+ echo "When specific space complexity group is chosen then (and only then) "
53+ echo "RTS memory restrictions are used accordingly. For example, "
54+ echo "bench.sh --benchmarks Data.Parser -- Data.Parser/o-1-space "
55+ echo "restricts Heap/Stack space for O(1) characterstics"
57+ echo "When using --commit-compare, by default comparative chart of HEAD^ vs HEAD"
58+ echo "commit is generated, in the 'charts' directory."
59+ echo "Use --base and --candidate to select the commits to compare."
61+ #echo "Any arguments after a '--' are passed directly to gauge"
65+#-----------------------------------------------------------------------------
66+# Reporting utility functions
67+#-----------------------------------------------------------------------------
70+ echo "Comparison groups:"
71+ for i in $COMPARISONS
80+# chart is expensive to build and usually not required to be rebuilt
81+cabal_which_report() {
82+ local path="./bin/$1"
90+ local prog_name="bench-report"
92+ local prog_path=$(cabal_which_report $prog_name)
93+ if test -x "$prog_path"
101+build_report_prog() {
102+ local prog_name="bench-report"
103+ local prog_path=$(cabal_which_report $prog_name)
106+ if test ! -x "$prog_path" -a "$BUILD_ONCE" = "0"
108+ echo "Building bench-report executables"
110+ pushd $BENCHMARK_DIR/bench-report
112+ cmd="$CABAL_EXECUTABLE install --installdir $SCRIPT_DIR bench-report"
113+ if test "$USE_NIX" -eq 0
115+ $cmd || die "bench-report build failed"
117+ nix-shell --run "$cmd" || die "bench-report build failed"
121+ elif test ! -x "$prog_path"
128+build_report_progs() {
129+ if test "$RAW" = "0"
131+ build_report_prog || exit 1
133+ prog=$(find_report_prog) || \
134+ die "Cannot find bench-report executable"
135+ echo "Using bench-report executable [$prog]"
139+# We run the benchmarks in isolation in a separate process so that different
140+# benchmarks do not interfere with other. To enable that we need to pass the
141+# benchmark exe path to gauge as an argument. Unfortunately it cannot find its
142+# own path currently.
144+# The path is dependent on the architecture and cabal version.
146+bench_output_file() {
148+ echo "charts/$bench_name/results.csv"
151+#------------------------------------------------------------------------------
153+#------------------------------------------------------------------------------
155+# tasty-bench does not like an option set twice
156+set_super_quick_mode () {
160+# For certain long benchmarks if the user has not requested super quick
161+# mode we anyway use a slightly quicker mode.
162+use_quicker_mode () {
163+ if test "$QUICK_MODE" -eq 0
169+#------------------------------------------------------------------------------
170+# Determine options from benchmark name
171+#------------------------------------------------------------------------------
173+# Global environment passed:
174+# BENCH_EXEC_PATH: the benchmark executable
175+# RTS_OPTIONS: additional RTS options
176+# QUICK_MODE: whether we are in quick mode
177+# USE_GAUGE: whether to use gauge or tasty-bench
178+# LONG: whether to use a large stream size
182+ local BENCH_NAME_ORIG
183+ BENCH_NAME_ORIG="$1"
186+ local SUPER_QUICK_OPTIONS
187+ local QUICKER_OPTIONS
188+ if test "$USE_GAUGE" -eq 0
190+ SUPER_QUICK_OPTIONS="--stdev 1000000"
191+ QUICKER_OPTIONS="--stdev 100"
193+ # Do not keep time limit as 0 otherwise GC stats may remain 0 in some cases.
194+ SUPER_QUICK_OPTIONS="--quick --min-duration 0 --time-limit 0.01 --include-first-iter"
195+ QUICKER_OPTIONS="--min-samples 3 --time-limit 1"
202+ if test "$USE_GAUGE" -eq 0
204+ # XXX this is a hack to make the "/" separated names used in the functions
205+ # determining options based on benchmark name. For tasty-bench the benchmark
206+ # names are separated by "." instead of "/".
207+ BENCH_NAME0=$(echo $BENCH_NAME_ORIG | sed -e s/^All\.//)
208+ BENCH_NAME1=$(echo $BENCH_NAME0 | cut -f1 -d '/')
209+ BENCH_NAME2=$(echo $BENCH_NAME0 | cut -f2- -d '/' | sed -e 's/\./\//g')
210+ BENCH_NAME="$BENCH_NAME1/$BENCH_NAME2"
212+ BENCH_NAME=$BENCH_NAME_ORIG
218+$(bench_rts_options $(basename $BENCH_EXEC_PATH) $BENCH_NAME) \
222+ local QUICK_MODE_TYPE
224+ $(if test "$QUICK_MODE" -ne 0; then set_super_quick_mode; fi) \
225+ $(bench_speed_options $(basename $BENCH_EXEC_PATH) $BENCH_NAME)"
227+ local QUICK_BENCH_OPTIONS
228+ for i in $QUICK_MODE_TYPE
231+ super_quick) QUICK_BENCH_OPTIONS="$SUPER_QUICK_OPTIONS"; break ;;
232+ quicker) QUICK_BENCH_OPTIONS="$QUICKER_OPTIONS"; break ;;
238+ local STREAM_SIZE_OPT
239+ if test "$LONG" -ne 0
241+ STREAM_SIZE=10000000
242+ STREAM_LEN=$(env LC_ALL=en_US.UTF-8 printf "--stream-size %'.f\n" $STREAM_SIZE)
243+ STREAM_SIZE_OPT="--stream-size $STREAM_SIZE"
246+ echo "$BENCH_NAME_ORIG: \
249+ $QUICK_BENCH_OPTIONS" \
252+ #------------------------------------------------------------------------------
253+ # Run benchmark with options and collect results
254+ #------------------------------------------------------------------------------
257+ output_file=$(bench_output_file $(basename $BENCH_EXEC_PATH))
258+ mkdir -p `dirname $output_file`
259+ rm -f ${output_file}.tmp
261+ local BENCH_NAME_ESC
262+ if test $USE_GAUGE -eq 0
264+ # Escape "\" and double quotes in benchmark names
265+ BENCH_NAME_ESC=$(echo "$BENCH_NAME_ORIG" | sed -e 's/\\/\\\\/g' | sed -e 's/"/\\"/g')
270+ $QUICK_BENCH_OPTIONS \
272+ --csv=${output_file}.tmp \
273+ -p '$0 == "'"$BENCH_NAME_ESC"'"'
275+ # Convert cpuTime field from picoseconds to seconds
276+ awk --version 2>&1 | grep -q "GNU Awk" \
277+ || die "Need GNU awk. [$(which awk)] is not GNU awk."
278+ tail -n +2 ${output_file}.tmp | \
279+ awk 'BEGIN {FPAT = "([^,]+)|(\"[^\"]+\")";OFS=","} {$2=$2/1000000000000;print}' \
285+ $QUICK_BENCH_OPTIONS \
287+ --csvraw=${output_file}.tmp \
288+ -m exact "$BENCH_NAME"
289+ tail -n +2 ${output_file}.tmp \
295+ local target_prog="$1"
296+ local target_name="$2"
297+ local output_file="$3"
300+ if test "$LONG" -ne 0
302+ MATCH="$target_name/o-1-space"
304+ MATCH="$BENCH_PREFIX"
306+ echo "name,iters,time,cycles,cpuTime,utime,stime,maxrss,minflt,majflt,nvcsw,nivcsw,allocated,numGcs,bytesCopied,mutatorWallSeconds,mutatorCpuSeconds,gcWallSeconds,gcCpuSeconds" >> $output_file
307+ # keep only benchmark names with shortest prefix e.g. "a/b/c" and "a/b", we
308+ # should only keep "a/b" otherwise benchmarks will run multiple times. why?
311+ | while read -r name; \
312+ do bench_exec_one "$name" "${GAUGE_ARGS[@]}" || exit 1; done \
313+ || die "Benchmark execution failed."
316+invoke_tasty_bench () {
317+ local target_prog="$1"
318+ local target_name="$2"
319+ local output_file="$3"
322+ if test "$LONG" -ne 0
324+ MATCH="-p /$target_name\/o-1-space/"
326+ if test -n "$BENCH_PREFIX"
329+ local escaped_name=$(echo "$BENCH_PREFIX" | sed -e 's/\//\\\//g')
330+ MATCH="-p /$escaped_name/"
333+ echo "Name,cpuTime,2*Stdev (ps),Allocated,bytesCopied" >> $output_file
334+ $target_prog -l $MATCH \
336+ | while read -r name; \
337+ do bench_exec_one "$name" "${GAUGE_ARGS[@]}" || exit 1; done \
338+ || die "Benchmark execution failed."
341+run_bench_target () {
342+ local package_name=$1
344+ local target_name=$3
347+ target_prog=$(cabal_target_prog $package_name $component $target_name) || \
348+ die "Cannot find executable for target $target_name"
350+ echo "Running executable $target_name ..."
352+ # Needed by bench_exec_one
353+ BENCH_EXEC_PATH=$target_prog
355+ local output_file=$(bench_output_file $target_name)
356+ mkdir -p `dirname $output_file`
357+ if test "$USE_GAUGE" -eq 0
358+ then invoke_tasty_bench "$target_prog" "$target_name" "$output_file"
359+ else invoke_gauge "$target_prog" "$target_name" "$output_file"
366+run_bench_targets() {
369+ run_bench_target $1 $2 $i
373+run_benches_comparing() {
376+ if test -z "$CANDIDATE"
378+ CANDIDATE=$(git rev-parse HEAD)
382+ # XXX Should be where the current branch is forked from master
385+ echo "Comparing baseline commit [$BASE] with candidate [$CANDIDATE]"
386+ echo "Checking out base commit [$BASE] for benchmarking"
387+ git checkout "$BASE" || die "Checkout of base commit [$BASE] failed"
389+ $BUILD_BENCH || die "build failed"
390+ run_bench_targets $BENCHMARK_PACKAGE_NAME b "$bench_list" target_exe_extra_args
392+ echo "Checking out candidate commit [$CANDIDATE] for benchmarking"
393+ git checkout "$CANDIDATE" || \
394+ die "Checkout of candidate [$CANDIDATE] commit failed"
396+ $BUILD_BENCH || die "build failed"
397+ run_bench_targets $BENCHMARK_PACKAGE_NAME b "$bench_list" target_exe_extra_args
398+ # XXX reset back to the original commit
401+backup_output_file() {
403+ local output_file=$(bench_output_file $bench_name)
405+ if test -e $output_file -a "$APPEND" != 1
407+ mv -f -v $output_file ${output_file}.prev
414+ for i in $bench_list
416+ backup_output_file $i
419+ if test "$COMMIT_COMPARE" = "0"
421+ run_bench_targets $BENCHMARK_PACKAGE_NAME b "$bench_list" target_exe_extra_args
423+ run_benches_comparing "$bench_list"
429+ prog=$(find_report_prog) || \
430+ die "Cannot find bench-graph executable"
435+ echo "Generating reports for ${i}..."
438+ $(test "$GRAPH" = 1 && echo "--graphs") \
439+ $(test "$SORT_BY_NAME" = 1 && echo "--sort-by-name") \
444+#-----------------------------------------------------------------------------
445+# Execution starts here
446+#-----------------------------------------------------------------------------
450+USE_GIT_CABAL=1 # This is used by set_common_vars
469+CABAL_BUILD_OPTIONS="--flag fusion-plugin "
471+#-----------------------------------------------------------------------------
473+#-----------------------------------------------------------------------------
478+ -h|--help|help) print_help ;;
479+ # options with arguments
480+ --benchmarks) shift; TARGETS=$1; shift ;;
481+ --targets) shift; TARGETS=$1; shift ;;
482+ --prefix) shift; BENCH_PREFIX="$1"; shift ;;
483+ --fields) shift; FIELDS=$1; shift ;;
484+ --base) shift; BASE=$1; shift ;;
485+ --candidate) shift; CANDIDATE=$1; shift ;;
486+ --with-compiler) shift; CABAL_WITH_COMPILER=$1; shift ;;
487+ --cabal-build-flags) shift; CABAL_BUILD_OPTIONS+=$1; shift ;;
488+ --cabal-build-options) shift; CABAL_BUILD_OPTIONS+=$1; shift ;;
489+ --rtsopts) shift; RTS_OPTIONS=$1; shift ;;
490+ --config) shift; BENCH_CONFIG_FILE=$1; shift ;;
492+ --slow) SLOW=1; shift ;;
493+ --quick) QUICK_MODE=1; shift ;;
494+ --compare) COMPARE=1; shift ;;
495+ --commit-compare) COMMIT_COMPARE=1; shift ;;
496+ --raw) RAW=1; shift ;;
497+ --append) APPEND=1; shift ;;
498+ --long) LONG=1; shift ;;
499+ --sort-by-name) SORT_BY_NAME=1; shift ;;
500+ --graphs) GRAPH=1; shift ;;
501+ --no-measure) MEASURE=0; shift ;;
502+ --dev-build) RUNNING_DEVBUILD=1; shift ;;
503+ --use-nix) USE_NIX=1; shift ;;
504+ --use-gauge) USE_GAUGE=1; shift ;;
506+ *) echo "Unknown flags: $*"; echo; print_help ;;
511+if test -z "$BENCH_CONFIG_FILE"
513+ die "Please use --config to specify config file"
516+source "$BENCH_CONFIG_FILE" || \
517+ die "Failed to source config file $BENCH_CONFIG_FILE"
519+# Defined in $BENCH_CONFIG_FILE
524+ FIELDS=$DEFAULT_FIELDS
529+#-----------------------------------------------------------------------------
531+#-----------------------------------------------------------------------------
533+only_real_benchmarks () {
537+ for j in $COMPARISONS
544+ if test "$SKIP" -eq 0
551+# defined in $BENCH_CONFIG_FILE
554+if test "$(has_item "$TARGETS" help)" = "help"
562+COMMON_FIELDS="allocated bytescopied cputime maxrss"
563+if test "$USE_GAUGE" -eq 1
565+ ALL_FIELDS="$COMMON_FIELDS time cycles utime stime minflt majflt nvcsw nivcsw"
567+ ALL_FIELDS="$COMMON_FIELDS"
569+if test "$(has_item "$FIELDS" help)" = "help"
571+ echo "Supported fields: $ALL_FIELDS"
572+ echo "Default fields: $DEFAULT_FIELDS"
576+if test "$LONG" -ne 0
578+ if test -n "$TARGETS"
580+ echo "Cannot specify benchmarks [$TARGETS] with --long"
583+ TARGETS=$infinite_grp
586+DEFAULT_TARGETS="$(all_grp)"
587+TARGETS=$(set_targets)
589+TARGETS_ORIG=$TARGETS
590+TARGETS=$(only_real_benchmarks)
592+echo "Using benchmark suites [$TARGETS]"
594+#-----------------------------------------------------------------------------
595+# Build reporting utility
596+#-----------------------------------------------------------------------------
598+# We need to build the report progs first at the current (latest) commit before
599+# checking out any other commit for benchmarking.
602+#-----------------------------------------------------------------------------
603+# Build and run targets
604+#-----------------------------------------------------------------------------
606+if test "$USE_GAUGE" -eq 1
608+ BUILD_FLAGS="--flag use-gauge"
611+BUILD_BENCH="$CABAL_EXECUTABLE v2-build $BUILD_FLAGS $CABAL_BUILD_OPTIONS --enable-benchmarks"
612+if test "$MEASURE" = "1"
614+ run_build "$BUILD_BENCH" $BENCHMARK_PACKAGE_NAME bench "$TARGETS"
615+ run_measurements "$TARGETS"
618+#-----------------------------------------------------------------------------
620+#-----------------------------------------------------------------------------
623+build_comparison_results () {
628+ constituents=$(eval "echo -n \$${name}")
629+ mkdir -p "charts/$name"
630+ dest_file="charts/$name/results.csv"
632+ for j in $constituents
634+ cat "charts/$j/results.csv" >> $dest_file
638+if test "$COMPARE" -eq 1
640+ DYN_CMP_GRP="$(echo "$TARGETS" | sed -e 's/ /_/g')_cmp"
641+ eval "$DYN_CMP_GRP=\"$TARGETS\""
642+ COMPARISON_REPORTS=$DYN_CMP_GRP
643+ build_comparison_results $DYN_CMP_GRP
645+ COMPARISON_REPORTS=""
648+for i in $COMPARISONS
650+ if test "$(has_item "$TARGETS_ORIG" $i)" = $i
652+ COMPARISON_REPORTS="$COMPARISON_REPORTS $i"
653+ build_comparison_results $i
659+ run_reports "$TARGETS"
660+ run_reports "$COMPARISON_REPORTS"
661+ if test -n "$DYN_CMP_GRP"
663+ rm -rf "charts/$DYN_CMP_GRP"