linux监测指定进程的CPU及物理内存消耗情况(c程序)

近日,由于工作要求,研究了一下如何在linux系统下对某个指定的单个进程进行监测,分析其CPU及物理内存的使用情况,并基于c语言写了一个独立的模块,完整的实现上述功能。现将整个模块的代码贴上,以便日后借鉴,并与同道中人分享。源码在ubuntu 16.04、嵌入式主板等多个系统下运行亲测正常,各类注释都比较齐全,对模块的使用方法也有介绍,此处就不再赘述,若有问题,自行阅读源码即可。
注:另有一份代码是基于bash脚本的,同样实现上述的功能,贴于另一篇博客,如有需要,可点击此处跳转。

/**************************************************************************************************
**
**  文件名称:  res_monitor.c
**  文件描述:  指定进程的[cpu/内存]资源使用情况监测
**  ===============================================================================================
**  创建信息:  | 2019-2-1 | LEON | 创建本模块
**  ===============================================================================================
**  修改信息:  单击此处添加....
**************************************************************************************************/
#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 READ_BUF_SIZE        512                                               /* 读取文件的缓存空间大小 */

#define LOG_ENABLE           "logon"                                           /* 启用日志输出的命令 */

#define MONITR_DATA_PATH     "monitor_log"                                     /* 日志文件的子目录 */
#define MONITR_DATA_BEXT     ".log"                                            /* 日志文件的后缀名 */

/*************************************************************************************************/
//                           模块静态变量定义
/*************************************************************************************************/
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_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);
    
    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_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_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_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 == 0) || (rtime > 60)) {
        printf("invalid refresh_time(%d)!!! valid range: 0-60\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]);
        } else {
            sprintf(sstr, "cpu %.2f%%, mem %d KB\n", get_cpu_stat(pid), get_mem_stat(pid));
        }
        record_loginfo(sstr);
        sleep(rtime);
    }
    
    return 0;
}
发布了165 篇原创文章 · 获赞 760 · 访问量 158万+

猜你喜欢

转载自blog.csdn.net/LEON1741/article/details/87098232
今日推荐