bench: add /usr/bin/time -v wrapper and parser

This commit is contained in:
balanza
2026-06-20 14:17:13 +02:00
parent 09d9902e14
commit 060c5bc37e
3 changed files with 161 additions and 0 deletions
+23
View File
@@ -0,0 +1,23 @@
Command being timed: "sh -c sleep 0.05"
User time (seconds): 0.00
System time (seconds): 0.00
Percent of CPU this job got: 1%
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:00.05
Average shared text size (kbytes): 0
Average unshared data size (kbytes): 0
Average stack size (kbytes): 0
Average total size (kbytes): 0
Maximum resident set size (kbytes): 2944
Average resident set size (kbytes): 0
Major (requiring I/O) page faults: 0
Minor (reclaiming a frame) page faults: 117
Voluntary context switches: 5
Involuntary context switches: 1
Swaps: 0
File system inputs: 0
File system outputs: 0
Socket messages sent: 0
Socket messages received: 0
Signals delivered: 0
Page size (bytes): 4096
Exit status: 0
+72
View File
@@ -0,0 +1,72 @@
#!/usr/bin/env bash
# /usr/bin/time -v wrapper and parser.
time_run() {
local out_txt="$1" cmd="$2"
/usr/bin/time -v -o "$out_txt" sh -c "$cmd"
}
# Parse a /usr/bin/time -v output file into a JSON object.
# Fields that don't appear in the input become null.
time_parse_v() {
local file="$1"
awk '
BEGIN {
keys["peak_rss_kb"] = "null"
keys["user_seconds"] = "null"
keys["sys_seconds"] = "null"
keys["wall_seconds"] = "null"
keys["voluntary_ctx_switches"] = "null"
keys["involuntary_ctx_switches"] = "null"
keys["major_page_faults"] = "null"
keys["minor_page_faults"] = "null"
keys["fs_inputs"] = "null"
keys["fs_outputs"] = "null"
}
function set(k, v) { keys[k] = v }
function wall_to_seconds(s, parts, n, h, m, sec) {
# Accept h:mm:ss(.frac) or m:ss(.frac)
n = split(s, parts, ":")
if (n == 3) { return parts[1]*3600 + parts[2]*60 + parts[3] + 0 }
else if (n == 2) { return parts[1]*60 + parts[2] + 0 }
else { return s + 0 }
}
/Maximum resident set size/ { set("peak_rss_kb", $NF + 0) }
/User time/ { set("user_seconds", $NF + 0) }
/System time/ { set("sys_seconds", $NF + 0) }
/Elapsed \(wall clock\) time/ {
# Last field is the formatted time
v = wall_to_seconds($NF)
set("wall_seconds", v)
}
/Voluntary context switches/ { set("voluntary_ctx_switches", $NF + 0) }
/Involuntary context switches/ { set("involuntary_ctx_switches", $NF + 0) }
/Major \(requiring I\/O\) page faults/ { set("major_page_faults", $NF + 0) }
/Minor \(reclaiming a frame\) page faults/ { set("minor_page_faults", $NF + 0) }
/File system inputs/ { set("fs_inputs", $NF + 0) }
/File system outputs/ { set("fs_outputs", $NF + 0) }
END {
# Emit JSON. Numbers are emitted bare (jq -r will keep them as-is),
# null literal stays null.
printf "{"
sep = ""
# Stable key order:
order = "peak_rss_kb user_seconds sys_seconds wall_seconds " \
"voluntary_ctx_switches involuntary_ctx_switches " \
"major_page_faults minor_page_faults fs_inputs fs_outputs"
n = split(order, ord, " ")
for (i = 1; i <= n; i++) {
k = ord[i]
v = keys[k]
if (v == "null") {
printf "%s\"%s\":null", sep, k
} else {
# User/system times are like "0.00"; keep as numeric literal
printf "%s\"%s\":%s", sep, k, v
}
sep = ","
}
printf "}\n"
}
' "$file"
}
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
. "${SCRIPT_DIR}/test_helpers.sh"
. "${SCRIPT_DIR}/common.sh"
. "${SCRIPT_DIR}/time.sh"
FIXTURE="${SCRIPT_DIR}/../fixtures/time-sample.txt"
test_parse_peak_rss() {
local got
got=$(time_parse_v "$FIXTURE" | jq -r '.peak_rss_kb')
assert_eq "2944" "$got" "peak_rss_kb"
}
test_parse_user_seconds() {
# jq normalises JSON numbers: 0.00 → 0
local got
got=$(time_parse_v "$FIXTURE" | jq -r '.user_seconds')
assert_eq "0" "$got" "user_seconds"
}
test_parse_wall_seconds_from_mm_ss() {
# 0:00.05 → 0.05 seconds
local got
got=$(time_parse_v "$FIXTURE" | jq -r '.wall_seconds')
assert_eq "0.05" "$got" "wall_seconds"
}
test_parse_minor_faults() {
local got
got=$(time_parse_v "$FIXTURE" | jq -r '.minor_page_faults')
assert_eq "117" "$got" "minor_page_faults"
}
test_parse_voluntary_ctx() {
local got
got=$(time_parse_v "$FIXTURE" | jq -r '.voluntary_ctx_switches')
assert_eq "5" "$got" "voluntary_ctx_switches"
}
test_parse_missing_field_is_null() {
local d; d=$(mktempdir)
printf '\tMaximum resident set size (kbytes): 1024\n' > "$d/partial.txt"
local got
got=$(time_parse_v "$d/partial.txt" | jq -r '.user_seconds')
assert_eq "null" "$got" "missing field becomes null"
rm -rf "$d"
}
test_time_run_captures_output() {
local d; d=$(mktempdir)
time_run "$d/out.txt" "true"
assert_file_exists "$d/out.txt" "time_run writes output"
assert_contains "Maximum resident set size" "$(cat "$d/out.txt")" "verbose output present"
rm -rf "$d"
}
test_parse_peak_rss
test_parse_user_seconds
test_parse_wall_seconds_from_mm_ss
test_parse_minor_faults
test_parse_voluntary_ctx
test_parse_missing_field_is_null
test_time_run_captures_output
printf 'time_test.sh: ok\n'