feat(狀態回報): 收尾寫一筆 skill-end 事件
現行紀錄只記「被叫用」,沒有成敗也沒有結束碼。跑完整輪的技能與開場就 中止的技能,在紀錄裡長得一模一樣。 start 由技能用量 hook 順手發,不必改技能文件。end 只能由技能自己在收尾 步驟寫——hook 接在技能工具呼叫上,而實際工作發生在之後的模型輪次,它在 原理上看不到成敗。有 start 沒有配對的 end,就是那一輪中止了。 status 五選一,每支技能各自寫明什麼情況選哪一個。找不到回報腳本就安靜 跳過,回報失敗一律不改變技能自己的結論。
This commit is contained in:
+277
-17
@@ -8,18 +8,18 @@
|
||||
#
|
||||
# collect 帶了 --out,finish 與 abort 就要帶同一個目錄,不然換不到本輪的用量快照。
|
||||
#
|
||||
# collect 讀四項來源、組出監控頁那三塊、把鎖拿在手上。
|
||||
# collect 讀各項來源、組出監控頁那三塊、把鎖拿在手上。
|
||||
# finish 在監控頁寫成功之後才呼叫:寫心跳、換上用量快照、放掉鎖。
|
||||
# abort 在監控頁沒寫成時呼叫:只放掉鎖,不寫心跳。
|
||||
#
|
||||
# 結束碼(三個子命令共用一張表,同一碼在不同子命令的成因寫在同一列):
|
||||
# 0 collect:四項全部讀到底(含「來源在、沒有資料」);finish:心跳寫好、快照換上、
|
||||
# 0 collect:各項全部讀到底(含「來源在、沒有資料」);finish:心跳寫好、快照換上、
|
||||
# 鎖放掉;abort:鎖放掉,本來就沒鎖也算
|
||||
# 1 collect:部分成功——至少一項失敗,也至少一項有結果。**結果照樣印得出來,呼叫端
|
||||
# 照樣要把這一輪寫上監控頁**,只是本輪判定要標成警示
|
||||
# 2 finish:找不到 jsc-hooks 的 hooks/heartbeat.sh,心跳沒有東西可寫。collect 不會回這
|
||||
# 一碼——心跳讀不到只是 D-09 這一項失敗,另外三項照跑
|
||||
# 3 collect:四項全部失敗,一項資料都沒有。這一輪還是要寫上監控頁,本輪判定標成異常
|
||||
# 一碼——心跳讀不到只是 D-09 這一項失敗,其餘各項照跑
|
||||
# 3 collect:各項全部失敗,一項資料都沒有。這一輪還是要寫上監控頁,本輪判定標成異常
|
||||
# 4 上一輪還在跑,本輪讓開(collect),或鎖已經不在自己手上(finish、abort)。這不是
|
||||
# 失敗,是刻意讓開:不寫心跳、不寫監控頁,下一輪再來
|
||||
# 5 檔案系統失敗:鎖建不起來或放不掉、暫存檔寫不進去、快照換不上,或 heartbeat.sh write
|
||||
@@ -34,7 +34,7 @@
|
||||
#
|
||||
# --- 心跳寫不寫,只看結果有沒有記下來 ---
|
||||
#
|
||||
# 四項的成敗不決定心跳。四項全失敗但監控頁寫成了,那一輪還是跑完了,證據也留下來了,
|
||||
# 各項的成敗不決定心跳。各項全失敗但監控頁寫成了,那一輪還是跑完了,證據也留下來了,
|
||||
# 心跳照寫,頁上判定是異常,看頁的人自己判斷。反過來,監控頁沒寫成就是這一輪沒有結果,
|
||||
# 心跳一定不寫:讓它自己過期,就是「巡檢在空轉」的唯一訊號。
|
||||
# 所以寫心跳一定是獨立的 finish,時序上排在監控頁寫成之後,不與 collect 綁在一起。
|
||||
@@ -49,14 +49,17 @@
|
||||
# 拿 --round 比對出鎖不是自己的,回 4 且不寫心跳。
|
||||
# 搶回來這件事會記在監控頁上(lock_broken=1),不會安靜發生。
|
||||
#
|
||||
# --- 這四項都是純讀取 ---
|
||||
# --- 這幾項都是純讀取 ---
|
||||
#
|
||||
# D-01 技能與呼叫鏈使用統計 jsc-log 的 tools/usage-stats.sh
|
||||
# D-04 版本落差與重啟閘門 jsc-hooks 的 version-guard.sh report、restart-gate.sh report
|
||||
# D-07 SDLC 階段鎖與工作包鎖 $JSC_HOME/sessions/*.stage、$JSC_HOME/wp/*.pr
|
||||
# D-09 心跳與閘門狀態自述 jsc-hooks 的 heartbeat.sh report
|
||||
# 四項各自獨立:一項的來源不見了、或回非 0,只讓那一項標成失敗,其餘三項照跑、照記。
|
||||
# 四項都不呼叫別的技能、不寫程式碼存取庫、不做決策。
|
||||
# D-11 執行狀態事件 jsc-hooks 的 tools/report-status.sh drain
|
||||
# 各項各自獨立:一項的來源不見了、或回非 0,只讓那一項標成失敗,其餘各項照跑、照記。
|
||||
# 各項都不呼叫別的技能、不寫程式碼存取庫、不做決策。
|
||||
# D-11 是唯一會動到別人狀態的一項:drain 會把事件流的位移往前推。理由與配套見下面
|
||||
# 「執行狀態事件為什麼由這支排空」。
|
||||
#
|
||||
# --- version-guard.sh report 的既有缺陷照實記 ---
|
||||
#
|
||||
@@ -100,6 +103,30 @@
|
||||
# 列,舊列從此不再更新。這一頁每 15 分鐘寫一次,重複列累積得很快。裸 HASH 只由
|
||||
# {主機名}/{登入帳號} 決定,上面三件事都動不到它。
|
||||
#
|
||||
# --- 執行狀態事件為什麼由這支排空 ---
|
||||
#
|
||||
# 事件流記的是每一支技能與每一支 hook 的執行結果。技能的 start 由 hook 免費記下,end 只能由
|
||||
# 技能自己在收尾時寫,所以「有 start 沒有配對的 end」就是那一輪中止了——那是這整套機制唯一
|
||||
# 分得出中止的訊號,也是這一項最重要的產出。
|
||||
#
|
||||
# 排空與彙整放在這支,不放在技能本文,有三個理由:
|
||||
# 1. drain 是消耗性讀取:它一讀完就把位移往前推,同一批事件不會再出現第二次。讀回來的內容
|
||||
# 只存在對話裡的話,模型少抄一行就是那一批事件永遠消失。寫成檔案才留得住。
|
||||
# 2. 一輪的事件動輒上百行 JSON,逐行判 status 與配對 start/end 交給模型做,既慢又會出錯。
|
||||
# 3. rotate 一定要緊接在 drain 後面跑。中間隔得越久,那段時間新寫進來的事件被搬進備份檔
|
||||
# 而從此不會被排空的機率越高。兩件事綁在同一支腳本的同一次執行,那個空窗才最小。
|
||||
#
|
||||
# 配對以 {session}+{name} 為鍵,不只看 name:五支 CLI 併發時同一支技能會有好幾個工作階段同時
|
||||
# 在跑,只看 name 會讓 A 工作階段的 end 去配掉 B 工作階段的 start,中止就被蓋掉了。
|
||||
#
|
||||
# 沒配對到的 start 會留在 $JSC_HOME/assistant/events-open.tsv,跨輪繼續配對。不留的話,一支
|
||||
# 跑超過一個巡檢週期的技能每一輪都會被報成中止——巡檢週期預設兩分鐘,那種誤報會多到沒人看。
|
||||
# 開著超過心跳門檻才算「疑似中止」,門檻以內的算「進行中」,兩種分開列。超過一天還沒配對到的
|
||||
# 就從檔案裡丟掉,那個檔案才不會無止境長大。
|
||||
#
|
||||
# 這一項失敗(找不到腳本、drain 回非預期結束碼、rotate 失敗)一律只讓這一項標成失敗或記一筆
|
||||
# 警示,不中止整輪:回報鏈自己壞掉,不可以把被回報的那一輪也拖下去。
|
||||
#
|
||||
# --- collect 的輸出 ---
|
||||
#
|
||||
# stdout 是 key=value,一行一個鍵,供呼叫端逐行取值。監控頁要用的 markdown 不印在
|
||||
@@ -113,10 +140,17 @@
|
||||
# item= 一項一行,欄位 status(ok、empty、fail)、rc、note
|
||||
# verdict= 正常、警示、異常
|
||||
# failed_sources= 讀不到的來源路徑,以「、」分隔;全部讀得到就是「無」
|
||||
# warn_sources= 本輪的警示來源,以「、」分隔;沒有警示就是「無」。四項全過卻判成警示
|
||||
# warn_sources= 本輪的警示來源,以「、」分隔;沒有警示就是「無」。各項全過卻判成警示
|
||||
# 時,原因只寫在這裡
|
||||
# tasks_total= tasks_failing= 待辦簿筆數與連續失敗筆數,只供目錄頁那一列用
|
||||
# pending= 本輪待人處理的筆數
|
||||
# events_total= 本輪排空到的事件筆數
|
||||
# events_bad= 其中 status 不是 ok 的筆數
|
||||
# events_unpaired= 有 start 沒有配對 end、而且已經開超過心跳門檻的筆數(疑似中止)
|
||||
# events_running= 有 start 沒有配對 end,但還在門檻以內的筆數(還在跑)
|
||||
# events_rotated= rotated、not-needed、failed、skipped 四選一
|
||||
# events_file= 本輪排空到的原始事件,一行一筆 JSON。drain 是消耗性讀取,這個檔案是
|
||||
# 那一批事件在被彙整之外唯一留下的完整原文
|
||||
# latest_file= 「最新一輪」那一塊,整塊換掉舊頁同名那一塊
|
||||
# summary_file= 「近 24 輪摘要」那一塊,表格裡先放本輪這一列,舊頁的資料列接在下面
|
||||
# summary_row_file= 只有本輪那一列,方便直接插到既有表格最上面
|
||||
@@ -133,6 +167,7 @@
|
||||
# JSC_ASSIST_RESTART_GATE_SH restart-gate.sh 路徑覆寫
|
||||
# JSC_ASSIST_USAGE_STATS_SH usage-stats.sh 路徑覆寫
|
||||
# JSC_ASSIST_HASH_ID hash-id 路徑覆寫
|
||||
# JSC_ASSIST_REPORT_STATUS_SH report-status.sh 路徑覆寫
|
||||
# JSC_ASSIST_PATROL_LOCK_TTL 鎖的逾時秒數;未設定時取心跳門檻,取不到就用 300
|
||||
set -u
|
||||
|
||||
@@ -141,6 +176,9 @@ STATE_DIR="$JSC_HOME/assistant"
|
||||
CURRENT="$JSC_HOME/current"
|
||||
LOCK="$STATE_DIR/patrol.lock"
|
||||
PREV_SNAP="$STATE_DIR/usage-prev.tsv"
|
||||
# 還沒配對到 end 的 start,跨輪留在這裡。放 $JSC_HOME/assistant 而不放 $RD:$RD 每一輪重寫,
|
||||
# 放那裡就等於不跨輪,跑超過一個週期的技能每輪都會被報成中止。
|
||||
EVENTS_OPEN="$STATE_DIR/events-open.tsv"
|
||||
RD="$STATE_DIR/patrol"
|
||||
|
||||
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" 2>/dev/null && pwd)
|
||||
@@ -173,7 +211,7 @@ warn_if_not_current() {
|
||||
warn_if_not_current
|
||||
|
||||
# 記一個警示來源。每一處把 WARN 設成 1 的地方都經過這裡,摘要表那一欄才看得出警示哪來——
|
||||
# 四項全過卻判成警示,光看成敗欄是查不出原因的。
|
||||
# 各項全過卻判成警示,光看成敗欄是查不出原因的。
|
||||
add_warn() { # $1=一句話講完的理由
|
||||
WARN=1
|
||||
if [ -z "$WARN_SOURCES" ]; then WARN_SOURCES="$1"; else WARN_SOURCES="$WARN_SOURCES、$1"; fi
|
||||
@@ -592,7 +630,7 @@ d09() {
|
||||
printf '| pid | %s。只給要找行程的人參考,不參與判定 |\n' "$(cell "${_pid:--}")"
|
||||
printf '| 心跳檔 | `%s` |\n' "$(cell "${_file:--}")"
|
||||
printf '\n心跳的判準只看 `ts` 距現在有沒有超過門檻,不看 pid 存活:五支 CLI 與容器裡的行程互相看不到彼此的 pid。閘門的判定留在 hook,助理只維持心跳,不參與判定。\n'
|
||||
printf '\n心跳新鮮代表上一輪巡檢跑完了,而且結果記上監控頁了。它不代表那一輪四項都成功——四項的成敗看這一塊上面的「本輪判定」。\n'
|
||||
printf '\n心跳新鮮代表上一輪巡檢跑完了,而且結果記上監控頁了。它不代表那一輪各項都成功——各項的成敗看這一塊上面的「本輪判定」。\n'
|
||||
} >>"$RD/d09.md"
|
||||
|
||||
case "$_st" in
|
||||
@@ -603,6 +641,216 @@ d09() {
|
||||
return 0
|
||||
}
|
||||
|
||||
# --- D-11 執行狀態事件 ---
|
||||
|
||||
# 一輪最多列幾筆明細。不設上限的話,一次壞掉的 hook 每次提示寫一筆,一輪就能把整頁灌爆,
|
||||
# 而灌爆的頁沒有人讀得完,等於這一節白寫。列不下的用一句話講清楚還有幾筆。
|
||||
EV_MAX_ROWS=50
|
||||
|
||||
# 把事件流的一行 JSON 攤成定位字元分隔的欄位。不引 JSON 解析器:巡檢跑在 cron 上,能倚賴的
|
||||
# 只有系統本來就有的工具,多一個相依就是多一種在某台機器上跑不起來的方式。
|
||||
# 取值以「鍵名加冒號加引號」定位,取到下一個引號為止。detail 裡若有被跳脫的引號會在那裡被切斷,
|
||||
# 那只影響顯示的長度,不影響筆數與配對,所以不為它多寫一套解析。
|
||||
events_to_tsv() { # $1=原始事件檔 $2=輸出檔
|
||||
awk '
|
||||
function jstr(s, k, r) {
|
||||
if (match(s, "\"" k "\":\"")) {
|
||||
r = substr(s, RSTART + length(k) + 4)
|
||||
if (match(r, "\"")) return substr(r, 1, RSTART - 1)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
function jnum(s, k, r) {
|
||||
if (match(s, "\"" k "\":[0-9]+")) { r = substr(s, RSTART, RLENGTH); sub(/^.*:/, "", r); return r }
|
||||
return ""
|
||||
}
|
||||
{
|
||||
gsub(/\t/, " ")
|
||||
printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", \
|
||||
jstr($0, "ts"), jstr($0, "cli"), jstr($0, "session"), jstr($0, "kind"), jstr($0, "name"), \
|
||||
jstr($0, "phase"), jstr($0, "status"), jnum($0, "exit"), jstr($0, "detail")
|
||||
}' "$1" >"$2" 2>/dev/null
|
||||
}
|
||||
|
||||
d11() {
|
||||
D11_STATUS=fail; D11_RC=0; D11_NOTE=''
|
||||
EV_TOTAL=0; EV_BAD=0; EV_UNPAIRED=0; EV_RUNNING=0; EV_ROTATED=skipped
|
||||
{
|
||||
printf '### 執行狀態事件\n\n'
|
||||
printf '資料出自 `%s`,由 `jsc-hooks` 的 `tools/report-status.sh drain` 排空,位移記在 `%s`。\n\n' \
|
||||
"\$JSC_HOME/usage/events.jsonl" "\$JSC_HOME/usage/scan-state/events.offset"
|
||||
printf '技能的 `start` 由 hook 記,`end` 只能由技能自己在收尾時寫。所以**有 `start` 沒有配對的 `end` 就是那一輪中止了**,配對以 `session` 加 `name` 為鍵。\n\n'
|
||||
} >"$RD/d11.md"
|
||||
|
||||
if ! _rs=$(find_tool hooks tools/report-status.sh "${JSC_ASSIST_REPORT_STATUS_SH:-}"); then
|
||||
D11_NOTE='找不到 jsc-hooks 的 tools/report-status.sh'
|
||||
D11_RC=127
|
||||
add_failed_source 'jsc-hooks/tools/report-status.sh'
|
||||
printf '**這一項失敗**:%s。這一輪沒有執行狀態事件,不是「每一支都跑成功」。\n' "$D11_NOTE" >>"$RD/d11.md"
|
||||
add_pending '執行狀態事件讀不到,jsc-hooks 沒裝或版本太舊' '執行狀態事件' '/jsc-cli:doctor'
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 排空。結束碼 3 是「沒有新事件」,那是正常狀態,不是失敗——每一輪都排空,多數輪次本來
|
||||
# 就沒有新事件。JSC_HOME 明寫成環境變數傳下去:這支腳本裡的 JSC_HOME 不見得是匯出的,
|
||||
# 子行程自己算預設值時,兩邊指到同一個目錄才算數。
|
||||
: >"$RD/events.raw"
|
||||
_rc=0
|
||||
JSC_HOME="$JSC_HOME" "$_rs" drain >"$RD/events.raw" 2>"$RD/d11.err" </dev/null || _rc=$?
|
||||
if [ "$_rc" -ne 0 ] && [ "$_rc" -ne 3 ]; then
|
||||
D11_RC="$_rc"
|
||||
D11_NOTE="report-status.sh drain 回 $_rc"
|
||||
add_failed_source "$_rs"
|
||||
printf '**這一項失敗**:%s。訊息:%s\n' "$D11_NOTE" "$(cell "$(cat "$RD/d11.err" 2>/dev/null)")" >>"$RD/d11.md"
|
||||
add_pending '事件流排空失敗,本輪沒有執行狀態證據' '執行狀態事件' '/jsc-hooks:repair'
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 輪替緊接在排空後面跑,而且只在排空成功時跑。排空失敗時位移的狀態是未知的,這時候輪替
|
||||
# 會把還沒排空的事件搬進備份檔,那一批從此不會再出現在任何一輪。
|
||||
_rrc=0
|
||||
_rout=$(JSC_HOME="$JSC_HOME" "$_rs" rotate 2>>"$RD/d11.err" </dev/null) || _rrc=$?
|
||||
if [ "$_rrc" -ne 0 ]; then
|
||||
EV_ROTATED=failed
|
||||
add_warn '事件流輪替失敗'
|
||||
add_pending "事件流輪替回 $_rrc,檔案會一直長大" '執行狀態事件' '/jsc-hooks:repair'
|
||||
elif [ -n "$_rout" ]; then
|
||||
EV_ROTATED=rotated
|
||||
else
|
||||
EV_ROTATED=not-needed
|
||||
fi
|
||||
|
||||
events_to_tsv "$RD/events.raw" "$RD/events.tsv"
|
||||
|
||||
_now=$(date +%s)
|
||||
_ttl="${HEARTBEAT_TTL:-}"
|
||||
case "$_ttl" in ''|*[!0-9]*) _ttl=300 ;; esac
|
||||
|
||||
: >"$RD/events-bad.tsv"; : >"$RD/events-unpaired.tsv"; : >"$RD/events-open.next"
|
||||
[ -f "$EVENTS_OPEN" ] || : >"$EVENTS_OPEN"
|
||||
# 兩個輸入檔:先讀上一輪留下來還開著的 start,再讀本輪排空到的事件。用 FILENAME 分辨是
|
||||
# 哪一個檔案,不用 NR==FNR:上一輪那個檔案是空的時候,NR==FNR 會把本輪第一筆事件誤當成
|
||||
# 舊資料,而「上一輪沒有開著的 start」正是最常見的情況。
|
||||
awk -F' ' -v prevf="$EVENTS_OPEN" -v now="$_now" -v ttl="$_ttl" -v maxage=86400 \
|
||||
-v openf="$RD/events-open.next" -v badf="$RD/events-bad.tsv" -v unpf="$RD/events-unpaired.tsv" '
|
||||
FILENAME == prevf { k = $1 SUBSEP $2; okind[k] = $3; oseen[k] = $4; ots[k] = $5; next }
|
||||
{
|
||||
total++
|
||||
if ($7 != "ok") { bad++; printf "%s\t%s\t%s\t%s\t%s\t%s\n", $1, $4, $5, $7, $8, $9 >badf }
|
||||
k = $3 SUBSEP $5
|
||||
if ($6 == "start") {
|
||||
okind[k] = $4
|
||||
# 第一次看到才記時間:同一支技能在同一個工作階段重複開場時,年紀要從最早那一次算起。
|
||||
if (!(k in oseen)) { oseen[k] = now; ots[k] = $1 }
|
||||
} else if ($6 == "end") { delete okind[k]; delete oseen[k]; delete ots[k] }
|
||||
}
|
||||
END {
|
||||
for (k in oseen) {
|
||||
age = now - oseen[k]
|
||||
# 開超過一天的丟掉。留著只會讓這個檔案無止境長大,而那麼久沒收尾的 start 早就報過了。
|
||||
if (age > maxage) continue
|
||||
split(k, p, SUBSEP)
|
||||
printf "%s\t%s\t%s\t%s\t%s\n", p[1], p[2], okind[k], oseen[k], ots[k] >openf
|
||||
if (age >= ttl) { unpaired++; printf "%s\t%s\t%s\t%s\t%s\n", p[2], okind[k], p[1], ots[k], age >unpf }
|
||||
else running++
|
||||
}
|
||||
printf "total=%d bad=%d unpaired=%d running=%d\n", total, bad, unpaired, running
|
||||
}' "$EVENTS_OPEN" "$RD/events.tsv" >"$RD/d11.counts" 2>>"$RD/d11.err"
|
||||
|
||||
_counts=$(cat "$RD/d11.counts" 2>/dev/null)
|
||||
for _kv in $_counts; do
|
||||
case "$_kv" in
|
||||
total=*) EV_TOTAL="${_kv#total=}" ;;
|
||||
bad=*) EV_BAD="${_kv#bad=}" ;;
|
||||
unpaired=*) EV_UNPAIRED="${_kv#unpaired=}" ;;
|
||||
running=*) EV_RUNNING="${_kv#running=}" ;;
|
||||
esac
|
||||
done
|
||||
for _v in EV_TOTAL EV_BAD EV_UNPAIRED EV_RUNNING; do
|
||||
eval "_x=\$$_v"
|
||||
case "$_x" in ''|*[!0-9]*) eval "$_v=0" ;; esac
|
||||
done
|
||||
|
||||
# 換上這一輪之後還開著的 start。換不上不算整項失敗:下一輪頂多重算一次年紀,不會漏報。
|
||||
cp "$RD/events-open.next" "$EVENTS_OPEN" 2>/dev/null \
|
||||
|| printf '**未配對清單存檔失敗**:`%s` 寫不進去,下一輪的年紀要重算。\n\n' "$EVENTS_OPEN" >>"$RD/d11.md"
|
||||
|
||||
{
|
||||
printf '| 項目 | 內容 |\n'
|
||||
printf '| --- | --- |\n'
|
||||
printf '| 本輪事件數 | %s |\n' "$EV_TOTAL"
|
||||
printf '| 非 ok 事件數 | %s |\n' "$EV_BAD"
|
||||
printf '| 有 start 沒有 end(開超過 %s 秒,疑似中止) | %s |\n' "$_ttl" "$EV_UNPAIRED"
|
||||
printf '| 有 start 沒有 end(未達門檻,還在跑) | %s |\n' "$EV_RUNNING"
|
||||
printf '| 事件流輪替 | %s |\n' "$EV_ROTATED"
|
||||
printf '\n#### 非 ok 事件明細\n\n'
|
||||
} >>"$RD/d11.md"
|
||||
|
||||
if [ -s "$RD/events-bad.tsv" ]; then
|
||||
{
|
||||
printf '| 時間 | 類別 | 名稱 | status | 結束碼 | detail |\n'
|
||||
printf '| --- | --- | --- | --- | ---: | --- |\n'
|
||||
} >>"$RD/d11.md"
|
||||
_n=0
|
||||
while IFS=' ' read -r _ets _ekind _ename _est _eex _edt; do
|
||||
[ -n "$_ename" ] || continue
|
||||
_n=$(( _n + 1 ))
|
||||
[ "$_n" -le "$EV_MAX_ROWS" ] || continue
|
||||
printf '| %s | %s | %s | %s | %s | %s |\n' \
|
||||
"$(cell "$_ets")" "$(cell "$_ekind")" "$(cell "$_ename")" \
|
||||
"$(cell "$_est")" "$(cell "${_eex:--}")" "$(cell "${_edt:--}")" >>"$RD/d11.md"
|
||||
done <"$RD/events-bad.tsv"
|
||||
[ "$_n" -gt "$EV_MAX_ROWS" ] \
|
||||
&& printf '\n本輪非 ok 事件共 %s 筆,上表只列前 %s 筆。完整原文在 `%s`。\n' \
|
||||
"$_n" "$EV_MAX_ROWS" "$RD/events.raw" >>"$RD/d11.md"
|
||||
else
|
||||
printf '本輪沒有 status 不是 ok 的事件。\n' >>"$RD/d11.md"
|
||||
fi
|
||||
|
||||
{
|
||||
printf '\n#### 有 start 沒有配對的 end\n\n'
|
||||
printf '這一節就是中止的證據。`start` 由 hook 免費記下,`end` 要技能自己寫,所以只有 `start` 的那一筆,代表那一支技能沒有跑到收尾那一步。\n\n'
|
||||
} >>"$RD/d11.md"
|
||||
if [ -s "$RD/events-unpaired.tsv" ]; then
|
||||
{
|
||||
printf '| 名稱 | 類別 | session | start 時間 | 已開著(秒) |\n'
|
||||
printf '| --- | --- | --- | --- | ---: |\n'
|
||||
} >>"$RD/d11.md"
|
||||
_n=0
|
||||
while IFS=' ' read -r _uname _ukind _usess _uts _uage; do
|
||||
[ -n "$_uname" ] || continue
|
||||
_n=$(( _n + 1 ))
|
||||
[ "$_n" -le "$EV_MAX_ROWS" ] || continue
|
||||
printf '| %s | %s | %s | %s | %s |\n' \
|
||||
"$(cell "$_uname")" "$(cell "$_ukind")" "$(cell "$_usess")" \
|
||||
"$(cell "$_uts")" "$(cell "$_uage")" >>"$RD/d11.md"
|
||||
done <"$RD/events-unpaired.tsv"
|
||||
[ "$_n" -gt "$EV_MAX_ROWS" ] \
|
||||
&& printf '\n未配對的 `start` 共 %s 筆,上表只列前 %s 筆。\n' "$_n" "$EV_MAX_ROWS" >>"$RD/d11.md"
|
||||
else
|
||||
printf '本輪沒有開超過門檻又沒收尾的 `start`。\n' >>"$RD/d11.md"
|
||||
fi
|
||||
printf '\n未達門檻的 `start` 不列進上表,它們多半只是還在跑。跨輪繼續配對,紀錄留在 `%s`;不跨輪的話,跑超過一個巡檢週期的技能每一輪都會被報成中止。\n' \
|
||||
"$EVENTS_OPEN" >>"$RD/d11.md"
|
||||
|
||||
if [ "$EV_BAD" -gt 0 ]; then
|
||||
add_warn '有非 ok 的執行狀態事件'
|
||||
add_pending "本輪有 $EV_BAD 筆執行狀態不是 ok 的事件" '執行狀態事件' '照明細表的名稱找那一支技能或 hook'
|
||||
fi
|
||||
if [ "$EV_UNPAIRED" -gt 0 ]; then
|
||||
add_warn '有技能只有 start 沒有 end'
|
||||
add_pending "本輪有 $EV_UNPAIRED 支技能只有 start 沒有 end,那幾輪中止了" '執行狀態事件' '照明細表的名稱重跑那一支技能'
|
||||
fi
|
||||
|
||||
if [ "$EV_TOTAL" -gt 0 ]; then
|
||||
D11_STATUS=ok
|
||||
else
|
||||
D11_STATUS=empty
|
||||
printf '\n來源讀得到,本輪沒有新事件(`drain` 回 3)。那是正常狀態,不是失敗:多數輪次本來就沒有新的技能或 hook 跑過。\n' >>"$RD/d11.md"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# --- 待辦簿筆數(只供目錄頁那一列用)---
|
||||
|
||||
count_tasks() {
|
||||
@@ -646,9 +894,9 @@ compose() {
|
||||
printf '| 巡檢時間 | %s |\n' "$AT"
|
||||
printf '| 觸發方式 | %s |\n' "$TRIGGER"
|
||||
printf '| 本輪判定 | %s |\n' "$VERDICT"
|
||||
printf '| 本輪項目 | 四項:D-01 使用統計、D-04 版本與重啟閘門、D-07 階段鎖與工作包鎖、D-09 心跳自述。成功 %s 項、失敗 %s 項 |\n' "$OK_COUNT" "$FAIL_COUNT"
|
||||
printf '| 本輪項目 | 五項:D-01 使用統計、D-04 版本與重啟閘門、D-07 階段鎖與工作包鎖、D-09 心跳自述、D-11 執行狀態事件。成功 %s 項、失敗 %s 項 |\n' "$OK_COUNT" "$FAIL_COUNT"
|
||||
printf '| 讀不到的來源 | %s |\n' "$(cell "${FAILED_SOURCES:-無}")"
|
||||
# 警示來源緊接在讀不到的來源後面:兩列語意相近,而且四項讀取全部成功、判定卻是警示
|
||||
# 警示來源緊接在讀不到的來源後面:兩列語意相近,而且各項讀取全部成功、判定卻是警示
|
||||
# 時,這一塊裡只有這一列講得出原因,跟摘要表那一欄是同一個理由。
|
||||
printf '| 警示來源 | %s |\n' "$(cell "${WARN_SOURCES:-無}")"
|
||||
if [ "$LOCK_BROKEN" -eq 1 ]; then
|
||||
@@ -657,6 +905,7 @@ compose() {
|
||||
printf '\n'
|
||||
cat "$RD/d09.md"; printf '\n'
|
||||
cat "$RD/d01.md"; printf '\n'
|
||||
cat "$RD/d11.md"; printf '\n'
|
||||
printf '### hook 執行期錯誤\n\n'
|
||||
printf '**這一輪不做這一項。** D-02 hook 錯誤巡檢還沒實作,這一節沒有資料不代表沒有 hook 錯誤。要現在查就跑 `/jsc-hooks:hooks-install` 的錯誤掃描,或直接跑 `jsc-hooks` 的 `tools/scan-hook-errors.sh`。\n\n'
|
||||
cat "$RD/d04.md"; printf '\n'
|
||||
@@ -672,8 +921,10 @@ compose() {
|
||||
} >"$RD/latest.md"
|
||||
|
||||
# 摘要表的那一列。欄位刻意只有五個,一列要能一眼看完,才看得出是從哪一輪開始壞的。
|
||||
# 「警示來源」那一欄不能省:四項讀取全部成功、但讀到的內容有警示時,判定是警示而成敗欄
|
||||
# 是 4/4,沒有這一欄的話,看的人不知道警示哪來。
|
||||
# 「警示來源」那一欄不能省:各項讀取全部成功、但讀到的內容有警示時,判定是警示而成敗欄
|
||||
# 是滿分,沒有這一欄的話,看的人不知道警示哪來。
|
||||
# 欄名寫「各項成敗」而不寫項數:巡檢項目會增加,欄名寫死數字就要跟著改,而舊頁那些列的
|
||||
# 欄名不會跟著改,同一張表就會有兩種欄名。
|
||||
printf '| %s | %s | %s/%s | %s | %s |\n' \
|
||||
"$AT" "$VERDICT" "$OK_COUNT" "$ITEM_TOTAL" "$PEND_COUNT" \
|
||||
"$(cell "${WARN_SOURCES:-無}")" >"$RD/summary-row.md"
|
||||
@@ -682,7 +933,7 @@ compose() {
|
||||
{
|
||||
printf '## 近 24 輪摘要\n\n'
|
||||
printf '一輪一列,最新的在最上面,超過 24 列就丟掉最舊的那一列。\n\n'
|
||||
printf '| 巡檢時間 | 本輪判定 | 四項成敗 | 待人處理 | 警示來源 |\n'
|
||||
printf '| 巡檢時間 | 本輪判定 | 各項成敗 | 待人處理 | 警示來源 |\n'
|
||||
printf '| --- | --- | --- | ---: | --- |\n'
|
||||
cat "$RD/summary-row.md"
|
||||
} >"$RD/summary.md"
|
||||
@@ -697,7 +948,7 @@ compose() {
|
||||
printf '> 目錄頁 `MONITOR_CONTENTS` 在 `JSC_WIKI_REPO_CONTENTS` 解出的專用存取庫,和這頁不同庫。\n'
|
||||
printf '> 那一頁只更新自己那一列,別台機器的列一個字都不動,寫入交給 `jsc-gitea/tools/wiki-contents.sh upsert`。\n\n'
|
||||
printf '```mermaid\nflowchart LR\n'
|
||||
printf ' A[巡檢一輪] --> B[收攏四項結果]\n'
|
||||
printf ' A[巡檢一輪] --> B[收攏各項結果]\n'
|
||||
printf ' B --> C[讀回舊頁]\n'
|
||||
printf ' C --> D[換掉最新一輪那一塊]\n'
|
||||
printf ' D --> E[本輪摘要列插到表格最上面,截到 24 列]\n'
|
||||
@@ -783,8 +1034,10 @@ case "$CMD" in
|
||||
d01
|
||||
d04
|
||||
d07
|
||||
d11
|
||||
count_tasks
|
||||
tally "$D01_STATUS"; tally "$D04_STATUS"; tally "$D07_STATUS"; tally "$D09_STATUS"
|
||||
tally "$D11_STATUS"
|
||||
|
||||
HASH=''
|
||||
if _hi=$(find_tool gitea tools/hash-id "${JSC_ASSIST_HASH_ID:-}"); then
|
||||
@@ -806,12 +1059,19 @@ case "$CMD" in
|
||||
printf 'item=D-04 status=%s rc=%s note=%s\n' "$D04_STATUS" "$D04_RC" "$D04_NOTE"
|
||||
printf 'item=D-07 status=%s rc=%s note=%s\n' "$D07_STATUS" "$D07_RC" "$D07_NOTE"
|
||||
printf 'item=D-09 status=%s rc=%s note=%s\n' "$D09_STATUS" "$D09_RC" "$D09_NOTE"
|
||||
printf 'item=D-11 status=%s rc=%s note=%s\n' "$D11_STATUS" "$D11_RC" "$D11_NOTE"
|
||||
printf 'verdict=%s\n' "$VERDICT"
|
||||
printf 'failed_sources=%s\n' "${FAILED_SOURCES:-無}"
|
||||
printf 'warn_sources=%s\n' "${WARN_SOURCES:-無}"
|
||||
printf 'tasks_total=%s\n' "$TASKS_TOTAL"
|
||||
printf 'tasks_failing=%s\n' "$TASKS_FAILING"
|
||||
printf 'pending=%s\n' "$PEND_COUNT"
|
||||
printf 'events_total=%s\n' "$EV_TOTAL"
|
||||
printf 'events_bad=%s\n' "$EV_BAD"
|
||||
printf 'events_unpaired=%s\n' "$EV_UNPAIRED"
|
||||
printf 'events_running=%s\n' "$EV_RUNNING"
|
||||
printf 'events_rotated=%s\n' "$EV_ROTATED"
|
||||
printf 'events_file=%s\n' "$RD/events.raw"
|
||||
printf 'latest_file=%s\n' "$RD/latest.md"
|
||||
printf 'summary_file=%s\n' "$RD/summary.md"
|
||||
printf 'summary_row_file=%s\n' "$RD/summary-row.md"
|
||||
|
||||
@@ -440,9 +440,14 @@ print_allow_rules() {
|
||||
# 少了規則就會停在權限詢問,而那一輪沒有人可以按同意。
|
||||
# link-check.sh 同理:兩次寫入前都要先驗連結,少了這一條,驗證那一步就停在權限詢問,那一輪
|
||||
# 什麼都寫不成。它排在寫入之前,所以擋住它等於整輪報廢。
|
||||
# report-status.sh 是執行狀態事件那一支,這一輪會直接叫它兩次以上:排空與輪替由 patrol.sh
|
||||
# 代跑(那是已放行指令的子行程,不會再問一次),但收尾那一筆 skill-end 是技能自己用 Bash 叫的,
|
||||
# 那一次就要這一條規則。少了它,那一輪會停在最後一步的權限詢問,而排程那一輪沒有人可以按
|
||||
# 同意:那一輪的收尾事件寫不出去,start 永遠配不到 end,下一輪就把一輪其實做完的巡檢報成中止。
|
||||
for _s in "$CURRENT/jsc-assist/tools/schedule.sh" \
|
||||
"$CURRENT/jsc-assist/tools/patrol.sh" \
|
||||
"$CURRENT/jsc-hooks/hooks/heartbeat.sh" \
|
||||
"$CURRENT/jsc-hooks/tools/report-status.sh" \
|
||||
"$CURRENT/jsc-gitea/tools/gitea.sh" \
|
||||
"$CURRENT/jsc-gitea/tools/wiki-contents.sh" \
|
||||
"$CURRENT/jsc-gitea/tools/link-check.sh"; do
|
||||
|
||||
Reference in New Issue
Block a user