. "$BST_LIB" # BST-TIMEOUT: 600 # # MEASURED at 20 s on this guest (2026-08-17), which is NOT what the bound is # sized for: both runs currently die in milliseconds at fact 0 below, so that # figure measures a suite whose two heavy cases do not execute. The bound is # sized for the working case instead — two 5-second runs plus the cleanup phase, # which walks and unlinks everything the run wrote and on x86_64 is the slower # half — so the file does not need re-timing on the day brintos/linux#31 lands # and the runs start costing what they should. Re-measure then and bring it down # if 600 proves generous. # # dbench 4.0 (meta-oe recipes-benchmark) -- two benchmarks out of one main(): # `dbench` replays a captured NetBench operation mix against a directory, and # `tbench` replays the same mix as SMB-shaped request/response packets over a # socket against `tbench_srv`. Both ends of the socket half run in this guest # over loopback, which is the only interface it has. # # FOUR FACTS SHAPE THIS FILE, AND THE FIRST ONE IS WHY HALF OF IT IS RED. # # 0. NEITHER BINARY CAN RUN AT ALL ON THIS ARM YET, AND NO PATCH TO dbench # CHANGES THAT. Both are one main() that allocates its per-client counter # array with shmget/shmat and gates the launch on a semget/semop barrier; # the parent then READS those counters to enforce -t and to compute the # throughput figure, so the shared mapping is not an implementation detail, # it is how the benchmark reports anything. System V IPC is unwired in the # wasm32 dispatch table (brintos/linux#31, open), so the very first call # returns ENOSYS and the run dies before the load file is opened: # # can't get private shared memory of 552 bytes: Function not implemented # # Every other route to memory shared with a forked child is held by a # platform gap too, which is why this is attributed rather than patched # around: shm_open + mmap(MAP_SHARED) is ENOSYS (measured by 79-libqb, # whose ring buffers are skipped for it), and a WRITABLE MAP_SHARED # mapping — file or anonymous — is brintos/linux#30 (measured by # 78-liburcu, which leaves urcu-bp undriven for it). Rewriting dbench to # report over a pipe would be a different benchmark, not a port. # # So sysv-shm-usable below is the anchor case, and every case downstream of # it says so in its own message. All of them are stated POSITIVELY: each # goes green by itself when #31 lands, and none needs editing then. # # 1. A dbench RUN'S ONLY BOUND IS SIGALRM, AND THAT NEEDED A PATCH. The # handler is what compares elapsed time against -t and sets every client's # `done` flag; the parent arms alarm(PRINT_FREQ) and then blocks in # waitpid(). alarm(2) is ENOSYS on this arm (brintos/linux#77), so stock # dbench and stock tbench replay the loadfile forever, rewinding at EOF, # and print nothing past "releasing clients" -- no progress line, no # operation table, no Throughput. dbench_%.bbappend moves the report into # the parent's own wait loop; measured on x86_64/glibc with an LD_PRELOAD # alarm(2) returning ENOSYS, that is the whole difference between a run # killed at 20 s with no output and one that ends in 2 s with a rate. Its # effect is NOT observable here yet — fact 0 kills the run earlier — so it # is carried on the x86 measurement alone, and run-completes is what will # read it once #31 lands. # 2. THE KERNEL'S OWN VIEW OF SysV IPC IS PRESENT, and asserting it separately # is what makes fact 0 legible rather than mysterious: CONFIG_SYSVIPC=y and # /proc/sysvipc/{shm,sem} read back fine, so kernel-has-sysvipc PASSES on # the same image where shmget returns ENOSYS. That pair — subsystem # compiled in, syscall row missing — is the evidence #31 rests on, and it # is measured here rather than assumed. # 3. dbench DOES NOT REMOVE THE DIRECTORIES IT CREATED, only the files in # them. nb_deltree() unlinks every file and recurses, and never rmdir()s; # nb_cleanup() then rmdir()s the one `clients` directory, which fails # because the tree below it is still there. Measured on x86_64/glibc: 0 # files and 13 empty directories left behind. So the cleanup case asserts # that no FILE survives -- asserting an empty tree would be red on every # host, which is a wrong assertion rather than a finding. # # THE POLLING IS BUILTIN-ONLY. `read < /proc/uptime` is a builtin; `cut` and # `date` are execs, and an exec-per-iteration poll grows the host guest process # by gigabytes (brintos/hwjs-cc#69 leaks a park slot per exec). `sleep N` does # not sleep (brintos/linux#48) and timeout(1) does not fire (#77), so each run # is bounded by this file killing it. require dbench dbench/present VER=4.00 PORT=7003 PORT_HEX=1B5B LOAD=/usr/share/client.txt D=/tmp/.bst-dbench W=$D/work rm -rf "$D"; mkdir -p "$W" now() { read NOW _rest < /proc/uptime; NOW=${NOW%.*}; } spin() { _i=0; while [ $_i -lt 20000 ]; do _i=$((_i + 1)); done; } # listening -- true iff /proc/net/tcp carries a LISTEN (state 0A) # row for that local port. Read from /proc rather than through `ss` so the case # asserts the KERNEL's view and cannot fail for iproute2's reasons. listening() { awk -v p=":$1" '$4 == "0A" && index($2, p) { hit = 1 } END { exit !hit }' \ /proc/net/tcp 2>/dev/null } wait_listen() { now; _s=$NOW while :; do listening "$1" && return 0 now; [ $((NOW - _s)) -ge "$2" ] && return 1 spin done } # run_bounded -- run with a wall-clock bound, since # nothing on this image can impose one. Sets RB_RC to the command's status, or # 124 iff WE killed it. A done-sentinel FILE is what is polled: a finished # background child is a ZOMBIE until reaped and `kill -0` SUCCEEDS on one, so a # kill-0 poll reports every case as timed-out even when it completed. run_bounded() { _lim=$1; _tag=$2; shift 2 rm -f "$D/$_tag.done" { "$@" > "$D/$_tag.log" 2>&1; echo $? > "$D/$_tag.done"; } & _p=$! now; _s=$NOW while [ ! -f "$D/$_tag.done" ]; do now if [ $((NOW - _s)) -ge "$_lim" ]; then kill -9 "$_p" 2>/dev/null RB_RC=124 return 0 fi spin done read RB_RC < "$D/$_tag.done" return 0 } # throughput_of -- the MB/sec figure off the final summary line, # Throughput 249.955 MB/sec 1 clients 1 procs max_latency=10.251 ms throughput_of() { awk '/^Throughput/ { print $2; exit }' "$1"; } # positive -- the rate exists and is greater than zero. A run # that ends without ever reaching an execute-phase report prints "Throughput 0 # MB/sec", so completing and measuring are asserted separately. positive() { _v=$(throughput_of "$2") if awk -v v="$_v" 'BEGIN { exit !(v ~ /^[0-9]+(\.[0-9]+)?$/ && v + 0 > 0) }'; then bst_ok "$1" else bst_notok "$1" "parsed '$_v' from: $(_bst_1line "$(tail -3 "$2" 2>&1)")" fi } # op_count -- the count column of one row of the operation # table dbench prints at the end. Proves the loadfile was actually REPLAYED, # not merely opened: a run that starts and is killed prints no table at all. op_count() { awk -v o="$2" '$1 == o { print $2; exit }' "$1"; } replayed() { _n=$(op_count "$2" "$3") if awk -v n="$_n" 'BEGIN { exit !(n ~ /^[0-9]+$/ && n + 0 > 0) }'; then bst_ok "$1" else bst_notok "$1" "no positive $3 row in the operation table (parsed '$_n')" fi } # --- packaging --------------------------------------------------------------- # All three binaries and the loadfile, which is the one file both binaries # default to (-c) and which meta-oe packages into `tbench` rather than # `dbench` -- so this case is also what notices if the image ever installs one # package without the other. miss="" for f in /usr/bin/dbench /usr/bin/tbench /usr/bin/tbench_srv; do [ -x "$f" ] || miss="$miss $f" done [ -s "$LOAD" ] || miss="$miss $LOAD" check_eq dbench/inventory "" "${miss# }" # busybox has no dbench or tbench applet, so a bare `dbench` can only be this # port. check_eq dbench/resolves-to-port "/usr/bin/dbench" "$(command -v dbench)" # --- identity, no run involved ----------------------------------------------- # dbench has no working -v (the usage text advertises one, popt rejects it), so # the banner every invocation prints is the identity. It carries the version # the Makefile was built with -- 4.00, where the recipe's PV is 4.0 -- so a PV # bump surfaces here rather than as a puzzling output change downstream. check_out dbench/version-banner "dbench version $VER" sh -c 'dbench 2>&1; exit 0' # With no NPROCS dbench prints popt's own option list, not its show_usage() text, # so the needle is the long option; `-t timelimit` appears only in the usage # dbench prints when popt PARSING fails, which a bare invocation does not do. check_out dbench/usage-has-timelimit "--timelimit" sh -c 'dbench 2>&1; exit 0' check_out tbench/version-banner "dbench version $VER" sh -c 'tbench 2>&1; exit 0' # --- the surface the port rests on (fact 2) ---------------------------------- ipcmiss="" for f in /proc/sysvipc/shm /proc/sysvipc/sem; do [ -r "$f" ] || ipcmiss="$ipcmiss $f" done check_eq dbench/kernel-has-sysvipc "" "${ipcmiss# }" # --- dbench: the filesystem half -------------------------------------------- # -t 5 rather than -t 2 deliberately. The warmup is timelimit/5, so -t 5 gives # one warmup second and then ~4 execute reports; the throughput figure is only # computed in an execute-phase report, and on a guest this slow a 2-second run # can reach cleanup having produced none, reporting a real completion with a # rate of 0. run_bounded 150 dbench dbench -t 5 -D "$W" -c "$LOAD" 1 sed 's/^/DBENCH /' "$D/dbench.log" 2>/dev/null # THE ANCHOR CASE for fact 0, asserted from the tool's own words rather than # from a syscall probe: dbench says exactly which allocation failed and why. # Stated positively -- ok when the message is absent -- so it goes green by # itself the day the wasm32 table carries the SysV rows. IPC=0 if grep -q "get private shared memory" "$D/dbench.log" 2>/dev/null; then IPC=1 bst_notok dbench/sysv-shm-usable \ "brintos/linux#31: shmget is ENOSYS on this arm, so neither dbench nor tbench can allocate the client counter array they measure through: $(_bst_1line "$(grep -m1 'shared memory' "$D/dbench.log")")" elif grep -q "barrier semaphore" "$D/dbench.log" 2>/dev/null; then IPC=1 bst_notok dbench/sysv-shm-usable \ "brintos/linux#31: the shm allocation now works but the semget/semop launch barrier does not: $(_bst_1line "$(grep -m1 'barrier semaphore' "$D/dbench.log")")" else bst_ok dbench/sysv-shm-usable fi # `downstream ` -- a case that cannot be established because the # run died at fact 0. Reported `not ok`, never `skip`: the tool is PRESENT and # broken here, and a skip would make the suite green over a benchmark that # cannot produce a single number. downstream() { bst_notok "$1" "downstream of dbench/sysv-shm-usable (brintos/linux#31) -- $2"; } if [ "$RB_RC" -eq 0 ]; then bst_ok dbench/run-completes elif [ "$IPC" -eq 1 ]; then downstream dbench/run-completes "the run exits 1 before the loadfile is opened" else bst_notok dbench/run-completes "rc=$RB_RC $(_bst_1line "$(cat "$D/dbench.log" 2>&1)")"; fi if [ "$IPC" -eq 1 ]; then downstream dbench/throughput-line "no run, so no summary line" downstream dbench/throughput-positive "no run, so no rate to parse" downstream dbench/replayed-ops "no run, so no operation table" downstream dbench/replayed-writes "no run, so no operation table" # NOT asserted as a pass. An empty working directory here means nothing was # ever written, so the cleanup assertion would report a green it did not # measure -- the one failure shape that looks like success. downstream dbench/cleanup-removes-files \ "nothing was written, so cleanup is unmeasured (an empty tree here is not evidence)" else check_out dbench/throughput-line "Throughput" cat "$D/dbench.log" positive dbench/throughput-positive "$D/dbench.log" replayed dbench/replayed-ops "$D/dbench.log" NTCreateX replayed dbench/replayed-writes "$D/dbench.log" WriteX # fact 3: the files go, the empty directories stay. `find -type f` is one exec, # not one per iteration. left=$(find "$W" -type f 2>/dev/null | wc -l) if [ "$left" -eq 0 ]; then bst_ok dbench/cleanup-removes-files else bst_notok dbench/cleanup-removes-files \ "$left file(s) still under $W after the run: $(_bst_1line "$(find "$W" -type f 2>/dev/null | head -3)")" fi fi # --- tbench: the socket half ------------------------------------------------ # tbench_srv's port is a compile-time constant with no option to change it, so # the suite cannot move off 7003. With something already LISTENing there, # srv-listens would pass against a server this suite did not start. if listening "$PORT_HEX"; then bst_notok dbench/tbench-port-free-before-start \ "something is already LISTENing on $PORT -- the assertions below would not be about this suite's server" else bst_ok dbench/tbench-port-free-before-start fi tbench_srv > "$D/srv.log" 2>&1 & SRV=$! if wait_listen "$PORT_HEX" 30; then bst_ok dbench/tbench-srv-listens else bst_notok dbench/tbench-srv-listens \ "no LISTEN row for $PORT in /proc/net/tcp: $(_bst_1line "$(cat "$D/srv.log" 2>&1)")" fi run_bounded 150 tbench tbench -t 5 -c "$LOAD" 1 127.0.0.1 sed 's/^/TBENCH /' "$D/tbench.log" 2>/dev/null TIPC=0 grep -q "get private shared memory\|barrier semaphore" "$D/tbench.log" 2>/dev/null && TIPC=1 if [ "$RB_RC" -eq 0 ]; then bst_ok dbench/tbench-run-completes elif [ "$TIPC" -eq 1 ]; then downstream dbench/tbench-run-completes \ "tbench is the same main() as dbench and dies at the same allocation, before it connects" else bst_notok dbench/tbench-run-completes "rc=$RB_RC $(_bst_1line "$(cat "$D/tbench.log" 2>&1)")"; fi if [ "$TIPC" -eq 1 ]; then downstream dbench/tbench-throughput-positive "no run, so no rate to parse" downstream dbench/tbench-replayed-ops "no run, so no operation table" else positive dbench/tbench-throughput-positive "$D/tbench.log" replayed dbench/tbench-replayed-ops "$D/tbench.log" ReadX fi # --- stopping the server ---------------------------------------------------- # tbench_srv has no shutdown command, so stopping it is a signal, and # brintos/linux#21 says signal delivery does not work at all on this arm. An # explicit SIGTERM to a default disposition is a different path from a # kernel-posted SIGALRM into a handler, and this case measures that one. kill "$SRV" 2>/dev/null now; _s=$NOW while listening "$PORT_HEX"; do now; [ $((NOW - _s)) -ge 15 ] && break spin done if listening "$PORT_HEX"; then bst_notok dbench/tbench-srv-stops-on-signal \ "kill $SRV returned 0 and tbench_srv is still LISTENing on $PORT 15 s later (brintos/linux#21 shape?)" else bst_ok dbench/tbench-srv-stops-on-signal fi kill -9 "$SRV" 2>/dev/null rm -rf "$D"