Qt C++跨平台系统资源监控工具开发实战:CPU/内存/磁盘数据获取与可视化

在实际开发中,无论是进行系统性能分析、资源瓶颈排查,还是构建一个简单的桌面监控小工具,掌握如何获取系统核心资源(如CPU、内存、磁盘)的使用情况都是一项基础且实用的技能。对于使用Qt C++进行桌面应用开发的同学来说,这不仅是课程设计或毕业大作业中一个很好的选题方向,也是深入理解操作系统底层API和Qt跨平台特性的绝佳实践。

本文将带你从零开始,使用Qt C++框架,构建一个跨平台的系统资源监控工具。我们将重点解决几个核心问题:如何在不同操作系统(Windows/Linux)上获取CPU使用率、内存占用和磁盘空间信息;如何将这些数据通过Qt的图表组件(如QChart)进行可视化;以及如何将整个项目打包成独立的可执行文件。整个过程会涵盖环境搭建、核心代码实现、界面设计、数据可视化以及项目打包,最终你将得到一个功能完整、界面直观的监控工具,可以直接用于课程设计或作为个人技术积累。

1. 理解系统资源监控的核心概念与跨平台挑战

在动手编码之前,我们需要明确要监控哪些资源,以及在不同操作系统上获取这些数据的差异。一个基本的系统监控工具通常关注以下核心指标:

  • CPU使用率 :反映处理器在单位时间内的繁忙程度。计算方式通常是统计CPU在用户态、内核态以及空闲状态下的时间差。
  • 内存使用情况 :包括物理内存总量、已使用量、空闲量,以及交换分区(Swap)的使用情况。
  • 磁盘空间 :显示各个磁盘分区或挂载点的总容量、已用空间和可用空间。

