CPU 压力测试模块 · 架构设计与实现

CPU 压力测试模块 · 架构设计与实现

关键词:Node.js 插件运行时 · C++ 原生插件(node-addon-api)· PID 闭环负载控制 · 实时指标上报


一、背景与定位

在嵌入式/边缘设备的自动化测试中,"CPU 压力测试"用于验证处理器在持续高负载下的计算能力、稳定性与热表现。它不只是简单地把 CPU 跑满,而更关注三点:

  1. 负载可控------能稳定地把 CPU 压到指定的目标利用率(如 95%),而不是忽高忽低;
  2. 过程可观测------实时采集 CPU 利用率、控制量、温度,并推送到前端做曲线展示;
  3. 结果可判定------依据温度上限等阈值自动给出 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
  • 编排执行(runrunStressTest :创建 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::threadrun()
  • 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.jscomplete 后做统计与判定,把结构化 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 衔接(如"结果上报"插件)。


八、设计亮点总结

  1. 闭环 PID 控载:不是"野蛮跑满",而是把负载精确收敛到目标值,测试可重复、可解释。
  2. 原生 C++ 执行:脱离 Node 单线程与 GC 抖动,计时与负载更精确,适合压测场景。
  3. 线程安全桥接 :用 Napi::ThreadSafeFunction 把多线程指标安全地回灌 JS,兼顾性能与安全。
  4. 实时可视化:指标流式广播,前端可实时观察"负载跟随目标 + 温度变化"的动态过程。
  5. 健壮的兜底:参数校验、插件加载失败兜底、超时强停,避免测试流程卡死。
  6. 跨平台采集:同一套代码适配 Windows / Linux 的 CPU 与温度读取。
  7. 可独立运行:除作为插件外,还提供 CLI 变体(见下),便于在目标设备上直接验证。

十、小结

runtime/plugs/stress/cpu 是一个结构清晰、工程化程度高 的压力测试模块:JS 层负责任务编排、配置与可视化,C++ 层负责实时、可控的负载生成与 PID 闭环调节,二者通过 N-API 的 ThreadSafeFunction 安全桥接。它既能在组态 IDE 中作为标准节点被编排,也能以 CLI 形式独立验证。

相关推荐
momo4 小时前
智析AgentData-Analysis-Agent 0→1 架构、搭建与联调实战报告
架构
是个西兰花5 小时前
Linux:深入解析Linux线程原理与实现
linux·运维·c++·线程·互斥锁
XUHUOJUN5 小时前
Azure Stack Hub 套餐、计划与订阅模型深度解析(上篇)
架构·azure stack
运维大师5 小时前
【Linux运维极简教程】07-磁盘管理与LVM
linux·运维·服务器
乖巧的妹子5 小时前
刷题总结知识
linux·服务器·windows
Sam_Deep_Thinking5 小时前
DDD里的领域服务,到底什么时候该用?
程序员·架构·系统架构·ddd
JouYY6 小时前
大模型底层学习(一)-Transformer 架构总览
架构·llm·agent
拳里剑气6 小时前
Linux:基础IO
linux·运维·服务器·io
小小龙学IT6 小时前
Day 2-3:Linux 文件与目录操作
linux
小保CPP6 小时前
OCR C++ Tesseract基础用法
c++·人工智能·ocr·模式识别·光学字符识别