Linux编程: 使用 C++ 监控进程和线程的 CPU 使用情况

0. 概述

在 Linux 系统中,监控进程和线程的 CPU 使用情况可以帮助我们找出系统瓶颈和性能热点。本文将介绍两种方法使用 C++ 来监控 Linux 系统中进程和线程的 CPU 使用情况。

1. 方法一:基于 /proc 文件系统读取 CPU 信息

1.1 了解 /proc 文件系统

/proc 文件系统是一个虚拟文件系统,它为我们提供了访问内核数据结构和各种系统信息的途径。通过读取 /proc 下的特定文件,我们可以获取系统中各个进程和线程的详细状态信息。特别地,以下文件对于获取进程和线程的 CPU 使用情况非常有用:

  • /proc/[pid]/stat:包含进程的状态信息,包括用户态和内核态的 CPU 时间。
  • /proc/[pid]/task/[tid]/stat:包含指定进程内的各线程的状态信息,包括用户态和内核态的 CPU 时间。

1.2 读取 /proc 文件系统的实现

为了监控指定进程和线程的 CPU 使用情况,我们可以编写一个 C++ 类来读取 /proc 文件系统中的信息并进行处理。以下是一个示例实现,用于监控进程和线程的 CPU 使用情况:

#include <dirent.h>
#include <vector>
#include <unordered_set>
#include <memory>
#include <iostream>
#include <fstream>
#include <sstream>
#include <cstring>
#include <limits>
#include <unistd.h>

// 用于监控进程和线程CPU负载的类
class ProcessCpuLoad {
public:
    // 用于存储结果的数据结构
    struct Result {
        int id;          // 进程ID或线程ID
        float load;      // CPU负载
        std::string name; // 进程名称或线程名称
    };

    // 根据进程或线程名称或PID列表运行一次监控
    void RunOnce(const std::vector<std::string>& process_names_or_pids) {
        results_.clear();
        std::unordered_set<std::string> target_set(process_names_or_pids.begin(), process_names_or_pids.end());
        std::unique_ptr<DIR, decltype(&closedir)> dir(opendir("/proc"), closedir);
        if (!dir) {
            std::cerr << "Failed to open /proc directory." << std::endl;
            return;
        }

        struct dirent* entry;
        while ((entry = readdir(dir.get())) != nullptr) {
            if (entry->d_type == DT_DIR) {
                int pid = atoi(entry->d_name);
                if (pid > 0) {
                    std::string comm_path = "/proc/" + std::string(entry->d_name) + "/comm";
                    try {
                        std::ifstream comm_file(comm_path);
                        if (!comm_file.is_open()) {
                            std::cerr << "Failed to open " << comm_path << std::endl;
                            continue;
                        }

                        char comm[256];
                        if (comm_file >> comm) {
                            std::string pid_str = std::to_string(pid);
                            bool match_found = false;
                            for (const auto& target : target_set) {
                                if (pid_str.find(target) != std::string::npos || std::string(comm).find(target) != std::string::npos) {
                                    match_found = true;
                                    break;
                                }
                            }
                            if (match_found) {
                                // 监控该进程及其线程
                                MonitorProcessAndThreads(pid, std::string(comm));
                            }
                        }
                    } catch (const std::exception& e) {
                        std::cerr << e.what() << std::endl;
                    }
                }
            }
        }
    }

    // 获取监控结果
    std::vector<Result> GetResult() const {
        return results_;
    }

private:
    // 监控进程及其所有线程的CPU负载
    void MonitorProcessAndThreads(int pid, const std::string& process_name) {
        std::string task_path = "/proc/" + std::to_string(pid) + "/task";
        std::unique_ptr<DIR, decltype(&closedir)> task_dir(opendir(task_path.c_str()), closedir);
        if (!task_dir) {
            std::cerr << "Failed to open " << task_path << std::endl;
            return;
        }

        struct dirent* task_entry;
        while ((task_entry = readdir(task_dir.get())) != nullptr) {
            if (task_entry->d_type == DT_DIR) {
                int tid = atoi(task_entry->d_name);
                if (tid > 0) {
                    std::string stat_path = task_path + "/" + std::string(task_entry->d_name) + "/stat";
                    try {
                        std::ifstream stat_file(stat_path);
                        if (!stat_file.is_open()) {
                            std::cerr << "Failed to open " << stat_path << std::endl;
                            continue;
                        }

                        unsigned long utime, stime;
                        stat_file.ignore(std::numeric_limits<std::streamsize>::max(), ')');
                        stat_file.ignore(std::numeric_limits<std::streamsize>::max(), ' ');
                        for (int i = 0; i < 13; ++i) stat_file.ignore(std::numeric_limits<std::streamsize>::max(), ' ');
                        stat_file >> utime >> stime;

                        float total_time = static_cast<float>(utime + stime) / sysconf(_SC_CLK_TCK);
                        results_.push_back({tid, total_time, process_name + " (Thread " + std::to_string(tid) + ")"});
                    } catch (const std::exception& e) {
                        std::cerr << e.what() << std::endl;
                    }
                }
            }
        }
    }

