CPU 压力测试模块 · 架构设计与实现
关键词:Node.js 插件运行时 · C++ 原生插件(node-addon-api)· PID 闭环负载控制 · 实时指标上报
一、背景与定位
在嵌入式/边缘设备的自动化测试中,"CPU 压力测试"用于验证处理器在持续高负载下的计算能力、稳定性与热表现。它不只是简单地把 CPU 跑满,而更关注三点:
- 负载可控------能稳定地把 CPU 压到指定的目标利用率(如 95%),而不是忽高忽低;
- 过程可观测------实时采集 CPU 利用率、控制量、温度,并推送到前端做曲线展示;
- 结果可判定------依据温度上限等阈值自动给出 PASS / FAIL 结论。
本模块采用**「Node.js 运行时编排 + C++ 原生插件执行」**的分层架构:用 JS 层对接测试框架与前端,用 C++ 层做实时、低抖动的负载生成与闭环控制。
二、整体架构
模块由三层组成,自下而上分别是原生插件层(C++ / N-API)、运行时层(Node.js ESM)、插件注册与前端交互层。
#mermaid-svg-oM6b9IjqaCaSXI8C{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-oM6b9IjqaCaSXI8C .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-oM6b9IjqaCaSXI8C .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-oM6b9IjqaCaSXI8C .error-icon{fill:#552222;}#mermaid-svg-oM6b9IjqaCaSXI8C .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-oM6b9IjqaCaSXI8C .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-oM6b9IjqaCaSXI8C .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-oM6b9IjqaCaSXI8C .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-oM6b9IjqaCaSXI8C .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-oM6b9IjqaCaSXI8C .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-oM6b9IjqaCaSXI8C .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-oM6b9IjqaCaSXI8C .marker{fill:#333333;stroke:#333333;}#mermaid-svg-oM6b9IjqaCaSXI8C .marker.cross{stroke:#333333;}#mermaid-svg-oM6b9IjqaCaSXI8C svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-oM6b9IjqaCaSXI8C p{margin:0;}#mermaid-svg-oM6b9IjqaCaSXI8C .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-oM6b9IjqaCaSXI8C .cluster-label text{fill:#333;}#mermaid-svg-oM6b9IjqaCaSXI8C .cluster-label span{color:#333;}#mermaid-svg-oM6b9IjqaCaSXI8C .cluster-label span p{background-color:transparent;}#mermaid-svg-oM6b9IjqaCaSXI8C .label text,#mermaid-svg-oM6b9IjqaCaSXI8C span{fill:#333;color:#333;}#mermaid-svg-oM6b9IjqaCaSXI8C .node rect,#mermaid-svg-oM6b9IjqaCaSXI8C .node circle,#mermaid-svg-oM6b9IjqaCaSXI8C .node ellipse,#mermaid-svg-oM6b9IjqaCaSXI8C .node polygon,#mermaid-svg-oM6b9IjqaCaSXI8C .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-oM6b9IjqaCaSXI8C .rough-node .label text,#mermaid-svg-oM6b9IjqaCaSXI8C .node .label text,#mermaid-svg-oM6b9IjqaCaSXI8C .image-shape .label,#mermaid-svg-oM6b9IjqaCaSXI8C .icon-shape .label{text-anchor:middle;}#mermaid-svg-oM6b9IjqaCaSXI8C .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-oM6b9IjqaCaSXI8C .rough-node .label,#mermaid-svg-oM6b9IjqaCaSXI8C .node .label,#mermaid-svg-oM6b9IjqaCaSXI8C .image-shape .label,#mermaid-svg-oM6b9IjqaCaSXI8C .icon-shape .label{text-align:center;}#mermaid-svg-oM6b9IjqaCaSXI8C .node.clickable{cursor:pointer;}#mermaid-svg-oM6b9IjqaCaSXI8C .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-oM6b9IjqaCaSXI8C .arrowheadPath{fill:#333333;}#mermaid-svg-oM6b9IjqaCaSXI8C .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-oM6b9IjqaCaSXI8C .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-oM6b9IjqaCaSXI8C .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-oM6b9IjqaCaSXI8C .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-oM6b9IjqaCaSXI8C .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-oM6b9IjqaCaSXI8C .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-oM6b9IjqaCaSXI8C .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-oM6b9IjqaCaSXI8C .cluster text{fill:#333;}#mermaid-svg-oM6b9IjqaCaSXI8C .cluster span{color:#333;}#mermaid-svg-oM6b9IjqaCaSXI8C div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-oM6b9IjqaCaSXI8C .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-oM6b9IjqaCaSXI8C rect.text{fill:none;stroke-width:0;}#mermaid-svg-oM6b9IjqaCaSXI8C .icon-shape,#mermaid-svg-oM6b9IjqaCaSXI8C .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-oM6b9IjqaCaSXI8C .icon-shape p,#mermaid-svg-oM6b9IjqaCaSXI8C .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-oM6b9IjqaCaSXI8C .icon-shape .label rect,#mermaid-svg-oM6b9IjqaCaSXI8C .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-oM6b9IjqaCaSXI8C .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-oM6b9IjqaCaSXI8C .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-oM6b9IjqaCaSXI8C :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 原生插件层 (C++ / N-API)
运行时层 (Node.js ESM)
前端 / 测试编排
broadcast 推送
cpu_load / cpu_test_complete
require('./build/Release/cpu_stress.node')
metrics / log / complete
intensity ∈ 0,1
当前负载 / 温度
组态 IDE 前端
(实时曲线 / 结果卡片)
测试框架
(RuntimeComponent 容器)
StressCpuRuntime
runtime.js
· 加载原生插件
· 配置解析
· 指标收集与统计
· 广播 / 判定
CpuStressAddon
cpu_stress_addon.cc
Napi::ObjectWrap
start / stop / isRunning / getThreadCount
CpuStressController
(控制循环)
Napi::ThreadSafeFunction
(线程安全回调)
CPUMonitor
· get_cpu_usage
· get_temperature
PIDController
· compute()
LoadTask ×N
· 忙/闲占空比负载
三、分层设计详解
3.1 运行时层 ------ runtime.js
StressCpuRuntime 继承自框架基类 RuntimeComponent,是 JS 侧的总指挥。
核心职责:
- 加载原生插件 :在 ESM 环境下用
createRequire加载编译产物build/Release/cpu_stress.node,并做失败兜底(返回null,避免整流程崩溃)。 - 配置解析(
init):读取测试参数并设定默认值。
| 参数 | 字段 | 默认值 |
|---|---|---|
| 测试时长 | duration |
60 s |
| 工作线程数 | threads |
4 |
| 目标 CPU 利用率 | cpuThreshold |
95 % |
| 采样间隔 | sampleInterval |
1.0 s |
| 比例增益 | kp |
0.05 |
| 积分增益 | ki |
0 |
| 微分增益 | kd |
0 |
| 温度上限 | maxTemp |
85 °C |
- 编排执行(
run→runStressTest) :创建CpuStressAddon实例,调用start(options, callback)。回调按data.type分流:log:映射到 info/debug/warning/error 日志级别;metrics:追加到metricsHistory,并通过broadcastCallback向前端推送{type:'cpu_load', cpuLoad, targetLoad, controlSignal, temperature, ...}实时数据;complete:结束 Promise,触发收尾统计。
- 超时保护 :
setTimeout兜底在duration + 5s强制stop(),防止原生侧异常挂死。 - 统计与判定(
run) :遍历metricsHistory计算 avg/max/min 负载与温度;判定逻辑以温度不超过上限 为通过条件(passed = maxTemp <= maxTemp),并汇总成testResults通过cpu_test_complete广播给前端。
3.2 原生插件层 ------ cpu_stress_addon.cc
用 node-addon-api(N-API) 把 C++ 控制逻辑暴露给 JS。
CpuStressAddon(Napi::ObjectWrap) 提供 4 个实例方法:
start(options, callback):参数校验(target∈0,100、interval>0、PID 非负)→ 创建ThreadSafeFunction→ 构造CpuStressController→ 在独立std::thread中run();stop():调用controller_->shutdown()并设置停止标志;isRunning()/getThreadCount():状态查询。
线程安全回调(关键设计) :C++ 工作线程不能直接调用 JS 函数。模块通过 Napi::ThreadSafeFunction::New 创建 TSFN,在控制循环中用 NonBlockingCall 把 {type, ...} 对象安全地 抛回 JS 主线程,再统一由 runtime.js 的回调分发。这既保证了实时性,又避免了 V8 句柄跨线程的崩溃风险。
3.3 闭环控制模块 ------ CpuStressController
控制器是 C++ 侧的"大脑",持有三件套:
#mermaid-svg-AfnjcOAnndbeoF5c{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-AfnjcOAnndbeoF5c .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-AfnjcOAnndbeoF5c .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-AfnjcOAnndbeoF5c .error-icon{fill:#552222;}#mermaid-svg-AfnjcOAnndbeoF5c .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-AfnjcOAnndbeoF5c .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-AfnjcOAnndbeoF5c .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-AfnjcOAnndbeoF5c .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-AfnjcOAnndbeoF5c .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-AfnjcOAnndbeoF5c .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-AfnjcOAnndbeoF5c .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-AfnjcOAnndbeoF5c .marker{fill:#333333;stroke:#333333;}#mermaid-svg-AfnjcOAnndbeoF5c .marker.cross{stroke:#333333;}#mermaid-svg-AfnjcOAnndbeoF5c svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-AfnjcOAnndbeoF5c p{margin:0;}#mermaid-svg-AfnjcOAnndbeoF5c g.classGroup text{fill:#9370DB;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-AfnjcOAnndbeoF5c g.classGroup text .title{font-weight:bolder;}#mermaid-svg-AfnjcOAnndbeoF5c .cluster-label text{fill:#333;}#mermaid-svg-AfnjcOAnndbeoF5c .cluster-label span{color:#333;}#mermaid-svg-AfnjcOAnndbeoF5c .cluster-label span p{background-color:transparent;}#mermaid-svg-AfnjcOAnndbeoF5c .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-AfnjcOAnndbeoF5c .cluster text{fill:#333;}#mermaid-svg-AfnjcOAnndbeoF5c .cluster span{color:#333;}#mermaid-svg-AfnjcOAnndbeoF5c .nodeLabel,#mermaid-svg-AfnjcOAnndbeoF5c .edgeLabel{color:#131300;}#mermaid-svg-AfnjcOAnndbeoF5c .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-AfnjcOAnndbeoF5c .label text{fill:#131300;}#mermaid-svg-AfnjcOAnndbeoF5c .labelBkg{background:#ECECFF;}#mermaid-svg-AfnjcOAnndbeoF5c .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-AfnjcOAnndbeoF5c .classTitle{font-weight:bolder;}#mermaid-svg-AfnjcOAnndbeoF5c .node rect,#mermaid-svg-AfnjcOAnndbeoF5c .node circle,#mermaid-svg-AfnjcOAnndbeoF5c .node ellipse,#mermaid-svg-AfnjcOAnndbeoF5c .node polygon,#mermaid-svg-AfnjcOAnndbeoF5c .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-AfnjcOAnndbeoF5c .divider{stroke:#9370DB;stroke-width:1;}#mermaid-svg-AfnjcOAnndbeoF5c g.clickable{cursor:pointer;}#mermaid-svg-AfnjcOAnndbeoF5c g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-AfnjcOAnndbeoF5c g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-AfnjcOAnndbeoF5c .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-AfnjcOAnndbeoF5c .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-AfnjcOAnndbeoF5c .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-AfnjcOAnndbeoF5c .dashed-line{stroke-dasharray:3;}#mermaid-svg-AfnjcOAnndbeoF5c .dotted-line{stroke-dasharray:1 2;}#mermaid-svg-AfnjcOAnndbeoF5c #compositionStart,#mermaid-svg-AfnjcOAnndbeoF5c .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-AfnjcOAnndbeoF5c #compositionEnd,#mermaid-svg-AfnjcOAnndbeoF5c .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-AfnjcOAnndbeoF5c #dependencyStart,#mermaid-svg-AfnjcOAnndbeoF5c .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-AfnjcOAnndbeoF5c #dependencyStart,#mermaid-svg-AfnjcOAnndbeoF5c .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-AfnjcOAnndbeoF5c #extensionStart,#mermaid-svg-AfnjcOAnndbeoF5c .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-AfnjcOAnndbeoF5c #extensionEnd,#mermaid-svg-AfnjcOAnndbeoF5c .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-AfnjcOAnndbeoF5c #aggregationStart,#mermaid-svg-AfnjcOAnndbeoF5c .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-AfnjcOAnndbeoF5c #aggregationEnd,#mermaid-svg-AfnjcOAnndbeoF5c .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-AfnjcOAnndbeoF5c #lollipopStart,#mermaid-svg-AfnjcOAnndbeoF5c .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-AfnjcOAnndbeoF5c #lollipopEnd,#mermaid-svg-AfnjcOAnndbeoF5c .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-AfnjcOAnndbeoF5c .edgeTerminals{font-size:11px;line-height:initial;}#mermaid-svg-AfnjcOAnndbeoF5c .classTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-AfnjcOAnndbeoF5c .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-AfnjcOAnndbeoF5c .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-AfnjcOAnndbeoF5c :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 每线程一个
CpuStressController
-target_load_
-sample_interval_
-stop_flag_ : atomic<bool>
-intensity_ : atomic<double>
-pid_ : unique_ptr<PIDController>
-monitor_ : CPUMonitor
-worker_threads_ : vector<thread>
+run()
+shutdown()
CPUMonitor
+get_cpu_usage() : double
+get_temperature() : double
+get_num_cores() : int
PIDController
+compute(current) : double
+reset()
+set_params(kp,ki,kd)
LoadTask
-intensity_ptr_ : atomic<double>
-stop_ptr_ : atomic<bool>
+execute()
CPUMonitor:跨平台采集。CPU 利用率在 Windows 用GetSystemTimes、Linux 用/proc/stat两次采样差分计算;温度在 Linux 遍历/sys/class/thermal/thermal_zone0..9取最大值(Windows 返回 -1)。PIDController:经典 PID,输出u ∈ [0,1]即"负载强度",详见第四节。LoadTask:真正吃 CPU 的执行体,每个工作线程一个,循环读取共享intensity_决定忙/闲比例。
四、核心算法:PID 闭环负载控制
压力测试的本质是一个闭环控制系统:目标是让"实际 CPU 利用率"稳定跟随"目标利用率"。
#mermaid-svg-8wnmCboPxVfGJnkY{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-8wnmCboPxVfGJnkY .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8wnmCboPxVfGJnkY .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8wnmCboPxVfGJnkY .error-icon{fill:#552222;}#mermaid-svg-8wnmCboPxVfGJnkY .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8wnmCboPxVfGJnkY .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8wnmCboPxVfGJnkY .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8wnmCboPxVfGJnkY .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8wnmCboPxVfGJnkY .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8wnmCboPxVfGJnkY .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8wnmCboPxVfGJnkY .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8wnmCboPxVfGJnkY .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8wnmCboPxVfGJnkY .marker.cross{stroke:#333333;}#mermaid-svg-8wnmCboPxVfGJnkY svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8wnmCboPxVfGJnkY p{margin:0;}#mermaid-svg-8wnmCboPxVfGJnkY .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-8wnmCboPxVfGJnkY .cluster-label text{fill:#333;}#mermaid-svg-8wnmCboPxVfGJnkY .cluster-label span{color:#333;}#mermaid-svg-8wnmCboPxVfGJnkY .cluster-label span p{background-color:transparent;}#mermaid-svg-8wnmCboPxVfGJnkY .label text,#mermaid-svg-8wnmCboPxVfGJnkY span{fill:#333;color:#333;}#mermaid-svg-8wnmCboPxVfGJnkY .node rect,#mermaid-svg-8wnmCboPxVfGJnkY .node circle,#mermaid-svg-8wnmCboPxVfGJnkY .node ellipse,#mermaid-svg-8wnmCboPxVfGJnkY .node polygon,#mermaid-svg-8wnmCboPxVfGJnkY .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8wnmCboPxVfGJnkY .rough-node .label text,#mermaid-svg-8wnmCboPxVfGJnkY .node .label text,#mermaid-svg-8wnmCboPxVfGJnkY .image-shape .label,#mermaid-svg-8wnmCboPxVfGJnkY .icon-shape .label{text-anchor:middle;}#mermaid-svg-8wnmCboPxVfGJnkY .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-8wnmCboPxVfGJnkY .rough-node .label,#mermaid-svg-8wnmCboPxVfGJnkY .node .label,#mermaid-svg-8wnmCboPxVfGJnkY .image-shape .label,#mermaid-svg-8wnmCboPxVfGJnkY .icon-shape .label{text-align:center;}#mermaid-svg-8wnmCboPxVfGJnkY .node.clickable{cursor:pointer;}#mermaid-svg-8wnmCboPxVfGJnkY .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-8wnmCboPxVfGJnkY .arrowheadPath{fill:#333333;}#mermaid-svg-8wnmCboPxVfGJnkY .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-8wnmCboPxVfGJnkY .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-8wnmCboPxVfGJnkY .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8wnmCboPxVfGJnkY .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-8wnmCboPxVfGJnkY .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8wnmCboPxVfGJnkY .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-8wnmCboPxVfGJnkY .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-8wnmCboPxVfGJnkY .cluster text{fill:#333;}#mermaid-svg-8wnmCboPxVfGJnkY .cluster span{color:#333;}#mermaid-svg-8wnmCboPxVfGJnkY div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-8wnmCboPxVfGJnkY .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-8wnmCboPxVfGJnkY rect.text{fill:none;stroke-width:0;}#mermaid-svg-8wnmCboPxVfGJnkY .icon-shape,#mermaid-svg-8wnmCboPxVfGJnkY .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8wnmCboPxVfGJnkY .icon-shape p,#mermaid-svg-8wnmCboPxVfGJnkY .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-8wnmCboPxVfGJnkY .icon-shape .label rect,#mermaid-svg-8wnmCboPxVfGJnkY .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8wnmCboPxVfGJnkY .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-8wnmCboPxVfGJnkY .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-8wnmCboPxVfGJnkY :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 控制量 u ∈ 0,1
intensity
current load
deadband 2%
积分限幅
输出限幅
目标利用率
setpoint (target)
PID
共享原子变量
intensity_
LoadTask 工作线程
busy = u × 50ms
idle = (1-u) × 50ms
CPU 实际负载
CPUMonitor
(采样)
控制律(离散 PID):
error = setpoint − current
delta_u = Kp·error + Ki·∫error + Kd·(error − prev_error)
delta_u *= 0.1 // 步长收敛系数,抑制过冲
output += delta_u
output = clamp(output, 0, 1) // 即 intensity
稳定性设计要点(pid_controller.hpp):
- 死区(deadband = 2%) :当
|error| < 2%直接维持当前输出,避免小幅抖动导致的乒乓振荡; - 积分限幅(±100):防止积分饱和(windup);
- 输出限幅 0,1:保证负载占空比合法;
- 步长系数 0.1:每周期只微调控制量,进一步平滑收敛。
负载执行(LoadTask::execute):以 50ms 为一个小微周期,按强度切分:
intensity_ (原子) LoadTask 循环 intensity_ (原子) LoadTask 循环 #mermaid-svg-BbJj0ubSqoExXayu{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-BbJj0ubSqoExXayu .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-BbJj0ubSqoExXayu .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-BbJj0ubSqoExXayu .error-icon{fill:#552222;}#mermaid-svg-BbJj0ubSqoExXayu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-BbJj0ubSqoExXayu .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-BbJj0ubSqoExXayu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-BbJj0ubSqoExXayu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-BbJj0ubSqoExXayu .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-BbJj0ubSqoExXayu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-BbJj0ubSqoExXayu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-BbJj0ubSqoExXayu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-BbJj0ubSqoExXayu .marker.cross{stroke:#333333;}#mermaid-svg-BbJj0ubSqoExXayu svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-BbJj0ubSqoExXayu p{margin:0;}#mermaid-svg-BbJj0ubSqoExXayu .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-BbJj0ubSqoExXayu text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-BbJj0ubSqoExXayu .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-BbJj0ubSqoExXayu .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-BbJj0ubSqoExXayu .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-BbJj0ubSqoExXayu .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-BbJj0ubSqoExXayu #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-BbJj0ubSqoExXayu .sequenceNumber{fill:white;}#mermaid-svg-BbJj0ubSqoExXayu #sequencenumber{fill:#333;}#mermaid-svg-BbJj0ubSqoExXayu #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-BbJj0ubSqoExXayu .messageText{fill:#333;stroke:none;}#mermaid-svg-BbJj0ubSqoExXayu .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-BbJj0ubSqoExXayu .labelText,#mermaid-svg-BbJj0ubSqoExXayu .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-BbJj0ubSqoExXayu .loopText,#mermaid-svg-BbJj0ubSqoExXayu .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-BbJj0ubSqoExXayu .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-BbJj0ubSqoExXayu .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-BbJj0ubSqoExXayu .noteText,#mermaid-svg-BbJj0ubSqoExXayu .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-BbJj0ubSqoExXayu .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-BbJj0ubSqoExXayu .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-BbJj0ubSqoExXayu .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-BbJj0ubSqoExXayu .actorPopupMenu{position:absolute;}#mermaid-svg-BbJj0ubSqoExXayu .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-BbJj0ubSqoExXayu .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-BbJj0ubSqoExXayu .actor-man circle,#mermaid-svg-BbJj0ubSqoExXayu line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-BbJj0ubSqoExXayu :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} altidle \> 0.5ms loop每 50ms 微周期 读取 intensity = ubusy = u × 0.05s(2000 次 sqrt/sin/cos 计算)sleep((1-u) × 0.05s)
内层为纯浮点计算(sqrt(i)·sin(i)·cos(i)),不依赖 IO,确保吃满核心;外层 sleep 用来"泄压",从而把整体利用率精确调到 u。
五、运行数据流与实时上报
一次测试的完整时序如下:
CPUMonitor LoadTask ×N CpuStressController CpuStressAddon (C++) StressCpuRuntime 前端 CPUMonitor LoadTask ×N CpuStressController CpuStressAddon (C++) StressCpuRuntime 前端 #mermaid-svg-M0Y6npn9rlvFUu54{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-M0Y6npn9rlvFUu54 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-M0Y6npn9rlvFUu54 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-M0Y6npn9rlvFUu54 .error-icon{fill:#552222;}#mermaid-svg-M0Y6npn9rlvFUu54 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-M0Y6npn9rlvFUu54 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-M0Y6npn9rlvFUu54 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-M0Y6npn9rlvFUu54 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-M0Y6npn9rlvFUu54 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-M0Y6npn9rlvFUu54 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-M0Y6npn9rlvFUu54 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-M0Y6npn9rlvFUu54 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-M0Y6npn9rlvFUu54 .marker.cross{stroke:#333333;}#mermaid-svg-M0Y6npn9rlvFUu54 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-M0Y6npn9rlvFUu54 p{margin:0;}#mermaid-svg-M0Y6npn9rlvFUu54 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-M0Y6npn9rlvFUu54 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-M0Y6npn9rlvFUu54 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-M0Y6npn9rlvFUu54 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-M0Y6npn9rlvFUu54 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-M0Y6npn9rlvFUu54 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-M0Y6npn9rlvFUu54 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-M0Y6npn9rlvFUu54 .sequenceNumber{fill:white;}#mermaid-svg-M0Y6npn9rlvFUu54 #sequencenumber{fill:#333;}#mermaid-svg-M0Y6npn9rlvFUu54 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-M0Y6npn9rlvFUu54 .messageText{fill:#333;stroke:none;}#mermaid-svg-M0Y6npn9rlvFUu54 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-M0Y6npn9rlvFUu54 .labelText,#mermaid-svg-M0Y6npn9rlvFUu54 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-M0Y6npn9rlvFUu54 .loopText,#mermaid-svg-M0Y6npn9rlvFUu54 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-M0Y6npn9rlvFUu54 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-M0Y6npn9rlvFUu54 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-M0Y6npn9rlvFUu54 .noteText,#mermaid-svg-M0Y6npn9rlvFUu54 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-M0Y6npn9rlvFUu54 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-M0Y6npn9rlvFUu54 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-M0Y6npn9rlvFUu54 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-M0Y6npn9rlvFUu54 .actorPopupMenu{position:absolute;}#mermaid-svg-M0Y6npn9rlvFUu54 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-M0Y6npn9rlvFUu54 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-M0Y6npn9rlvFUu54 .actor-man circle,#mermaid-svg-M0Y6npn9rlvFUu54 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-M0Y6npn9rlvFUu54 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} loop每个 sampleInterval new CpuStressAddon()start({threads,target,interval,kp,ki,kd,duration}, cb)run() (独立线程)启动 N 个工作线程get_cpu_usage() / get_temperature()current load, tempPID.compute(current) → intensityintensity_ = uTSFN.NonBlockingCall(metrics)cb({type:'metrics', load, target, controlSignal, temperature, elapsed})broadcast({type:'cpu_load', ...})时长到 / stop() → completecb({type:'complete'})统计 avg/max/min + 判定 passedbroadcast({type:'cpu_test_complete', testResults})
要点:
- 实时性 :每个采样周期都会产生一条
metrics并立即广播,前端可绘制"负载-目标-温度"随时间的曲线; - 双向控制 :前端/框架可通过
stop()随时中止,TSFN 释放与线程join都有保障,无资源泄漏; - 结果闭环 :
runtime.js在complete后做统计与判定,把结构化testResults回传,供后续节点(如"结果上报"插件)消费。
六、构建与跨平台
原生插件通过 node-gyp 编译,binding.gyp 仅把 cppBk/cpu_stress_addon.cc 编入 cpu_stress 目标,并链接 pthread(Linux)/ kernel32.lib(Windows)。
#mermaid-svg-RyqzYxMQSrBMTH9j{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-RyqzYxMQSrBMTH9j .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-RyqzYxMQSrBMTH9j .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-RyqzYxMQSrBMTH9j .error-icon{fill:#552222;}#mermaid-svg-RyqzYxMQSrBMTH9j .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-RyqzYxMQSrBMTH9j .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-RyqzYxMQSrBMTH9j .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-RyqzYxMQSrBMTH9j .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-RyqzYxMQSrBMTH9j .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-RyqzYxMQSrBMTH9j .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-RyqzYxMQSrBMTH9j .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-RyqzYxMQSrBMTH9j .marker{fill:#333333;stroke:#333333;}#mermaid-svg-RyqzYxMQSrBMTH9j .marker.cross{stroke:#333333;}#mermaid-svg-RyqzYxMQSrBMTH9j svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-RyqzYxMQSrBMTH9j p{margin:0;}#mermaid-svg-RyqzYxMQSrBMTH9j .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-RyqzYxMQSrBMTH9j .cluster-label text{fill:#333;}#mermaid-svg-RyqzYxMQSrBMTH9j .cluster-label span{color:#333;}#mermaid-svg-RyqzYxMQSrBMTH9j .cluster-label span p{background-color:transparent;}#mermaid-svg-RyqzYxMQSrBMTH9j .label text,#mermaid-svg-RyqzYxMQSrBMTH9j span{fill:#333;color:#333;}#mermaid-svg-RyqzYxMQSrBMTH9j .node rect,#mermaid-svg-RyqzYxMQSrBMTH9j .node circle,#mermaid-svg-RyqzYxMQSrBMTH9j .node ellipse,#mermaid-svg-RyqzYxMQSrBMTH9j .node polygon,#mermaid-svg-RyqzYxMQSrBMTH9j .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-RyqzYxMQSrBMTH9j .rough-node .label text,#mermaid-svg-RyqzYxMQSrBMTH9j .node .label text,#mermaid-svg-RyqzYxMQSrBMTH9j .image-shape .label,#mermaid-svg-RyqzYxMQSrBMTH9j .icon-shape .label{text-anchor:middle;}#mermaid-svg-RyqzYxMQSrBMTH9j .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-RyqzYxMQSrBMTH9j .rough-node .label,#mermaid-svg-RyqzYxMQSrBMTH9j .node .label,#mermaid-svg-RyqzYxMQSrBMTH9j .image-shape .label,#mermaid-svg-RyqzYxMQSrBMTH9j .icon-shape .label{text-align:center;}#mermaid-svg-RyqzYxMQSrBMTH9j .node.clickable{cursor:pointer;}#mermaid-svg-RyqzYxMQSrBMTH9j .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-RyqzYxMQSrBMTH9j .arrowheadPath{fill:#333333;}#mermaid-svg-RyqzYxMQSrBMTH9j .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-RyqzYxMQSrBMTH9j .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-RyqzYxMQSrBMTH9j .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RyqzYxMQSrBMTH9j .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-RyqzYxMQSrBMTH9j .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RyqzYxMQSrBMTH9j .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-RyqzYxMQSrBMTH9j .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-RyqzYxMQSrBMTH9j .cluster text{fill:#333;}#mermaid-svg-RyqzYxMQSrBMTH9j .cluster span{color:#333;}#mermaid-svg-RyqzYxMQSrBMTH9j div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-RyqzYxMQSrBMTH9j .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-RyqzYxMQSrBMTH9j rect.text{fill:none;stroke-width:0;}#mermaid-svg-RyqzYxMQSrBMTH9j .icon-shape,#mermaid-svg-RyqzYxMQSrBMTH9j .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RyqzYxMQSrBMTH9j .icon-shape p,#mermaid-svg-RyqzYxMQSrBMTH9j .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-RyqzYxMQSrBMTH9j .icon-shape .label rect,#mermaid-svg-RyqzYxMQSrBMTH9j .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RyqzYxMQSrBMTH9j .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-RyqzYxMQSrBMTH9j .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-RyqzYxMQSrBMTH9j :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 源码 cpu_stress_addon.cc
(+ pid_controller.hpp / cpu_monitor.hpp / worker_task.hpp)
node-gyp configure
node-gyp build
build/Release/cpu_stress.node
runtime.js 通过 require 加载
build_linux.sh
(自动检查/安装 node-gyp、node-addon-api)
- 跨平台采集 :
#if defined(_WIN32)分支区分 Windows(GetSystemTimes)与 Linux(/proc/stat、/sys/class/thermal); - Linux 一键编译 :
build_linux.sh在目标设备上自动补齐依赖并产出.node。
七、插件注册
模块在插件清单中以 stress 类型注册(见 cppBk/package.json):
json
{
"id": "stress-cpu",
"i18nKey": "stress.cpu",
"name": "CPU压力测试",
"icon": "💻",
"path": "plugs/stress/cpu",
"type": "stress",
"version": "1.0.0",
"inputs": ["trigger"],
"outputs": ["success", "error"]
}
它会作为测试工作流中的一个节点 被组态编排,上游 trigger 触发、下游 success/error 衔接(如"结果上报"插件)。
八、设计亮点总结
- 闭环 PID 控载:不是"野蛮跑满",而是把负载精确收敛到目标值,测试可重复、可解释。
- 原生 C++ 执行:脱离 Node 单线程与 GC 抖动,计时与负载更精确,适合压测场景。
- 线程安全桥接 :用
Napi::ThreadSafeFunction把多线程指标安全地回灌 JS,兼顾性能与安全。 - 实时可视化:指标流式广播,前端可实时观察"负载跟随目标 + 温度变化"的动态过程。
- 健壮的兜底:参数校验、插件加载失败兜底、超时强停,避免测试流程卡死。
- 跨平台采集:同一套代码适配 Windows / Linux 的 CPU 与温度读取。
- 可独立运行:除作为插件外,还提供 CLI 变体(见下),便于在目标设备上直接验证。
十、小结
runtime/plugs/stress/cpu 是一个结构清晰、工程化程度高 的压力测试模块:JS 层负责任务编排、配置与可视化,C++ 层负责实时、可控的负载生成与 PID 闭环调节,二者通过 N-API 的 ThreadSafeFunction 安全桥接。它既能在组态 IDE 中作为标准节点被编排,也能以 CLI 形式独立验证。