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++ 程序可以高效地处理和记录这些信息.更多请查看: