/**************************************************************************************************
**
** 文件名称: monitor.c
** 文件描述: 指定进程的[cpu/内存]资源使用情况监测
** ===============================================================================================
** 创建信息: | 2023-08-14 |
** ===============================================================================================
** 修改信息:
**************************************************************************************************/
#include <time.h>
#include <ctype.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <assert.h>
#include <printf.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <dirent.h>
#include <sys/time.h>
#include <sys/types.h>
/*************************************************************************************************/
// CPU占用率计算原理:
// 1、读取/proc/pid/stat文件,其中记录了从开机到现在,本进程所占用的CPU时间(单位jiffies)
// 2、然后再读取/proc/stat文件,其中记录了从开机到现在,系统所占用的CPU时间(单位jiffies)
// 3、取两个时间点,这两个时间点的进程耗时差,除以系统耗时差,得到的就是该进程的CPU占用率
/*************************************************************************************************/
// 内存占用率计算原理:
// 读取/proc/pid/status文件,其中以VmRSS开头的行,记录了该进程的物理内存值
/*************************************************************************************************/
/*************************************************************************************************/
// 下面这段话详细的解释了RSS内存的意义,及其与VSZ内存的区别
//
// RSS(Resident Set Size),常驻内存集大小,表示进程在RAM中占用了多少内存,并不包含在SWAP中占用的虚拟内存
// 即使是在内存中的使用了共享库的内存大小也一并计算在内,包含了完整的在stack和heap中的内存
//
// VSZ(Virtual Memory Size),虚拟内存大小,表明了该进程可以访问的所有内存,包括被交换的内存和共享库内存
//
// 如果进程A的二进制文件大小为500KB,并且链接到了2500KB的共享库,有200KB的stack/heap大小
// 这200KB中又有100KB位于内存中,100KB位于SWAP空间中,并且加载了1000KB的共享库和400KB的自身二进制文件。则
// RSS: 400K + 1000K + 100K = 1500K; VSZ: 500K + 2500K + 200K = 3200K
/*************************************************************************************************/
/*************************************************************************************************/
// 模块宏定义
/*************************************************************************************************/
#define CPU_START_POS 14 /* stat文件的有效起始行数 */
#define CPU_AFFINITY_POS 6 /* stat文件的有效起始行数 */
#define READ_BUF_SIZE 512 /* 读取文件的缓存空间大小 */
#define LOG_ENABLE "logon" /* 启用日志输出的命令 */
#define MONITR_DATA_PATH "monitor_log" /* 日志文件的子目录 */
#define MONITR_DATA_BEXT ".log" /* 日志文件的后缀名 */
/*************************************************************************t************************/
// 模块静态变量定义
/*************************************************************************************************/
static long s_cur_pro_cpu, s_pre_pro_cpu; /* 指定程序的本轮/前轮CPU时间 */
static long s_cur_sys_cpu, s_pre_sys_cpu; /* 整个系统的本轮/前轮CPU时间 */
static int s_cur_cpu_affinity, s_pre_cpu_affinity; /* cpu亲和性 */
static long s_cur_pro_core, s_pre_pro_core; /* 指定程序的本轮/前轮CPU时间 */
static long s_cur_sys_core, s_pre_sys_core; /* 整个系统的本轮/前轮CPU时间 */
static int s_needlogfile; /* 是否要输出到日志文件 */
static char s_recfilepath[128]; /* 日志文件的路径信息 */
/**************************************************************************************************
** 函数名称: excute_cmd
** 功能描述: 执行shell脚本,并解析出最终的执行结果
** 输入参数: 脚本命令
** 输出参数: 无
** 返回参数: 返回1表示脚本命令执行成功,返回-1表示执行失败
**************************************************************************************************/
static int excute_cmd(char *comand)
{
int status;
status = system(comand);
if (status == -1) { /* 系统子进程创建失败 */
return -1;
}
if (WIFEXITED(status) == 0) { /* shell拉起失败或未正常执行结束 */
return -1;
}
if (WEXITSTATUS(status) != 0) { /* mkdir命令执行失败 */
return -1;
}
return 1;
}
/**************************************************************************************************
** 函数名称: target_is_exist
** 功能描述: 检查目标是否存在 【目标可以是目录和文件】
** 输入参数: 无
** 输出参数: 无
** 返回参数: 返回1表示目标存在,返回-1表示目标不存在
**************************************************************************************************/
static int target_is_exist(char *target_path)
{
int result;
result = access(target_path, F_OK);
return result;
}
/**************************************************************************************************
** 函数名称: make_sudir
** 功能描述: 创建目录 【可以建立多级子目录】
** 输入参数: 子目录路径
** 输出参数: 无
** 返回参数: 返回0表示该目录已经存在,返回1表示创建成功,返回-1表示创建失败
**************************************************************************************************/
static int make_sudir(char *fpath)
{
char *comand;
int pathlen, result;
if (target_is_exist(fpath) == 1) { /* 目标文件夹已经存在 */
return 0;
}
pathlen = strlen(fpath) + 16; /* 16的长度是为shell命令而留 */
comand = (char *)malloc(pathlen);
assert(comand != NULL);
memset(comand, 0, pathlen);
sprintf(comand, "mkdir -p %s", fpath);
result = excute_cmd(comand);
free(comand); /* 这里要记得释放内存 */
return result;
}
/**************************************************************************************************
** 函数名称: write_recfile
** 功能描述: 将信息写入到日志文件中
** 输入参数: 无
** 输出参数: 无
** 返回参数: 无
**************************************************************************************************/
static void write_recfile(char *log_path, char *log_text)
{
FILE *log_hdl;
log_hdl = fopen(log_path, "a");
assert(log_hdl != NULL); /* 打开日志记录文件 */
assert(fseek(log_hdl, 0, SEEK_END) == 0); /* 定位到尾开始写入 */
assert(fwrite(log_text, strlen(log_text), 1, log_hdl) == 1); /* 写入信息内容 */
assert(fclose(log_hdl) == 0); /* 关闭日志记录文件 */
}
/**************************************************************************************************
** 函数名称: generate_rfile_path
** 功能描述: 生成文件名路径信息
** 输入参数: 无
** 输出参数: 无
** 返回参数: 无
**************************************************************************************************/
static void generate_rfile_path(char *target)
{
struct timeval tv;
struct tm tm;
char timestr[32];
memset(timestr, 0, sizeof(timestr));
gettimeofday(&tv, NULL);
tm = *localtime(&tv.tv_sec);
sprintf(timestr, "%.4d%.2d%.2d_%.2d%.2d%.2d", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
assert(make_sudir(MONITR_DATA_PATH) != -1);
memset(s_recfilepath, 0, sizeof(s_recfilepath));
sprintf(s_recfilepath, "%s/%s_%s%s", MONITR_DATA_PATH, target, timestr, MONITR_DATA_BEXT);
printf("log is enabled. log_file \"%s\"\n", s_recfilepath);
}
/**************************************************************************************************
** 函数名称: record_loginfo
** 功能描述: 将信息记录到日志文件,并进行打印输出
** 输入参数: 无
** 输出参数: 无
** 返回参数: 无
**************************************************************************************************/
static void record_loginfo(char *info)
{
struct timeval tv;
struct tm tm;
int tlen;
char *wstr;
tlen = strlen(info) + 32; /* 32用于存放时间戳 */
wstr = malloc(tlen);
assert(wstr != 0);
gettimeofday(&tv, NULL);
tm = *localtime(&tv.tv_sec);
sprintf(wstr, "[%04d/%02d/%02d %02d:%02d:%02d] %s", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, info);
printf("%s", wstr); /* 输出到终端显示 */
if (s_needlogfile > 0) {
write_recfile(s_recfilepath, wstr); /* 记录到日志文件 */
}
}
/**************************************************************************************************
** 函数名称: find_pid_by_name
** 功能描述: 根据进程名得到进程ID
** 输入参数: 无
** 输出参数: 无
** 返回参数: 无
**************************************************************************************************/
int find_pid_by_name(char* pidName)
{
DIR *prodir;
FILE *status;
struct dirent *next;
char finame[READ_BUF_SIZE];
char tmpbuf[READ_BUF_SIZE];
char pcname[READ_BUF_SIZE];
prodir = opendir("/proc"); /* proc中包括当前的进程信息 */
assert(prodir != NULL);
// if (prodir == NULL) {
// return 0;
// }
while ((next = readdir(prodir)) != NULL) { /* 逐个检索所有目录 */
if (strcmp(next->d_name, "..") == 0) {
continue;
}
if (!isdigit(*next->d_name)) { /* 进程目录必须是数字的 */
continue;
}
sprintf(finame, "/proc/%s/status", next->d_name); /* 拼凑出完整的目录名称 */
if (!(status = fopen(finame, "r"))) {
continue;
}
if (fgets(tmpbuf, READ_BUF_SIZE - 1, status) == NULL) { /* 读取目录下的文件 */
fclose(status);
continue;
}
fclose(status);
sscanf(tmpbuf, "%*s %s", pcname); /* 提取出其中的有效内容 */
if (strcmp(pcname, pidName) == 0) { /* 与所输入的进程名符合 */
return strtol(next->d_name, NULL, 0);
}
}
return 0;
}
/**************************************************************************************************
** 函数名称: get_items_by_pos
** 功能描述: 在字符串中寻找第N次空格出现的地方
** 输入参数: 无
** 输出参数: 无
** 返回参数: 无
**************************************************************************************************/
char *get_items_by_pos(char *buff, unsigned int numb)
{
char *crpos;
int i, ttlen, count;
crpos = buff;
ttlen = strlen(buff);
count = 0;
for (i = 0; i < ttlen; i++) {
if (' ' == *crpos) { /* 以空格为标记符进行识别 */
count++;
if (count == (numb - 1)) { /* 全部个数都找完了 */
crpos++;
break;
}
}
crpos++;
}
return crpos;
}
/**************************************************************************************************
** 函数名称: get_pro_cpu_time
** 功能描述: 获取某个进程的CPU时间(从开机到现在,单位jiffies)
** 输入参数: 无
** 输出参数: 无
** 返回参数: 无
**************************************************************************************************/
long get_pro_cpu_time(unsigned int pid)
{
FILE *fd;
char *vpos, buff[1024];
long utime, stime, cutime, cstime;
sprintf(buff, "/proc/%d/stat", pid); /* 读取进程的状态文件 */
fd = fopen(buff, "r");
assert(fd != NULL);
assert(fgets(buff, sizeof(buff), fd) != NULL); /* 读取文件内容到缓冲区 */
vpos = get_items_by_pos(buff, CPU_START_POS); /* 读取指定的条目内容 */
sscanf(vpos, "%ld %ld %ld %ld", &utime, &stime, &cutime, &cstime); /* 将条目内容拆分成实际的数据 */
fclose(fd);
return (utime + stime + cutime + cstime);
}
/**************************************************************************************************
** 函数名称: get_pro_cpu_affinity
** 功能描述: 获取某CPU亲和性
** 输入参数: 无
** 输出参数: 无
** 返回参数: 无
**************************************************************************************************/
unsigned int get_pro_cpu_affinity(unsigned int pid)
{
FILE *fd;
char *vpos, buff[1024];
int affinity;
sprintf(buff, "ps -o psr -p %d", pid); /* 读取进程的状态文件 */
fd = popen(buff, "r");
assert(fd != NULL);
assert(fgets(buff, sizeof(buff), fd) != NULL); /* 读取文件内容到缓冲区 */
assert(fgets(buff, sizeof(buff), fd) != NULL);
vpos = get_items_by_pos(buff, 2); /* 读取指定的条目内容 */
sscanf(vpos, "%d", &affinity); /* 将条目内容拆分成实际的数据 */
fclose(fd);
return affinity;
}
/**************************************************************************************************
** 函数名称: get_sys_cpu_time
** 功能描述: 获取整个系统的CPU时间(从开机到现在,单位jiffies)
** 输入参数: 无
** 输出参数: 无
** 返回参数: 无
**************************************************************************************************/
long get_sys_cpu_time(void)
{
FILE *fd;
char name[32], buff[1024];
long user, nice, syst, idle;
fd = fopen("/proc/stat", "r"); /* 读取系统的状态文件 */
assert(fd != NULL);
assert(fgets(buff, sizeof(buff), fd) != NULL); /* 读取文件内容到缓冲区 */
sscanf(buff, "%s %ld %ld %ld %ld", name, &user, &nice, &syst, &idle); /* 将条目内容拆分成实际的数据 */
fclose(fd);
return (user + nice + syst + idle);
}
/**************************************************************************************************
** 函数名称: get_sys_core_time
** 功能描述: 获取系统单个核心的CPU时间(从开机到现在,单位jiffies)
** 输入参数: 无
** 输出参数: 无
** 返回参数: 无
**************************************************************************************************/
long get_sys_core_time(int num)
{
FILE *fd;
char cpu[128] = {0};
char name[32] = {0};
long user, nice, syst, idle;
fd = fopen("/proc/stat", "r"); /* 读取系统的状态文件 */
assert(fd != NULL);
/* 读取文件内容到缓冲区 */
for (size_t i = 0; i <= num + 1; i++) {
assert(fgets(cpu, sizeof(cpu), fd) != NULL);
}
sscanf(cpu, "%s %ld %ld %ld %ld", name, &user, &nice, &syst, &idle); /* 将条目内容拆分成实际的数据 */
fclose(fd);
return (user + nice + syst + idle);
}
/**************************************************************************************************
** 函数名称: get_cpu_stat
** 功能描述: 获取进程的CPU使用率
** 输入参数: 无
** 输出参数: 无
** 返回参数: 本轮时间片里,该进程的CPU使用率,单位百分比
**************************************************************************************************/
float get_cpu_stat(unsigned int pid)
{
float ratio;
s_cur_pro_cpu = get_pro_cpu_time(pid);
s_cur_sys_cpu = get_sys_cpu_time();
if ((s_cur_pro_cpu == s_pre_pro_cpu) || (s_cur_sys_cpu == s_pre_sys_cpu) || (s_cur_pro_cpu == 0) || (s_cur_sys_cpu == 0)) {
ratio = 0;
} else {
ratio = (100.0 * (s_cur_pro_cpu - s_pre_pro_cpu)) / (s_cur_sys_cpu - s_pre_sys_cpu);
}
s_pre_pro_cpu = s_cur_pro_cpu;
s_pre_sys_cpu = s_cur_sys_cpu;
return ratio;
}
/**************************************************************************************************
** 函数名称: get_core1_stat
** 功能描述: 获取进程的CPU使用率
** 输入参数: 无
** 输出参数: 无
** 返回参数: 本轮时间片里,该进程的CPU使用率,单位百分比
**************************************************************************************************/
float get_core_stat(unsigned int pid, unsigned int *corenum)
{
float ratio;
s_cur_cpu_affinity = get_pro_cpu_affinity(pid);
s_cur_pro_core = get_pro_cpu_time(pid);
s_cur_sys_core = get_sys_core_time(s_cur_cpu_affinity);
if ((s_cur_pro_core == s_pre_pro_core) || (s_cur_sys_core == s_pre_sys_core) || (s_cur_pro_core == 0) || (s_cur_sys_core == 0) || (s_cur_cpu_affinity != s_pre_cpu_affinity)) {
ratio = 0;
} else {
ratio = (100.0 * (s_cur_pro_core - s_pre_pro_core)) / (s_cur_sys_core - s_pre_sys_core);
}
s_pre_pro_core = s_cur_pro_core;
s_pre_sys_core = s_cur_sys_core;
s_pre_cpu_affinity = s_cur_cpu_affinity;
*corenum = s_cur_cpu_affinity;
return ratio;
}
/**************************************************************************************************
** 函数名称: get_mem_stat
** 功能描述: 获取进程的内存使用情况
** 输入参数: 进程ID值
** 输出参数: 无
** 返回参数: 此刻,该进程的物理内存占用量,单位KB
**************************************************************************************************/
unsigned int get_mem_stat(unsigned int pid)
{
FILE *fd;
int vmrss;
char *valid, sbuff[32], tbuff[1024];
sprintf(tbuff, "/proc/%d/status", pid); /* 在proc目录下查找进程对应文件 */
fd = fopen(tbuff, "r");
assert(fd != NULL);
while (1) { /* 对文件内容进行逐行搜索 */
assert(fgets(tbuff, sizeof(tbuff), fd) != NULL); /* 文件读取出错 */
valid = strstr(tbuff, "VmRSS"); /* 在该行内容中搜索关键词 */
if (valid != NULL) { /* 结果非空则表示搜索成功 */
break;
}
}
sscanf(tbuff, "%s %d", sbuff, &vmrss); /* 将该行内容拆成符合需要的格式 */
fclose(fd);
return vmrss;
}
/**************************************************************************************************
** 函数名称: main
** 功能描述: 主函数
** 输入参数: 需要监测的进程名称,刷新的频率,以及是否要输出到日志文件
** 输出参数: 无
** 返回参数: 无
** 使用方法: 输入"monitor_pc cheese 1",表示以1s的频率监控cheese进程的CPU及物理内存使用情况
** 使用方法: 如果需要将监控数据同步输出到日志文件,则输入"monitor_pc cheese 1 logon"即可
**************************************************************************************************/
int main(int argc, char *argv[])
{
unsigned int pid, rtime;
char *name_pos, *proc_name, sstr[128];
if (argc < 3) {
printf("invalid cmd!!! should specify \"proc_name\" and \"refresh_time\"\n");
return 1;
}
proc_name = argv[0]; /* 获取进程名称 */
name_pos = strrchr(proc_name, '/');
if (NULL != name_pos){ /* 提取出路径最后部分的文件名 */
proc_name = name_pos + 1;
}
printf("proc \"%s\" starting...\n", proc_name);
rtime = atoi(argv[2]); /* 获取刷新时间 */
if ((rtime < 10000) || (rtime > 1000000)) {
printf("invalid refresh_time(%d)!!! valid range: 10000-1000000\n", rtime);
return 1;
}
s_needlogfile = 0; /* 默认不需要启用日志输出功能 */
if ((argc == 4) && (strcmp(argv[3], LOG_ENABLE) == 0)) { /* 判断用户输入的命令是否符合要求 */
generate_rfile_path(argv[1]); /* 生成日志文件的路径信息 */
s_needlogfile = 1;
}
while (1) {
pid = find_pid_by_name(argv[1]);
if (pid == 0) {
sprintf(sstr, "proc \"%s\" not found...\n", argv[1]);
record_loginfo(sstr);
} else {
unsigned int num;
sprintf(sstr, "cpu %.2f%%,\t core%d %.2f%%,\t mem %d KB\n", get_cpu_stat(pid), num, get_core_stat(pid, &num), get_mem_stat(pid));
record_loginfo(sstr);
}
usleep(rtime);
}
return 0;
}
# CMakeLists.txt
cmake_minimum_required(VERSION 3.10.2)
project(res_monitor)
if(CMAKE_COMPILER_IS_GNUCC)
message("COMPILER IS GNUCC")
ADD_DEFINITIONS ( -std=c++11 )
endif(CMAKE_COMPILER_IS_GNUCC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g")
include_directories(${CMAKE_SOURCE_DIR}/)
# FILE(GLOB_RECURSE SOURCE_FILES ${CMAKE_SOURCE_DIR}/*.c)
# ADD_EXECUTABLE(${PROJECT_NAME} main.c ${SOURCE_FILES})
ADD_EXECUTABLE(${PROJECT_NAME} main.c)
[2023/08/14 11:58:09] cpu 0.11%, core1 0.00%, mem 35552 KB
[2023/08/14 11:58:10] cpu 0.50%, core1 1.01%, mem 35552 KB
[2023/08/14 11:58:11] cpu 0.50%, core1 0.99%, mem 35552 KB
[2023/08/14 11:58:12] cpu 0.00%, core1 0.00%, mem 35552 KB
[2023/08/14 11:58:13] cpu 0.50%, core1 1.00%, mem 35552 KB
[2023/08/14 11:58:14] cpu 0.51%, core1 1.02%, mem 35552 KB
[2023/08/14 11:58:15] cpu 0.50%, core1 1.00%, mem 35552 KB
[2023/08/14 11:58:16] cpu 0.50%, core1 1.00%, mem 35552 KB
[2023/08/14 11:58:17] cpu 0.00%, core1 0.00%, mem 35552 KB
[2023/08/14 11:58:18] cpu 0.50%, core1 0.99%, mem 34596 KB
[2023/08/14 11:58:19] cpu 0.50%, core1 1.01%, mem 34596 KB
[2023/08/14 11:58:20] cpu 0.99%, core0 0.00%, mem 34596 KB
[2023/08/14 11:58:21] cpu 0.00%, core0 0.00%, mem 34596 KB
taskset -p 0x03 101252
ps -eF | grep mcusimulator
ps -o psr -p 101252