feat: CPU SAT via stress-ng + BMC version via ipmitool
BMC: - collector/board.go: collectBMCFirmware() via ipmitool mc info, graceful skip if /dev/ipmi0 absent - collector/collector.go: append BMC firmware record to snap.Firmware - app/panel.go: show BMC version in TUI right-panel header alongside BIOS CPU SAT: - platform/sat.go: RunCPUAcceptancePack(baseDir, durationSec) — lscpu + sensors before/after + stress-ng - app/app.go: RunCPUAcceptancePack + RunCPUAcceptancePackResult methods, satRunner interface updated - app/panel.go: CPU row now reads real PASS/FAIL from cpu-*/summary.txt via satStatuses(); cpuDetailResult shows last SAT summary + audit data - tui/types.go: actionRunCPUSAT, confirmBody for CPU test with mode label - tui/screen_health_check.go: hcCPUDurations [60,300,900]s; hcRunSingle(CPU)→confirm screen; executeRunAll uses RunCPUAcceptancePackResult - tui/forms.go: actionRunCPUSAT → RunCPUAcceptancePackResult with mode duration - cmd/bee/main.go: bee sat cpu [--duration N] subcommand Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -70,7 +70,7 @@ func printRootUsage(w io.Writer) {
|
|||||||
bee export --target <device>
|
bee export --target <device>
|
||||||
bee support-bundle --output stdout|file:<path>
|
bee support-bundle --output stdout|file:<path>
|
||||||
bee web --listen :80 --audit-path `+app.DefaultAuditJSONPath+`
|
bee web --listen :80 --audit-path `+app.DefaultAuditJSONPath+`
|
||||||
bee sat nvidia|memory|storage
|
bee sat nvidia|memory|storage|cpu [--duration <seconds>]
|
||||||
bee version
|
bee version
|
||||||
bee help [command]`)
|
bee help [command]`)
|
||||||
}
|
}
|
||||||
@@ -346,43 +346,58 @@ func runWeb(args []string, stdout, stderr io.Writer) int {
|
|||||||
|
|
||||||
func runSAT(args []string, stdout, stderr io.Writer) int {
|
func runSAT(args []string, stdout, stderr io.Writer) int {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
fmt.Fprintln(stderr, "usage: bee sat nvidia|memory|storage")
|
fmt.Fprintln(stderr, "usage: bee sat nvidia|memory|storage|cpu [--duration <seconds>]")
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
if args[0] == "help" || args[0] == "--help" || args[0] == "-h" {
|
if args[0] == "help" || args[0] == "--help" || args[0] == "-h" {
|
||||||
fmt.Fprintln(stdout, "usage: bee sat nvidia|memory|storage")
|
fmt.Fprintln(stdout, "usage: bee sat nvidia|memory|storage|cpu [--duration <seconds>]")
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
if args[0] != "nvidia" && args[0] != "memory" && args[0] != "storage" {
|
|
||||||
fmt.Fprintf(stderr, "bee sat: unknown target %q\n", args[0])
|
fs := flag.NewFlagSet("sat", flag.ContinueOnError)
|
||||||
fmt.Fprintln(stderr, "usage: bee sat nvidia|memory|storage")
|
fs.SetOutput(stderr)
|
||||||
|
duration := fs.Int("duration", 0, "stress-ng duration in seconds (cpu only; default: 60)")
|
||||||
|
if err := fs.Parse(args[1:]); err != nil {
|
||||||
|
if err == flag.ErrHelp {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
if len(args) > 1 {
|
if fs.NArg() != 0 {
|
||||||
fmt.Fprintln(stderr, "usage: bee sat nvidia|memory|storage")
|
fmt.Fprintf(stderr, "bee sat: unexpected arguments\n")
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
target := args[0]
|
||||||
|
if target != "nvidia" && target != "memory" && target != "storage" && target != "cpu" {
|
||||||
|
fmt.Fprintf(stderr, "bee sat: unknown target %q\n", target)
|
||||||
|
fmt.Fprintln(stderr, "usage: bee sat nvidia|memory|storage|cpu [--duration <seconds>]")
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
application := app.New(platform.New())
|
application := app.New(platform.New())
|
||||||
var (
|
var (
|
||||||
archive string
|
archive string
|
||||||
err error
|
err error
|
||||||
label string
|
|
||||||
)
|
)
|
||||||
switch args[0] {
|
switch target {
|
||||||
case "nvidia":
|
case "nvidia":
|
||||||
label = "nvidia"
|
|
||||||
archive, err = application.RunNvidiaAcceptancePack("")
|
archive, err = application.RunNvidiaAcceptancePack("")
|
||||||
case "memory":
|
case "memory":
|
||||||
label = "memory"
|
|
||||||
archive, err = application.RunMemoryAcceptancePack("")
|
archive, err = application.RunMemoryAcceptancePack("")
|
||||||
case "storage":
|
case "storage":
|
||||||
label = "storage"
|
|
||||||
archive, err = application.RunStorageAcceptancePack("")
|
archive, err = application.RunStorageAcceptancePack("")
|
||||||
|
case "cpu":
|
||||||
|
dur := *duration
|
||||||
|
if dur <= 0 {
|
||||||
|
dur = 60
|
||||||
|
}
|
||||||
|
archive, err = application.RunCPUAcceptancePack("", dur)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("run sat", "target", label, "err", err)
|
slog.Error("run sat", "target", target, "err", err)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
slog.Info("sat archive written", "target", label, "path", archive)
|
slog.Info("sat archive written", "target", target, "path", archive)
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ func TestRunSATHelp(t *testing.T) {
|
|||||||
if rc != 0 {
|
if rc != 0 {
|
||||||
t.Fatalf("rc=%d want 0", rc)
|
t.Fatalf("rc=%d want 0", rc)
|
||||||
}
|
}
|
||||||
if !strings.Contains(stdout.String(), "usage: bee sat nvidia|memory|storage") {
|
if !strings.Contains(stdout.String(), "usage: bee sat nvidia|memory|storage|cpu") {
|
||||||
t.Fatalf("stdout missing sat help:\n%s", stdout.String())
|
t.Fatalf("stdout missing sat help:\n%s", stdout.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -177,8 +177,8 @@ func TestRunSATRejectsExtraArgs(t *testing.T) {
|
|||||||
if rc != 2 {
|
if rc != 2 {
|
||||||
t.Fatalf("rc=%d want 2", rc)
|
t.Fatalf("rc=%d want 2", rc)
|
||||||
}
|
}
|
||||||
if !strings.Contains(stderr.String(), "usage: bee sat nvidia|memory|storage") {
|
if !strings.Contains(stderr.String(), "bee sat: unexpected arguments") {
|
||||||
t.Fatalf("stderr missing sat usage:\n%s", stderr.String())
|
t.Fatalf("stderr missing sat error:\n%s", stderr.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ type satRunner interface {
|
|||||||
RunNvidiaAcceptancePackWithOptions(ctx context.Context, baseDir string, durationSec int, sizeMB int, gpuIndices []int) (string, error)
|
RunNvidiaAcceptancePackWithOptions(ctx context.Context, baseDir string, durationSec int, sizeMB int, gpuIndices []int) (string, error)
|
||||||
RunMemoryAcceptancePack(baseDir string) (string, error)
|
RunMemoryAcceptancePack(baseDir string) (string, error)
|
||||||
RunStorageAcceptancePack(baseDir string) (string, error)
|
RunStorageAcceptancePack(baseDir string) (string, error)
|
||||||
|
RunCPUAcceptancePack(baseDir string, durationSec int) (string, error)
|
||||||
ListNvidiaGPUs() ([]platform.NvidiaGPU, error)
|
ListNvidiaGPUs() ([]platform.NvidiaGPU, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -437,6 +438,22 @@ func (a *App) RunMemoryAcceptancePackResult(baseDir string) (ActionResult, error
|
|||||||
return ActionResult{Title: "Memory SAT", Body: body}, err
|
return ActionResult{Title: "Memory SAT", Body: body}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) RunCPUAcceptancePack(baseDir string, durationSec int) (string, error) {
|
||||||
|
if strings.TrimSpace(baseDir) == "" {
|
||||||
|
baseDir = DefaultSATBaseDir
|
||||||
|
}
|
||||||
|
return a.sat.RunCPUAcceptancePack(baseDir, durationSec)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) RunCPUAcceptancePackResult(baseDir string, durationSec int) (ActionResult, error) {
|
||||||
|
path, err := a.RunCPUAcceptancePack(baseDir, durationSec)
|
||||||
|
body := "Archive written."
|
||||||
|
if path != "" {
|
||||||
|
body = "Archive written to " + path
|
||||||
|
}
|
||||||
|
return ActionResult{Title: "CPU SAT", Body: body}, err
|
||||||
|
}
|
||||||
|
|
||||||
func (a *App) RunStorageAcceptancePack(baseDir string) (string, error) {
|
func (a *App) RunStorageAcceptancePack(baseDir string) (string, error) {
|
||||||
if strings.TrimSpace(baseDir) == "" {
|
if strings.TrimSpace(baseDir) == "" {
|
||||||
baseDir = DefaultSATBaseDir
|
baseDir = DefaultSATBaseDir
|
||||||
@@ -592,6 +609,7 @@ func latestSATSummaries() []string {
|
|||||||
{label: "NVIDIA SAT", prefix: "gpu-nvidia-"},
|
{label: "NVIDIA SAT", prefix: "gpu-nvidia-"},
|
||||||
{label: "Memory SAT", prefix: "memory-"},
|
{label: "Memory SAT", prefix: "memory-"},
|
||||||
{label: "Storage SAT", prefix: "storage-"},
|
{label: "Storage SAT", prefix: "storage-"},
|
||||||
|
{label: "CPU SAT", prefix: "cpu-"},
|
||||||
}
|
}
|
||||||
var out []string
|
var out []string
|
||||||
for _, item := range patterns {
|
for _, item := range patterns {
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ type fakeSAT struct {
|
|||||||
runNvidiaFn func(string) (string, error)
|
runNvidiaFn func(string) (string, error)
|
||||||
runMemoryFn func(string) (string, error)
|
runMemoryFn func(string) (string, error)
|
||||||
runStorageFn func(string) (string, error)
|
runStorageFn func(string) (string, error)
|
||||||
|
runCPUFn func(string, int) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f fakeSAT) RunNvidiaAcceptancePack(baseDir string) (string, error) {
|
func (f fakeSAT) RunNvidiaAcceptancePack(baseDir string) (string, error) {
|
||||||
@@ -122,6 +123,13 @@ func (f fakeSAT) RunStorageAcceptancePack(baseDir string) (string, error) {
|
|||||||
return f.runStorageFn(baseDir)
|
return f.runStorageFn(baseDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f fakeSAT) RunCPUAcceptancePack(baseDir string, durationSec int) (string, error) {
|
||||||
|
if f.runCPUFn != nil {
|
||||||
|
return f.runCPUFn(baseDir, durationSec)
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
func TestNetworkStatusFormatsInterfacesAndRoute(t *testing.T) {
|
func TestNetworkStatusFormatsInterfacesAndRoute(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,9 @@ func (a *App) LoadHardwarePanel() HardwarePanelData {
|
|||||||
for _, fw := range snap.Hardware.Firmware {
|
for _, fw := range snap.Hardware.Firmware {
|
||||||
if fw.DeviceName == "BIOS" && fw.Version != "" {
|
if fw.DeviceName == "BIOS" && fw.Version != "" {
|
||||||
header = append(header, "BIOS: "+fw.Version)
|
header = append(header, "BIOS: "+fw.Version)
|
||||||
break
|
}
|
||||||
|
if fw.DeviceName == "BMC" && fw.Version != "" {
|
||||||
|
header = append(header, "BMC: "+fw.Version)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ip := formatIPLine(a.network.ListInterfaces); ip != "" {
|
if ip := formatIPLine(a.network.ListInterfaces); ip != "" {
|
||||||
@@ -57,7 +59,7 @@ func (a *App) LoadHardwarePanel() HardwarePanelData {
|
|||||||
if cpu := formatCPULine(snap.Hardware.CPUs); cpu != "" {
|
if cpu := formatCPULine(snap.Hardware.CPUs); cpu != "" {
|
||||||
rows = append(rows, ComponentRow{
|
rows = append(rows, ComponentRow{
|
||||||
Key: "CPU",
|
Key: "CPU",
|
||||||
Status: "N/A",
|
Status: statuses["cpu"],
|
||||||
Detail: strings.TrimPrefix(cpu, "CPU: "),
|
Detail: strings.TrimPrefix(cpu, "CPU: "),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -97,7 +99,7 @@ func (a *App) LoadHardwarePanel() HardwarePanelData {
|
|||||||
func (a *App) ComponentDetailResult(key string) ActionResult {
|
func (a *App) ComponentDetailResult(key string) ActionResult {
|
||||||
switch key {
|
switch key {
|
||||||
case "CPU":
|
case "CPU":
|
||||||
return a.cpuDetailResult()
|
return a.cpuDetailResult(false)
|
||||||
case "MEM":
|
case "MEM":
|
||||||
return a.satDetailResult("memory", "memory-", "MEM detail")
|
return a.satDetailResult("memory", "memory-", "MEM detail")
|
||||||
case "GPU":
|
case "GPU":
|
||||||
@@ -111,19 +113,37 @@ func (a *App) ComponentDetailResult(key string) ActionResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) cpuDetailResult() ActionResult {
|
func (a *App) cpuDetailResult(satOnly bool) ActionResult {
|
||||||
|
var b strings.Builder
|
||||||
|
|
||||||
|
// Show latest SAT summary if available.
|
||||||
|
satResult := a.satDetailResult("cpu", "cpu-", "CPU SAT")
|
||||||
|
if satResult.Body != "No test results found. Run a test first." {
|
||||||
|
fmt.Fprintln(&b, "=== Last SAT ===")
|
||||||
|
fmt.Fprintln(&b, satResult.Body)
|
||||||
|
fmt.Fprintln(&b)
|
||||||
|
}
|
||||||
|
|
||||||
|
if satOnly {
|
||||||
|
body := strings.TrimSpace(b.String())
|
||||||
|
if body == "" {
|
||||||
|
body = "No CPU SAT results found. Run a test first."
|
||||||
|
}
|
||||||
|
return ActionResult{Title: "CPU SAT", Body: body}
|
||||||
|
}
|
||||||
|
|
||||||
raw, err := os.ReadFile(DefaultAuditJSONPath)
|
raw, err := os.ReadFile(DefaultAuditJSONPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ActionResult{Title: "CPU", Body: "No audit data."}
|
return ActionResult{Title: "CPU", Body: strings.TrimSpace(b.String())}
|
||||||
}
|
}
|
||||||
var snap schema.HardwareIngestRequest
|
var snap schema.HardwareIngestRequest
|
||||||
if err := json.Unmarshal(raw, &snap); err != nil {
|
if err := json.Unmarshal(raw, &snap); err != nil {
|
||||||
return ActionResult{Title: "CPU", Body: "Audit data unreadable."}
|
return ActionResult{Title: "CPU", Body: strings.TrimSpace(b.String())}
|
||||||
}
|
}
|
||||||
if len(snap.Hardware.CPUs) == 0 {
|
if len(snap.Hardware.CPUs) == 0 {
|
||||||
return ActionResult{Title: "CPU", Body: "No CPU data in last audit."}
|
return ActionResult{Title: "CPU", Body: strings.TrimSpace(b.String())}
|
||||||
}
|
}
|
||||||
var b strings.Builder
|
fmt.Fprintln(&b, "=== Audit ===")
|
||||||
for i, cpu := range snap.Hardware.CPUs {
|
for i, cpu := range snap.Hardware.CPUs {
|
||||||
fmt.Fprintf(&b, "CPU %d\n", i)
|
fmt.Fprintf(&b, "CPU %d\n", i)
|
||||||
if cpu.Model != nil {
|
if cpu.Model != nil {
|
||||||
@@ -220,6 +240,7 @@ func satStatuses() map[string]string {
|
|||||||
"gpu": "N/A",
|
"gpu": "N/A",
|
||||||
"memory": "N/A",
|
"memory": "N/A",
|
||||||
"storage": "N/A",
|
"storage": "N/A",
|
||||||
|
"cpu": "N/A",
|
||||||
}
|
}
|
||||||
patterns := []struct {
|
patterns := []struct {
|
||||||
key string
|
key string
|
||||||
@@ -228,6 +249,7 @@ func satStatuses() map[string]string {
|
|||||||
{"gpu", "gpu-nvidia-"},
|
{"gpu", "gpu-nvidia-"},
|
||||||
{"memory", "memory-"},
|
{"memory", "memory-"},
|
||||||
{"storage", "storage-"},
|
{"storage", "storage-"},
|
||||||
|
{"cpu", "cpu-"},
|
||||||
}
|
}
|
||||||
for _, item := range patterns {
|
for _, item := range patterns {
|
||||||
matches, err := filepath.Glob(filepath.Join(DefaultSATBaseDir, item.prefix+"*/summary.txt"))
|
matches, err := filepath.Glob(filepath.Join(DefaultSATBaseDir, item.prefix+"*/summary.txt"))
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"bee/audit/internal/schema"
|
"bee/audit/internal/schema"
|
||||||
"bufio"
|
"bufio"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@@ -16,6 +17,14 @@ var execDmidecode = func(typeNum string) (string, error) {
|
|||||||
return string(out), nil
|
return string(out), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var execIpmitool = func(args ...string) (string, error) {
|
||||||
|
out, err := exec.Command("ipmitool", args...).Output()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(out), nil
|
||||||
|
}
|
||||||
|
|
||||||
// collectBoard runs dmidecode for types 0, 1, 2 and returns the board record
|
// collectBoard runs dmidecode for types 0, 1, 2 and returns the board record
|
||||||
// plus the BIOS firmware entry. Any failure is logged and returns zero values.
|
// plus the BIOS firmware entry. Any failure is logged and returns zero values.
|
||||||
func collectBoard() (schema.HardwareBoard, []schema.HardwareFirmwareRecord) {
|
func collectBoard() (schema.HardwareBoard, []schema.HardwareFirmwareRecord) {
|
||||||
@@ -69,6 +78,45 @@ func parseBoard(type1, type2 string) schema.HardwareBoard {
|
|||||||
return board
|
return board
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// collectBMCFirmware collects BMC firmware version via ipmitool mc info.
|
||||||
|
// Returns nil if ipmitool is missing, /dev/ipmi0 is absent, or any error occurs.
|
||||||
|
func collectBMCFirmware() []schema.HardwareFirmwareRecord {
|
||||||
|
if _, err := exec.LookPath("ipmitool"); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if _, err := os.Stat("/dev/ipmi0"); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out, err := execIpmitool("mc", "info")
|
||||||
|
if err != nil {
|
||||||
|
slog.Info("bmc: ipmitool mc info unavailable", "err", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
version := parseBMCFirmwareRevision(out)
|
||||||
|
if version == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
slog.Info("bmc: collected", "version", version)
|
||||||
|
return []schema.HardwareFirmwareRecord{
|
||||||
|
{DeviceName: "BMC", Version: version},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseBMCFirmwareRevision extracts the "Firmware Revision" field from ipmitool mc info output.
|
||||||
|
func parseBMCFirmwareRevision(out string) string {
|
||||||
|
for _, line := range strings.Split(out, "\n") {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
key, val, ok := strings.Cut(line, ":")
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(key) == "Firmware Revision" {
|
||||||
|
return strings.TrimSpace(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// parseBIOSFirmware extracts BIOS version from dmidecode type 0 output.
|
// parseBIOSFirmware extracts BIOS version from dmidecode type 0 output.
|
||||||
func parseBIOSFirmware(type0 string) []schema.HardwareFirmwareRecord {
|
func parseBIOSFirmware(type0 string) []schema.HardwareFirmwareRecord {
|
||||||
fields := parseDMIFields(type0, "BIOS Information")
|
fields := parseDMIFields(type0, "BIOS Information")
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ func Run(_ runtimeenv.Mode) schema.HardwareIngestRequest {
|
|||||||
board, biosFW := collectBoard()
|
board, biosFW := collectBoard()
|
||||||
snap.Board = board
|
snap.Board = board
|
||||||
snap.Firmware = append(snap.Firmware, biosFW...)
|
snap.Firmware = append(snap.Firmware, biosFW...)
|
||||||
|
snap.Firmware = append(snap.Firmware, collectBMCFirmware()...)
|
||||||
|
|
||||||
snap.CPUs = collectCPUs()
|
snap.CPUs = collectCPUs()
|
||||||
|
|
||||||
|
|||||||
@@ -74,6 +74,18 @@ func (s *System) RunMemoryAcceptancePack(baseDir string) (string, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *System) RunCPUAcceptancePack(baseDir string, durationSec int) (string, error) {
|
||||||
|
if durationSec <= 0 {
|
||||||
|
durationSec = 60
|
||||||
|
}
|
||||||
|
return runAcceptancePack(baseDir, "cpu", []satJob{
|
||||||
|
{name: "01-lscpu.log", cmd: []string{"lscpu"}},
|
||||||
|
{name: "02-sensors-before.log", cmd: []string{"sensors"}},
|
||||||
|
{name: "03-stress-ng.log", cmd: []string{"stress-ng", "--cpu", "0", "--cpu-method", "all", "--timeout", fmt.Sprintf("%d", durationSec)}},
|
||||||
|
{name: "04-sensors-after.log", cmd: []string{"sensors"}},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (s *System) RunStorageAcceptancePack(baseDir string) (string, error) {
|
func (s *System) RunStorageAcceptancePack(baseDir string) (string, error) {
|
||||||
if baseDir == "" {
|
if baseDir == "" {
|
||||||
baseDir = "/var/log/bee-sat"
|
baseDir = "/var/log/bee-sat"
|
||||||
|
|||||||
@@ -92,6 +92,13 @@ func (m model) updateConfirm(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
|||||||
result, err := m.app.RunStorageAcceptancePackResult("")
|
result, err := m.app.RunStorageAcceptancePackResult("")
|
||||||
return resultMsg{title: result.Title, body: result.Body, err: err, back: screenHealthCheck}
|
return resultMsg{title: result.Title, body: result.Body, err: err, back: screenHealthCheck}
|
||||||
}
|
}
|
||||||
|
case actionRunCPUSAT:
|
||||||
|
m.busyTitle = "CPU test"
|
||||||
|
durationSec := hcCPUDurations[m.hcMode]
|
||||||
|
return m, func() tea.Msg {
|
||||||
|
result, err := m.app.RunCPUAcceptancePackResult("", durationSec)
|
||||||
|
return resultMsg{title: result.Title, body: result.Body, err: err, back: screenHealthCheck}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case "ctrl+c":
|
case "ctrl+c":
|
||||||
return m, tea.Quit
|
return m, tea.Quit
|
||||||
@@ -103,7 +110,7 @@ func (m model) confirmCancelTarget() screen {
|
|||||||
switch m.pendingAction {
|
switch m.pendingAction {
|
||||||
case actionExportBundle:
|
case actionExportBundle:
|
||||||
return screenExportTargets
|
return screenExportTargets
|
||||||
case actionRunAll, actionRunMemorySAT, actionRunStorageSAT:
|
case actionRunAll, actionRunMemorySAT, actionRunStorageSAT, actionRunCPUSAT:
|
||||||
return screenHealthCheck
|
return screenHealthCheck
|
||||||
default:
|
default:
|
||||||
return screenMain
|
return screenMain
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ const (
|
|||||||
// hcModeDurations maps mode index (0=Quick,1=Standard,2=Express) to GPU stress seconds.
|
// hcModeDurations maps mode index (0=Quick,1=Standard,2=Express) to GPU stress seconds.
|
||||||
var hcModeDurations = [3]int{600, 3600, 28800}
|
var hcModeDurations = [3]int{600, 3600, 28800}
|
||||||
|
|
||||||
|
// hcCPUDurations maps mode index to CPU stress-ng seconds.
|
||||||
|
var hcCPUDurations = [3]int{60, 300, 900}
|
||||||
|
|
||||||
func (m model) enterHealthCheck() (tea.Model, tea.Cmd) {
|
func (m model) enterHealthCheck() (tea.Model, tea.Cmd) {
|
||||||
m.screen = screenHealthCheck
|
m.screen = screenHealthCheck
|
||||||
if !m.hcInitialized {
|
if !m.hcInitialized {
|
||||||
@@ -126,12 +129,10 @@ func (m model) hcRunSingle(idx int) (tea.Model, tea.Cmd) {
|
|||||||
m.cursor = 0
|
m.cursor = 0
|
||||||
return m, nil
|
return m, nil
|
||||||
case hcCPU:
|
case hcCPU:
|
||||||
m.busy = true
|
m.pendingAction = actionRunCPUSAT
|
||||||
m.busyTitle = "CPU"
|
m.screen = screenConfirm
|
||||||
return m, func() tea.Msg {
|
m.cursor = 0
|
||||||
r := m.app.ComponentDetailResult("CPU")
|
return m, nil
|
||||||
return resultMsg{title: r.Title, body: r.Body, back: screenHealthCheck}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
@@ -150,6 +151,7 @@ func (m model) hcRunAll() (tea.Model, tea.Cmd) {
|
|||||||
|
|
||||||
func (m model) executeRunAll() (tea.Model, tea.Cmd) {
|
func (m model) executeRunAll() (tea.Model, tea.Cmd) {
|
||||||
durationSec := hcModeDurations[m.hcMode]
|
durationSec := hcModeDurations[m.hcMode]
|
||||||
|
durationIdx := m.hcMode
|
||||||
sel := m.hcSel
|
sel := m.hcSel
|
||||||
app := m.app
|
app := m.app
|
||||||
m.busy = true
|
m.busy = true
|
||||||
@@ -197,8 +199,13 @@ func (m model) executeRunAll() (tea.Model, tea.Cmd) {
|
|||||||
parts = append(parts, "=== STORAGE ===\n"+body)
|
parts = append(parts, "=== STORAGE ===\n"+body)
|
||||||
}
|
}
|
||||||
if sel[hcCPU] {
|
if sel[hcCPU] {
|
||||||
r := app.ComponentDetailResult("CPU")
|
cpuDur := hcCPUDurations[durationIdx]
|
||||||
parts = append(parts, "=== CPU ===\n"+r.Body)
|
r, err := app.RunCPUAcceptancePackResult("", cpuDur)
|
||||||
|
body := r.Body
|
||||||
|
if err != nil {
|
||||||
|
body += "\nERROR: " + err.Error()
|
||||||
|
}
|
||||||
|
parts = append(parts, "=== CPU ===\n"+body)
|
||||||
}
|
}
|
||||||
combined := strings.Join(parts, "\n\n")
|
combined := strings.Join(parts, "\n\n")
|
||||||
if combined == "" {
|
if combined == "" {
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ const (
|
|||||||
actionRunAll actionKind = "run_all"
|
actionRunAll actionKind = "run_all"
|
||||||
actionRunMemorySAT actionKind = "run_memory_sat"
|
actionRunMemorySAT actionKind = "run_memory_sat"
|
||||||
actionRunStorageSAT actionKind = "run_storage_sat"
|
actionRunStorageSAT actionKind = "run_storage_sat"
|
||||||
|
actionRunCPUSAT actionKind = "run_cpu_sat"
|
||||||
)
|
)
|
||||||
|
|
||||||
type model struct {
|
type model struct {
|
||||||
@@ -173,6 +174,9 @@ func (m model) confirmBody() (string, string) {
|
|||||||
return "Memory test", "Run memtester?"
|
return "Memory test", "Run memtester?"
|
||||||
case actionRunStorageSAT:
|
case actionRunStorageSAT:
|
||||||
return "Storage test", "Run storage diagnostic pack?"
|
return "Storage test", "Run storage diagnostic pack?"
|
||||||
|
case actionRunCPUSAT:
|
||||||
|
modes := []string{"Quick (60s)", "Standard (300s)", "Express (900s)"}
|
||||||
|
return "CPU test", "Run stress-ng? Mode: " + modes[m.hcMode]
|
||||||
default:
|
default:
|
||||||
return "Confirm", "Proceed?"
|
return "Confirm", "Proceed?"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## BMC версия через IPMI
|
## BMC версия через IPMI
|
||||||
|
|
||||||
**Статус:** не реализовано.
|
**Статус:** реализовано.
|
||||||
|
|
||||||
Добавить сбор версии BMC firmware в board collector:
|
Добавить сбор версии BMC firmware в board collector:
|
||||||
- Команда: `ipmitool mc info` → поле `Firmware Revision`
|
- Команда: `ipmitool mc info` → поле `Firmware Revision`
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
## CPU acceptance test через stress-ng
|
## CPU acceptance test через stress-ng
|
||||||
|
|
||||||
**Статус:** не реализовано. CPU в Health Check всегда `N/A`.
|
**Статус:** реализовано. CPU в Health Check получает PASS/FAIL из summary.txt.
|
||||||
|
|
||||||
Добавить CPU SAT на базе `stress-ng`:
|
Добавить CPU SAT на базе `stress-ng`:
|
||||||
- Bake `stress-ng` в ISO (добавить в `bee.list.chroot`)
|
- Bake `stress-ng` в ISO (добавить в `bee.list.chroot`)
|
||||||
|
|||||||
Reference in New Issue
Block a user