这些信息在Windows和Linux系统上,需要通过不同的系统API或读取不同的系统文件来获取。这是开发跨平台监控工具的第一个挑战。Qt框架本身提供了一些跨平台的抽象(如 QStorageInfo 用于磁盘信息),但对于CPU和内存的详细数据,我们仍需编写平台相关的代码,并通过条件编译( #ifdef )来隔离。

第二个挑战是数据的动态更新与可视化。我们需要一个定时器( QTimer )来周期性地(例如每秒一次)采集数据,并实时更新到用户界面上。Qt的图表模块 QtCharts 非常适合用来绘制动态变化的折线图或柱状图,直观展示CPU和内存的使用趋势。

2. 环境准备与项目创建

2.1 开发环境搭建

首先,确保你的开发环境已就绪。你需要安装以下组件:

  1. Qt SDK :从Qt官网或国内镜像站下载Qt Online Installer或离线安装包。建议选择长期支持版本(如Qt 5.15.x 或 Qt 6.2+)。安装时,务必勾选与你编译器匹配的Qt套件(如 MSVC 2019 64-bit )以及 Qt Charts 模块。
  2. C++编译器
    • Windows :安装Visual Studio 2019或2022,并确保安装时包含了“使用C++的桌面开发”工作负载。这会自动安装所需的Microsoft Visual C++ Redistributable。如果遇到“microsoft visual c++ 14.0 or greater is required”错误,请单独下载并安装对应版本的VC++运行库。
    • Linux :通常系统自带g++,使用包管理器安装即可(如 sudo apt install g++ )。
  3. 集成开发环境(IDE) :推荐使用Qt Creator,它与Qt框架集成度最高。也可以使用VSCode配合CMake和Qt插件,但这需要额外的配置。

2.2 创建Qt Widgets项目

打开Qt Creator,按照以下步骤创建项目:

  1. 选择 文件 -> 新建文件或项目
  2. 选择 Application -> Qt Widgets Application ,点击 选择
  3. 输入项目名称(如 SystemMonitor )和存储路径。
  4. Kit Selection 页面,选择你安装的Qt套件(如 Desktop Qt 5.15.2 MSVC2019 64bit )。
  5. 类信息 页面,基类选择 QMainWindow ,类名可保持为 MainWindow 。这将自动生成一个带菜单栏、工具栏和状态栏的主窗口。
  6. 完成创建。

2.3 配置项目文件(.pro)

创建项目后,我们需要修改 .pro 文件来添加必要的模块。由于我们要使用图表,必须链接 QtCharts 模块。

打开项目根目录下的 .pro 文件(如 SystemMonitor.pro ),在末尾添加以下行:

# 添加charts模块
QT += charts

# 如果是Windows,可能需要链接额外的库(如Pdh.lib用于性能数据)
win32 {
    LIBS += -lpdh
}

# 如果是Linux,可能需要链接额外的库(如读取/proc文件系统通常不需要额外链接)
unix:!macx {
    # 对于Linux,通常不需要额外链接,但确保有基本库
    LIBS += -lrt
}

保存 .pro 文件后,Qt Creator会提示你重新运行qmake来应用更改。点击 构建 -> 运行qmake

3. 设计监控工具的用户界面

我们将使用Qt Designer来设计主界面。双击项目文件树中的 mainwindow.ui 文件,打开设计器。

我们的界面可以包含以下区域:

  • 顶部 :标题和全局信息(如当前时间、系统运行时间)。
  • 中部左侧 :使用 QChartView (需要先提升为 QChartView 控件)来显示CPU和内存使用率的实时折线图。
  • 中部右侧 :使用多个 QLabel QProgressBar 来直观显示CPU核心、内存、交换空间的实时使用百分比和具体数值。
  • 底部 :使用 QTableWidget QTreeWidget 来显示各个磁盘分区的详细信息(盘符、总大小、已用、可用、使用率)。
  • 状态栏 :用于显示数据更新时间或临时信息。

设计步骤:

  1. 从左侧 Widget Box 拖拽 Vertical Layout Horizontal Layout Group Box Label Progress Bar Table Widget 等控件到中心区域。
  2. 合理使用布局管理器(Layouts)来确保窗口缩放时控件能自适应。
  3. 为需要动态更新的控件设置好 objectName ,以便在代码中引用。例如:
    • CPU进度条: cpuProgressBar
    • 内存进度条: memProgressBar
    • CPU数值标签: labelCpuUsage
    • 图表视图: chartView (需要先放置一个 QWidget ,然后右键 -> 提升为... ,在“提升的类名称”中填入 QChartView ,头文件填入 #include <QtCharts/QChartView> ,点击添加并提升)。

一个简单的界面布局示例如下(通过布局管理器组合):

[标题标签]
---------------------------------
[图表区域 (QChartView)] | [CPU/内存信息面板 (GroupBox)]
                         | - CPU: [ProgressBar] [Label]
                         | - 内存: [ProgressBar] [Label]
                         | - 交换: [ProgressBar] [Label]
---------------------------------
[磁盘信息表格 (QTableWidget)]
---------------------------------
[状态栏]

4. 实现跨平台的系统资源获取逻辑

这是工具的核心。我们将创建单独的类来封装资源获取功能,以实现更好的代码组织。在项目中新建一个C++类,命名为 SystemInfo

4.1 systeminfo.h 头文件

#ifndef SYSTEMINFO_H
#define SYSTEMINFO_H

#include <QObject>
#include <QVector>
#include <QStorageInfo>

class SystemInfo : public QObject
{
    Q_OBJECT
public:
    explicit SystemInfo(QObject *parent = nullptr);
    ~SystemInfo();

    // CPU相关
    double getCpuUsage(); // 获取总体CPU使用率(百分比)
    int getCpuCoreCount(); // 获取CPU核心数
    QVector<double> getCpuUsagePerCore(); // 获取每个核心的使用率(可选)

    // 内存相关
    struct MemoryInfo {
        quint64 totalPhysical;   // 物理内存总量 (Bytes)
        quint64 usedPhysical;    // 已用物理内存 (Bytes)
        quint64 freePhysical;    // 空闲物理内存 (Bytes)
        quint64 totalSwap;       // 交换分区总量 (Bytes)
        quint64 usedSwap;        // 已用交换分区 (Bytes)
        quint64 freeSwap;        // 空闲交换分区 (Bytes)
    };
    MemoryInfo getMemoryInfo();

    // 磁盘相关
    QList<QStorageInfo> getDiskInfo(); // 获取磁盘信息列表

private:
    // 平台相关的私有数据和方法
#ifdef Q_OS_WIN
    // Windows平台私有成员
    void* m_pdhQuery;
    void* m_pdhTotalCpuCounter;
    quint64 m_lastCpuTotalTime;
    quint64 m_lastCpuIdleTime;
    bool initWindowsCpuCounter();
    double calculateCpuUsageWindows();
#endif

#ifdef Q_OS_LINUX
    // Linux平台私有成员
    QVector<quint64> m_lastCpuTotal;
    QVector<quint64> m_lastCpuIdle;
    bool readProcStat(quint64 &total, quint64 &idle);
#endif

    MemoryInfo m_lastMemInfo;
};

#endif // SYSTEMINFO_H

4.2 systeminfo.cpp 源文件(核心实现)

这里展示关键的平台相关实现。由于篇幅限制,仅展示核心思路和部分代码。

#include "systeminfo.h"
#include <QDebug>
#include <QDir>

#ifdef Q_OS_WIN
#include <windows.h>
#include <pdh.h>
#pragma comment(lib, "pdh.lib")
#endif

#ifdef Q_OS_LINUX
#include <sys/sysinfo.h>
#include <fstream>
#endif

SystemInfo::SystemInfo(QObject *parent) : QObject(parent)
{
    // 初始化平台相关资源
#ifdef Q_OS_WIN
    m_pdhQuery = nullptr;
    m_pdhTotalCpuCounter = nullptr;
    m_lastCpuTotalTime = 0;
    m_lastCpuIdleTime = 0;
    initWindowsCpuCounter();
#endif

#ifdef Q_OS_LINUX
    // 初始化CPU时间记录向量
    int coreCount = getCpuCoreCount() + 1; // +1 for aggregate “cpu”
    m_lastCpuTotal.resize(coreCount);
    m_lastCpuIdle.resize(coreCount);
    // 读取一次初始值
    for(int i=0; i<coreCount; ++i){
        // 首次读取,为后续计算差值做准备
        quint64 total, idle;
        if(readProcStat(total, idle, i)){
            m_lastCpuTotal[i] = total;
            m_lastCpuIdle[i] = idle;
        }
    }
#endif
}

SystemInfo::~SystemInfo()
{
#ifdef Q_OS_WIN
    if (m_pdhQuery) {
        PdhCloseQuery((PDH_HQUERY)m_pdhQuery);
    }
#endif
}

// 获取CPU核心数(跨平台)
int SystemInfo::getCpuCoreCount()
{
#ifdef Q_OS_WIN
    SYSTEM_INFO sysInfo;
    GetSystemInfo(&sysInfo);
    return sysInfo.dwNumberOfProcessors;
#endif

#ifdef Q_OS_LINUX
    // 读取 /proc/cpuinfo 中 processor 出现的次数
    QFile file("/proc/cpuinfo");
    if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        QTextStream in(&file);
        int count = 0;
        while (!in.atEnd()) {
            QString line = in.readLine();
            if (line.startsWith("processor")) {
                count++;
            }
        }
        file.close();
        return count > 0 ? count : 1;
    }
    return 1;
#endif
    return 1; // 默认值
}

// 获取总体CPU使用率(跨平台接口)
double SystemInfo::getCpuUsage()
{
#ifdef Q_OS_WIN
    return calculateCpuUsageWindows();
#endif

#ifdef Q_OS_LINUX
    quint64 total, idle;
    if (!readProcStat(total, idle, 0)) { // 索引0代表总的“cpu”行
        return 0.0;
    }
    quint64 totalDiff = total - m_lastCpuTotal[0];
    quint64 idleDiff = idle - m_lastCpuIdle[0];
    m_lastCpuTotal[0] = total;
    m_lastCpuIdle[0] = idle;

    if (totalDiff == 0) {
        return 0.0;
    }
    double usage = 100.0 * (1.0 - ((double)idleDiff / totalDiff));
    return qBound(0.0, usage, 100.0); // 限制在0-100之间
#endif
    return 0.0;
}

// 获取内存信息(跨平台)
SystemInfo::MemoryInfo SystemInfo::getMemoryInfo()
{
    MemoryInfo info = {0};
#ifdef Q_OS_WIN
    MEMORYSTATUSEX memStat;
    memStat.dwLength = sizeof(memStat);
    if (GlobalMemoryStatusEx(&memStat)) {
        info.totalPhysical = memStat.ullTotalPhys;
        info.freePhysical = memStat.ullAvailPhys;
        info.usedPhysical = info.totalPhysical - info.freePhysical;
        info.totalSwap = memStat.ullTotalPageFile;
        info.freeSwap = memStat.ullAvailPageFile;
        info.usedSwap = info.totalSwap - info.freeSwap;
    }
#endif

#ifdef Q_OS_LINUX
    struct sysinfo si;
    if (sysinfo(&si) == 0) {
        // 注意:sysinfo 返回的单位是 “内存页数”,需要乘以页大小(通常为4KB)
        long pageSize = sysconf(_SC_PAGESIZE);
        info.totalPhysical = si.totalram * si.mem_unit;
        info.freePhysical = si.freeram * si.mem_unit;
        info.usedPhysical = info.totalPhysical - info.freePhysical;
        info.totalSwap = si.totalswap * si.mem_unit;
        info.freeSwap = si.freeswap * si.mem_unit;
        info.usedSwap = info.totalSwap - info.freeSwap;
    }
#endif
    m_lastMemInfo = info; // 可选:缓存上一次结果
    return info;
}

// 获取磁盘信息(使用Qt跨平台类)
QList<QStorageInfo> SystemInfo::getDiskInfo()
{
    return QStorageInfo::mountedVolumes();
}

// ---------- Windows 平台特定实现 ----------
#ifdef Q_OS_WIN
bool SystemInfo::initWindowsCpuCounter()
{
    PDH_STATUS status;
    status = PdhOpenQuery(NULL, 0, (PDH_HQUERY*)&m_pdhQuery);
    if (status != ERROR_SUCCESS) return false;

    // 添加“\Processor(_Total)\% Processor Time”计数器
    status = PdhAddCounter((PDH_HQUERY)m_pdhQuery,
                           TEXT("\\Processor(_Total)\\% Processor Time"),
                           0,
                           (PDH_HCOUNTER*)&m_pdhTotalCpuCounter);
    return (status == ERROR_SUCCESS);
}

double SystemInfo::calculateCpuUsageWindows()
{
    if (!m_pdhQuery || !m_pdhTotalCpuCounter) return 0.0;

    PDH_STATUS status;
    status = PdhCollectQueryData((PDH_HQUERY)m_pdhQuery);
    if (status != ERROR_SUCCESS) return 0.0;

    // 等待一小段时间(如500ms)再收集一次数据,计算瞬时使用率
    // 注意:更简单的做法是直接读取PDH_FMT_DOUBLE格式的瞬时值,但需要系统支持。
    // 这里采用简易的两次采样法。
    Sleep(500); // 等待500毫秒
    status = PdhCollectQueryData((PDH_HQUERY)m_pdhQuery);
    if (status != ERROR_SUCCESS) return 0.0;

    PDH_FMT_COUNTERVALUE counterVal;
    status = PdhGetFormattedCounterValue((PDH_HCOUNTER)m_pdhTotalCpuCounter,
                                         PDH_FMT_DOUBLE,
                                         NULL,
                                         &counterVal);
    if (status == ERROR_SUCCESS && counterVal.CStatus == PDH_CSTATUS_VALID_DATA) {
        return counterVal.doubleValue;
    }
    return 0.0;
}
#endif

// ---------- Linux 平台特定实现 ----------
#ifdef Q_OS_LINUX
bool SystemInfo::readProcStat(quint64 &total, quint64 &idle, int cpuIndex)
{
    QFile file("/proc/stat");
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        return false;
    }
    QTextStream in(&file);
    QString line;
    int currentIndex = -1; // “cpu”行索引为-1, “cpu0”为0,以此类推
    while (!in.atEnd()) {
        line = in.readLine();
        if (line.startsWith("cpu")) {
            currentIndex++;
            if (currentIndex == cpuIndex) {
                QStringList parts = line.split(' ', QString::SkipEmptyParts);
                if (parts.size() >= 5) { // 至少需要 user, nice, system, idle, iowait
                    // 总时间 = user + nice + system + idle + iowait + irq + softirq + steal + guest + guest_nice
                    // 这里简化计算,取前7个值(到steal为止)之和为total,取第4个(idle)和第5个(iowait)之和为idle
                    quint64 user = parts[1].toULongLong();
                    quint64 nice = parts[2].toULongLong();
                    quint64 system = parts[3].toULongLong();
                    quint64 idleVal = parts[4].toULongLong();
                    quint64 iowait = parts.size() > 5 ? parts[5].toULongLong() : 0;
                    // 可选:加上更多状态
                    quint64 irq = parts.size() > 6 ? parts[6].toULongLong() : 0;
                    quint64 softirq = parts.size() > 7 ? parts[7].toULongLong() : 0;
                    quint64 steal = parts.size() > 8 ? parts[8].toULongLong() : 0;

                    total = user + nice + system + idleVal + iowait + irq + softirq + steal;
                    idle = idleVal + iowait;
                    file.close();
                    return true;
                }
                break;
            }
        } else if (line.startsWith("intr")) {
            // 已经过了cpu行,没找到对应的cpuIndex
            break;
        }
    }
    file.close();
    return false;
}
#endif

