feat: 飞牛NAS成本审核更新 - 新增SKU、更新价格表、清理归档及订单记录,同步auto-check改动

This commit is contained in:
12600k-rog-d4
2026-08-27 00:44:44 +08:00
parent bde60dbea0
commit a172a9bb5e
82 changed files with 1804 additions and 1180 deletions

View File

@@ -0,0 +1,611 @@
package stress
import (
"fmt"
"strings"
)
// ============================
// 性能/温度基准(让用户对结果有感性认知)
// ============================
//
// 评级说明(与"超过正常"程度绑定4 档):
// - 远低于正常 red #dc2626
// - 达到正常 green #16a34a
// - 超过正常 blue #3b82f6
// - 远超过正常 purple #7c3aed
//
// 评分方向:
// - higher_better: 数值越大越好CPU/内存跑分、IOPS、吞吐
// - lower_better : 数值越小越好(温度)
//
// 阈值Cutoffs3 个分界点将数值划分为 4 档。
// - higher_better: <c0 远低于, c0~c1 正常, c1~c2 超过, ≥c2 远超过
// - lower_better : >c0 远低于(危险), c1~c0 正常, c2~c1 超过, ≤c2 远超过(优秀)
//
// 阈值仅作参考,应根据实际硬件代际持续校准;下文取自常见消费级/主流企业级硬件中位水平。
// Level 评级
type Level string
const (
LevelPoor Level = "poor" // 远低于正常
LevelNormal Level = "normal" // 达到正常
LevelGood Level = "good" // 超过正常
LevelExcellent Level = "excellent" // 远超过正常
)
// LevelDisplay 评级显示元数据
type LevelDisplay struct {
Text string // 远低于正常 / 达到正常 / 超过正常 / 远超过正常
Color string // 颜色(与报告 CSS 协调)
Order int // 0/1/2/3 用于排序与综合评级
}
var levelDisplay = map[Level]LevelDisplay{
LevelPoor: {Text: "远低于正常", Color: "#dc2626", Order: 0},
LevelNormal: {Text: "达到正常", Color: "#16a34a", Order: 1},
LevelGood: {Text: "超过正常", Color: "#3b82f6", Order: 2},
LevelExcellent: {Text: "远超过正常", Color: "#7c3aed", Order: 3},
}
// LevelMeta 取评级显示信息,未知时返回 normal
func LevelMeta(l Level) LevelDisplay {
if d, ok := levelDisplay[l]; ok {
return d
}
return LevelDisplay{Text: string(l), Color: "#64748b", Order: 1}
}
// Direction 评分方向
type Direction string
const (
DirHigherBetter Direction = "higher" // 越高越好
DirLowerBetter Direction = "lower" // 越低越好
)
// MetricBench 单个指标的基准配置
type MetricBench struct {
Key string // 指标键score / max_temp / iops_total / bw_total
Label string // 显示名
Unit string // 单位
Direction Direction // 评分方向
Cutoffs [3]float64
Note string // 备注(用于展示)
}
// ============================
// 各指标基准(消费/主流企业级硬件参考)
// ============================
// CPU 跑分stress-ng bogo ops/s4 线程参考(线程越多分越高)
var benchCPU = MetricBench{
Key: "score", Label: "CPU 跑分", Unit: "ops/s",
Direction: DirHigherBetter,
Cutoffs: [3]float64{8000, 20000, 40000},
Note: "stress-ng bogo ops/s4 线程参考;线程数越多分越高)",
}
// 内存跑分
var benchMemory = MetricBench{
Key: "score", Label: "内存跑分", Unit: "ops/s",
Direction: DirHigherBetter,
Cutoffs: [3]float64{5000, 15000, 30000},
Note: "stress-ng bogo ops/s",
}
// 综合工况CPU+内存 综合
var benchFull = MetricBench{
Key: "score", Label: "综合跑分", Unit: "ops/s",
Direction: DirHigherBetter,
Cutoffs: [3]float64{6000, 18000, 35000},
Note: "CPU+内存综合 ops/s",
}
// 内存精压MB/sDDR4 一般 20-50 GB/s受通道数和频率影响
var benchMemNative = MetricBench{
Key: "score", Label: "内存带宽", Unit: "MB/s",
Direction: DirHigherBetter,
Cutoffs: [3]float64{3000, 8000, 15000},
Note: "内存读写带宽",
}
// 磁盘 4K 随机读写 IOPS
var benchDiskIOPS = MetricBench{
Key: "iops_total", Label: "磁盘 IOPS", Unit: "ops/s",
Direction: DirHigherBetter,
Cutoffs: [3]float64{1000, 10000, 50000},
Note: "4K 随机读写:<1k≈HDD, 1k-10k≈低端 SSD, 10k-50k≈SATA/NVMe 入门, >50k≈NVMe 中高端",
}
// 磁盘读写总带宽
var benchDiskBW = MetricBench{
Key: "bw_total", Label: "磁盘吞吐", Unit: "MB/s",
Direction: DirHigherBetter,
Cutoffs: [3]float64{100, 500, 2000},
Note: "读写总带宽:<100≈HDD, 100-500≈低端 SSD, 500-2000≈SATA/NVMe 入门, >2000≈NVMe Gen3+",
}
// 最高温度°C越低越好
var benchTemp = MetricBench{
Key: "max_temp", Label: "最高温度", Unit: "°C",
Direction: DirLowerBetter,
Cutoffs: [3]float64{90, 75, 60}, // 阈值由高到低poor/normal, normal/good, good/excellent
Note: "<60°C 优秀, 60-75°C 正常, 75-90°C 偏高, >90°C 危险",
}
// ============================
// 硬件基准库(按 CPU 型号匹配)
// ============================
//
// 设计目的:不同代/不同档位的硬件不能用同一套阈值比较。
// 例如 i3 与 i7 跑分差距数倍J4125 与 N100 也不能直接比较。
//
// 使用方式:
// - MatchHardware(cpuModel) 依据 CPU 型号子串匹配(大小写不敏感)
// - 匹配成功 → 使用该硬件的专属阈值评估
// - 未匹配 → 所有指标强制返回 LevelNormal"达到正常"),不判定好坏
// - 库中字段为 nil → 退到默认基准
//
// 维护说明:库按"匹配顺序"查找,越靠前越优先。新增型号请在最匹配的
// 位置追加i5-12xxx 必须在 i5 之前),否则会被更宽泛的规则先匹配。
// HardwareBench 一类硬件的基准
type HardwareBench struct {
// Match CPU 型号匹配子串(大小写不敏感),如 "i7-12"、"N100"、"Ryzen 5"
Match string
// Note 硬件说明,会显示在报告备注里
Note string
// CPU/Memory/Full/MemBand 各类跑分基准nil 表示使用默认基准
CPU *MetricBench
Memory *MetricBench
Full *MetricBench
MemBand *MetricBench
}
// hardwareLibrary 硬件基准库(按匹配顺序查找,越靠前越优先)
// 数值参考stress-ng bogo ops/s @ 默认线程数;内存带宽为 stream/memcpy 估算
// 维护提醒:每代 CPU 阈值约为上一代的 1.15~1.30 倍,跨代需重新校准
var hardwareLibrary = []HardwareBench{
// ============ Intel 飞牛常用低功耗 ============
{
Match: "N100", Note: "Intel N100 (Alder Lake-N, 4C/4T, 6W)",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{10000, 22000, 38000}, Note: "stress-ng bogo ops/s4 线程)"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{5000, 12000, 22000}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{8000, 18000, 30000}, Note: "CPU+内存综合"},
MemBand: &MetricBench{Key: "score", Label: "内存带宽", Unit: "MB/s", Direction: DirHigherBetter, Cutoffs: [3]float64{3000, 8000, 15000}, Note: "单通道 DDR5 带宽上限"},
},
{
Match: "N305", Note: "Intel N305 (Alder Lake-N, 8C/8T, 15W)",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{20000, 40000, 65000}, Note: "stress-ng bogo ops/s4 线程)"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{8000, 18000, 35000}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{15000, 30000, 50000}, Note: "CPU+内存综合"},
MemBand: &MetricBench{Key: "score", Label: "内存带宽", Unit: "MB/s", Direction: DirHigherBetter, Cutoffs: [3]float64{4000, 10000, 18000}, Note: "DDR5 双通道带宽"},
},
{
Match: "N95", Note: "Intel N95 (Alder Lake-N, 4C/4T, 15W)",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{10000, 20000, 35000}, Note: "stress-ng bogo ops/s4 线程)"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{5000, 11000, 20000}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{8000, 16000, 28000}, Note: "CPU+内存综合"},
MemBand: &MetricBench{Key: "score", Label: "内存带宽", Unit: "MB/s", Direction: DirHigherBetter, Cutoffs: [3]float64{3000, 7000, 13000}, Note: "单通道 DDR4/DDR5 带宽"},
},
{
Match: "N5105", Note: "Intel Celeron N5105 (Jasper Lake, 4C/4T, 10W)",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{7000, 14000, 25000}, Note: "stress-ng bogo ops/s4 线程)"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{4000, 9000, 16000}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{5000, 12000, 22000}, Note: "CPU+内存综合"},
},
{
Match: "N5095", Note: "Intel Celeron N5095 (Jasper Lake, 4C/4T, 15W)",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{6000, 12000, 22000}, Note: "stress-ng bogo ops/s4 线程)"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{4000, 8000, 15000}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{5000, 10000, 19000}, Note: "CPU+内存综合"},
},
{
Match: "J4125", Note: "Intel Celeron J4125 (Gemini Lake, 4C/4T, 10W)",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{5000, 10000, 18000}, Note: "stress-ng bogo ops/s4 线程)"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{3000, 6000, 12000}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{4000, 8000, 15000}, Note: "CPU+内存综合"},
},
{
Match: "J4105", Note: "Intel Celeron J4105 (Gemini Lake, 4C/4T, 10W)",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{4500, 9000, 16000}, Note: "stress-ng bogo ops/s4 线程)"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{3000, 6000, 11000}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{4000, 7500, 14000}, Note: "CPU+内存综合"},
},
{
Match: "J3455", Note: "Intel Celeron J3455 (Apollo Lake, 4C/4T, 10W)",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{3500, 7500, 13000}, Note: "stress-ng bogo ops/s4 线程)"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{2500, 5000, 9500}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{3000, 6500, 12000}, Note: "CPU+内存综合"},
},
{
Match: "J1900", Note: "Intel Celeron J1900 (Bay Trail, 4C/4T, 10W)",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{2500, 5500, 10000}, Note: "stress-ng bogo ops/s4 线程)"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{2000, 4000, 7000}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{2000, 4500, 8500}, Note: "CPU+内存综合"},
},
{
Match: "Celeron", Note: "Intel Celeron (其他型号)",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{5000, 12000, 22000}, Note: "Celeron 系列通用参考"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{3000, 7000, 14000}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{4000, 10000, 18000}, Note: "CPU+内存综合"},
},
{
Match: "Atom", Note: "Intel Atom",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{2000, 5000, 9000}, Note: "Atom 系列通用参考"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{1500, 3500, 6500}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{1800, 4500, 8000}, Note: "CPU+内存综合"},
},
// ============ Intel Core i9按代匹配 ============
{
Match: "i9-13", Note: "13代 i9 (Raptor Lake)",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{50000, 100000, 160000}, Note: "stress-ng bogo ops/s4 线程)"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{20000, 50000, 90000}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{30000, 70000, 120000}, Note: "CPU+内存综合"},
},
{
Match: "i9-12", Note: "12代 i9 (Alder Lake)",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{40000, 85000, 140000}, Note: "stress-ng bogo ops/s4 线程)"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{18000, 45000, 80000}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{28000, 60000, 100000}, Note: "CPU+内存综合"},
},
{
Match: "i9-11", Note: "11代 i9 (Rocket Lake)",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{35000, 75000, 125000}, Note: "stress-ng bogo ops/s4 线程)"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{15000, 38000, 70000}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{25000, 55000, 95000}, Note: "CPU+内存综合"},
},
{
Match: "i9-10", Note: "10代 i9 (Comet Lake)",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{30000, 65000, 110000}, Note: "stress-ng bogo ops/s4 线程)"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{14000, 35000, 65000}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{22000, 48000, 85000}, Note: "CPU+内存综合"},
},
{
Match: "i9-", Note: "Intel Core i9 (其他代)",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{25000, 55000, 95000}, Note: "i9 通用参考"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{12000, 30000, 60000}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{20000, 45000, 80000}, Note: "CPU+内存综合"},
},
// ============ Intel Core i7按代匹配 ============
{
Match: "i7-13", Note: "13代 i7 (Raptor Lake)",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{40000, 85000, 140000}, Note: "stress-ng bogo ops/s4 线程)"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{18000, 45000, 80000}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{28000, 60000, 100000}, Note: "CPU+内存综合"},
},
{
Match: "i7-12", Note: "12代 i7 (Alder Lake)",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{32000, 70000, 115000}, Note: "stress-ng bogo ops/s4 线程)"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{16000, 40000, 70000}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{24000, 52000, 90000}, Note: "CPU+内存综合"},
},
{
Match: "i7-11", Note: "11代 i7 (Rocket Lake)",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{28000, 60000, 100000}, Note: "stress-ng bogo ops/s4 线程)"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{14000, 35000, 60000}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{22000, 48000, 80000}, Note: "CPU+内存综合"},
},
{
Match: "i7-10", Note: "10代 i7 (Comet Lake)",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{25000, 55000, 90000}, Note: "stress-ng bogo ops/s4 线程)"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{12000, 30000, 55000}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{20000, 42000, 72000}, Note: "CPU+内存综合"},
},
{
Match: "i7-8", Note: "8/9代 i7 (Coffee Lake)",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{20000, 45000, 75000}, Note: "stress-ng bogo ops/s4 线程)"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{10000, 25000, 45000}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{16000, 35000, 60000}, Note: "CPU+内存综合"},
},
{
Match: "i7-7", Note: "7代 i7 (Kaby Lake)",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{16000, 35000, 60000}, Note: "stress-ng bogo ops/s4 线程)"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{8000, 20000, 38000}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{12000, 28000, 48000}, Note: "CPU+内存综合"},
},
{
Match: "i7-", Note: "Intel Core i7 (其他代)",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{18000, 40000, 70000}, Note: "i7 通用参考"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{9000, 22000, 42000}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{14000, 32000, 55000}, Note: "CPU+内存综合"},
},
// ============ Intel Core i5按代匹配 ============
{
Match: "i5-13", Note: "13代 i5 (Raptor Lake)",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{30000, 65000, 105000}, Note: "stress-ng bogo ops/s4 线程)"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{14000, 35000, 60000}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{22000, 48000, 80000}, Note: "CPU+内存综合"},
},
{
Match: "i5-12", Note: "12代 i5 (Alder Lake)",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{25000, 55000, 90000}, Note: "stress-ng bogo ops/s4 线程)"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{12000, 30000, 55000}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{18000, 40000, 70000}, Note: "CPU+内存综合"},
},
{
Match: "i5-11", Note: "11代 i5 (Rocket Lake)",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{22000, 48000, 80000}, Note: "stress-ng bogo ops/s4 线程)"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{11000, 27000, 48000}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{16000, 35000, 60000}, Note: "CPU+内存综合"},
},
{
Match: "i5-10", Note: "10代 i5 (Comet Lake)",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{20000, 42000, 70000}, Note: "stress-ng bogo ops/s4 线程)"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{10000, 25000, 45000}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{14000, 32000, 55000}, Note: "CPU+内存综合"},
},
{
Match: "i5-8", Note: "8/9代 i5 (Coffee Lake)",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{15000, 32000, 55000}, Note: "stress-ng bogo ops/s4 线程)"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{8000, 18000, 35000}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{10000, 24000, 42000}, Note: "CPU+内存综合"},
},
{
Match: "i5-", Note: "Intel Core i5 (其他代)",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{14000, 30000, 50000}, Note: "i5 通用参考"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{7000, 17000, 32000}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{10000, 22000, 40000}, Note: "CPU+内存综合"},
},
// ============ Intel Core i3按代匹配 ============
{
Match: "i3-13", Note: "13代 i3 (Raptor Lake)",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{20000, 42000, 70000}, Note: "stress-ng bogo ops/s4 线程)"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{10000, 24000, 45000}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{15000, 32000, 55000}, Note: "CPU+内存综合"},
},
{
Match: "i3-12", Note: "12代 i3 (Alder Lake)",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{18000, 38000, 62000}, Note: "stress-ng bogo ops/s4 线程)"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{9000, 22000, 40000}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{13000, 28000, 50000}, Note: "CPU+内存综合"},
},
{
Match: "i3-10", Note: "10代 i3 (Comet Lake)",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{14000, 30000, 50000}, Note: "stress-ng bogo ops/s4 线程)"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{7000, 17000, 32000}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{10000, 22000, 40000}, Note: "CPU+内存综合"},
},
{
Match: "i3-", Note: "Intel Core i3 (其他代)",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{10000, 22000, 38000}, Note: "i3 通用参考"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{5500, 13000, 25000}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{8000, 18000, 32000}, Note: "CPU+内存综合"},
},
// ============ Intel Xeon ============
{
Match: "Xeon", Note: "Intel Xeon",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{30000, 70000, 120000}, Note: "Xeon 通用参考(按核心数浮动大)"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{15000, 35000, 70000}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{22000, 50000, 90000}, Note: "CPU+内存综合"},
},
// ============ AMD Ryzen按系列 ============
{
Match: "Ryzen 9", Note: "AMD Ryzen 9",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{40000, 85000, 145000}, Note: "stress-ng bogo ops/s4 线程)"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{18000, 45000, 85000}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{30000, 65000, 110000}, Note: "CPU+内存综合"},
},
{
Match: "Ryzen 7", Note: "AMD Ryzen 7",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{28000, 60000, 100000}, Note: "stress-ng bogo ops/s4 线程)"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{13000, 32000, 60000}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{20000, 45000, 78000}, Note: "CPU+内存综合"},
},
{
Match: "Ryzen 5", Note: "AMD Ryzen 5",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{22000, 48000, 80000}, Note: "stress-ng bogo ops/s4 线程)"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{11000, 26000, 48000}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{16000, 36000, 62000}, Note: "CPU+内存综合"},
},
{
Match: "Ryzen 3", Note: "AMD Ryzen 3",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{14000, 30000, 50000}, Note: "stress-ng bogo ops/s4 线程)"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{7000, 17000, 32000}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{10000, 22000, 40000}, Note: "CPU+内存综合"},
},
{
Match: "Athlon", Note: "AMD Athlon",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{5000, 11000, 20000}, Note: "Athlon 通用参考"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{3000, 7000, 13000}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{4000, 9000, 17000}, Note: "CPU+内存综合"},
},
{
Match: "AMD", Note: "AMD 处理器 (其他型号)",
CPU: &MetricBench{Key: "score", Label: "CPU 跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{8000, 18000, 32000}, Note: "AMD 通用参考"},
Memory: &MetricBench{Key: "score", Label: "内存跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{4000, 10000, 18000}, Note: "stress-ng bogo ops/s"},
Full: &MetricBench{Key: "score", Label: "综合跑分", Unit: "ops/s", Direction: DirHigherBetter, Cutoffs: [3]float64{6000, 14000, 25000}, Note: "CPU+内存综合"},
},
}
// MatchHardware 根据 CPU 型号匹配硬件基准(大小写不敏感,子串匹配)
// 返回 nil 表示未在库中(调用方应将所有指标视为"达到正常",不做好坏判定)
func MatchHardware(cpuModel string) *HardwareBench {
if cpuModel == "" {
return nil
}
lower := strings.ToLower(cpuModel)
for i := range hardwareLibrary {
if strings.Contains(lower, strings.ToLower(hardwareLibrary[i].Match)) {
return &hardwareLibrary[i]
}
}
return nil
}
// ============================
// 评估函数
// ============================
// EvalBench 根据基准评估单个数值
func EvalBench(b MetricBench, value float64) Level {
c := b.Cutoffs
switch b.Direction {
case DirHigherBetter:
switch {
case value < c[0]:
return LevelPoor
case value < c[1]:
return LevelNormal
case value < c[2]:
return LevelGood
default:
return LevelExcellent
}
case DirLowerBetter:
switch {
case value > c[0]:
return LevelPoor
case value > c[1]:
return LevelNormal
case value > c[2]:
return LevelGood
default:
return LevelExcellent
}
}
return LevelNormal
}
// ReferenceText 生成"参考范围"展示文本
func (b MetricBench) ReferenceText() string {
c := b.Cutoffs
if b.Direction == DirHigherBetter {
return fmt.Sprintf(
"参考: <%.0f%s 远低于 · %.0f-%.0f%s 正常 · %.0f-%.0f%s 良好 · ≥%.0f%s 卓越",
c[0], b.Unit, c[0], c[1], b.Unit, c[1], c[2], b.Unit, c[2], b.Unit,
)
}
return fmt.Sprintf(
"参考: >%.0f%s 危险 · %.0f-%.0f%s 偏高 · %.0f-%.0f%s 正常 · ≤%.0f%s 优秀",
c[0], b.Unit, c[1], c[0], b.Unit, c[2], c[1], b.Unit, c[2], b.Unit,
)
}
// BenchItem 一次基准评估的展示单元(序列化到报告)
type BenchItem struct {
Key string `json:"key"`
Label string `json:"label"`
Value string `json:"value"` // 格式化后的值,如 "72.0 °C"
ValueNum float64 `json:"value_num"` // 原始数值
Unit string `json:"unit"`
Level Level `json:"level"`
LevelText string `json:"level_text"`
LevelColor string `json:"level_color"`
Reference string `json:"reference"`
Note string `json:"note"`
}
// OverallLevel 取多个评级中"最差"(最差用于提示风险)
func OverallLevel(items []BenchItem) Level {
if len(items) == 0 {
return ""
}
worst := LevelExcellent
for _, it := range items {
if LevelMeta(it.Level).Order < LevelMeta(worst).Order {
worst = it.Level
}
}
return worst
}
// EvaluateResult 对一个 Result 评估所有可用指标的基准
// 返回的列表按"重要性"排序:跑分 → 温度 → IOPS → 吞吐
// cpuModel: 设备 CPU 型号(来自 sysInfo["cpu"]);空串或不在库中时,所有指标项
// 强制返回 LevelNormal"达到正常"),不做好坏判定,避免误报
func EvaluateResult(r Result, cpuModel string) []BenchItem {
hb := MatchHardware(cpuModel)
inLibrary := hb != nil
var items []BenchItem
makeItem := func(b MetricBench, v float64) BenchItem {
// 未在库中 → 强制 normal已匹配 → 按专属阈值评估
lvl := LevelNormal
if inLibrary {
lvl = EvalBench(b, v)
}
d := LevelMeta(lvl)
return BenchItem{
Key: b.Key,
Label: b.Label,
Value: fmt.Sprintf("%.1f %s", v, b.Unit),
ValueNum: v,
Unit: b.Unit,
Level: lvl,
LevelText: d.Text,
LevelColor: d.Color,
Reference: b.ReferenceText(),
Note: b.Note,
}
}
// pickBench 选基准:库中有专属 → 专属;库中无该字段或未在库 → 默认
pickBench := func(field *MetricBench, defaultBench MetricBench) MetricBench {
if hb != nil && field != nil {
return *field
}
return defaultBench
}
// 1) 跑分(按测试类型选基准)
if r.Score > 0 {
var b MetricBench
switch r.Type {
case TestCPU:
if hb != nil {
b = pickBench(hb.CPU, benchCPU)
} else {
b = benchCPU
}
case TestMemory:
if hb != nil {
b = pickBench(hb.Memory, benchMemory)
} else {
b = benchMemory
}
case TestFull:
if hb != nil {
b = pickBench(hb.Full, benchFull)
} else {
b = benchFull
}
case TestMemNative:
if hb != nil {
b = pickBench(hb.MemBand, benchMemNative)
} else {
b = benchMemNative
}
}
if b.Key != "" {
items = append(items, makeItem(b, r.Score))
}
}
// 2) 温度max_temp 存在即评估)
// 温度阈值为通用参考60/75/90°C未在库中同样会按通用阈值评估并强制 normal
if t, ok := r.Metrics["max_temp"]; ok && t > 0 {
items = append(items, makeItem(benchTemp, t))
}
// 3) 磁盘 IOPS通用参考与 CPU 无关)
if t, ok := r.Metrics["iops_total"]; ok && t > 0 {
items = append(items, makeItem(benchDiskIOPS, t))
}
// 4) 磁盘吞吐(通用参考,与 CPU 无关)
if t, ok := r.Metrics["bw_total"]; ok && t > 0 {
items = append(items, makeItem(benchDiskBW, t))
}
return items
}

View File

@@ -0,0 +1,246 @@
package stress
import (
"strings"
"testing"
)
// TestEvalBench 验证基准评估逻辑
func TestEvalBench(t *testing.T) {
tests := []struct {
name string
bench MetricBench
val float64
want Level
}{
// CPU 跑分越高越好4 档
{"CPU-远低于", benchCPU, 5000, LevelPoor},
{"CPU-正常", benchCPU, 15000, LevelNormal},
{"CPU-超过", benchCPU, 30000, LevelGood},
{"CPU-远超过", benchCPU, 50000, LevelExcellent},
{"CPU-边界-正常下限", benchCPU, 8000, LevelNormal},
{"CPU-边界-超过下限", benchCPU, 20000, LevelGood},
{"CPU-边界-远超过下限", benchCPU, 40000, LevelExcellent},
// 温度(越低越好)
{"Temp-远超过(危险)", benchTemp, 95, LevelPoor},
{"Temp-偏高", benchTemp, 80, LevelNormal},
{"Temp-正常", benchTemp, 70, LevelGood},
{"Temp-优秀", benchTemp, 50, LevelExcellent},
{"Temp-边界-正常", benchTemp, 90, LevelNormal},
{"Temp-边界-良好", benchTemp, 75, LevelGood},
{"Temp-边界-优秀", benchTemp, 60, LevelExcellent},
// 磁盘 IOPS
{"IOPS-远低于(HDD)", benchDiskIOPS, 500, LevelPoor},
{"IOPS-正常(低端SSD)", benchDiskIOPS, 5000, LevelNormal},
{"IOPS-超过(SATA)", benchDiskIOPS, 30000, LevelGood},
{"IOPS-远超过(NVMe)", benchDiskIOPS, 80000, LevelExcellent},
// 磁盘吞吐
{"BW-远低于(HDD)", benchDiskBW, 50, LevelPoor},
{"BW-正常", benchDiskBW, 300, LevelNormal},
{"BW-超过", benchDiskBW, 1000, LevelGood},
{"BW-远超过", benchDiskBW, 3000, LevelExcellent},
}
for _, tc := range tests {
got := EvalBench(tc.bench, tc.val)
if got != tc.want {
t.Errorf("%s: val=%.0f got=%s want=%s", tc.name, tc.val, got, tc.want)
}
}
}
// TestEvaluateResult 验证 Result 评估
func TestEvaluateResult(t *testing.T) {
// 模拟一次 CPU 压测结果:跑分 25000超过正常最高温度 70°C良好
r := Result{
Type: TestCPU,
Score: 25000,
Metrics: map[string]float64{
"max_temp": 70,
},
}
// 使用 N100 库CPU 跑分阈值 10000/22000/38000 → 25000 介于 22k-38k应为 LevelGood
items := EvaluateResult(r, "Intel N100")
if len(items) != 2 {
t.Fatalf("期望 2 个评估项(跑分+温度),实际 %d", len(items))
}
// 第一项是 CPU 跑分
if items[0].Level != LevelGood {
t.Errorf("CPU 跑分评级错误: got=%s want=%s", items[0].Level, LevelGood)
}
// 第二项是温度70°C 在 60-75 之间,应为 LevelGood
if items[1].Level != LevelGood {
t.Errorf("温度评级错误: got=%s want=%s", items[1].Level, LevelGood)
}
// 综合评级应取最差
if OverallLevel(items) != LevelGood {
t.Errorf("综合评级错误: got=%s", OverallLevel(items))
}
}
// TestEvaluateResultDisk 验证磁盘测试评估
func TestEvaluateResultDisk(t *testing.T) {
// 模拟一次磁盘压测:吞吐 1500MB/s良好IOPS 80000远超过
r := Result{
Type: TestDiskIO,
Score: 1500, // score 是 bw_total
Metrics: map[string]float64{
"iops_total": 80000,
"bw_total": 1500,
},
}
// 磁盘阈值与 CPU 型号无关,传任意库内型号都能正常评估
items := EvaluateResult(r, "Intel N100")
if len(items) != 2 {
t.Fatalf("期望 2 个评估项,实际 %d", len(items))
}
// 找 IOPS 项
var iops, bw BenchItem
for _, it := range items {
switch it.Key {
case "iops_total":
iops = it
case "bw_total":
bw = it
}
}
if iops.Level != LevelExcellent {
t.Errorf("IOPS 80000 应评为远超过正常: got=%s", iops.Level)
}
if bw.Level != LevelGood {
t.Errorf("吞吐 1500 应评为超过正常: got=%s", bw.Level)
}
}
// TestMatchHardware 验证硬件库匹配
func TestMatchHardware(t *testing.T) {
tests := []struct {
cpu string
wantNil bool
wantNote string // 仅检查 Note 包含的子串
}{
// 飞牛常用低功耗
{"Intel(R) Celeron(R) N100 CPU @ 0.80GHz", false, "N100"},
{"Intel N305", false, "N305"},
{"12th Gen Intel(R) Core(TM) i3-12100", false, "12代 i3"},
{"13th Gen Intel(R) Core(TM) i5-13500", false, "13代 i5"},
{"11th Gen Intel(R) Core(TM) i7-11700K", false, "11代 i7"},
{"Intel(R) Core(TM) i9-12900K CPU", false, "12代 i9"},
{"Intel(R) Celeron(R) J4125 CPU @ 2.00GHz", false, "J4125"},
{"Intel(R) Celeron(R) N5095 @ 2.00GHz", false, "N5095"},
// AMD
{"AMD Ryzen 5 5600 6-Core Processor", false, "Ryzen 5"},
{"AMD Ryzen 7 5800X 8-Core", false, "Ryzen 7"},
{"AMD Ryzen 9 5900X", false, "Ryzen 9"},
// 大小写不敏感
{"INTEL N100", false, "N100"},
{"intel celeron n100", false, "N100"},
// 不在库中
{"", true, ""},
{"Some Unknown CPU XYZ", true, ""},
{"Apple M1", true, ""},
{"Loongson 3A5000", true, ""},
}
for _, tc := range tests {
got := MatchHardware(tc.cpu)
if tc.wantNil {
if got != nil {
t.Errorf("cpu=%q 应返回 nil得到 %v (Note=%s)", tc.cpu, got, got.Note)
}
continue
}
if got == nil {
t.Errorf("cpu=%q 应匹配到硬件,得到 nil", tc.cpu)
continue
}
if !strings.Contains(got.Note, tc.wantNote) {
t.Errorf("cpu=%q 匹配到 %q但 Note 应包含 %q", tc.cpu, got.Note, tc.wantNote)
}
}
}
// TestMatchHardware_Priority 验证匹配顺序优先级i5-12xxx 必须优先于 i5 通用)
func TestMatchHardware_Priority(t *testing.T) {
hb := MatchHardware("Intel Core i5-12400")
if hb == nil {
t.Fatal("i5-12400 应匹配到硬件")
}
if !strings.Contains(hb.Note, "12代 i5") {
t.Errorf("i5-12400 应优先匹配 12代 i5实际 Note=%s", hb.Note)
}
// 不带代号的 i5 应匹配通用条目
hb = MatchHardware("Intel Core i5-4400") // 老 4 代
if hb == nil {
t.Fatal("i5-4400 应匹配到硬件")
}
if !strings.Contains(hb.Note, "i5") {
t.Errorf("i5-4400 应匹配 i5 系列,实际 Note=%s", hb.Note)
}
}
// TestEvaluateResult_HardwareAware 验证同一分数在不同硬件下的评级不同
func TestEvaluateResult_HardwareAware(t *testing.T) {
r := Result{Type: TestCPU, Score: 25000}
mr := map[string]float64{"max_temp": 70}
// N100阈值 10000/22000/38000 → 25000 介于 22k-38k → Good
rN100 := r
rN100.Metrics = mr
itemsN100 := EvaluateResult(rN100, "Intel N100")
if itemsN100[0].Level != LevelGood {
t.Errorf("N100 跑分 25k 应为 Good实际 %s", itemsN100[0].Level)
}
// i3-12100阈值 18000/38000/62000 → 25000 介于 18k-38k → Normal
itemsI3 := EvaluateResult(r, "Intel Core i3-12100")
if itemsI3[0].Level != LevelNormal {
t.Errorf("i3-12100 跑分 25k 应为 Normal实际 %s", itemsI3[0].Level)
}
// i7-12700阈值 32000/70000/115000 → 25000 < 32k → Poor
itemsI7 := EvaluateResult(r, "Intel Core i7-12700")
if itemsI7[0].Level != LevelPoor {
t.Errorf("i7-12700 跑分 25k 应为 Poor明显低于 i7 水平),实际 %s", itemsI7[0].Level)
}
}
// TestEvaluateResult_UnknownHardware 验证未在库中的硬件全部返回 normal
func TestEvaluateResult_UnknownHardware(t *testing.T) {
r := Result{
Type: TestCPU,
Score: 25000,
Metrics: map[string]float64{
"max_temp": 70,
"iops_total": 80000,
"bw_total": 1500,
},
}
// 1) 空字符串
items := EvaluateResult(r, "")
if len(items) != 4 {
t.Fatalf("空 cpuModel 应返回 4 项,实际 %d", len(items))
}
for _, it := range items {
if it.Level != LevelNormal {
t.Errorf("空 cpuModel 时 %s 应为 Normal实际 %s", it.Label, it.Level)
}
}
// 2) 未知型号
items = EvaluateResult(r, "SomeUnknownCPU XYZ")
if len(items) != 4 {
t.Fatalf("未知 cpuModel 应返回 4 项,实际 %d", len(items))
}
for _, it := range items {
if it.Level != LevelNormal {
t.Errorf("未知 cpuModel 时 %s 应为 Normal实际 %s", it.Label, it.Level)
}
}
}

View File

@@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"time"
)
@@ -51,16 +52,20 @@ type chartPayload struct {
// testCard 单个测试卡片数据
type testCard struct {
Name string `json:"name"`
Label string `json:"label"`
Status string `json:"status"`
Duration string `json:"duration"`
Score float64 `json:"score"`
ScoreUnit string `json:"score_unit"`
ScoreHint string `json:"score_hint"`
Metrics []kvPair `json:"metrics"`
Output string `json:"output"`
Chart *chartPayload `json:"chart,omitempty"`
Name string `json:"name"`
Label string `json:"label"`
Status string `json:"status"`
Duration string `json:"duration"`
Score float64 `json:"score"`
ScoreUnit string `json:"score_unit"`
ScoreHint string `json:"score_hint"`
Metrics []kvPair `json:"metrics"`
Benchmarks []BenchItem `json:"benchmarks,omitempty"` // 性能/温度基准评级
OverallLevel Level `json:"overall_level,omitempty"` // 综合评级(各项最差)
OverallText string `json:"overall_text,omitempty"`
OverallColor string `json:"overall_color,omitempty"`
Output string `json:"output"`
Chart *chartPayload `json:"chart,omitempty"`
}
// reportHTMLData 报告整体数据
@@ -76,7 +81,14 @@ type reportHTMLData struct {
}
// WriteReportHTML 生成综合 HTML 报告(跑分卡片 + 多曲线图),返回文件路径
// 默认文件名report-<ip>.html
func WriteReportHTML(ip string, report *Report, reportDir string) (string, error) {
filename := fmt.Sprintf("report-%s.html", SanitizeFilename(ip))
return WriteReportHTMLTo(ip, report, reportDir, filename)
}
// WriteReportHTMLTo 生成综合 HTML 报告到指定文件名,返回完整文件路径
func WriteReportHTMLTo(ip string, report *Report, reportDir, filename string) (string, error) {
data := buildReportData(ip, report)
dataJSON, err := json.Marshal(data)
if err != nil {
@@ -88,11 +100,11 @@ func WriteReportHTML(ip string, report *Report, reportDir string) (string, error
if err := os.MkdirAll(reportDir, 0755); err != nil {
return "", err
}
filename := fmt.Sprintf("%s/report-%s.html", reportDir, SanitizeFilename(ip))
if err := os.WriteFile(filename, []byte(html), 0644); err != nil {
path := filepath.Join(reportDir, filename)
if err := os.WriteFile(path, []byte(html), 0644); err != nil {
return "", err
}
return filename, nil
return path, nil
}
func buildReportData(ip string, report *Report) reportHTMLData {
@@ -105,13 +117,18 @@ func buildReportData(ip string, report *Report) reportHTMLData {
Summary: report.Summary(),
SysInfo: report.SysInfo,
}
// 硬件型号:从 sysInfo["cpu"] 读取,用于按硬件库匹配基准
cpuModel := ""
if report.SysInfo != nil {
cpuModel = report.SysInfo["cpu"]
}
for _, res := range report.Results {
data.Cards = append(data.Cards, buildCard(res))
data.Cards = append(data.Cards, buildCard(cpuModel, res))
}
return data
}
func buildCard(res Result) testCard {
func buildCard(cpuModel string, res Result) testCard {
card := testCard{
Name: string(res.Type),
Label: testLabel(res.Type),
@@ -128,6 +145,16 @@ func buildCard(res Result) testCard {
Value: fmt.Sprintf("%.1f%s", res.Metrics[name], MetricUnit(name)),
})
}
// 性能/温度基准评级(仅在有数据时填充;按硬件库匹配专属阈值)
if items := EvaluateResult(res, cpuModel); len(items) > 0 {
card.Benchmarks = items
if lvl := OverallLevel(items); lvl != "" {
d := LevelMeta(lvl)
card.OverallLevel = lvl
card.OverallText = d.Text
card.OverallColor = d.Color
}
}
if len(res.Samples) > 0 {
if payload, ok := buildChart(res); ok {
card.Chart = &payload

View File

@@ -65,7 +65,7 @@ type Config struct {
TempLogInt time.Duration
TempLimit float64 // 温度上限(°C)0=不判定
// 压测统一在 Docker 容器中执行(工具预装在镜像里)
DockerImage string // 压测镜像名称,如 stress-test:latest
DockerImage string // 压测镜像名称,如 stress-test:20260824-153045由 build.sh 自动维护)
DockerTar string // 本地导出的镜像文件docker save目标机无镜像时上传并 load
DockerArgs string // Docker 运行参数,如 "--network=host --privileged"
}

View File

@@ -36,6 +36,18 @@ header .meta b{color:#fff;font-weight:600}
.metric{display:flex;flex-direction:column;gap:2px;padding:8px 12px;background:#f1f5f9;border-radius:8px;min-width:88px}
.metric .m-label{font-size:11px;color:#64748b}
.metric .m-value{font-size:15px;font-weight:700;color:#0f172a}
.bench{margin-top:14px;padding:12px 14px;background:#f8fafc;border-radius:10px;border:1px dashed #e2e8f0}
.bench-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;gap:10px;flex-wrap:wrap}
.bench-title{font-size:12px;font-weight:600;color:#475569;letter-spacing:.3px}
.bench-overall{display:inline-block;padding:3px 10px;border-radius:999px;font-size:12px;font-weight:700;color:#fff}
.bench-list{display:flex;flex-direction:column;gap:6px}
.bench-row{display:flex;align-items:center;flex-wrap:wrap;gap:8px;padding:6px 0;border-bottom:1px dashed #eef2f7}
.bench-row:last-child{border-bottom:none}
.bench-name{font-size:13px;font-weight:600;color:#0f172a;min-width:84px}
.bench-val{font-size:13px;color:#334155;font-variant-numeric:tabular-nums;min-width:96px}
.bench-tag{display:inline-block;padding:2px 8px;border-radius:6px;font-size:11px;font-weight:700;color:#fff;line-height:1.4}
.bench-ref{font-size:11px;color:#64748b;flex:1;min-width:0}
.bench-note{font-size:11px;color:#94a3b8;margin-top:4px;line-height:1.5}
.chart-box{position:relative;height:260px;margin-top:6px}
details.output{margin-top:14px;border-top:1px dashed #e2e8f0;padding-top:10px}
details.output summary{cursor:pointer;font-size:12px;color:#64748b;user-select:none}
@@ -84,6 +96,33 @@ function buildCard(card,idx){
card.metrics.forEach(function(x){h+='<div class="metric"><span class="m-label">'+esc(x.label)+'</span><span class="m-value">'+esc(x.value)+'</span></div>';});
h+='</div>';
}
if(card.benchmarks&&card.benchmarks.length){
var overall=card.overall_level?card.overall_level:'';
var overallText=card.overall_text||'';
var overallColor=card.overall_color||'#64748b';
h+='<div class="bench">';
h+='<div class="bench-head"><span class="bench-title">基准评估(参考消费/主流企业级硬件)</span>';
if(overall){h+='<span class="bench-overall" style="background:'+overallColor+'" title="综合评级(各项最差)">综合: '+esc(overallText)+'</span>';}
h+='</div>';
h+='<div class="bench-list">';
card.benchmarks.forEach(function(b){
h+='<div class="bench-row">';
h+='<span class="bench-name">'+esc(b.label)+'</span>';
h+='<span class="bench-val">'+esc(b.value)+'</span>';
h+='<span class="bench-tag" style="background:'+esc(b.level_color)+'">'+esc(b.level_text)+'</span>';
h+='<span class="bench-ref">'+esc(b.reference)+'</span>';
h+='</div>';
});
h+='</div>';
// 备注合并:去重后展示
var notes={};
card.benchmarks.forEach(function(b){if(b.note)notes[b.note]=1;});
var noteArr=Object.keys(notes);
if(noteArr.length){
h+='<div class="bench-note">说明: '+esc(noteArr.join(' · '))+'</div>';
}
h+='</div>';
}
if(card.chart){h+='<div class="chart-box"><canvas id="c'+idx+'"></canvas></div>';}
if(card.output&&card.status!=='pass'){
h+='<details class="output"><summary>详细输出</summary><pre>'+esc(card.output)+'</pre></details>';

View File

@@ -290,8 +290,12 @@ func (r *Runner) runInDocker() (string, error) {
}
// 2. 构建 Docker 运行命令(通过环境变量传入参数)
// 固定挂载:/sys 只读(容器内是独立 sysfssensors 需读宿主机硬件传感器节点才能采集温度);
// /etc/localtime 只读容器时区与宿主机一致dmesg -T 与采样时间戳才准确)。
containerName := fmt.Sprintf("auto-check-%d", time.Now().UnixNano())
dockerRunCmd := fmt.Sprintf("docker run --rm --name %s "+
"-v /sys:/sys:ro "+
"-v /etc/localtime:/etc/localtime:ro "+
"-e AUTOCHECK_TYPES=%s "+
"-e AUTOCHECK_DURATION=%d "+
"-e AUTOCHECK_THREADS=%d "+

View File

@@ -40,17 +40,17 @@ func runPrivileged(client *ssh.Client, cmd, sudoPwd string) (string, error) {
if out, err := sshclient.RunCommand(client, "id -u"); err == nil && strings.TrimSpace(out) == "0" {
return sshclient.RunCommand(client, cmd)
}
// 非 root尝试 sudo
// 非 root尝试 sudo-p '' 静默密码提示,避免提示文本污染命令输出)
if sudoPwd != "" {
// echo 密码 | sudo -S 执行;整条命令用双引号包裹,避免命令内部单引号嵌套冲突。
// 仅对命令内的双引号转义docker 命令均不含双引号,安全)。
quoted := strings.ReplaceAll(singleLine(cmd), `"`, `\"`)
wrapped := fmt.Sprintf("echo %s | sudo -S -- sh -c \"%s\"", shellQuote(sudoPwd), quoted)
wrapped := fmt.Sprintf("echo %s | sudo -S -p '' -- sh -c \"%s\"", shellQuote(sudoPwd), quoted)
return sshclient.RunCommand(client, wrapped)
}
// 无密码:先尝试免密 sudo (-n),失败则回退普通 sudo会提示需要密码
quoted := strings.ReplaceAll(singleLine(cmd), `"`, `\"`)
if out, err := sshclient.RunCommand(client, "sudo -n -- sh -c \""+quoted+"\""); err == nil {
if out, err := sshclient.RunCommand(client, "sudo -n -p '' -- sh -c \""+quoted+"\""); err == nil {
return out, nil
}
return sshclient.RunCommand(client, "sudo -- sh -c \""+quoted+"\"")

View File

@@ -196,6 +196,11 @@ func (r *Report) Summary() string {
// ToText 文本格式报告
func (r *Report) ToText() string {
// 从 sysInfo 提取 CPU 型号用于按硬件库匹配基准
cpuModel := ""
if r.SysInfo != nil {
cpuModel = r.SysInfo["cpu"]
}
var sb strings.Builder
sb.WriteString(fmt.Sprintf("══ [%s] 压力测试报告 ══\n", r.IP))
sb.WriteString(fmt.Sprintf(" 耗时: %s\n\n", r.Duration.Round(time.Millisecond)))
@@ -214,6 +219,13 @@ func (r *Report) ToText() string {
if res.Score > 0 {
sb.WriteString(fmt.Sprintf(" 跑分: %.1f %s\n", res.Score, ScoreUnit(res.Type)))
}
// 基准评级(让用户对结果有感性认知,按硬件库匹配专属阈值)
if items := EvaluateResult(res, cpuModel); len(items) > 0 {
for _, it := range items {
sb.WriteString(fmt.Sprintf(" [%s] %s %s — %s\n",
it.LevelText, it.Label, it.Value, it.Reference))
}
}
for _, name := range sortedMetricNames(res.Metrics) {
unit := MetricUnit(name)
sb.WriteString(fmt.Sprintf(" %s: %.1f%s\n", MetricLabel(name), res.Metrics[name], unit))