    std::vector<Result> results_; // 存储监控结果
};

1.3 解析与记录 CPU 负载

在监控进程和线程的 CPU 负载之后,我们可以将结果记录到文件中,以便后续分析。以下是一个用于记录结果的线程安全类:

#include <fstream>
#include <mutex>
#include <sstream>
#include <iomanip>

// CPU监控记录类
class CpuMonitor {
public:
    explicit CpuMonitor(const std::string& filename) : log_file_(filename, std::ios::out) {
        if (!log_file_.is_open()) {
            throw std::runtime_error("Failed to open log file: " + filename);
        } else {
            WriteHeader();
        }
    }

    ~CpuMonitor() {
        if (log_file_.is_open()) {
            log_file_.close();
        }
    }

    // 记录CPU负载
    void LogCpuLoad(uint64_t time_us, const std::vector<ProcessCpuLoad::Result>& results) {
        std::lock_guard<std::mutex> lock(mutex_);
        LogTimestamp(time_us);
        LogCpuUsage(results);
        WriteToFile();
    }

private:
    // 写入日志文件头
    void WriteHeader() {
        log_file_ << "Timestamp, ProcessID, ProcessName, CPULoad\n";
    }

    // 记录时间戳
    void LogTimestamp(uint64_t time_us) {
        buffer_ << "Timestamp: " << time_us << " us\n";
    }

    // 记录CPU使用情况
    void LogCpuUsage(const std::vector<ProcessCpuLoad::Result>& results) {
        for (const auto& result : results) {
            buffer_ << result.id << ", " << result.name << ", " << std::fixed << std::setprecision(2) << result.load << "s\n";
        }
    }

    // 写入文件
    void WriteToFile() {
        if (log_file_.is_open()) {
            log_file_ << buffer_.str();
            buffer_.str("");
        }
    }

    std::ofstream log_file_; // 日志文件流
    std::ostringstream buffer_; // 缓存区
    std::mutex mutex_; // 互斥锁,确保线程安全
};

2. 方法二:使用 top 命令读取 CPU 信息

2.1 使用 top 命令的优势

除了读取 /proc 文件系统外,另一种常见的方法是使用 top 命令,它提供了一个全面的进程和线程监控视图。我们可以通过命令行参数配置 top 的输出格式和更新频率,并将其结果导出到文件或通过管道传输到程序中进行分析。

2.2 解析 top 命令的输出

以下是一个示例命令,它将 top 的输出以批处理模式写入一个文件中:

top -b -H -n 1800 -d 1 > top_output.txt
  • -b:启用批处理模式。
  • -H:显示所有线程。
  • -n 1800:设置要输出的更新次数。
  • -d 1:每次更新之间的延迟为 1 秒。

我们可以通过解析 top_output.txt 文件来提取我们

感兴趣的线程 CPU 信息。以下是一个简单的解析器实现:

#include <fstream>
#include <string>
#include <vector>
#include <sstream>
#include <iostream>

// 线程信息结构体
struct ThreadInfo {
    int pid;          // 进程ID
    int tid;          // 线程ID
    double cpu_usage; // CPU使用率
    std::string command; // 线程或进程名称
};

// 解析top输出的函数
std::vector<ThreadInfo> ParseTopOutput(const std::string& filename) {
    std::vector<ThreadInfo> thread_infos;
    std::ifstream file(filename);
    std::string line;

    while (std::getline(file, line)) {
        if (line.empty() || line.find("top -") != std::string::npos || line.find("Tasks:") != std::string::npos ||
            line.find("Cpu(s):") != std::string::npos || line.find("Mem:") != std::string::npos ||
            line.find("PID") != std::string::npos) {
            continue;  // 跳过头部和汇总行
        }

        std::istringstream iss(line);
        ThreadInfo info;
        iss >> info.pid >> info.tid >> info.cpu_usage >> info.command;
        thread_infos.push_back(info);
    }

    return thread_infos;
}

3. 总结

本文介绍如何使用 C++ 程序读取进程和线程的 CPU 信息。两种主要的方法:读取 /proc 文件系统以及解析 top 命令的输出。每种方法都有其优势和适用场景,选择合适的方法可以帮助开发人员更好地监控和优化系统性能。通过合理的设计和实现,C++ 程序可以高效地处理和记录这些信息.更多请查看:

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

橘色的喵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值