5. 集成数据采集、图表展示与主窗口逻辑

5.1 在主窗口中集成SystemInfo和图表

mainwindow.h 中,引入必要的头文件并声明成员变量。

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QTimer>
#include <QtCharts/QChartView>
#include <QtCharts/QLineSeries>
#include <QtCharts/QValueAxis>
QT_CHARTS_USE_NAMESPACE // 使用Qt Charts命名空间

namespace Ui {
class MainWindow;
}

class SystemInfo; // 前向声明

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void updateSystemInfo(); // 定时更新槽函数

private:
    Ui::MainWindow *ui;
    SystemInfo *m_systemInfo;
    QTimer *m_updateTimer;

    // 图表相关
    QChart *m_cpuChart;
    QLineSeries *m_cpuSeries;
    QValueAxis *m_axisX;
    QValueAxis *m_axisY;
    int m_timeIndex; // X轴时间索引

    // 数据序列(用于图表历史数据)
    QVector<double> m_cpuHistory;
    const int HISTORY_LENGTH = 60; // 记录60个数据点(即60秒)
    void initChart();
    void updateChart(double cpuUsage);
};

#endif // MAINWINDOW_H

mainwindow.cpp 中,实现初始化、定时更新和图表刷新。

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "systeminfo.h"
#include <QDebug>
#include <QStorageInfo>
#include <QTableWidgetItem>
#include <QHeaderView>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow),
    m_systemInfo(new SystemInfo(this)),
    m_updateTimer(new QTimer(this)),
    m_timeIndex(0)
{
    ui->setupUi(this);
    setWindowTitle(tr("Qt 系统资源监控工具"));

    // 初始化图表
    initChart();

    // 初始化磁盘表格
    ui->diskTableWidget->setColumnCount(5);
    QStringList headers;
    headers << tr("盘符/挂载点") << tr("总容量") << tr("已用") << tr("可用") << tr("使用率");
    ui->diskTableWidget->setHorizontalHeaderLabels(headers);
    ui->diskTableWidget->horizontalHeader()->setStretchLastSection(true);

    // 连接定时器
    connect(m_updateTimer, &QTimer::timeout, this, &MainWindow::updateSystemInfo);
    m_updateTimer->start(1000); // 每秒更新一次

    // 立即更新一次
    updateSystemInfo();
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::initChart()
{
    m_cpuChart = new QChart();
    m_cpuSeries = new QLineSeries();
    m_cpuSeries->setName(tr("CPU使用率 (%)"));

    m_cpuChart->addSeries(m_cpuSeries);
    m_cpuChart->setTitle(tr("CPU使用率实时监控"));
    m_cpuChart->setAnimationOptions(QChart::NoAnimation); // 禁用动画以获得更好性能

    m_axisX = new QValueAxis;
    m_axisX->setRange(0, HISTORY_LENGTH - 1);
    m_axisX->setLabelFormat("%d");
    m_axisX->setTitleText(tr("时间 (秒前)"));
    // 隐藏X轴具体数值标签,因为我们用“秒前”表示
    m_axisX->setTickCount(6); // 显示6个刻度
    m_cpuChart->addAxis(m_axisX, Qt::AlignBottom);
    m_cpuSeries->attachAxis(m_axisX);

    m_axisY = new QValueAxis;
    m_axisY->setRange(0, 100);
    m_axisY->setTitleText(tr("使用率 (%)"));
    m_cpuChart->addAxis(m_axisY, Qt::AlignLeft);
    m_cpuSeries->attachAxis(m_axisY);

    // 将图表设置到UI中的QChartView
    ui->chartView->setChart(m_cpuChart);
    ui->chartView->setRenderHint(QPainter::Antialiasing);

    // 初始化历史数据
    m_cpuHistory.fill(0.0, HISTORY_LENGTH);
}

void MainWindow::updateSystemInfo()
{
    // 1. 更新CPU信息
    double cpuUsage = m_systemInfo->getCpuUsage();
    ui->cpuProgressBar->setValue(static_cast<int>(cpuUsage));
    ui->labelCpuUsage->setText(QString("%1%").arg(cpuUsage, 0, 'f', 1));

    // 2. 更新内存信息
    SystemInfo::MemoryInfo memInfo = m_systemInfo->getMemoryInfo();
    if (memInfo.totalPhysical > 0) {
        double memUsagePercent = 100.0 * memInfo.usedPhysical / memInfo.totalPhysical;
        ui->memProgressBar->setValue(static_cast<int>(memUsagePercent));
        ui->labelMemUsage->setText(QString("%1% (%2 / %3)")
                                      .arg(memUsagePercent, 0, 'f', 1)
                                      .arg(formatBytes(memInfo.usedPhysical))
                                      .arg(formatBytes(memInfo.totalPhysical)));
    }
    // 更新交换空间(如果有)
    if (memInfo.totalSwap > 0) {
        double swapUsagePercent = 100.0 * memInfo.usedSwap / memInfo.totalSwap;
        ui->swapProgressBar->setValue(static_cast<int>(swapUsagePercent));
        ui->labelSwapUsage->setText(QString("%1%").arg(swapUsagePercent, 0, 'f', 1));
    } else {
        ui->swapProgressBar->setValue(0);
        ui->labelSwapUsage->setText(tr("N/A"));
    }

    // 3. 更新磁盘信息
    QList<QStorageInfo> disks = m_systemInfo->getDiskInfo();
    ui->diskTableWidget->setRowCount(disks.size());
    for (int i = 0; i < disks.size(); ++i) {
        const QStorageInfo &disk = disks.at(i);
        qint64 total = disk.bytesTotal();
        qint64 free = disk.bytesFree();
        qint64 used = total - free;
        double usagePercent = total > 0 ? (100.0 * used / total) : 0.0;

        ui->diskTableWidget->setItem(i, 0, new QTableWidgetItem(disk.rootPath()));
        ui->diskTableWidget->setItem(i, 1, new QTableWidgetItem(formatBytes(total)));
        ui->diskTableWidget->setItem(i, 2, new QTableWidgetItem(formatBytes(used)));
        ui->diskTableWidget->setItem(i, 3, new QTableWidgetItem(formatBytes(free)));
        ui->diskTableWidget->setItem(i, 4, new QTableWidgetItem(QString("%1%").arg(usagePercent, 0, 'f', 1)));
    }

    // 4. 更新图表
    updateChart(cpuUsage);

    // 5. 更新状态栏
    ui->statusBar->showMessage(tr("最后更新: %1").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
}

void MainWindow::updateChart(double cpuUsage)
{
    // 将新数据点添加到历史记录
    m_cpuHistory.append(cpuUsage);
    if (m_cpuHistory.size() > HISTORY_LENGTH) {
        m_cpuHistory.removeFirst();
    }

    // 清空系列并重新添加所有点
    m_cpuSeries->clear();
    for (int i = 0; i < m_cpuHistory.size(); ++i) {
        m_cpuSeries->append(i, m_cpuHistory.at(i));
    }

    // 更新X轴范围,使其看起来是滚动的
    if (m_cpuHistory.size() >= HISTORY_LENGTH) {
        m_axisX->setRange(0, HISTORY_LENGTH - 1);
    } else {
        m_axisX->setRange(0, m_cpuHistory.size() - 1);
    }
}

// 辅助函数:格式化字节数为易读单位 (KB, MB, GB, TB)
QString MainWindow::formatBytes(qint64 bytes)
{
    const qint64 kb = 1024;
    const qint64 mb = kb * 1024;
    const qint64 gb = mb * 1024;
    const qint64 tb = gb * 1024;

    if (bytes >= tb) {
        return QString("%1 TB").arg(bytes / (double)tb, 0, 'f', 2);
    } else if (bytes >= gb) {
        return QString("%1 GB").arg(bytes / (double)gb, 0, 'f', 2);
    } else if (bytes >= mb) {
        return QString("%1 MB").arg(bytes / (double)mb, 0, 'f', 2);
    } else if (bytes >= kb) {
        return QString("%1 KB").arg(bytes / (double)kb, 0, 'f', 2);
    } else {
        return QString("%1 B").arg(bytes);
    }
}

5.2 编译与运行

完成以上代码后,点击Qt Creator左下角的绿色运行按钮(或按 Ctrl+R )进行编译和运行。如果一切配置正确,你将看到一个窗口,其中包含动态更新的CPU/内存进度条、磁盘信息表格以及一个实时绘制CPU使用率曲线的图表。

6. 常见问题排查与解决方案

在开发过程中,你可能会遇到以下典型问题:

问题现象 可能原因 检查与解决方案
编译错误: error: ‘QChart’ does not name a type 未在 .pro 文件中添加 QT += charts ,或未包含正确的头文件。 1. 检查 .pro 文件是否有 QT += charts
2. 在头文件中使用 #include <QtCharts/QChart> QT_CHARTS_USE_NAMESPACE
编译错误: undefined reference to ... (链接错误) 未链接必要的系统库(如Windows下的 pdh.lib )。 .pro 文件中使用 win32 { LIBS += -lpdh } 添加库链接。
程序运行后CPU/内存数据显示为0或不变 1. 平台相关代码未正确编译(条件编译错误)。
2. 系统API调用失败或权限不足。
3. 定时器未启动或槽函数未连接。
1. 使用 #ifdef Q_OS_WIN #ifdef Q_OS_LINUX 确保代码在正确平台编译。
2. 在 getCpuUsage 等函数开始处添加 qDebug() 输出,查看返回值。
3. 检查 m_updateTimer->start(1000) 是否执行,以及 connect 是否成功。
Linux下读取 /proc/stat 失败 文件路径错误或权限问题。 确保程序有读取 /proc/stat 的权限(通常有)。使用 QFile errorString() 打印具体错误。
Windows下PDH计数器初始化失败 PDH库未正确加载或计数器路径错误。 检查 PdhOpenQuery PdhAddCounter 的返回值。在Windows 10/11上,计数器路径通常是正确的。
图表不显示或显示异常 1. QChartView 未正确提升。
2. 图表系列未关联到坐标轴。
3. 坐标轴范围设置不当。
1. 在Qt Designer中确认 QChartView 控件已正确提升。
2. 在代码中确认 series->attachAxis(axis) 被调用。
3. 检查 axisX->setRange() 设置的值是否合理。
程序在Windows打包后无法在其他电脑运行 缺少Qt运行时库或VC++运行库。 使用 windeployqt 工具自动打包依赖,并确保目标电脑安装了对应版本的 Visual C++ Redistributable

注意 :跨平台代码的调试是关键。务必在目标平台上进行测试。在Linux下,可以使用 strace gdb 跟踪系统调用;在Windows下,可以使用Process Monitor或调试器查看API调用情况。

7. 项目打包与部署

7.1 Windows平台打包

  1. 使用Release模式编译 :在Qt Creator中,将构建套件切换到 Release 模式并重新编译。
  2. 找到可执行文件 :在项目构建目录的 release 文件夹下找到 .exe 文件。
  3. 使用windeployqt :打开Qt命令行(开始菜单 -> Qt -> 你的版本 -> Qt命令行),导航到 .exe 文件所在目录,执行:
    windeployqt YourAppName.exe
    
    此命令会自动将所需的Qt DLL、插件等复制到当前目录。
  4. 检查VC++运行库 :确保目标计算机安装了对应版本的Microsoft Visual C++ Redistributable。可以将必要的 vc_redist.x64.exe (或x86)与你的程序一起分发。
  5. 测试 :将整个文件夹复制到一台没有Qt开发环境的Windows电脑上,运行 .exe 文件,检查是否正常。

7.2 Linux平台打包

Linux下打包相对复杂,常见方法有:

  1. 分发AppImage :使用 linuxdeployqt 工具生成一个独立的AppImage文件。
  2. 制作DEB/RPM包 :为你的程序创建 .desktop 文件、图标,并编写 control 文件,使用 dpkg-deb rpmbuild 打包。
  3. 静态编译 :使用静态链接的Qt库进行编译,生成不依赖系统Qt的动态链接库。但这需要从源码编译静态Qt,过程较复杂。

对于课程设计,最简单的方式是提供源代码和编译说明( README.md ),要求用户自行安装Qt开发环境后编译。

8. 扩展方向与最佳实践

完成基础监控后,你可以考虑以下扩展,让项目更加完善:

  1. 监控更多指标 :网络流量(发送/接收)、GPU使用率、单个进程的资源消耗、电池状态(笔记本)、传感器温度等。
  2. 优化图表性能 :当历史数据点很多时, QLineSeries append 操作可能变慢。可以考虑使用 QLineSeries::replace() 来更新数据,或使用 QScatterSeries 只显示当前点。
  3. 添加配置功能 :允许用户自定义刷新频率、选择要监控的磁盘、设置图表颜色和样式、选择是否开机启动等。可以使用 QSettings 来保存配置。
  4. 实现日志记录 :将资源使用情况定期记录到文件中,便于后续分析。可以使用 QFile QTextStream ,注意避免频繁写文件影响性能。
  5. 设置资源告警 :当CPU或内存使用率超过某个阈值时,通过系统通知(如 QSystemTrayIcon )或改变界面颜色来提醒用户。
  6. 代码结构优化 :将数据获取、数据处理、UI更新进一步解耦,使用信号槽机制或 QThread 将耗时的数据采集放在子线程中,避免阻塞UI主线程导致界面卡顿。

最佳实践建议

  • 错误处理 :所有文件操作(如打开 /proc/stat )和系统API调用都应检查返回值,并进行适当的错误处理或提供默认值。
  • 资源释放 :在类的析构函数中正确释放平台相关的资源句柄(如Windows的PDH查询句柄)。
  • 性能考量 :数据采集频率不宜过高(通常1-2秒一次足够),避免不必要的系统开销。
  • 用户体验 :对于长时间运行的监控工具,添加一个系统托盘图标( QSystemTrayIcon )是很好的做法,允许用户最小化到托盘。
  • 版本管理 :使用Git等工具管理你的源代码,特别是当你尝试不同的跨平台实现方案时。
内容概要:本文聚焦于“通过ADMM进行TV-L1去噪”的研究,系统阐述了基于交替方向乘子法(ADMM)实现总变差(Total Variation, TV)正则化L1范数稀疏约束相结合的图像去噪模型。文中详细解析了TV-L1模型的数学构建及其在抑制椒盐噪声、保持图像边缘结构方面的优越性,重点介绍了ADMM算法如何将复杂的凸优化问题分解为多个可高效求解的子问题,提升收敛效率数值稳定性。配套提供的Matlab代码实现了完整的去噪流程,便于读者复现算法并开展实验验证。此外,文档还整合了电力系统、信号处理、路径规划、机器学习等多个领域的科研资源,凸显其作为综合性学术资料包的价值。; 适合人群:具备良好数学基础Matlab编程能力,从事图像处理、信号去噪、优化算法或相关领域研究的研究生、科研人员及工程技术人员。; 使用场景及目标:① 深入理解并复现基于ADMM的TV-L1图像去噪算法;② 掌握总变差正则化L1范数在稀疏噪声去除中的理论应用;③ 利用所提供的Matlab代码进行算法调试、性能评估二次开发;④ 借助附带的多领域科研案例拓展研究思路,推动跨学科技术创新。; 阅读建议:建议读者结合理论推导Matlab代码实践,逐步跟踪ADMM的迭代过程,观察其收敛行为去噪效果,同时可参考文档末尾提供的丰富科研资源链接,拓展技术视野研究深度。
内容概要:本文档为一篇博士论文的复现资料,聚焦于计及锁相环频率耦合效应的光伏逆变器序阻抗解析建模扫频稳定评估研究。基于Matlab编程Simulink仿真平台,构建了包含锁相环动态特性的光伏并网逆变器正负序阻抗模型,深入剖析其在弱电网条件下因锁相环引发的频率耦合机制,并采用小信号扫频法进行阻抗特性辨识系统稳定性分析。文档系统呈现了理论建模的数学推导过程、仿真模型搭建细节及核心代码实现,旨在完整复现并验证原论文的关键研究成果,帮助使用者掌握新能源发电系统接入弱电网时的小信号稳定性分析方法技术路径。; 适合人群:具备电力电子、自动控制及电力系统稳定性相关基础知识,熟练掌握Matlab/Simulink仿真工具,从事新能源并网技术、微电网稳定性分析、逆变器控制策略研究的研究生、科研人员及工程技术人员。; 使用场景及目标:① 深入理解光伏逆变器序阻抗建模理论,特别是锁相环导致的正负序频率交叉耦合现象;② 掌握基于扫频法的阻抗测量奈奎斯特稳定性判据应用,评估并网系统的稳定裕度;③ 复现高水平学术论文的核心成果,为自身科研项目提供可靠的理论依据、成熟的代码框架仿真技术参考。; 阅读建议:学习者应结合所提供的Matlab代码Simulink仿真模型,循序渐进地理解阻抗建模的理论推导实现逻辑,重点在于动手调试扫频模块以获取精确的阻抗频率响应曲线,并通过调整控制器参数、电网强度等变量,观察其对系统阻抗特性稳定性的影响,从而深化对理论知识的实践应用创新能力。
内容概要:本文围绕跟网型T型三电平逆变器的低电压穿越(LVRT)控制策略展开,基于Simulink搭建了完整的系统仿真模型,重点实现了改进电流环控制中点电位平衡控制两大核心技术。通过引入优化的控制算法,显著提升了逆变器在电网电压跌落等故障工况下的运行稳定性动态响应性能,有效抑制了传统T型三电平结构中存在的中点电位波动问题,增强了系统的可靠性和安全性。研究紧密结合工程实际,对关键控制模块进行了精细化设计仿真验证,具备较高的理论深度工程应用价值。; 适合人群:具备电力电子、自动控制及新能源发电系统基础知识的科研人员工程技术人员,特别适用于从事光伏、风电等新能源并网技术研究的研究生及以上层次学者;; 使用场景及目标:①研究三电平逆变器在弱电网条件下的低电压穿越能力故障响应特性;②优化改进电流环控制策略以提升系统动态性能抗干扰能力;③实现中点电位的主动平衡控制,保障多电平逆变器长期稳定运行;④为相关课题的控制算法开发、仿真建模实验验证提供完整的技术参考实现路径。; 阅读建议:建议结合Simulink仿真平台动手复现模型,重点关注控制器结构设计、参数整定方法及中点电位调控机制,可参照文中提及的博士论文案例深入理解序分量控制、电流环动态响应中点电位耦合机理的理论基础实现细节。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值