Skip to content

Commit f85674a

Browse files
committed
refactor(diagnostics): emit system.ntp_synchronized + top-level pi_throttle
Splits the misnamed pi_health wire block. NTP-sync is a universal host signal and moves into system.ntp_synchronized; the eight vcgencmd throttle bits become a flat top-level pi_throttle block, absent on non-Pi feeders. collect_pi_ntp_sync renames to collect_ntp_sync.
1 parent d22c448 commit f85674a

2 files changed

Lines changed: 52 additions & 47 deletions

File tree

scripts/airplanes-diagnostics.sh

Lines changed: 26 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -288,8 +288,8 @@ collect_network() {
288288
# bit 1 freq_capped_now bit 17 freq_capped_ever
289289
# bit 2 throttled_now bit 18 throttled_ever
290290
# bit 3 soft_temp_limit_now bit 19 soft_temp_limit_ever
291-
# Sets pi_health_* globals. Returns 0 on success, 1 if vcgencmd absent or
292-
# its output unparseable.
291+
# Sets the eight pi_*_now / pi_*_ever globals. Returns 0 on success, 1
292+
# if vcgencmd is absent or its output is unparseable.
293293
collect_pi_throttle() {
294294
command -v vcgencmd >/dev/null 2>&1 || return 1
295295
local raw value
@@ -308,8 +308,10 @@ collect_pi_throttle() {
308308
pi_soft_temp_limit_ever=$(( (n >> 19) & 1 ))
309309
}
310310

311-
# timedatectl show -p NTPSynchronized --value -> "yes" / "no" / ""
312-
collect_pi_ntp_sync() {
311+
# timedatectl show -p NTPSynchronized --value -> "yes" / "no" / "".
312+
# Universal host signal — every systemd Linux has this, not just Pis. Sits
313+
# at `system.ntp_synchronized` on the wire, alongside uptime and CPU.
314+
collect_ntp_sync() {
313315
command -v timedatectl >/dev/null 2>&1 || return 1
314316
local raw
315317
raw="$(timeout 3s timedatectl show -p NTPSynchronized --value 2>/dev/null)" || return 1
@@ -460,15 +462,15 @@ build_service_json() {
460462
| with_entries(select(.value != null and .value != ""))'
461463
}
462464

463-
# Build the pi_health block as JSON, or print "null" if neither sub-probe
464-
# produced data. Sub-probes are independent — a missing/broken
465-
# `timedatectl` doesn't suppress vcgencmd throttle data and vice versa.
466-
build_pi_health_json() {
467-
local throttle_json='null' ntp_json='null'
465+
# Build the flat pi_throttle block as JSON, or print "null" if vcgencmd
466+
# is absent or its output failed to parse. Pi-only — absent on every
467+
# non-Pi feeder. NTP-sync lives in `system.ntp_synchronized` now (it is
468+
# universal-host, not Pi-specific) and is plumbed separately.
469+
build_pi_throttle_json() {
468470
local pi_undervoltage_now=0 pi_freq_capped_now=0 pi_throttled_now=0 pi_soft_temp_limit_now=0
469471
local pi_undervoltage_ever=0 pi_freq_capped_ever=0 pi_throttled_ever=0 pi_soft_temp_limit_ever=0
470472
if command -v vcgencmd >/dev/null 2>&1 && collect_pi_throttle; then
471-
throttle_json="$(jq -nc \
473+
jq -nc \
472474
--argjson uv_now "$pi_undervoltage_now" \
473475
--argjson fc_now "$pi_freq_capped_now" \
474476
--argjson th_now "$pi_throttled_now" \
@@ -484,22 +486,10 @@ build_pi_health_json() {
484486
undervoltage_ever: ($uv_ever == 1),
485487
freq_capped_ever: ($fc_ever == 1),
486488
throttled_ever: ($th_ever == 1),
487-
soft_temp_limit_ever: ($st_ever == 1)}')"
488-
fi
489-
if command -v timedatectl >/dev/null 2>&1; then
490-
local ntp
491-
if ntp="$(collect_pi_ntp_sync)"; then
492-
ntp_json="$ntp"
493-
fi
494-
fi
495-
if [[ "$throttle_json" == 'null' && "$ntp_json" == 'null' ]]; then
496-
printf 'null'
489+
soft_temp_limit_ever: ($st_ever == 1)}'
497490
return
498491
fi
499-
jq -nc \
500-
--argjson throttle "$throttle_json" \
501-
--argjson ntp "$ntp_json" \
502-
'{throttle: $throttle, ntp_synchronized: $ntp}'
492+
printf 'null'
503493
}
504494

505495
# nullable_num VALUE — echoes the value if non-empty, otherwise "null".
@@ -689,8 +679,13 @@ main() {
689679
svc_978="$(build_service_json airplanes-978 "$(root_path /run/airplanes-978/state)")"
690680
fi
691681

692-
local pi_health_json
693-
pi_health_json="$(build_pi_health_json)"
682+
local pi_throttle_json ntp_sync_json
683+
pi_throttle_json="$(build_pi_throttle_json)"
684+
# Universal host-clock signal — emits "true"/"false" or null on
685+
# any non-systemd host (no `timedatectl`). Wire path:
686+
# `system.ntp_synchronized`. Server stamps `system.clock_skew_seconds`
687+
# separately at ingest.
688+
ntp_sync_json="$(collect_ntp_sync 2>/dev/null || printf 'null')"
694689

695690
local feed_scripts_version os_pretty_name os_id os_version_id kernel architecture image_release
696691
feed_scripts_version="$(get_feed_scripts_version || true)"
@@ -730,7 +725,8 @@ main() {
730725
--argjson svc_readsb "$svc_readsb" \
731726
--argjson svc_dump978 "$svc_dump978" \
732727
--argjson svc_978 "$svc_978" \
733-
--argjson pi_health "$pi_health_json" \
728+
--argjson pi_throttle "$pi_throttle_json" \
729+
--argjson ntp_synchronized "$ntp_sync_json" \
734730
--arg feed_scripts "${feed_scripts_version:-}" \
735731
--arg os_pretty_name "${os_pretty_name:-}" \
736732
--arg os_id "${os_id:-}" \
@@ -758,7 +754,8 @@ main() {
758754
disk: {
759755
used_percent: $disk_used_pct,
760756
total_bytes: $disk_total_bytes
761-
}
757+
},
758+
ntp_synchronized: $ntp_synchronized
762759
},
763760
network: {
764761
connection_type: $net_connection_type,
@@ -774,7 +771,7 @@ main() {
774771
architecture: $architecture,
775772
image_release: $image_release
776773
},
777-
pi_health: $pi_health
774+
pi_throttle: $pi_throttle
778775
}
779776
| def _prune:
780777
if type == "object" then

test/test_airplanes_diagnostics.bats

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -645,23 +645,30 @@ SH
645645
[ "$output" = '-58' ]
646646
}
647647

648-
@test "POST body omits pi_health when both vcgencmd and timedatectl are absent" {
648+
@test "POST body omits pi_throttle and system.ntp_synchronized when both vcgencmd and timedatectl are absent" {
649649
# Block both probes by stubbing them to fail. The default setup
650650
# doesn't ship vcgencmd or timedatectl stubs, but the dev host
651651
# likely has timedatectl on PATH (it's installed by systemd) which
652-
# would otherwise produce a pi_health.ntp_synchronized field.
652+
# would otherwise produce a system.ntp_synchronized field.
653653
cat > "$STUB_DIR/timedatectl" <<'SH'
654654
#!/usr/bin/env bash
655655
exit 1
656656
SH
657657
chmod +x "$STUB_DIR/timedatectl"
658658
run_script
659659
[ "$status" -eq 0 ]
660-
run jq '.pi_health // empty' "$BODY_LOG"
661-
[ -z "$output" ]
660+
# pi_throttle is absent on non-Pi (no vcgencmd).
661+
run jq -e 'has("pi_throttle") | not' "$BODY_LOG"
662+
[ "$status" -eq 0 ]
663+
# NTP-sync absent in system block (broken timedatectl).
664+
run jq -e '.system | has("ntp_synchronized") | not' "$BODY_LOG"
665+
[ "$status" -eq 0 ]
666+
# Old container is gone — sentinel against accidental regressions.
667+
run jq -e 'has("pi_health") | not' "$BODY_LOG"
668+
[ "$status" -eq 0 ]
662669
}
663670

664-
@test "pi_health throttle survives a broken timedatectl (sub-probes independent)" {
671+
@test "pi_throttle survives a broken timedatectl (probes are independent)" {
665672
cat > "$STUB_DIR/vcgencmd" <<'SH'
666673
#!/usr/bin/env bash
667674
[[ "$1" == "get_throttled" ]] && printf 'throttled=0x0\n'
@@ -674,15 +681,16 @@ SH
674681
chmod +x "$STUB_DIR/timedatectl"
675682
run_script
676683
[ "$status" -eq 0 ]
677-
# throttle block is present, all bits false
678-
run jq -er '.pi_health.throttle.throttled_now' "$BODY_LOG"
684+
# pi_throttle bits present, all false
685+
run jq -er '.pi_throttle.throttled_now' "$BODY_LOG"
679686
[ "$output" = 'false' ]
680-
# ntp_synchronized is dropped (broken probe)
681-
run jq '.pi_health.ntp_synchronized // empty' "$BODY_LOG"
682-
[ -z "$output" ]
687+
# NTP-sync is absent (broken probe). With separate top-level fields
688+
# the independence is now structural, but the sentinel stays.
689+
run jq -e '.system | has("ntp_synchronized") | not' "$BODY_LOG"
690+
[ "$status" -eq 0 ]
683691
}
684692

685-
@test "POST body pi_health bit decode for vcgencmd=0x50005" {
693+
@test "POST body pi_throttle bit decode for vcgencmd=0x50005" {
686694
cat > "$STUB_DIR/vcgencmd" <<'SH'
687695
#!/usr/bin/env bash
688696
[[ "$1" == "get_throttled" ]] && printf 'throttled=0x50005\n'
@@ -697,19 +705,19 @@ SH
697705
[ "$status" -eq 0 ]
698706
# bits 0, 2, 16, 18 set: undervoltage_now, throttled_now,
699707
# undervoltage_ever, throttled_ever — all true.
700-
run jq -er '.pi_health.throttle.undervoltage_now' "$BODY_LOG"
708+
run jq -er '.pi_throttle.undervoltage_now' "$BODY_LOG"
701709
[ "$output" = 'true' ]
702-
run jq -er '.pi_health.throttle.throttled_now' "$BODY_LOG"
710+
run jq -er '.pi_throttle.throttled_now' "$BODY_LOG"
703711
[ "$output" = 'true' ]
704-
run jq -er '.pi_health.throttle.undervoltage_ever' "$BODY_LOG"
712+
run jq -er '.pi_throttle.undervoltage_ever' "$BODY_LOG"
705713
[ "$output" = 'true' ]
706-
run jq -er '.pi_health.throttle.throttled_ever' "$BODY_LOG"
714+
run jq -er '.pi_throttle.throttled_ever' "$BODY_LOG"
707715
[ "$output" = 'true' ]
708-
run jq -er '.pi_health.throttle.freq_capped_now' "$BODY_LOG"
716+
run jq -er '.pi_throttle.freq_capped_now' "$BODY_LOG"
709717
[ "$output" = 'false' ]
710-
run jq -er '.pi_health.throttle.soft_temp_limit_ever' "$BODY_LOG"
718+
run jq -er '.pi_throttle.soft_temp_limit_ever' "$BODY_LOG"
711719
[ "$output" = 'false' ]
712-
run jq -er '.pi_health.ntp_synchronized' "$BODY_LOG"
720+
run jq -er '.system.ntp_synchronized' "$BODY_LOG"
713721
[ "$output" = 'true' ]
714722
}
715723

0 commit comments

Comments
 (0)