fix(webui): repair broken scenario Run button onclick, dedupe build.sh overlay staging
- page_scenario.go: onclick built via JSON.stringify() embedded raw double
quotes inside a double-quoted HTML attribute, truncating the attribute so
the click handler never compiled; pass the name through an escaped
data-scenario-name attribute instead.
- build.sh: overlay staging rsyncs (OVERLAY_DIR->stage, stage->includes.chroot)
ran without --delete, so a scenario removed from the repo (a9924b0) stayed
baked into every ISO built from the persistent stage cache since — the
"second script" in the Scenario page's list.
- blackbox: rewritten around a deterministic local zip + incremental
patch-the-changed-suffix onto removable media, instead of walking/copying
~90 files through a synchronous ntfs-3g FUSE mount every cycle. journalctl
captures are now "--since last sync" (were "--since boot", growing with
uptime) and metrics.db is excluded (was copied whole every cycle).
- scenario: nvbandwidth-acs-ab now escalates GPU count (same-socket pair,
other socket's pair, one cross-socket pair, all GPUs) under each ACS state
instead of always running all 6 GPUs at once, using a new `bee
gpu-bandwidth-groups` subcommand that discovers socket layout from
`nvidia-smi topo -m` at runtime — gpu_indices is host-specific, so this
can't be baked into the scenario file.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -572,6 +572,13 @@ func adjustFlushPeriod(current, duration time.Duration, success bool, fastCycles
|
||||
return next
|
||||
}
|
||||
|
||||
// syncCycle stages the current export tree on fast local storage (never
|
||||
// touching the removable-media mountpoint for the expensive part), packs it
|
||||
// into a single deterministic zip, and patches only the changed suffix of
|
||||
// that zip onto the target device — see blackbox_archive.go for why: walking
|
||||
// and rewriting ~90 small files through a synchronous FUSE mount (ntfs-3g
|
||||
// -o sync) was taking ~2x the flush period, dominated by journalctl output
|
||||
// that grows with uptime and got fully re-read/re-written every cycle.
|
||||
func (w *blackboxWorker) syncCycle() error {
|
||||
target, marker := w.snapshotTarget()
|
||||
mountpoint, mountedByBee, err := ensureMountedTarget(target, marker.EnrollmentID)
|
||||
@@ -580,24 +587,59 @@ func (w *blackboxWorker) syncCycle() error {
|
||||
}
|
||||
w.recordMountpoint(mountpoint, mountedByBee)
|
||||
|
||||
root := filepath.Join(mountpoint, w.runtime.bootFolder)
|
||||
if err := os.MkdirAll(root, 0755); err != nil {
|
||||
stageRoot := filepath.Join(w.runtime.exportDir, ".blackbox-stage", w.enrollmentID)
|
||||
if err := os.RemoveAll(stageRoot); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := categorizeExportTree(w.runtime.exportDir, root); err != nil {
|
||||
// includeMetricsDB=false: metrics.db is a live, growing SQLite file: past
|
||||
// versions copied it whole every cycle, which alone could dominate a
|
||||
// cycle's cost as it grew. Not carried onto the blackbox mirror.
|
||||
if err := categorizeExportTree(w.runtime.exportDir, stageRoot, false); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := w.captureSnapshots(stageRoot, w.lastCaptureSince()); err != nil {
|
||||
return err
|
||||
}
|
||||
// Same doc pair the support bundle ships at its root — a blackbox
|
||||
// capture on removable media has no manifest.txt/support-bundle
|
||||
// equivalent to explain its layout, so without this an agent handed
|
||||
// only the media would have nothing pointing it at README.md.
|
||||
if err := writeBundleDocs(root); err != nil {
|
||||
if err := writeBundleDocs(stageRoot); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := w.captureSnapshots(root); err != nil {
|
||||
|
||||
cacheDir := filepath.Join(w.runtime.exportDir, ".blackbox-cache")
|
||||
if err := os.MkdirAll(cacheDir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
return syncFilesystem(root)
|
||||
cachedPath := filepath.Join(cacheDir, w.enrollmentID+".zip")
|
||||
newZipPath := filepath.Join(cacheDir, w.enrollmentID+".zip.new")
|
||||
if err := buildZipArchive(stageRoot, newZipPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
targetPath := filepath.Join(mountpoint, w.runtime.bootFolder+" blackbox.zip")
|
||||
if err := patchArchiveOnTarget(targetPath, newZipPath, cachedPath); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Rename(newZipPath, cachedPath); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// lastCaptureSince is the "--since" boundary for this cycle's incremental
|
||||
// journalctl captures: the previous successful sync, or boot time on the
|
||||
// very first cycle. Using the last sync instead of always "--since boot"
|
||||
// keeps each cycle's journalctl output bounded by the flush period instead
|
||||
// of growing with total uptime.
|
||||
func (w *blackboxWorker) lastCaptureSince() time.Time {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
if w.lastSyncAt.IsZero() {
|
||||
return w.runtime.bootStarted
|
||||
}
|
||||
return w.lastSyncAt
|
||||
}
|
||||
|
||||
func (w *blackboxWorker) cleanup() {
|
||||
@@ -623,13 +665,25 @@ func (w *blackboxWorker) recordMountpoint(mountpoint string, mountedByBee bool)
|
||||
w.mountedByBee = mountedByBee
|
||||
}
|
||||
|
||||
func (w *blackboxWorker) captureSnapshots(root string) error {
|
||||
if err := captureCommandAtomic(filepath.Join(root, "tasks", "_services", "combined.journal.log"), "journalctl", "--no-pager", "--since", w.runtime.bootStarted.Format(time.RFC3339)); err != nil {
|
||||
// captureSnapshots writes this cycle's journalctl/dmesg/status snapshots
|
||||
// into root (a local staging tree — see syncCycle). journalctl output is
|
||||
// captured incrementally ("--since" the last successful cycle, not boot) and
|
||||
// appended as a new timestamped entry under journal-increments/ each cycle,
|
||||
// rather than overwriting one ever-growing file: re-dumping "--since boot"
|
||||
// every cycle made each cycle's journalctl call (and the file it produced)
|
||||
// grow with total uptime, independent of how much actually happened since
|
||||
// the last sync.
|
||||
func (w *blackboxWorker) captureSnapshots(root string, since time.Time) error {
|
||||
cycleTS := blackboxNow().Format("20060102-150405.000")
|
||||
sinceArg := since.Format(time.RFC3339)
|
||||
incDir := filepath.Join(root, "tasks", "_services", "journal-increments")
|
||||
|
||||
if err := captureCommandAtomic(filepath.Join(incDir, "combined-"+cycleTS+".log"), "journalctl", "--no-pager", "--since", sinceArg); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, svc := range supportBundleServices {
|
||||
dir := filepath.Join(root, serviceBundleDir(svc))
|
||||
if err := captureCommandAtomic(filepath.Join(dir, svc+".journal.log"), "journalctl", "--no-pager", "-u", svc, "--since", w.runtime.bootStarted.Format(time.RFC3339)); err != nil {
|
||||
if err := captureCommandAtomic(filepath.Join(incDir, svc+"-"+cycleTS+".log"), "journalctl", "--no-pager", "-u", svc, "--since", sinceArg); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := captureCommandAtomic(filepath.Join(dir, svc+".status.txt"), "systemctl", "status", svc, "--no-pager"); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user