This commit is contained in:
12600k-rog-d4
2026-08-13 22:49:50 +08:00
parent 5e296bfe19
commit 2aaaa16ae2
24 changed files with 1415 additions and 327 deletions

View File

@@ -18,11 +18,14 @@ func BuildDeviceReport(dev *model.Device, rpt *stress.Report) *Report {
b.AddHost(HostResult{MAC: dev.MAC, IP: dev.IP})
for _, res := range rpt.Results {
b.AddStressResult(dev.MAC, StressResult{
Type: string(res.Type),
Status: res.Status,
Duration: res.Duration.String(),
Error: res.Error,
Output: res.Output,
Type: string(res.Type),
Status: res.Status,
Duration: res.Duration.String(),
Score: res.Score,
ScoreUnit: stress.ScoreUnit(res.Type),
Metrics: res.OrderedMetrics(),
Error: res.Error,
Output: res.Output,
})
}
b.EndPhase(rpt.Summary())

View File

@@ -7,6 +7,8 @@ import (
"path/filepath"
"strings"
"time"
"auto-check/pkg/stress"
)
// PhaseResult 单阶段结果
@@ -19,23 +21,25 @@ type PhaseResult struct {
// HostResult 单台设备结果MAC 为唯一标识)
type HostResult struct {
MAC string `json:"mac" yaml:"mac"`
IP string `json:"ip" yaml:"ip"`
Alive bool `json:"alive" yaml:"alive"`
OpenPort int `json:"open_port,omitempty" yaml:"open_port,omitempty"`
SSHLogin bool `json:"ssh_login" yaml:"ssh_login"`
SSHErr string `json:"ssh_error,omitempty" yaml:"ssh_error,omitempty"`
Stress []StressResult `json:"stress,omitempty" yaml:"stress,omitempty"`
Curves map[string][]float64 `json:"curves,omitempty" yaml:"curves,omitempty"` // 指标名 → 时间序列(渲染为 sparkline
MAC string `json:"mac" yaml:"mac"`
IP string `json:"ip" yaml:"ip"`
Alive bool `json:"alive" yaml:"alive"`
OpenPort int `json:"open_port,omitempty" yaml:"open_port,omitempty"`
SSHLogin bool `json:"ssh_login" yaml:"ssh_login"`
SSHErr string `json:"ssh_error,omitempty" yaml:"ssh_error,omitempty"`
Stress []StressResult `json:"stress,omitempty" yaml:"stress,omitempty"`
}
// StressResult 单项压力测试结果
type StressResult struct {
Type string `json:"type" yaml:"type"`
Status string `json:"status" yaml:"status"` // pass / fail / skip
Duration string `json:"duration" yaml:"duration"`
Output string `json:"output,omitempty" yaml:"output,omitempty"`
Error string `json:"error,omitempty" yaml:"error,omitempty"`
Type string `json:"type" yaml:"type"`
Status string `json:"status" yaml:"status"` // pass / fail / skip
Duration string `json:"duration" yaml:"duration"`
Score float64 `json:"score,omitempty" yaml:"score,omitempty"`
ScoreUnit string `json:"score_unit,omitempty" yaml:"score_unit,omitempty"`
Metrics []stress.MetricItem `json:"metrics,omitempty" yaml:"metrics,omitempty"`
Output string `json:"output,omitempty" yaml:"output,omitempty"`
Error string `json:"error,omitempty" yaml:"error,omitempty"`
}
// Report 检测报告
@@ -209,6 +213,12 @@ func (r *Report) ToText() string {
icon = "✗"
}
sb.WriteString(fmt.Sprintf(" │ %s %-10s 耗时: %s\n", icon, s.Type, s.Duration))
if s.Score > 0 {
sb.WriteString(fmt.Sprintf(" │ 跑分: %.1f %s\n", s.Score, s.ScoreUnit))
}
for _, m := range s.Metrics {
sb.WriteString(fmt.Sprintf(" │ %s: %.1f%s\n", m.Label, m.Value, m.Unit))
}
if s.Error != "" {
sb.WriteString(fmt.Sprintf(" │ 错误: %s\n", s.Error))
}

View File

@@ -2,7 +2,6 @@ package report
import (
"fmt"
"math"
"strings"
"text/template"
"time"
@@ -50,29 +49,6 @@ func RenderTemplate(tpl string, data interface{}, cfg TemplateConfig) (string, e
},
// 分隔线
"hr": func(sharp rune) string { return strings.Repeat(string(sharp), cfg.Width) },
// 曲线图(时间序列 → sparkline
"sparkline": func(data []float64, width int) string {
return Sparkline(data, width)
},
// 曲线摘要: 最高/最低/平均
"curveSummary": func(data []float64) string {
if len(data) == 0 {
return "无数据"
}
min, max := math.MaxFloat64, -math.MaxFloat64
var sum float64
for _, v := range data {
if v < min {
min = v
}
if v > max {
max = v
}
sum += v
}
avg := sum / float64(len(data))
return fmt.Sprintf("max=%.1f min=%.1f avg=%.1f", max, min, avg)
},
}
t, err := template.New("report").Funcs(funcMap).Parse(tpl)
@@ -86,76 +62,3 @@ func RenderTemplate(tpl string, data interface{}, cfg TemplateConfig) (string, e
}
return sb.String(), nil
}
// ============================
// Sparkline — 80mm 热敏纸曲线图
// ============================
// sparkChars 8 级块状字符(低→高)
var sparkChars = []rune("▁▂▃▄▅▆▇█")
// Sparkline 将时间序列渲染为一行块状曲线
// width 为字符宽度80mm 纸 ≤42
func Sparkline(data []float64, width int) string {
if len(data) == 0 {
return "(无数据)"
}
if width <= 0 {
width = 42
}
if width > 42 {
width = 42
}
// 单点
if len(data) == 1 {
return fmt.Sprintf("▊ %.1f", data[0])
}
// 按宽度采样(超出宽度时等距抽取)
step := float64(len(data)) / float64(width)
sampled := make([]float64, 0, width)
for i := 0; i < width && int(float64(i)*step) < len(data); i++ {
idx := int(float64(i) * step)
if idx >= len(data) {
break
}
sampled = append(sampled, data[idx])
}
// 归一化到 0-7
min, max := math.MaxFloat64, -math.MaxFloat64
for _, v := range sampled {
if v < min {
min = v
}
if v > max {
max = v
}
}
var sb strings.Builder
if max-min < 1e-9 {
// 全相同值
idx := 0
if max > 0 {
idx = 3
}
for range sampled {
sb.WriteRune(sparkChars[idx])
}
return sb.String()
}
for _, v := range sampled {
idx := int((v - min) / (max - min) * 7)
if idx < 0 {
idx = 0
}
if idx > 7 {
idx = 7
}
sb.WriteRune(sparkChars[idx])
}
return sb.String()
}

View File

@@ -18,9 +18,8 @@
{{ end }}{{ end }}{{ if eq .Phase "SSH 登录" }}{{ range .Hosts }}{{ if .SSHLogin }}✓{{ else }}✗{{ end }} {{ padRight .MAC 19 }} {{ padRight .IP 15 }} {{ .SSHErr }}
{{ end }}{{ end }}{{ if eq .Phase "压力测试" }}{{ range .Hosts }}{{ padRight .MAC 19 }} {{ padRight .IP 15 }}
{{ range .Stress }}{{ if eq .Status "pass" }}✓{{ else }}✗{{ end }} {{ padRight .Type 12 }} {{ .Status }} {{ .Duration }}
{{ if .Error }} ERR: {{ trunc .Error 34 }}{{ end }}{{ end }}{{ range $name, $data := .Curves }} [{{ $name }}] {{ curveSummary $data }}
{{ sparkline $data 38 }}
{{ end }}{{ end }}{{ end }}{{ end }}
{{ if .Score }} 跑分: {{ printf "%.1f" .Score }} {{ .ScoreUnit }}{{ end }}{{ range .Metrics }} {{ .Label }}: {{ printf "%.1f" .Value }}{{ .Unit }}
{{ end }}{{ if .Error }} ERR: {{ trunc .Error 34 }}{{ end }}{{ end }}{{ end }}{{ end }}{{ end }}
{{ "" }}
{{ hr '═' }}
报告结束