Files
praxis/scripts/proxmox/timing.sh
T
Praxis CI bb17615f41 feat(P01): SLICE-03+04 — PVE API layer (8 scripts) + health-check
SLICE-03 (devops-engineer): ported from coreci/scripts/proxmox/:
  api.sh (verbatim), ct-exists.sh, lxc-clone.sh (4GB/16GB, hostname=praxis),
  lxc-config.sh (praxis env vars, praxis-firstboot.sh snippet), lxc-start.sh,
  stage-snippet.sh (G-101 FIX: bakes GITEA_TOKEN into snippet via sed),
  timing.sh (verbatim), rollback.sh (proxy code removed)
SLICE-04 (devops-engineer): health-check.sh adapted for /health:8789
  (G-104 FIX: timeout bumped 300s→600s for Docker build margin)

REQ-DEPLOY-03, 04, 05, 07, 08 covered.

---ci---
project: praxis
phase: 1
milestone: v0.2
status: execute
slice: 03-04
wave: 2
---/ci---
2026-08-01 14:18:23 +00:00

102 lines
3.8 KiB
Bash
Executable File

# CoreCI — deploy-stage timing helper (P11 — IDEATE-39).
#
# Sourced (not executed) by the deploy orchestrators
# (proxy-deploy.sh, lxc-deploy.sh) to emit structured slog-style
# JSON timing lines for each deploy stage to stderr, where a log
# aggregator (or `2>>timing.log`) can pick them up.
#
# Usage:
# . /path/to/timing.sh
# timing_start clone
# ... clone work ...
# timing_end clone
#
# Emits one JSON line per timing_end to stderr:
# {"event":"deploy_timing","stage":"clone","duration_s":3}
#
# Optional node_exporter textfile collector: if the env var
# NODE_TEXTFILE_COLLECTOR_DIR points to a writable directory, the
# latest per-stage duration is ALSO written there as
# `coreci_deploy_timing_<stage>.prom` so a node_exporter textfile
# collector scrapes it. If the dir is unset or unwritable, only the
# JSON log is emitted (the structured-log-first decision, PLAN v3.6
# P11 Wave 2).
#
# Dependencies: date (POSIX epoch via +%s). jq is NOT required (the
# JSON line is constructed with printf so there is no external dep
# on the slow path). Idempotent: re-sourcing is harmless (the
# _TIMING_STARTS associative state is reset on source, but the
# orchestrator sources exactly once at startup).
#
# NOTE: Ported verbatim from coreci. The metric/prefix names retain
# the `coreci_` origin identifier for compatibility with existing
# node_exporter dashboards; rename in a follow-up if desired.
#
# shellcheck shell=sh
# _TIMING_STARTS is a flat file-backed map (stage → epoch seconds).
# POSIX sh has no associative arrays, so we use a single newline-
# separated string of "stage=epoch" records and scan it. Stages are
# short identifiers (clone/config/start/health/smoke) so the linear
# scan is trivially cheap.
_TIMING_STARTS=""
# timing_start <stage> — record the current epoch for <stage>.
# Overwrites a prior start for the same stage (idempotent re-entry).
timing_start() {
_stage="$1"
_now=$(date +%s)
# Drop any prior record for this stage, then append the fresh one.
_TIMING_STARTS="$(printf '%s\n' "$_TIMING_STARTS" \
| while IFS= read -r _line; do
case "$_line" in
"${_stage}="*) ;;
*) [ -n "$_line" ] && printf '%s\n' "$_line" ;;
esac
done)"
_TIMING_STARTS="${_TIMING_STARTS:+${_TIMING_STARTS}
}${_stage}=${_now}"
}
# timing_end <stage> — compute duration since timing_start <stage>,
# emit the JSON line to stderr, and optionally write the textfile
# collector entry. If no start was recorded for <stage>, emit nothing
# (defensive — a stray timing_end with no start is a no-op).
timing_end() {
_stage="$1"
_now=$(date +%s)
_start=""
# Scan the records for the matching stage.
_rest=""
while IFS= read -r _line; do
[ -n "$_line" ] || continue
case "$_line" in
"${_stage}="*)
_start="${_line#*=}"
;;
*)
_rest="${_rest:+${_rest}
}${_line}"
;;
esac
done <<EOF
${_TIMING_STARTS}
EOF
[ -n "$_start" ] || return 0
_duration=$((_now - _start))
_TIMING_STARTS="$_rest"
# Structured JSON to stderr (slog-style: single-line JSON).
printf '{"event":"deploy_timing","stage":"%s","duration_s":%s}\n' \
"$_stage" "$_duration" >&2
# Optional node_exporter textfile collector.
if [ -n "${NODE_TEXTFILE_COLLECTOR_DIR:-}" ] && \
[ -d "$NODE_TEXTFILE_COLLECTOR_DIR" ] && \
[ -w "$NODE_TEXTFILE_COLLECTOR_DIR" ]; then
_tf="${NODE_TEXTFILE_COLLECTOR_DIR}/coreci_deploy_timing_${_stage}.prom"
{
printf '# HELP coreci_deploy_timing_seconds Duration of the %s deploy stage.\n' "$_stage"
printf '# TYPE coreci_deploy_timing_seconds gauge\n'
printf 'coreci_deploy_timing_seconds{stage="%s"} %s\n' "$_stage" "$_duration"
} > "$_tf" 2>/dev/null || true
fi
}