0. 概要
在Linux系统中,监控进程的CPU负载是系统性能调试和分析的重要手段。
本文将介绍一个简单的记录CPU负载的小工具的实现。
目标是编写一个程序,它能够启动一个虚拟进程并定期监控其CPU使用情况,并将结果记录到一个文件中。具体需求包括:
- 创建并启动一个虚拟进程。
- 定期读取指定进程的CPU使用情况。
主要思想可参考之前的文章
1. 主要实现
1.1 监控进程的CPU负载
编写一个类来监控进程的CPU负载信息。
#include <dirent.h>
#include <vector>
#include <unordered_set>
#include <memory>
class ProcessCpuLoad {
public:
struct Result {
int id;
float load;
std::string name;
};
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 stat_path = "/proc/" + std::string(entry->d_name) + "/stat";
std::string comm_path = "/proc/" + std::string(entry->d_name) + "/comm";
try {
FilePtr stat_file(stat_path, "r");
FilePtr comm_file(comm_path, "r");
char comm[256];
if (fscanf(comm_file.get(), "%255s", comm) != 1) {
std::cerr << "Failed to read from " << comm_path << std::endl;
continue;
}
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) { // 部分匹配目标进程
unsigned long utime, stime;
fseek(stat_file.get(), 13 * sizeof(unsigned long), SEEK_SET); // Skip first 13 fields
if (fscanf(stat_file.get(), "%lu %lu", &utime, &stime) != 2) {
std::cerr << "Failed to read from " << stat_path << std::endl;
continue;
}
float total_time = static_cast<float>(utime + stime) / sysconf(_SC_CLK_TCK);
results_.push_back({pid, total_time, std::string(comm)});
}
} catch (const std::runtime_error& e) {
std::cerr << e.what() << std::endl;
}
}
}
}
}
std::vector<Result> GetResult() const {
return results_;
}
private:
std::vector<Result> results_;
};
1.2 记录CPU负载信息
还需要一个类来记录这些信息,并使用线程安全的方式进行日志记录。
#include <fstream>
#include <mutex>
#include <sstream>
#include <iomanip>
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();
}
}
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";
}
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. 测试程序
2.1 创建虚拟进程
我们需要一个脚本来创建虚拟进程。这个脚本将被循环执行,并在接收到SIGTERM信号时优雅地退出。
#include <fstream>
#include <iostream>
#include <sys/stat.h>
bool CreateDummyProcessScript(const std::string& scriptPath) {
std::ofstream scriptFile(scriptPath);
if (scriptFile.is_open()) {
scriptFile << "#!/bin/bash\n"
<< "function handle_signal() {\n"
<< " echo \"[dummy_process] Received signal: $1\"\n"
<< " exit 0\n"
<< "}\n"
<< "trap 'handle_signal SIGTERM' SIGTERM\n"
<< "while true; do\n"
<< " echo \"dummy_process is running...\"\n"
<< " sleep 1\n"
<< "done\n";
scriptFile.close();
if (chmod(scriptPath.c_str(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == -1) {
std::cerr << "Failed to set execute permission on " << scriptPath << std::endl;
return false;
}
return true;
} else {
std::cerr << "Failed to create " << scriptPath << std::endl;
return false;
}
}
这个脚本会不断输出“dummy_process is running…”并在接收到SIGTERM信号时退出。
2.2 启动虚拟进程
使用fork
和execl
函数来启动这个虚拟进程。
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <iostream>
pid_t StartDummyProcess(const std::string& scriptPath) {
pid_t pid = fork();
if (pid < 0) {
std::cerr << "Failed to fork process" << std::endl;
return -1;
} else if (pid == 0) {
execl(scriptPath.c_str(), scriptPath.c_str(), static_cast<char*>(nullptr));
_exit(EXIT_FAILURE); // exec never returns
}
return pid;
}
2.3 主程序
最后,编写主程序来启动虚拟进程并定期记录其CPU负载信息。
#include <iostream>
#include <thread>
#include <chrono>
#include <unordered_map>
void TestCpuMonitor(int count_max, const std::vector<std::string>& process_names_or_pids) {
ProcessCpuLoad cpu_load;
CpuMonitor cpu_monitor("/tmp/cpuload.txt");
int count = 0;
auto init_time = std::chrono::steady_clock::now();
std::unordered_map<int, float> previous_cpu_times;
while (count++ < count_max) {
std::this_thread::sleep_for(std::chrono::seconds(2));
cpu_load.RunOnce(process_names_or_pids);
auto now = std::chrono::steady_clock::now();
auto time_us = std::chrono::duration_cast<std::chrono::microseconds
>(now - init_time).count();
auto results = cpu_load.GetResult();
// 计算每个进程的CPU使用率
for (auto& result : results) {
if (previous_cpu_times.find(result.id) != previous_cpu_times.end()) {
float previous_time = previous_cpu_times[result.id];
result.load = (result.load - previous_time) / 2.0f; // 每两秒采样一次,计算使用率
} else {
result.load = 0.0f;
}
previous_cpu_times[result.id] = result.load;
}
// 输出调试信息
std::cout << "Time: " << time_us << " us" << std::endl;
for (const auto& result : results) {
std::cout << "ProcessID: " << result.id << ", ProcessName: " << result.name << ", CPULoad: " << result.load << "%" << std::endl;
}
cpu_monitor.LogCpuLoad(time_us, results);
}
}
int main() {
const std::string scriptPath = "/tmp/dummy_process.sh";
if (!CreateDummyProcessScript(scriptPath)) {
return EXIT_FAILURE;
}
pid_t pid = StartDummyProcess(scriptPath);
if (pid < 0) {
return EXIT_FAILURE;
}
std::cout << "Started dummy process with PID: " << pid << std::endl;
std::vector<std::string> processes_to_monitor = {"dummy_process"};
TestCpuMonitor(5, processes_to_monitor);
// 终止虚拟进程
kill(pid, SIGTERM);
waitpid(pid, nullptr, 0);
return 0;
}
3. 完整代码
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <chrono>
#include <csignal>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <memory>
#include <mutex>
#include <sstream>
#include <string>
#include <thread>
#include <unordered_map>
#include <unordered_set>
#include <vector>
// RAII包装类,用于管理文件指针
class FilePtr {
public:
explicit FilePtr(const std::string& path, const char* mode) {
file_ = fopen(path.c_str(), mode);
if (!file_) {
throw std::runtime_error("Failed to open file: " + path);
}
}
~FilePtr() {
if (file_) {
fclose(file_);
}
}
FILE* get() const {
return file_;
}
private:
FILE* file_;
};
// 模拟的进程CPU负载信息类
class ProcessCpuLoad {
public:
struct Result {
int id;
float load;
std::string name;
};
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 stat_path = "/proc/" + std::string(entry->d_name) + "/stat";
std::string comm_path = "/proc/" + std::string(entry->d_name) + "/comm";
try {
FilePtr stat_file(stat_path, "r");
FilePtr comm_file(comm_path, "r");
char comm[256];
if (fscanf(comm_file.get(), "%255s", comm) != 1) {
std::cerr << "Failed to read from " << comm_path << std::endl;
continue;
}
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) { // 部分匹配目标进程
unsigned long utime, stime;
fseek(stat_file.get(), 13 * sizeof(unsigned long), SEEK_SET); // Skip first 13 fields
if (fscanf(stat_file.get(), "%lu %lu", &utime, &stime) != 2) {
std::cerr << "Failed to read from " << stat_path << std::endl;
continue;
}
float total_time = static_cast<float>(utime + stime) / sysconf(_SC_CLK_TCK);
results_.push_back({pid, total_time, std::string(comm)});
}
} catch (const std::runtime_error& e) {
std::cerr << e.what() << std::endl;
}
}
}
}
}
std::vector<Result> GetResult() const {
return results_;
}
private:
std::vector<Result> results_;
};
// CPU监控类:负责记录CPU及各进程的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_;
};
// 测试CpuMonitor类
void TestCpuMonitor(int count_max, const std::vector<std::string>& process_names_or_pids) {
ProcessCpuLoad cpu_load;
CpuMonitor cpu_monitor("/tmp/cpuload.txt");
int count = 0;
auto init_time = std::chrono::steady_clock::now();
std::unordered_map<int, float> previous_cpu_times;
while (count++ < count_max) {
std::this_thread::sleep_for(std::chrono::seconds(2));
cpu_load.RunOnce(process_names_or_pids);
auto now = std::chrono::steady_clock::now();
auto time_us = std::chrono::duration_cast<std::chrono::microseconds>(now - init_time).count();
auto results = cpu_load.GetResult();
// 计算每个进程的CPU使用率
for (auto& result : results) {
if (previous_cpu_times.find(result.id) != previous_cpu_times.end()) {
float previous_time = previous_cpu_times[result.id];
result.load = (result.load - previous_time) / 2.0f; // 每两秒采样一次,计算使用率
} else {
result.load = 0.0f;
}
previous_cpu_times[result.id] = result.load;
}
// 输出调试信息
std::cout << "Time: " << time_us << " us" << std::endl;
for (const auto& result : results) {
std::cout << "ProcessID: " << result.id << ", ProcessName: " << result.name << ", CPULoad: " << result.load << "%"
<< std::endl;
}
cpu_monitor.LogCpuLoad(time_us, results);
}
}
/ 如下为测试代码 /
// 创建虚拟进程脚本
bool CreateDummyProcessScript(const std::string& scriptPath) {
std::ofstream scriptFile(scriptPath);
if (scriptFile.is_open()) {
scriptFile << "#!/bin/bash\n";
scriptFile << "\n";
scriptFile << "# Function to handle signals\n";
scriptFile << "function handle_signal() {\n";
scriptFile << " echo \"[dummy_process] Received signal: $1\"\n";
scriptFile << " exit 0\n"; // Exit gracefully on signal
scriptFile << "}\n";
scriptFile << "\n";
scriptFile << "# Trap signals\n";
scriptFile << "trap 'handle_signal SIGTERM' SIGTERM\n"; // Handle SIGTERM signal
scriptFile << "\n";
scriptFile << "# Main loop\n";
scriptFile << "while true; do\n";
scriptFile << " echo \"dummy_process is running...\"\n";
scriptFile << " sleep 1\n";
scriptFile << "done\n";
scriptFile.close();
// Add executable permission to the script
if (chmod(scriptPath.c_str(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == -1) {
std::cerr << "Failed to set execute permission on " << scriptPath << std::endl;
return false;
}
return true;
} else {
std::cerr << "Failed to create " << scriptPath << std::endl;
return false;
}
}
// 启动虚拟进程
pid_t StartDummyProcess(const std::string& scriptPath) {
pid_t pid = fork();
if (pid < 0) {
std::cerr << "Failed to fork process" << std::endl;
return -1;
} else if (pid == 0) {
// Child process
execl(scriptPath.c_str(), scriptPath.c_str(), static_cast<char*>(nullptr));
_exit(EXIT_FAILURE); // exec never returns
}
return pid;
}
int main() {
const std::string scriptPath = "/tmp/dummy_process.sh";
if (!CreateDummyProcessScript(scriptPath)) {
return EXIT_FAILURE;
}
pid_t pid = StartDummyProcess(scriptPath);
if (pid < 0) {
return EXIT_FAILURE;
}
std::cout << "Started dummy process with PID: " << pid << std::endl;
std::vector<std::string> processes_to_monitor = {"dummy_process"};
TestCpuMonitor(5, processes_to_monitor);
// 终止虚拟进程
kill(pid, SIGTERM);
waitpid(pid, nullptr, 0);
return 0;
}
执行结果
# g++ -std=c++14 -o cpu_load cpu_load.cpp
$ cat /tmp/cpuload.txt
Timestamp, ProcessID, ProcessName, CPULoad
Timestamp: 2008916 us
15097, dummy_process.s, 0.00s
Timestamp: 4023839 us
15097, dummy_process.s, 35.28s
Timestamp: 6034096 us
15097, dummy_process.s, 17.64s
Timestamp: 8041633 us
15097, dummy_process.s, 26.46s
Timestamp: 10050709 us
15097, dummy_process.s, 22.05s