| ci-bench: cross-platform CI race scaffold (rickub CI vs GitHub Actions) 0baf736 Olivier Girardot yesterday | 1 | #!/usr/bin/env bash |
| 2 | # emit_timing.sh — sourced by every benchmark step (CI and local). |
| 3 | # |
| 4 | # Emits ONE JSON line per step to $GITHUB_WORKSPACE/results.jsonl (falling back |
| 5 | # to ./results.jsonl). Schema: |
| 6 | # {"step": <name>, "platform": <github|rickub|local>, "run_id": <id>, |
| 7 | # "start": <iso8601>, "end": <iso8601>, "status": <ok|fail|skipped>, |
| 8 | # "duration_ms": <int>, # convenience, = end - start |
| 9 | # "value": <json>, "unit": <string>, "reason": <string>} # optional extras |
| 10 | # |
| 11 | # Public API: |
| 12 | # bench_step STEP CMD [ARGS...] run CMD, record ok/fail (compound bodies: |
| 13 | # bench_step x bash -c '...; ...') |
| 14 | # bench_skip STEP REASON record a skipped step (e.g. no docker) |
| 15 | # bench_metric STEP VALUE UNIT record a measurement now (value is raw JSON: |
| 16 | # number, "string", or null) |
| 17 | # bench_mark STEP zero-duration marker (e.g. job-start) |
| 18 | # |
| 19 | # Determined at source time (env overrides win): |
| 20 | # BENCH_PLATFORM github | rickub | local (default: inferred) |
| 21 | # BENCH_RUN_ID stable identifier for the whole run (default: GITHUB_RUN_ID, |
| 22 | # else local-<utcstamp>-<pid>) |
| 23 | # |
| 24 | # This file sets NO shell options on purpose: it is sourced into the caller's |
| 25 | # shell and must not change its behaviour. |
| 26 | |
| 27 | # --- clock helpers ----------------------------------------------------------- |
| 28 | # Prefer GNU date's %N (all Linux CI guests); BSD date (macOS) prints a literal |
| 29 | # "N" which the regex rejects; then python3; then whole-second granularity. |
| 30 | _bench_now_iso() { |
| 31 | local t |
| 32 | t=$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ 2>/dev/null || true) |
| 33 | # A valid timestamp contains no "N"; BSD date prints a literal one for %3N. |
| 34 | case $t in |
| 35 | *N*) ;; # %3N unsupported (BSD date) — fall through |
| 36 | *) printf '%s\n' "$t"; return 0 ;; |
| 37 | esac |
| 38 | t=$(python3 -c 'import datetime |
| 39 | print(datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3]+"Z")' 2>/dev/null || true) |
| 40 | case $t in |
| 41 | 2[0-9][0-9][0-9]-*) printf '%s\n' "$t"; return 0 ;; |
| 42 | esac |
| 43 | date -u +%Y-%m-%dT%H:%M:%S.000Z |
| 44 | } |
| 45 | |
| 46 | _bench_iso_to_ms() { # ISO (above format) -> epoch ms; python3 when available |
| 47 | python3 - "$1" <<'PY' 2>/dev/null || printf '%s\n' "$(_bench_iso_to_ms_shell "$1")" |
| 48 | import sys, datetime |
| 49 | s = sys.argv[1] |
| 50 | print(int(datetime.datetime.strptime(s, "%Y-%m-%dT%H:%M:%S.%fZ").replace(tzinfo=datetime.timezone.utc).timestamp() * 1000)) |
| 51 | PY |
| 52 | } |
| 53 | |
| 54 | _bench_iso_to_ms_shell() { # fallback: second granularity (documented coarser) |
| 55 | local s=${1%%.*} |
| 56 | date -u -j -f '%Y-%m-%dT%H:%M:%S' "$s" +%s 2>/dev/null \ |
| 57 | || date -u -d "$s" +%s 2>/dev/null \ |
| 58 | || printf '0\n' |
| 59 | } |
| 60 | |
| 61 | # --- identity ---------------------------------------------------------------- |
| 62 | _bench_platform() { |
| 63 | if [ -n "${BENCH_PLATFORM:-}" ]; then |
| 64 | printf '%s\n' "$BENCH_PLATFORM" |
| 65 | elif [ -n "${GITHUB_SERVER_URL:-}" ]; then |
| 66 | case $GITHUB_SERVER_URL in |
| 67 | *github.com*) printf 'github\n' ;; |
| 68 | *) printf 'rickub\n' ;; |
| 69 | esac |
| 70 | else |
| 71 | printf 'local\n' |
| 72 | fi |
| 73 | } |
| 74 | |
| 75 | _bench_run_id() { |
| 76 | if [ -n "${BENCH_RUN_ID:-}" ]; then printf '%s\n' "$BENCH_RUN_ID" |
| 77 | elif [ -n "${GITHUB_RUN_ID:-}" ]; then |
| 78 | printf '%s-attempt%s\n' "${GITHUB_RUN_ID}" "${GITHUB_RUN_ATTEMPT:-1}" |
| 79 | else |
| 80 | printf 'local-%s-%s\n' "$(date -u +%Y%m%dT%H%M%SZ)" "$$" |
| 81 | fi |
| 82 | } |
| 83 | |
| 84 | # --- record emission --------------------------------------------------------- |
| 85 | _bench_results_file() { |
| 86 | local dir=${GITHUB_WORKSPACE:-$PWD} |
| 87 | printf '%s/results.jsonl\n' "$dir" |
| 88 | } |
| 89 | |
| 90 | _bench_esc() { # minimal JSON string escaping (control chars flattened) |
| 91 | printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' | tr '\n\r\t' ' ' |
| 92 | } |
| 93 | |
| 94 | # _bench_emit STEP STATUS START END [VALUE UNIT REASON] |
| 95 | _bench_emit() { |
| 96 | local step=$1 status=$2 start=$3 end=$4 value=${5-} unit=${6-} reason=${7-} |
| 97 | local line f dur_ms |
| 98 | f=$(_bench_results_file) |
| 99 | dur_ms=$(( $(_bench_iso_to_ms "$end") - $(_bench_iso_to_ms "$start") )) |
| 100 | [ "$dur_ms" -lt 0 ] && dur_ms=0 |
| 101 | line='{"step": "'$(_bench_esc "$step")'"' |
| 102 | line+=', "platform": "'$(_bench_esc "$(_bench_platform)")'"' |
| 103 | line+=', "run_id": "'$(_bench_esc "$(_bench_run_id)")'"' |
| 104 | line+=', "start": "'$(_bench_esc "$start")'"' |
| 105 | line+=', "end": "'$(_bench_esc "$end")'"' |
| 106 | line+=', "status": "'$(_bench_esc "$status")'"' |
| 107 | line+=', "duration_ms": '"$dur_ms" |
| 108 | [ -n "$value" ] && line+=', "value": '"$value" |
| 109 | [ -n "$unit" ] && line+=', "unit": "'$(_bench_esc "$unit")'"' |
| 110 | [ -n "$reason" ] && line+=', "reason": "'$(_bench_esc "$reason")'"' |
| 111 | line+='}' |
| 112 | printf '%s\n' "$line" >>"$f" |
| 113 | } |
| 114 | |
| 115 | # --- public API -------------------------------------------------------------- |
| 116 | bench_step() { # STEP CMD [ARGS...] |
| 117 | # Records ok/fail AND propagates the command's exit code, so a failed |
| 118 | # workload fails the CI step (not silently recorded as a red data point). |
| 119 | local step=$1; shift |
| 120 | local start end status rc |
| 121 | start=$(_bench_now_iso) |
| 122 | rc=0 |
| 123 | "$@" || rc=$? |
| 124 | [ "$rc" -eq 0 ] && status=ok || status=fail |
| 125 | end=$(_bench_now_iso) |
| 126 | _bench_emit "$step" "$status" "$start" "$end" |
| 127 | return "$rc" |
| 128 | } |
| 129 | |
| 130 | bench_skip() { # STEP REASON |
| 131 | local now=$(_bench_now_iso) |
| 132 | _bench_emit "$1" skipped "$now" "$now" '' '' "$2" |
| 133 | } |
| 134 | |
| 135 | bench_metric() { # STEP VALUE UNIT (VALUE is raw JSON: 123, "ext4", null) |
| 136 | local now=$(_bench_now_iso) |
| 137 | _bench_emit "$1" ok "$now" "$now" "$2" "$3" |
| 138 | } |
| 139 | |
| 140 | bench_mark() { # STEP |
| 141 | local now=$(_bench_now_iso) |
| 142 | _bench_emit "$1" ok "$now" "$now" |
| 143 | } |