fix(webui): dispatch "scenario" tasks in the real task runner, collapse duplicate target switch
The Scenario page's Run button enqueued a task with target "scenario" that never launched any load: the queue runs each task in an external bee-worker subprocess (RunPersistedTask -> executeTaskWithOptions in task_runner.go), whose target switch had no "scenario" case, so the task died with "unknown target: scenario". The case had only been added to taskQueue.runTask's switch in tasks.go — a stale copy exercised solely by unit tests, which is why the tests passed while the button did nothing. Add the "scenario" case (ReadScenario -> ParseScenarioJSON -> RunScenario) to executeTaskWithOptions, and collapse runTask into a thin delegate to it so there is a single target dispatch. The old runTask switch had already drifted into a subset (missing nvme-format, *-write, raid-*, nvidia-config, ...); removing it eliminates the divergence that hid this bug. Also drop the now-orphaned taskQueue.statusDB helper. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -231,6 +231,22 @@ func executeTaskWithOptions(opts *HandlerOptions, t *Task, j *jobState, ctx cont
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
archive, err = a.RunNCCLTests(ctx, "", t.params.GPUIndices, j.append)
|
archive, err = a.RunNCCLTests(ctx, "", t.params.GPUIndices, j.append)
|
||||||
|
case "scenario":
|
||||||
|
if a == nil {
|
||||||
|
err = fmt.Errorf("app not configured")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
var data []byte
|
||||||
|
data, err = a.ReadScenario(t.params.ScenarioName)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
var spec platform.ScenarioSpec
|
||||||
|
spec, err = platform.ParseScenarioJSON(data)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
archive, err = a.RunScenario(ctx, "", spec, j.append)
|
||||||
case "nvidia-stress":
|
case "nvidia-stress":
|
||||||
if a == nil {
|
if a == nil {
|
||||||
err = fmt.Errorf("app not configured")
|
err = fmt.Errorf("app not configured")
|
||||||
|
|||||||
@@ -846,364 +846,14 @@ func setCPUGovernor(governor string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// runTask executes the work for a task, writing output to j.
|
// runTask executes the work for a task, writing output to j. It delegates
|
||||||
|
// to executeTaskWithOptions — the exact same target dispatch the external
|
||||||
|
// task runner (RunPersistedTask, see task_runner.go) uses in its own
|
||||||
|
// subprocess — so the in-process path (recovered tasks, unit tests) and the
|
||||||
|
// subprocess path can never drift to different target coverage. Adding a new
|
||||||
|
// task target means editing one switch, not two.
|
||||||
func (q *taskQueue) runTask(t *Task, j *jobState, ctx context.Context) {
|
func (q *taskQueue) runTask(t *Task, j *jobState, ctx context.Context) {
|
||||||
if q.opts == nil {
|
executeTaskWithOptions(q.opts, t, j, ctx)
|
||||||
j.append("ERROR: handler options not configured")
|
|
||||||
j.finish("handler options not configured")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
a := q.opts.App
|
|
||||||
|
|
||||||
recovered := len(j.lines) > 0
|
|
||||||
j.append(fmt.Sprintf("Starting %s...", t.Name))
|
|
||||||
if recovered {
|
|
||||||
j.append(fmt.Sprintf("Recovered after bee-web restart at %s", time.Now().UTC().Format(time.RFC3339)))
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
archive string
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
switch t.Target {
|
|
||||||
case "nvidia":
|
|
||||||
if a == nil {
|
|
||||||
err = fmt.Errorf("app not configured")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
diagLevel := 2
|
|
||||||
if t.params.StressMode {
|
|
||||||
diagLevel = 3
|
|
||||||
}
|
|
||||||
if len(t.params.GPUIndices) > 0 || diagLevel > 0 {
|
|
||||||
result, e := a.RunNvidiaAcceptancePackWithOptions(
|
|
||||||
ctx, "", diagLevel, t.params.GPUIndices, j.append,
|
|
||||||
)
|
|
||||||
if e != nil {
|
|
||||||
err = e
|
|
||||||
} else {
|
|
||||||
archive = result.Body
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
archive, err = a.RunNvidiaAcceptancePack("", j.append)
|
|
||||||
}
|
|
||||||
case "nvidia-targeted-stress":
|
|
||||||
if a == nil {
|
|
||||||
err = fmt.Errorf("app not configured")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
dur := t.params.Duration
|
|
||||||
if dur <= 0 {
|
|
||||||
dur = 300
|
|
||||||
}
|
|
||||||
archive, err = a.RunNvidiaTargetedStressValidatePack(ctx, "", dur, t.params.GPUIndices, j.append)
|
|
||||||
case "nvidia-bench-perf":
|
|
||||||
if a == nil {
|
|
||||||
err = fmt.Errorf("app not configured")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
archive, err = a.RunNvidiaBenchmarkCtx(ctx, "", platform.NvidiaBenchmarkOptions{
|
|
||||||
Profile: t.params.BenchmarkProfile,
|
|
||||||
SizeMB: t.params.SizeMB,
|
|
||||||
GPUIndices: t.params.GPUIndices,
|
|
||||||
ExcludeGPUIndices: t.params.ExcludeGPUIndices,
|
|
||||||
RunNCCL: t.params.RunNCCL,
|
|
||||||
ParallelGPUs: t.params.ParallelGPUs,
|
|
||||||
RampStep: t.params.RampStep,
|
|
||||||
RampTotal: t.params.RampTotal,
|
|
||||||
RampRunID: t.params.RampRunID,
|
|
||||||
}, j.append)
|
|
||||||
case "nvidia-bench-power":
|
|
||||||
if a == nil {
|
|
||||||
err = fmt.Errorf("app not configured")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
archive, err = a.RunNvidiaPowerBenchCtx(ctx, app.DefaultBeeBenchPowerDir, platform.NvidiaBenchmarkOptions{
|
|
||||||
Profile: t.params.BenchmarkProfile,
|
|
||||||
GPUIndices: t.params.GPUIndices,
|
|
||||||
ExcludeGPUIndices: t.params.ExcludeGPUIndices,
|
|
||||||
RampStep: t.params.RampStep,
|
|
||||||
RampTotal: t.params.RampTotal,
|
|
||||||
RampRunID: t.params.RampRunID,
|
|
||||||
}, j.append)
|
|
||||||
case "nvidia-bench-autotune":
|
|
||||||
if a == nil {
|
|
||||||
err = fmt.Errorf("app not configured")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
archive, err = a.RunNvidiaPowerSourceAutotuneCtx(ctx, app.DefaultBeeBenchAutotuneDir, platform.NvidiaBenchmarkOptions{
|
|
||||||
Profile: t.params.BenchmarkProfile,
|
|
||||||
SizeMB: t.params.SizeMB,
|
|
||||||
}, t.params.BenchmarkKind, j.append)
|
|
||||||
case "nvidia-compute":
|
|
||||||
if a == nil {
|
|
||||||
err = fmt.Errorf("app not configured")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
dur := t.params.Duration
|
|
||||||
if t.params.BurnProfile != "" && dur <= 0 {
|
|
||||||
dur = resolveBurnPreset(t.params.BurnProfile).DurationSec
|
|
||||||
}
|
|
||||||
rampPlan, planErr := resolveNvidiaRampPlan(t.params.BurnProfile, t.params.StaggerGPUStart, t.params.GPUIndices)
|
|
||||||
if planErr != nil {
|
|
||||||
err = planErr
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if t.params.BurnProfile != "" && t.params.StaggerGPUStart && dur <= 0 {
|
|
||||||
dur = rampPlan.DurationSec
|
|
||||||
}
|
|
||||||
if rampPlan.StaggerSeconds > 0 {
|
|
||||||
j.append(fmt.Sprintf("NVIDIA staggered ramp-up enabled: %ds per GPU; post-ramp hold: %ds; total runtime: %ds", rampPlan.StaggerSeconds, dur, rampPlan.TotalDurationSec))
|
|
||||||
}
|
|
||||||
archive, err = a.RunNvidiaOfficialComputePack(ctx, "", dur, t.params.GPUIndices, rampPlan.StaggerSeconds, j.append)
|
|
||||||
case "nvidia-targeted-power":
|
|
||||||
if a == nil {
|
|
||||||
err = fmt.Errorf("app not configured")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
dur := t.params.Duration
|
|
||||||
if t.params.BurnProfile != "" && dur <= 0 {
|
|
||||||
dur = resolveBurnPreset(t.params.BurnProfile).DurationSec
|
|
||||||
}
|
|
||||||
archive, err = a.RunNvidiaTargetedPowerPack(ctx, "", dur, t.params.GPUIndices, j.append)
|
|
||||||
case "nvidia-pulse":
|
|
||||||
if a == nil {
|
|
||||||
err = fmt.Errorf("app not configured")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
dur := t.params.Duration
|
|
||||||
if t.params.BurnProfile != "" && dur <= 0 {
|
|
||||||
dur = resolveBurnPreset(t.params.BurnProfile).DurationSec
|
|
||||||
}
|
|
||||||
archive, err = a.RunNvidiaPulseTestPack(ctx, "", dur, t.params.GPUIndices, j.append)
|
|
||||||
case "nvidia-bandwidth":
|
|
||||||
if a == nil {
|
|
||||||
err = fmt.Errorf("app not configured")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
archive, err = a.RunNvidiaBandwidthPack(ctx, "", t.params.GPUIndices, j.append)
|
|
||||||
case "nvidia-interconnect":
|
|
||||||
if a == nil {
|
|
||||||
err = fmt.Errorf("app not configured")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
archive, err = a.RunNCCLTests(ctx, "", t.params.GPUIndices, j.append)
|
|
||||||
case "scenario":
|
|
||||||
if a == nil {
|
|
||||||
err = fmt.Errorf("app not configured")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
var data []byte
|
|
||||||
data, err = a.ReadScenario(t.params.ScenarioName)
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
var spec platform.ScenarioSpec
|
|
||||||
spec, err = platform.ParseScenarioJSON(data)
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
archive, err = a.RunScenario(ctx, "", spec, j.append)
|
|
||||||
case "nvidia-stress":
|
|
||||||
if a == nil {
|
|
||||||
err = fmt.Errorf("app not configured")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
dur := t.params.Duration
|
|
||||||
if t.params.BurnProfile != "" && dur <= 0 {
|
|
||||||
dur = resolveBurnPreset(t.params.BurnProfile).DurationSec
|
|
||||||
}
|
|
||||||
rampPlan, planErr := resolveNvidiaRampPlan(t.params.BurnProfile, t.params.StaggerGPUStart, t.params.GPUIndices)
|
|
||||||
if planErr != nil {
|
|
||||||
err = planErr
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if t.params.BurnProfile != "" && t.params.StaggerGPUStart && dur <= 0 {
|
|
||||||
dur = rampPlan.DurationSec
|
|
||||||
}
|
|
||||||
if rampPlan.StaggerSeconds > 0 {
|
|
||||||
j.append(fmt.Sprintf("NVIDIA staggered ramp-up enabled: %ds per GPU; post-ramp hold: %ds; total runtime: %ds", rampPlan.StaggerSeconds, dur, rampPlan.TotalDurationSec))
|
|
||||||
}
|
|
||||||
archive, err = runNvidiaStressPackCtx(a, ctx, "", platform.NvidiaStressOptions{
|
|
||||||
DurationSec: dur,
|
|
||||||
Loader: t.params.Loader,
|
|
||||||
GPUIndices: t.params.GPUIndices,
|
|
||||||
ExcludeGPUIndices: t.params.ExcludeGPUIndices,
|
|
||||||
StaggerSeconds: rampPlan.StaggerSeconds,
|
|
||||||
}, j.append)
|
|
||||||
case "memory":
|
|
||||||
if a == nil {
|
|
||||||
err = fmt.Errorf("app not configured")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
sizeMB, passes := resolveMemoryValidatePreset(t.params.BurnProfile, t.params.StressMode)
|
|
||||||
j.append(fmt.Sprintf("Memory validate preset: %d MB x %d pass(es)", sizeMB, passes))
|
|
||||||
archive, err = runMemoryAcceptancePackCtx(a, ctx, "", sizeMB, passes, j.append)
|
|
||||||
case "storage":
|
|
||||||
if a == nil {
|
|
||||||
err = fmt.Errorf("app not configured")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
archive, err = runStorageAcceptancePackCtx(a, ctx, "", t.params.StressMode, j.append)
|
|
||||||
case "nvidia-config":
|
|
||||||
if a == nil {
|
|
||||||
err = fmt.Errorf("app not configured")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
archive, err = runNvidiaConfigCheckPackCtx(a, ctx, "", j.append)
|
|
||||||
case "cpu":
|
|
||||||
if a == nil {
|
|
||||||
err = fmt.Errorf("app not configured")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
dur := t.params.Duration
|
|
||||||
if t.params.BurnProfile != "" && dur <= 0 {
|
|
||||||
dur = resolveBurnPreset(t.params.BurnProfile).DurationSec
|
|
||||||
}
|
|
||||||
if dur <= 0 {
|
|
||||||
if t.params.StressMode {
|
|
||||||
dur = 1800
|
|
||||||
} else {
|
|
||||||
dur = 60
|
|
||||||
}
|
|
||||||
}
|
|
||||||
j.append(fmt.Sprintf("CPU stress duration: %ds", dur))
|
|
||||||
archive, err = runCPUAcceptancePackCtx(a, ctx, "", dur, j.append)
|
|
||||||
case "amd":
|
|
||||||
if a == nil {
|
|
||||||
err = fmt.Errorf("app not configured")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
archive, err = runAMDAcceptancePackCtx(a, ctx, "", j.append)
|
|
||||||
case "amd-mem":
|
|
||||||
if a == nil {
|
|
||||||
err = fmt.Errorf("app not configured")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
archive, err = runAMDMemIntegrityPackCtx(a, ctx, "", j.append)
|
|
||||||
case "amd-bandwidth":
|
|
||||||
if a == nil {
|
|
||||||
err = fmt.Errorf("app not configured")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
archive, err = runAMDMemBandwidthPackCtx(a, ctx, "", j.append)
|
|
||||||
case "amd-stress":
|
|
||||||
if a == nil {
|
|
||||||
err = fmt.Errorf("app not configured")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
dur := t.params.Duration
|
|
||||||
if t.params.BurnProfile != "" && dur <= 0 {
|
|
||||||
dur = resolveBurnPreset(t.params.BurnProfile).DurationSec
|
|
||||||
}
|
|
||||||
archive, err = runAMDStressPackCtx(a, ctx, "", dur, j.append)
|
|
||||||
case "memory-stress":
|
|
||||||
if a == nil {
|
|
||||||
err = fmt.Errorf("app not configured")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
dur := t.params.Duration
|
|
||||||
if t.params.BurnProfile != "" && dur <= 0 {
|
|
||||||
dur = resolveBurnPreset(t.params.BurnProfile).DurationSec
|
|
||||||
}
|
|
||||||
archive, err = runMemoryStressPackCtx(a, ctx, "", dur, j.append)
|
|
||||||
case "sat-stress":
|
|
||||||
if a == nil {
|
|
||||||
err = fmt.Errorf("app not configured")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
dur := t.params.Duration
|
|
||||||
if t.params.BurnProfile != "" && dur <= 0 {
|
|
||||||
dur = resolveBurnPreset(t.params.BurnProfile).DurationSec
|
|
||||||
}
|
|
||||||
archive, err = runSATStressPackCtx(a, ctx, "", dur, j.append)
|
|
||||||
case "platform-stress":
|
|
||||||
if a == nil {
|
|
||||||
err = fmt.Errorf("app not configured")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
opts := resolvePlatformStressPreset(t.params.BurnProfile)
|
|
||||||
opts.Components = t.params.PlatformComponents
|
|
||||||
archive, err = a.RunPlatformStress(ctx, "", opts, j.append)
|
|
||||||
case "audit":
|
|
||||||
if a == nil {
|
|
||||||
err = fmt.Errorf("app not configured")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
result, e := a.RunAuditNow(q.opts.RuntimeMode)
|
|
||||||
if e != nil {
|
|
||||||
err = e
|
|
||||||
} else {
|
|
||||||
for _, line := range splitLines(result.Body) {
|
|
||||||
j.append(line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "support-bundle":
|
|
||||||
j.append("Building support bundle...")
|
|
||||||
archive, err = buildSupportBundle(q.opts.ExportDir)
|
|
||||||
case "install":
|
|
||||||
if strings.TrimSpace(t.params.Device) == "" {
|
|
||||||
err = fmt.Errorf("device is required")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
installLogPath := platform.InstallLogPath(t.params.Device)
|
|
||||||
j.append("Install log: " + installLogPath)
|
|
||||||
err = streamCmdJob(j, installCommand(ctx, t.params.Device, installLogPath))
|
|
||||||
case "install-to-ram":
|
|
||||||
if a == nil {
|
|
||||||
err = fmt.Errorf("app not configured")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
err = a.RunInstallToRAM(ctx, j.append)
|
|
||||||
default:
|
|
||||||
j.append("ERROR: unknown target: " + t.Target)
|
|
||||||
j.finish("unknown target")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the SAT archive was produced, check overall_status and write to component DB.
|
|
||||||
if archive != "" {
|
|
||||||
archivePath := app.ExtractArchivePath(archive)
|
|
||||||
if err == nil {
|
|
||||||
if app.ReadSATOverallStatus(archivePath) == "FAILED" {
|
|
||||||
if reason := app.SATFailureDetail(archivePath); reason != "" {
|
|
||||||
err = fmt.Errorf("SAT FAILED: %s", reason)
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("SAT overall_status=FAILED (see summary.txt)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// A user-aborted run (ctx canceled) may still have produced a partial
|
|
||||||
// archive/summary.txt — that incomplete result must not overwrite the
|
|
||||||
// component status DB, which is why this is skipped here rather than
|
|
||||||
// relying on satKeyStatus's PARTIAL/UNSUPPORTED handling below.
|
|
||||||
if db := q.statusDB(); db != nil && ctx.Err() == nil {
|
|
||||||
app.ApplySATResultToDB(db, t.Target, archivePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if ctx.Err() != nil {
|
|
||||||
j.append("Aborted.")
|
|
||||||
j.finish("aborted")
|
|
||||||
} else {
|
|
||||||
j.append("ERROR: " + err.Error())
|
|
||||||
j.finish(err.Error())
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if archive != "" {
|
|
||||||
j.append("Archive: " + archive)
|
|
||||||
}
|
|
||||||
j.finish("")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *taskQueue) statusDB() *app.ComponentStatusDB {
|
|
||||||
if q.opts == nil || q.opts.App == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return q.opts.App.StatusDB
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func splitLines(s string) []string {
|
func splitLines(s string) []string {
|
||||||
|
|||||||
Reference in New Issue
Block a user