正常
Step 1:跨平台系统读取 + CPU 两次采样差值
← 手册首页 · 下一步 Step 2 自绘历史曲线 →
Step 1:内存跨平台读取 + CPU 采样器状态机
目标
写一个 system_stats.h/.cpp,提供两个能力:① readMemoryStats() 自由函数——传入无参数,返回一个 MemoryStats 结构体(总量/可用/占用%/是否有效);② CpuSampler 类——构造后 sample() 取基线,隔一会儿再 utilization() 得到 0..100 的占用率。这一步只做数据层(纯系统读取 + 算法),不碰 UI——先在 main 里用 qDebug 打印验证内存和 CPU% 数值合理。
提示
MemoryStats结构体怎么定?qint64 total_bytes / available_bytes(字节)、int used_percent(-1 表无效)、bool valid。Linux 读到的单位是 KiB,记得× 1024转字节。- 内存为什么用
MemAvailable而不是MemFree?MemFree是完全空闲的内存,Linux 把空闲内存拿去做 buffer/cache 后MemFree会很小,用它算占用会把缓存当占用、高估占用率。MemAvailable(3.3+ 内核)含可回收缓存,才是真能用的内存。 /proc/meminfo怎么逐行扫?QFile+QTextStream,readLine()循环;行格式"MemTotal: 16384000 kB"。写个parseKibhelper:indexOf(':')取冒号位置、mid(colon+1).simplified()去空白、indexOf(' ')切出数字、toLongLong × 1024。两项都拿到就break,不必读完整个文件。- CPU 为什么不能单次直读?
/proc/stat第一行是开机以来累计 jiffies 计数,单次读到的是「从开机到现在 CPU 累计忙了多少」,不是「现在多忙」。必须sample()存基线(prev_busy_/prev_total_),隔几百毫秒后utilization()再读一次,用差值Δbusy/Δtotal算这段时间窗的占用。 /proc/stat哪几项算 busy? aggregate(全核合计)行cpu user nice system idle iowait irq softirq steal ...——busy = user + nice + system + irq + softirq + steal + iowait(iowait 算 busy 因核被占着等 IO);total = busy + idle。- 怎么锁定 aggregate 行、别误匹配单核行?
/proc/stat紧跟 aggregate 行后面还有cpu0/cpu1/... 每核一行,前缀都是cpu。首行判定用startsWith("cpu")会连单核行cpu0一起匹配。要收紧成startsWith("cpu ")——aggregate 行cpu后跟空格,单核行cpu0紧跟数字、不匹配。读第一行readLine()后立刻校验前缀。 - 数字字段怎么校验、别静默变 0? aggregate 行 split 出的字符串逐项
toLongLong(&ok)——必备字段(user/nice/system/idle)必须各自带独立ok、逐个校验,任一失败即return false。坑:若用单个共享ok串接 8 个toLongLong、最后不再校验,中间任一字段畸形会被后续字段把ok覆盖成 true,畸形必备字段静默变 0 却判成功(review Qt6 实测复现,CPU% 长期算错且无报错)。可选字段(iowait 起向后)用parts.value(n)越界返回空串、toLongLong失败→0 即可,不必强校验。 - ⚠️ Windows 路径怎么写? 内存走
GlobalMemoryStatusEx(MEMORYSTATUSEX结构,调用前必须dwLength = sizeof(MEMORYSTATUSEX),否则函数失败;dwMemoryLoad直接给 0..100 占用比);CPU 走GetSystemTimes(返回 idle/kernel/user 三个FILETIME,注意 kernel 含 idle,故busy = kernel - idle + user)。整段用#ifdef Q_OS_WIN包住,#include <windows.h>。这条路径未实跑,需 Windows 实机复验。 utilization()要不要滚动基线? 要——算完差值后立刻把本次读数覆盖到prev_(滑动窗口),下一拍算「上一拍到这一拍」,CPU% 跟得上当前负载(否则永远和开机基线比、越来越钝)。- 两种失败语义怎么区分?
sample()失败 →has_prev_=false丢弃旧基线(否则下次新读数减陈旧基线得出荒谬值);utilization()本次readCounters失败 → 保留旧基线只返回 -1(下次成功还能用旧基线)。 - 计数回绕要不要防? 要——理论上单调增,但虚拟化/容器见过跳变。钳制:
d_total <= 0→-1、d_busy < 0→0、d_busy > d_total→total。
检查点
在 main 里写个测试循环:readMemoryStats() 打印 used_percent(应是你当前内存占用、30-80% 合理);构造 CpuSampler,sample() 后 QThread::sleep(1),再 utilization() 打印(应是 0..100 合理值)。内存数值不离谱、CPU% 随负载变化 = 数据层通了。
QFile/QTextStream 不熟?先读 文件与 IO。 QObject 状态机不熟(
CpuSampler继承 QObject 存基线)?先读 QObject 与元对象系统。 进阶想深挖 QtGlobal 平台宏分发?看 QtGlobal 平台宏。
对照答案
MemoryStats结构体(字段 + valid 语义):demo/system_stats.h:22-32CpuSampler接口(sample/utilization/readCounters + 基线成员):demo/system_stats.h:38-60readMemoryStatsLinux 分支(逐行扫 + MemAvailable + 四舍五入):demo/system_stats.cpp:29-62readMemoryStatsWindows 分支(⚠️ 未验):demo/system_stats.cpp:63-75readMemoryStatsfallback(未覆盖平台):demo/system_stats.cpp:76-79parseKibhelper(冒号→数字→KiB 转字节):demo/system_stats.cpp:83-95sample取基线(失败丢旧基线):demo/system_stats.cpp:100-113utilization差值计算(滚动基线 + 钳制防御):demo/system_stats.cpp:115-139readCountersLinux 分支(/proc/stataggregate 解析 +startsWith("cpu ")收紧 + 必备字段逐个校验 + busy/total 拼装):demo/system_stats.cpp:142-183readCountersWindows 分支(⚠️ 未验,busy = kernel - idle + user):demo/system_stats.cpp:184-207readCountersfallback:demo/system_stats.cpp:208-212- 平台
#ifdef分发(include 隔离):demo/system_stats.cpp:13-19
下一步:自绘 CPU 历史曲线(滚动窗口 + QPainter 折线 + 面积填充)——Step 2 自绘历史曲线。