@rubytech/create-maxy-code 0.1.476 → 0.1.477
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block-sqlite.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block-sqlite.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block-sqlite.test.js +150 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block-sqlite.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block.test.js +248 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.d.ts +90 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.js +105 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js +88 -10
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js.map +1 -1
- package/payload/platform/scripts/cpu-triage.sh +313 -0
- package/payload/server/{chunk-LBCMFD4O.js → chunk-JECAP3Z2.js} +126 -37
- package/payload/server/maxy-edge.js +1 -1
- package/payload/server/public/activity.html +6 -6
- package/payload/server/public/assets/{AdminLoginScreens-DximwPlS.js → AdminLoginScreens-mEKP4pDi.js} +1 -1
- package/payload/server/public/assets/{AdminShell-BehbH-Oa.js → AdminShell-BwSaZ88d.js} +1 -1
- package/payload/server/public/assets/{Checkbox-DM-eHqOS.js → Checkbox-CN4uQ80w.js} +1 -1
- package/payload/server/public/assets/activity-CIQ8ozNZ.js +1 -0
- package/payload/server/public/assets/{admin-CAkDnGdk.js → admin-BVhgfgs1.js} +1 -1
- package/payload/server/public/assets/{browser-DXl8hOoi.js → browser-D-HMmHcM.js} +1 -1
- package/payload/server/public/assets/{calendar-DP6hn4-6.js → calendar-tGWvFyFn.js} +1 -1
- package/payload/server/public/assets/chat-DkttVxAz.js +1 -0
- package/payload/server/public/assets/chevron-left-CZ4ez9G5.js +1 -0
- package/payload/server/public/assets/data-P-mcmnNi.js +1 -0
- package/payload/server/public/assets/{graph-labels-CZykslZM.js → graph-labels-qnBleOE6.js} +1 -1
- package/payload/server/public/assets/{graph-CHcYoEJ5.js → graph-zEw610xK.js} +1 -1
- package/payload/server/public/assets/{maximize-2-31AZEcMS.js → maximize-2-BlTjXT_Y.js} +1 -1
- package/payload/server/public/assets/{operator-BGGpdsO_.js → operator-C7oIw2PG.js} +1 -1
- package/payload/server/public/assets/{page-DOeiiqbR.js → page-CTP7OFZa.js} +1 -1
- package/payload/server/public/assets/{page-BloC6ygA.js → page-DL6Zsdvk.js} +1 -1
- package/payload/server/public/assets/{public-CGgO6IZv.js → public-r1A9dqR_.js} +1 -1
- package/payload/server/public/assets/{rotate-ccw-BlAi6iFg.js → rotate-ccw-CzkkKx4-.js} +1 -1
- package/payload/server/public/assets/{tasks-C1giFRRL.js → tasks-fIJwYFWG.js} +1 -1
- package/payload/server/public/assets/{time-entry-format-CiUUQdq7.js → time-entry-format-j669DgXj.js} +1 -1
- package/payload/server/public/assets/{triangle-alert-CVqKuLoa.js → triangle-alert-BPgCgIef.js} +1 -1
- package/payload/server/public/assets/{useCopyFeedback-ksyUe_g1.js → useCopyFeedback-BmLa2aes.js} +1 -1
- package/payload/server/public/assets/{useSelectionMode-BPrBkdTd.js → useSelectionMode-DnnM7A8-.js} +1 -1
- package/payload/server/public/assets/{useSubAccountSwitcher-K4kYeN6i.css → useSubAccountSwitcher-C_E8h07P.css} +1 -1
- package/payload/server/public/assets/{useVoiceRecorder-CTXP8oTW.js → useVoiceRecorder-u2dwZfen.js} +1 -1
- package/payload/server/public/browser.html +5 -5
- package/payload/server/public/calendar.html +6 -6
- package/payload/server/public/chat.html +12 -12
- package/payload/server/public/data.html +11 -11
- package/payload/server/public/graph.html +11 -11
- package/payload/server/public/index.html +14 -14
- package/payload/server/public/operator.html +14 -14
- package/payload/server/public/public.html +12 -12
- package/payload/server/public/tasks.html +5 -5
- package/payload/server/server.js +173 -60
- package/payload/server/public/assets/activity-hDwUeQd6.js +0 -1
- package/payload/server/public/assets/chat-DhOfVGYM.js +0 -1
- package/payload/server/public/assets/chevron-left-DYNBoLii.js +0 -1
- package/payload/server/public/assets/data-bq8fTo_s.js +0 -1
- /package/payload/server/public/assets/{useSubAccountSwitcher-DzgzTRRn.js → useSubAccountSwitcher-CFa6ZAT1.js} +0 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# cpu-triage.sh — sustained CPU triage for a Maxy Code device.
|
|
3
|
+
#
|
|
4
|
+
# WHY THIS EXISTS, AND WHY IT SAMPLES FOR MINUTES RATHER THAN SECONDS
|
|
5
|
+
#
|
|
6
|
+
# The booking reconcile loop runs on a 120 s cycle and a tick costs roughly
|
|
7
|
+
# 800 ms of network wait plus a garbage-collection burst. Measured on laptop
|
|
8
|
+
# 192.168.88.10 (2026-07-20), the SAME server process over the SAME method read
|
|
9
|
+
# 0.2%, 0.2%, 0.2% in three consecutive 25 s windows and 10.8% in a 20 s window.
|
|
10
|
+
# Every one of those readings was correct. Any sample shorter than the period
|
|
11
|
+
# either lands on a tick or misses it, so spot checks on this platform produce
|
|
12
|
+
# contradictory answers indefinitely. This tool therefore samples across at
|
|
13
|
+
# least one full cycle and reports a DISTRIBUTION (mean, peak, and what share of
|
|
14
|
+
# the window sat above threshold), never a single number.
|
|
15
|
+
#
|
|
16
|
+
# THREE INSTRUMENTS THIS DELIBERATELY DOES NOT USE, EACH HAVING PRODUCED A FALSE
|
|
17
|
+
# CONCLUSION IN THE 2026-07-20 INVESTIGATION:
|
|
18
|
+
# * `ps %cpu` / `top` %CPU — LIFETIME averages. A 40-minute-old process read
|
|
19
|
+
# 8.5% against an 8-day-old peer at 0.1%; both were actually 0.2% live. This
|
|
20
|
+
# nearly shipped as an "85x regression".
|
|
21
|
+
# * `iostat -c` first line — a SINCE-BOOT average. Read as live it reported a
|
|
22
|
+
# machine "42% busy" that was 87% idle at that moment.
|
|
23
|
+
# * `ns_last_pid` deltas — counts THREAD creation as well as forks, and was
|
|
24
|
+
# compared against a single 10 s sample used as a "baseline". Produced a
|
|
25
|
+
# false 5.6x churn regression. Fork rate here comes from /proc/stat's
|
|
26
|
+
# `processes` counter, which counts forks only.
|
|
27
|
+
# Everything below is a DELTA over a stated window. No lifetime averages.
|
|
28
|
+
#
|
|
29
|
+
# Aggregate CPU is also avoided as a headline: two hot cores out of twenty
|
|
30
|
+
# average down to ~3% and vanish, which is exactly how a real, operator-visible
|
|
31
|
+
# condition was dismissed as "nothing changed". Per-core is the primary view.
|
|
32
|
+
#
|
|
33
|
+
# Unprivileged by design: the admin assistant runs without sudo, so this uses
|
|
34
|
+
# only /proc and the user's own cgroup tree. `perf` and `bpftrace` need root and
|
|
35
|
+
# are named in the escalation hint rather than invoked.
|
|
36
|
+
#
|
|
37
|
+
# EXIT CODES (gates, for deterministic callers)
|
|
38
|
+
# 0 no core sustained above threshold — nothing to escalate
|
|
39
|
+
# 1 one or more cores sustained above threshold — findings in the report
|
|
40
|
+
# 2 usage error or unmet precondition (nothing measured)
|
|
41
|
+
|
|
42
|
+
set -uo pipefail
|
|
43
|
+
|
|
44
|
+
WINDOW=300 # total sampling window, seconds (>= 2 reconcile cycles)
|
|
45
|
+
INTERVAL=10 # sample cadence, seconds
|
|
46
|
+
THRESHOLD=15 # per-core busy % that counts as "hot"
|
|
47
|
+
SUSTAIN=50 # % of samples above THRESHOLD to call a core "sustained"
|
|
48
|
+
TOPN=12 # processes to report
|
|
49
|
+
JSON=0
|
|
50
|
+
CONTROL="" # optional peer brand known NOT to have the change
|
|
51
|
+
|
|
52
|
+
usage() {
|
|
53
|
+
cat >&2 <<USAGE
|
|
54
|
+
usage: cpu-triage.sh [--window S] [--interval S] [--threshold PCT]
|
|
55
|
+
[--sustain PCT] [--top N] [--control BRAND] [--json]
|
|
56
|
+
|
|
57
|
+
--window total sampling window in seconds (default 300, min 120)
|
|
58
|
+
--interval sample cadence in seconds (default 10)
|
|
59
|
+
--threshold per-core busy %% treated as hot (default 15)
|
|
60
|
+
--sustain %% of samples above threshold to call it sustained (default 50)
|
|
61
|
+
--top processes to report (default 12)
|
|
62
|
+
--control a brand on the PREVIOUS build, for like-for-like comparison.
|
|
63
|
+
The single most valuable input: on 2026-07-20 an un-upgraded
|
|
64
|
+
peer collapsed a suspected regression into a measurement artifact.
|
|
65
|
+
--json emit machine-readable JSON instead of the text report
|
|
66
|
+
USAGE
|
|
67
|
+
exit 2
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
while [ $# -gt 0 ]; do
|
|
71
|
+
case "$1" in
|
|
72
|
+
--window) WINDOW="${2:-}"; shift 2 ;;
|
|
73
|
+
--interval) INTERVAL="${2:-}"; shift 2 ;;
|
|
74
|
+
--threshold) THRESHOLD="${2:-}"; shift 2 ;;
|
|
75
|
+
--sustain) SUSTAIN="${2:-}"; shift 2 ;;
|
|
76
|
+
--top) TOPN="${2:-}"; shift 2 ;;
|
|
77
|
+
--control) CONTROL="${2:-}"; shift 2 ;;
|
|
78
|
+
--json) JSON=1; shift ;;
|
|
79
|
+
-h|--help) usage ;;
|
|
80
|
+
*) echo "cpu-triage: unknown argument '$1'" >&2; usage ;;
|
|
81
|
+
esac
|
|
82
|
+
done
|
|
83
|
+
|
|
84
|
+
case "$WINDOW$INTERVAL$THRESHOLD$SUSTAIN$TOPN" in *[!0-9]*) usage ;; esac
|
|
85
|
+
[ "$INTERVAL" -ge 1 ] || usage
|
|
86
|
+
if [ "$WINDOW" -lt 120 ]; then
|
|
87
|
+
echo "cpu-triage: refusing a window under 120s — shorter than the reconcile cycle," >&2
|
|
88
|
+
echo " which is what makes spot readings contradict each other." >&2
|
|
89
|
+
exit 2
|
|
90
|
+
fi
|
|
91
|
+
[ -r /proc/stat ] || { echo "cpu-triage: /proc/stat unreadable — Linux only" >&2; exit 2; }
|
|
92
|
+
|
|
93
|
+
SAMPLES=$(( WINDOW / INTERVAL ))
|
|
94
|
+
[ "$SAMPLES" -ge 2 ] || { echo "cpu-triage: window/interval must yield >= 2 samples" >&2; exit 2; }
|
|
95
|
+
|
|
96
|
+
CLK=$(getconf CLK_TCK 2>/dev/null || echo 100)
|
|
97
|
+
TMP=$(mktemp -d "${TMPDIR:-/tmp}/cpu-triage.XXXXXX") || exit 2
|
|
98
|
+
trap 'rm -rf "$TMP"' EXIT INT TERM
|
|
99
|
+
|
|
100
|
+
CG_BASE="/sys/fs/cgroup/user.slice/user-$(id -u).slice/user@$(id -u).service/app.slice"
|
|
101
|
+
|
|
102
|
+
# ---------------------------------------------------------------------------
|
|
103
|
+
# Sampling. One loop, one cadence, every measure derived from the same instants
|
|
104
|
+
# so per-core, per-process and per-service figures are directly comparable.
|
|
105
|
+
# ---------------------------------------------------------------------------
|
|
106
|
+
snapshot() {
|
|
107
|
+
local idx="$1"
|
|
108
|
+
# NOTE: every redirect below MUST be `>>`. Each sample is a fresh awk process,
|
|
109
|
+
# and awk's `>` truncates the file once per invocation — so `>` silently keeps
|
|
110
|
+
# only the LAST sample, leaving nothing to diff. That bug shipped into the
|
|
111
|
+
# first test run and produced an empty per-core report with mean 0%.
|
|
112
|
+
awk -v idx="$idx" -v out="$TMP" '
|
|
113
|
+
FILENAME == "/proc/stat" {
|
|
114
|
+
if ($1 ~ /^cpu[0-9]+$/) {
|
|
115
|
+
total = 0
|
|
116
|
+
for (i = 2; i <= NF; i++) total += $i
|
|
117
|
+
# busy excludes idle (field 5) and iowait (field 6)
|
|
118
|
+
print idx, $1, total - $5 - $6, total >> (out "/cores.raw")
|
|
119
|
+
}
|
|
120
|
+
if ($1 == "processes") print idx, $2 >> (out "/forks.raw")
|
|
121
|
+
next
|
|
122
|
+
}
|
|
123
|
+
# /proc/<pid>/stat — comm may contain spaces and parens; the greedy .*\)
|
|
124
|
+
# anchors on the LAST close paren, which is the only safe split.
|
|
125
|
+
{
|
|
126
|
+
if (match($0, /^[0-9]+ \(.*\) /)) {
|
|
127
|
+
pid = $1
|
|
128
|
+
rest = substr($0, RSTART + RLENGTH)
|
|
129
|
+
n = split(rest, f, " ")
|
|
130
|
+
if (n >= 13) print idx, pid, f[12] + f[13] >> (out "/procs.raw")
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
' /proc/stat /proc/[0-9]*/stat 2>/dev/null
|
|
134
|
+
|
|
135
|
+
# Per-service cgroup CPU. Counts CHILD processes, which per-process sampling
|
|
136
|
+
# attributes nowhere — the original wrangler churn lived entirely in children.
|
|
137
|
+
#
|
|
138
|
+
# Read every cpu.stat in ONE awk rather than a shell loop forking per service.
|
|
139
|
+
# This tool reports fork rate, so its own churn is measurement noise it would
|
|
140
|
+
# otherwise attribute to the box: the loop form cost ~16 forks per sample.
|
|
141
|
+
if [ -d "$CG_BASE" ]; then
|
|
142
|
+
awk -v idx="$idx" '
|
|
143
|
+
/^usage_usec/ {
|
|
144
|
+
n = split(FILENAME, p, "/")
|
|
145
|
+
svc = p[n-1]; sub(/\.service$/, "", svc)
|
|
146
|
+
print idx, svc, $2
|
|
147
|
+
}
|
|
148
|
+
' "$CG_BASE"/*.service/cpu.stat >> "$TMP/cgroup.raw" 2>/dev/null
|
|
149
|
+
fi
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
HOST=$(hostname 2>/dev/null || echo unknown)
|
|
153
|
+
NCPU=$(awk '/^cpu[0-9]+/{n++} END{print n+0}' /proc/stat)
|
|
154
|
+
STARTED=$(date -Is 2>/dev/null || date)
|
|
155
|
+
|
|
156
|
+
if [ "$JSON" -eq 0 ]; then
|
|
157
|
+
echo "cpu-triage on $HOST — sampling ${WINDOW}s at ${INTERVAL}s cadence (${SAMPLES} samples, ${NCPU} cores)"
|
|
158
|
+
echo "this spans at least one 120s reconcile cycle; spot readings on this platform are not reliable"
|
|
159
|
+
echo
|
|
160
|
+
fi
|
|
161
|
+
|
|
162
|
+
i=0
|
|
163
|
+
while [ "$i" -lt "$SAMPLES" ]; do
|
|
164
|
+
snapshot "$i"
|
|
165
|
+
i=$(( i + 1 ))
|
|
166
|
+
[ "$i" -lt "$SAMPLES" ] && sleep "$INTERVAL"
|
|
167
|
+
done
|
|
168
|
+
ENDED=$(date -Is 2>/dev/null || date)
|
|
169
|
+
|
|
170
|
+
[ -s "$TMP/cores.raw" ] || { echo "cpu-triage: no samples captured" >&2; exit 2; }
|
|
171
|
+
|
|
172
|
+
# ---------------------------------------------------------------------------
|
|
173
|
+
# Analysis — distribution per core, not a single number.
|
|
174
|
+
# ---------------------------------------------------------------------------
|
|
175
|
+
awk -v thr="$THRESHOLD" '
|
|
176
|
+
{ key = $2
|
|
177
|
+
if (prev_total[key] != "") {
|
|
178
|
+
dt = $4 - prev_total[key]
|
|
179
|
+
db = $3 - prev_busy[key]
|
|
180
|
+
if (dt > 0) {
|
|
181
|
+
pct = db / dt * 100
|
|
182
|
+
sum[key] += pct; n[key]++
|
|
183
|
+
if (pct > peak[key]) peak[key] = pct
|
|
184
|
+
if (pct > thr) hot[key]++
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
prev_total[key] = $4; prev_busy[key] = $3
|
|
188
|
+
}
|
|
189
|
+
END {
|
|
190
|
+
for (k in sum)
|
|
191
|
+
printf "%s %.2f %.2f %.1f %d\n", k, sum[k]/n[k], peak[k], (hot[k]+0)/n[k]*100, n[k]
|
|
192
|
+
}
|
|
193
|
+
' "$TMP/cores.raw" | sort -k2 -rn > "$TMP/cores.out"
|
|
194
|
+
|
|
195
|
+
awk -v clk="$CLK" -v iv="$INTERVAL" '
|
|
196
|
+
{ key = $2
|
|
197
|
+
if (prev[key] != "") {
|
|
198
|
+
d = $3 - prev[key]
|
|
199
|
+
if (d >= 0) {
|
|
200
|
+
pct = d / (clk * iv) * 100
|
|
201
|
+
sum[key] += pct; n[key]++
|
|
202
|
+
if (pct > peak[key]) peak[key] = pct
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
prev[key] = $3
|
|
206
|
+
}
|
|
207
|
+
END { for (k in sum) if (sum[k]/n[k] >= 0.05) printf "%s %.2f %.2f\n", k, sum[k]/n[k], peak[k] }
|
|
208
|
+
' "$TMP/procs.raw" | sort -k2 -rn > "$TMP/procs.out"
|
|
209
|
+
|
|
210
|
+
if [ -s "${TMP}/cgroup.raw" ]; then
|
|
211
|
+
awk -v iv="$INTERVAL" '
|
|
212
|
+
{ key = $2
|
|
213
|
+
if (prev[key] != "") {
|
|
214
|
+
d = $3 - prev[key]
|
|
215
|
+
if (d >= 0) { pct = d / (iv * 1000000) * 100; sum[key] += pct; n[key]++
|
|
216
|
+
if (pct > peak[key]) peak[key] = pct }
|
|
217
|
+
}
|
|
218
|
+
prev[key] = $3
|
|
219
|
+
}
|
|
220
|
+
END { for (k in sum) printf "%s %.2f %.2f\n", k, sum[k]/n[k], peak[k] }
|
|
221
|
+
' "$TMP/cgroup.raw" | sort -k2 -rn > "$TMP/cgroup.out"
|
|
222
|
+
fi
|
|
223
|
+
|
|
224
|
+
FORKS_MIN=$(awk -v iv="$INTERVAL" 'NR==1{f=$2;i=$1} END{ if (NR>1) printf "%.0f", ($2-f)/((($1-i)*iv))*60 }' "$TMP/forks.raw" 2>/dev/null)
|
|
225
|
+
[ -n "$FORKS_MIN" ] || FORKS_MIN=0
|
|
226
|
+
|
|
227
|
+
HOT_COUNT=$(awk -v s="$SUSTAIN" '$4 >= s {n++} END{print n+0}' "$TMP/cores.out")
|
|
228
|
+
MEAN_CORE=$(awk '{s+=$2; n++} END{ if(n) printf "%.2f", s/n; else print "0" }' "$TMP/cores.out")
|
|
229
|
+
|
|
230
|
+
resolve() { tr '\0' ' ' < "/proc/$1/cmdline" 2>/dev/null | cut -c1-78 || true; }
|
|
231
|
+
|
|
232
|
+
# ---------------------------------------------------------------------------
|
|
233
|
+
# Report
|
|
234
|
+
# ---------------------------------------------------------------------------
|
|
235
|
+
if [ "$JSON" -eq 1 ]; then
|
|
236
|
+
printf '{\n "host": %s,\n "started": %s,\n "ended": %s,\n' "\"$HOST\"" "\"$STARTED\"" "\"$ENDED\""
|
|
237
|
+
printf ' "windowSec": %s, "intervalSec": %s, "samples": %s, "cores": %s,\n' "$WINDOW" "$INTERVAL" "$SAMPLES" "$NCPU"
|
|
238
|
+
printf ' "thresholdPct": %s, "sustainPct": %s,\n' "$THRESHOLD" "$SUSTAIN"
|
|
239
|
+
printf ' "meanPerCorePct": %s, "sustainedHotCores": %s, "forksPerMin": %s,\n' "$MEAN_CORE" "$HOT_COUNT" "$FORKS_MIN"
|
|
240
|
+
printf ' "perCore": ['
|
|
241
|
+
awk '{ printf "%s{\"cpu\":\"%s\",\"meanPct\":%s,\"peakPct\":%s,\"aboveThresholdPct\":%s}", (NR>1?",":""), $1,$2,$3,$4 }' "$TMP/cores.out"
|
|
242
|
+
printf '],\n "topProcesses": ['
|
|
243
|
+
head -n "$TOPN" "$TMP/procs.out" | while read -r pid mean peak; do
|
|
244
|
+
cl=$(resolve "$pid" | sed 's/\\/\\\\/g; s/"/\\"/g')
|
|
245
|
+
printf '{"pid":%s,"meanPct":%s,"peakPct":%s,"cmd":"%s"},' "$pid" "$mean" "$peak" "$cl"
|
|
246
|
+
done | sed 's/,$//'
|
|
247
|
+
printf '],\n "perService": ['
|
|
248
|
+
if [ -s "${TMP}/cgroup.out" ]; then
|
|
249
|
+
awk '{ printf "%s{\"service\":\"%s\",\"meanPct\":%s,\"peakPct\":%s}", (NR>1?",":""), $1,$2,$3 }' "$TMP/cgroup.out"
|
|
250
|
+
fi
|
|
251
|
+
printf ']\n}\n'
|
|
252
|
+
else
|
|
253
|
+
echo "PER-CORE over the window (mean / peak / share of window above ${THRESHOLD}%)"
|
|
254
|
+
echo " a core is 'sustained' when it exceeds threshold in >= ${SUSTAIN}% of samples"
|
|
255
|
+
awk -v thr="$THRESHOLD" -v s="$SUSTAIN" '
|
|
256
|
+
$2 >= 1 || $4 > 0 {
|
|
257
|
+
flag = ($4 >= s) ? " <== SUSTAINED" : ""
|
|
258
|
+
printf " %-7s mean=%6.2f%% peak=%6.2f%% above=%5.1f%% of window%s\n", $1, $2, $3, $4, flag
|
|
259
|
+
}' "$TMP/cores.out"
|
|
260
|
+
echo " ---"
|
|
261
|
+
printf " mean per-core %s%% sustained hot cores: %s of %s forks %s/min\n\n" "$MEAN_CORE" "$HOT_COUNT" "$NCPU" "$FORKS_MIN"
|
|
262
|
+
|
|
263
|
+
echo "TOP PROCESSES by measured delta (100% = one full core; NOT a lifetime average)"
|
|
264
|
+
head -n "$TOPN" "$TMP/procs.out" | while read -r pid mean peak; do
|
|
265
|
+
printf " %6.2f%% mean %6.2f%% peak pid=%-8s %s\n" "$mean" "$peak" "$pid" "$(resolve "$pid")"
|
|
266
|
+
done
|
|
267
|
+
echo
|
|
268
|
+
|
|
269
|
+
if [ -s "${TMP}/cgroup.out" ]; then
|
|
270
|
+
echo "PER-SERVICE cgroup CPU (child processes included — the platform's true footprint)"
|
|
271
|
+
tot=$(awk '{s+=$2} END{printf "%.2f", s}' "$TMP/cgroup.out")
|
|
272
|
+
awk '$2 >= 0.01 { printf " %-32s mean=%6.2f%% peak=%6.2f%%\n", $1, $2, $3 }' "$TMP/cgroup.out"
|
|
273
|
+
printf " ---\n all services %s%% of one core (%.2f%% of the %s-core box)\n\n" \
|
|
274
|
+
"$tot" "$(awk -v t="$tot" -v c="$NCPU" 'BEGIN{print t/c}')" "$NCPU"
|
|
275
|
+
if [ -n "$CONTROL" ]; then
|
|
276
|
+
cval=$(awk -v c="$CONTROL" '$1 == c {print $2}' "$TMP/cgroup.out")
|
|
277
|
+
if [ -n "$cval" ]; then
|
|
278
|
+
echo "CONTROL: '$CONTROL' is on the previous build and measured ${cval}% of one core."
|
|
279
|
+
echo " Compare peers against this, not against their own past readings. A peer that"
|
|
280
|
+
echo " did not get the change is worth more than any before/after inference."
|
|
281
|
+
echo
|
|
282
|
+
else
|
|
283
|
+
echo "CONTROL: '$CONTROL' not found among running services — comparison skipped."; echo
|
|
284
|
+
fi
|
|
285
|
+
fi
|
|
286
|
+
fi
|
|
287
|
+
|
|
288
|
+
if command -v sar >/dev/null 2>&1; then
|
|
289
|
+
y=$(date -d yesterday +%d 2>/dev/null)
|
|
290
|
+
if [ -n "$y" ] && [ -r "/var/log/sysstat/sa$y" ]; then
|
|
291
|
+
echo "HISTORY — same instrument, yesterday, for like-for-like regression checking"
|
|
292
|
+
sar -P ALL -f "/var/log/sysstat/sa$y" 2>/dev/null | awk -v thr="$THRESHOLD" '
|
|
293
|
+
/Average/ && $2 ~ /^[0-9]+$/ { b = 100 - $8; s += b; n++; if (b > thr) h++ }
|
|
294
|
+
END { if (n) printf " yesterday: mean per-core %.2f%% cores above %s%%: %d/%d\n", s/n, thr, h+0, n }'
|
|
295
|
+
echo
|
|
296
|
+
fi
|
|
297
|
+
fi
|
|
298
|
+
|
|
299
|
+
if [ "$HOT_COUNT" -gt 0 ]; then
|
|
300
|
+
echo "VERDICT: ${HOT_COUNT} core(s) sustained above ${THRESHOLD}%. Findings above."
|
|
301
|
+
echo " Before calling it a regression, check the desktop stack in the process list."
|
|
302
|
+
echo " On 2026-07-20 gnome-shell + gnome-system-monitor + Xorg accounted for ~41% of"
|
|
303
|
+
echo " a core, roughly three times the entire four-brand platform, and the System"
|
|
304
|
+
echo " Monitor window doing the observing was itself a third of that."
|
|
305
|
+
echo " To attribute short-lived process churn, escalate (needs root):"
|
|
306
|
+
echo " bpftrace -e 'tracepoint:syscalls:sys_enter_execve { @[comm, str(args->filename)] = count(); }'"
|
|
307
|
+
else
|
|
308
|
+
echo "VERDICT: no core sustained above ${THRESHOLD}%."
|
|
309
|
+
fi
|
|
310
|
+
fi
|
|
311
|
+
|
|
312
|
+
[ "$HOT_COUNT" -gt 0 ] && exit 1
|
|
313
|
+
exit 0
|