linux编程:一个简单的记录CPU负载的小工具

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 启动虚拟进程

使用forkexecl函数来启动这个虚拟进程。

#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
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

橘色的喵

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

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

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

打赏作者

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

抵扣说明:

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

余额充值