/*
** Copyright (C) 2001-2024 Zabbix SIA
**
** This program is free software: you can redistribute it and/or modify it under the terms of
** the GNU Affero General Public License as published by the Free Software Foundation, version 3.
**
** This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
** without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
** See the GNU Affero General Public License for more details.
**
** You should have received a copy of the GNU Affero General Public License along with this program.
** If not, see .
**/
#include "proc.h"
#include "zbxsysinfo.h"
#include "../sysinfo.h"
#include "../common/procstat.h"
#include "zbxstr.h"
#include "zbxregexp.h"
#include "zbxjson.h"
#include "zbxnum.h"
#include "zbxtime.h"
#include "zbxalgo.h"
#include
#define PROC_VAL_TYPE_TEXT 0
#define PROC_VAL_TYPE_NUM 1
#define PROC_VAL_TYPE_BYTE 2
#define PROC_ID_TYPE_USER 0
#define PROC_ID_TYPE_GROUP 1
typedef struct
{
pid_t pid;
uid_t uid;
char *name;
/* the process name taken from the 0th argument */
char *name_arg0;
/* process command line in format ... \0 */
char *cmdline;
}
zbx_sysinfo_proc_t;
typedef struct
{
unsigned int pid;
unsigned int tid;
zbx_uint64_t ppid;
char *name;
char *tname;
char *cmdline;
char *state;
zbx_uint64_t processes;
char *user;
char *group;
zbx_uint64_t uid;
zbx_uint64_t gid;
double cputime_user;
double cputime_system;
zbx_uint64_t ctx_switches;
zbx_uint64_t threads;
zbx_uint64_t page_faults;
zbx_uint64_t vsize;
double pmem;
zbx_uint64_t rss;
zbx_uint64_t data;
zbx_uint64_t exe;
zbx_uint64_t hwm;
zbx_uint64_t lck;
zbx_uint64_t lib;
zbx_uint64_t peak;
zbx_uint64_t pin;
zbx_uint64_t pte;
zbx_uint64_t size;
zbx_uint64_t stk;
zbx_uint64_t swap;
zbx_uint64_t memory;
}
proc_data_t;
ZBX_PTR_VECTOR_DECL(proc_data_ptr, proc_data_t *)
ZBX_PTR_VECTOR_IMPL(proc_data_ptr, proc_data_t *)
/******************************************************************************
* *
* Purpose: frees process data structure *
* *
******************************************************************************/
static void zbx_sysinfo_proc_free(zbx_sysinfo_proc_t *proc)
{
zbx_free(proc->name);
zbx_free(proc->name_arg0);
zbx_free(proc->cmdline);
zbx_free(proc);
}
/******************************************************************************
* *
* Purpose: frees process data structure *
* *
******************************************************************************/
static void proc_data_free(proc_data_t *proc_data)
{
zbx_free(proc_data->name);
zbx_free(proc_data->tname);
zbx_free(proc_data->cmdline);
zbx_free(proc_data->state);
zbx_free(proc_data->user);
zbx_free(proc_data->group);
zbx_free(proc_data);
}
/******************************************************************************
* *
* Purpose: Converts value to bytes according to input string size type *
* *
* Parameters: *
* srcstr - [IN] *
* num - [IN/OUT] value/bytes result *
* *
******************************************************************************/
static void convert_to_bytes(char *srcstr, zbx_uint64_t *num)
{
zbx_rtrim(srcstr, "\n");
if (0 == strcasecmp(srcstr, "kB"))
*num <<= 10;
else if (0 == strcasecmp(srcstr, "mB"))
*num <<= 20;
else if (0 == strcasecmp(srcstr, "GB"))
*num <<= 30;
else if (0 == strcasecmp(srcstr, "TB"))
*num <<= 40;
}
/******************************************************************************
* *
* Purpose: Reads value from a string in /proc file. *
* *
* Parameters: *
* f - [IN] file to read from *
* pos - [IN] position to start *
* label - [IN] label to look for, e.g. "VmData:\t" *
* type - [IN] value type *
* num - [OUT] numeric result *
* str - [OUT] string result *
* *
* Return value: SUCCEED - successful reading *
* NOTSUPPORTED - search string was not found. *
* FAIL - search string was found but could not be parsed *
* *
******************************************************************************/
static int read_value_from_proc_file(FILE *f, long pos, const char *label, int type, zbx_uint64_t *num, char **str)
{
char buf[MAX_STRING_LEN], *p_value, *p_unit = NULL;
size_t label_len;
int ret = NOTSUPPORTED;
if ((NULL == str && PROC_VAL_TYPE_TEXT == type) ||
(NULL == num && (PROC_VAL_TYPE_NUM == type || PROC_VAL_TYPE_BYTE == type)))
{
return FAIL;
}
label_len = strlen(label);
p_value = buf + label_len + 1;
pos = ftell(f);
while (NULL != fgets(buf, (int)sizeof(buf), f))
{
if (0 != strncmp(buf, label, label_len))
continue;
if (PROC_VAL_TYPE_BYTE == type)
{
if (NULL == (p_unit = strrchr(p_value, ' ')))
{
ret = FAIL;
break;
}
*p_unit++ = '\0';
}
while (' ' == *p_value || '\t' == *p_value)
p_value++;
zbx_rtrim(p_value, "\n");
if (PROC_VAL_TYPE_TEXT == type)
{
*str = zbx_strdup(NULL, p_value);
}
else if (FAIL == zbx_is_uint64(p_value, num))
{
ret = FAIL;
break;
}
if (PROC_VAL_TYPE_BYTE == type)
convert_to_bytes(p_unit, num);
ret = SUCCEED;
break;
}
if (0 != pos && NOTSUPPORTED == ret)
{
rewind(f);
ret = read_value_from_proc_file(f, pos, label, type, num, str);
}
return ret;
}
static int proc_read_id(FILE *f_status, int type, zbx_uint64_t *id)
{
char *id_s, *p;
if (SUCCEED != read_value_from_proc_file(f_status, 0, PROC_ID_TYPE_USER == type ? "Uid" : "Gid",
PROC_VAL_TYPE_TEXT, NULL, &id_s))
{
return FAIL;
}
if (NULL != (p = strchr(id_s, '\t')))
*p = '\0';
*id = (zbx_uint64_t)atoi(id_s);
zbx_free(id_s);
return SUCCEED;
}
static int get_cmdline(FILE *f_cmd, char **line, size_t *line_offset)
{
size_t line_alloc = ZBX_KIBIBYTE, n;
rewind(f_cmd);
*line = (char *)zbx_malloc(*line, line_alloc + 2);
*line_offset = 0;
while (0 != (n = fread(*line + *line_offset, 1, line_alloc - *line_offset, f_cmd)))
{
*line_offset += n;
if (0 != feof(f_cmd))
break;
line_alloc *= 2;
*line = (char *)zbx_realloc(*line, line_alloc + 2);
}
if (0 == ferror(f_cmd))
{
if (0 == *line_offset || '\0' != (*line)[*line_offset - 1])
(*line)[(*line_offset)++] = '\0';
if (1 == *line_offset || '\0' != (*line)[*line_offset - 2])
(*line)[(*line_offset)++] = '\0';
return SUCCEED;
}
zbx_free(*line);
return FAIL;
}
static int cmp_status(FILE *f_stat, const char *procname)
{
char tmp[MAX_STRING_LEN];
rewind(f_stat);
while (NULL != fgets(tmp, (int)sizeof(tmp), f_stat))
{
if (0 != strncmp(tmp, "Name:\t", 6))
continue;
zbx_rtrim(tmp + 6, "\n");
if (0 == strcmp(tmp + 6, procname))
return SUCCEED;
break;
}
return FAIL;
}
static int check_procname(FILE *f_cmd, FILE *f_stat, const char *procname)
{
char *tmp = NULL, *p;
size_t l;
int ret = SUCCEED;
if (NULL == procname || '\0' == *procname)
return SUCCEED;
/* process name in /proc/[pid]/status contains limited number of characters */
if (SUCCEED == cmp_status(f_stat, procname))
return SUCCEED;
if (SUCCEED == get_cmdline(f_cmd, &tmp, &l))
{
if (NULL == (p = strrchr(tmp, '/')))
p = tmp;
else
p++;
if (0 == strcmp(p, procname))
goto clean;
}
ret = FAIL;
clean:
zbx_free(tmp);
return ret;
}
static int check_user(FILE *f_stat, struct passwd *usrinfo)
{
zbx_uint64_t uid;
if (NULL == usrinfo || (SUCCEED == proc_read_id(f_stat, PROC_ID_TYPE_USER, &uid) && usrinfo->pw_uid == uid))
return SUCCEED;
return FAIL;
}
static int check_proccomm(FILE *f_cmd, const zbx_regexp_t *proccomm_rxp)
{
char *tmp = NULL;
size_t l;
int ret = SUCCEED;
if (NULL == proccomm_rxp)
return SUCCEED;
if (SUCCEED == get_cmdline(f_cmd, &tmp, &l))
{
l = l - 2;
for (size_t i = 0; i < l; i++)
if ('\0' == tmp[i])
tmp[i] = ' ';
if (0 == zbx_regexp_match_precompiled(tmp, proccomm_rxp))
goto clean;
}
ret = FAIL;
clean:
zbx_free(tmp);
return ret;
}
static int check_procstate(FILE *f_stat, int zbx_proc_stat)
{
char tmp[MAX_STRING_LEN], *p;
if (ZBX_PROC_STAT_ALL == zbx_proc_stat)
return SUCCEED;
rewind(f_stat);
while (NULL != fgets(tmp, (int)sizeof(tmp), f_stat))
{
if (0 != strncmp(tmp, "State:\t", 7))
continue;
p = tmp + 7;
switch (zbx_proc_stat)
{
case ZBX_PROC_STAT_RUN:
return ('R' == *p) ? SUCCEED : FAIL;
case ZBX_PROC_STAT_SLEEP:
return ('S' == *p) ? SUCCEED : FAIL;
case ZBX_PROC_STAT_ZOMB:
return ('Z' == *p) ? SUCCEED : FAIL;
case ZBX_PROC_STAT_DISK:
return ('D' == *p) ? SUCCEED : FAIL;
case ZBX_PROC_STAT_TRACE:
return ('T' == *p) ? SUCCEED : FAIL;
default:
return FAIL;
}
}
return FAIL;
}
/******************************************************************************
* *
* Purpose: Reads amount of memory in bytes from a string *
* For example, reading "VmSize: 176712 kB" from /proc/1/status *
* will produce a result 176712*1024 = 180953088 bytes *
* *
* Parameters: *
* srcstr - [IN] string to read from *
* label - [IN] label to look for, e.g. "VmData:\t" *
* bytes - [OUT] result in bytes *
* *
* Return value: SUCCEED - successful reading *
* FAIL - The search string was not found or *
* could not be parsed. *
* *
******************************************************************************/
static int byte_value_from_str(char *srcstr, const char *label, zbx_uint64_t *bytes)
{
size_t label_len = strlen(label);
char *p_unit;
if (0 == strncmp(srcstr, label, label_len))
{
if (NULL == (srcstr = strchr(srcstr, ' ')))
return FAIL;
zbx_ltrim(srcstr, " ");
if (NULL == (p_unit = strchr(srcstr, ' ')))
return FAIL;
*p_unit = '\0';
if (FAIL == zbx_is_uint64(srcstr, bytes))
return FAIL;
convert_to_bytes(p_unit + 1, bytes);
return SUCCEED;
}
return FAIL;
}
/******************************************************************************
* *
* Purpose: Retrieves program core memory usage in bytes *
* see getMemStats ps_mem.py v3.14 *
* *
* Parameters: *
* pid - [IN] *
* bytes - [OUT] result in bytes *
* *
******************************************************************************/
static void get_pid_mem_stats(const char *pid, zbx_uint64_t *bytes)
{
zbx_uint64_t shared = 0, private = 0, private_huge = 0, shared_huge = 0;
FILE *f;
char tmp[MAX_STRING_LEN];
zbx_snprintf(tmp, sizeof(tmp), "/proc/%s/smaps_rollup", pid);
if (NULL == (f = fopen(tmp, "r")))
{
zbx_snprintf(tmp, sizeof(tmp), "/proc/%s/smaps", pid);
f = fopen(tmp, "r");
}
if (NULL != f)
{
zbx_uint64_t pss = 0, num;
int have_pss = 0;
while (NULL != fgets(tmp, (int)sizeof(tmp), f))
{
if (SUCCEED == byte_value_from_str(tmp, "Private_Hugetlb:", &num))
{
private_huge += num;
}
else if (SUCCEED == byte_value_from_str(tmp, "Shared_Hugetlb:", &num))
{
shared_huge += num;
}
else if (SUCCEED == byte_value_from_str(tmp, "Shared", &num))
{
shared += num;
}
else if (SUCCEED == byte_value_from_str(tmp, "Private", &num))
{
private += num;
}
else if (SUCCEED == byte_value_from_str(tmp, "Pss:", &num))
{
have_pss = 1;
pss += num;
/* add 0.5KiB as this avg error due to truncation */
pss += 512;
}
}
if (1 == have_pss)
shared = pss - private;
private += private_huge;
zbx_fclose(f);
}
else
{
char *statm_rss_str, *tmp_str;
zbx_uint64_t rss, psize;
psize = (zbx_uint64_t)getpagesize() / 1024;
zbx_snprintf(tmp, sizeof(tmp), "/proc/%s/statm", pid);
if (NULL == (f = fopen(tmp, "r")))
goto out;
if (NULL == fgets(tmp, (int)sizeof(tmp), f))
{
zbx_fclose(f);
goto out;
}
zbx_fclose(f);
if (NULL == (statm_rss_str = strchr(tmp, ' ')))
goto out;
statm_rss_str++;
if (NULL == (tmp_str = strchr(statm_rss_str, ' ')))
goto out;
*tmp_str = '\0';
if (FAIL == zbx_is_uint64(statm_rss_str, &rss))
goto out;
rss *= psize;
#ifdef LINUX_VERSION_CODE
if ((KERNEL_VERSION(2, 6, 1) >= LINUX_VERSION_CODE) &&
(KERNEL_VERSION(2, 6, 9) <= LINUX_VERSION_CODE))
{
shared = 0;
shared_huge = 0;
private = rss;
}
else
{
#endif
tmp_str++;
if (NULL == (statm_rss_str = strchr(tmp_str, ' ')))
goto out;
*statm_rss_str = '\0';
if (FAIL == zbx_is_uint64(tmp_str, &shared))
goto out;
shared *= psize;
shared_huge = 0;
private = rss - shared;
#ifdef LINUX_VERSION_CODE
}
#endif
private <<= 10;
shared <<= 10;
}
*bytes = shared + private + shared_huge;
return;
out:
*bytes = ZBX_MAX_UINT64;
}
/******************************************************************************
* *
* Purpose: Reads amount of memory in bytes from a string in /proc file. *
* For example, reading "VmSize: 176712 kB" from /proc/1/status *
* will produce a result 176712*1024 = 180953088 bytes *
* *
* Parameters: *
* f - [IN] file to read from *
* label - [IN] label to look for, e.g. "VmData:\t" *
* guard - [IN] label before which to stop, e.g. "VmStk:\t" (optional) *
* bytes - [OUT] result in bytes *
* *
* Return value: SUCCEED - successful reading *
* NOTSUPPORTED - The search string was not found. For example, *
* /proc/NNN/status files for kernel threads do *
* not contain "VmSize:" string. *
* FAIL - The search string was found but could not be parsed. *
* *
******************************************************************************/
int byte_value_from_proc_file(FILE *f, const char *label, const char *guard, zbx_uint64_t *bytes)
{
char buf[MAX_STRING_LEN], *p_value, *p_unit;
size_t label_len, guard_len;
long pos = 0;
int ret = NOTSUPPORTED;
label_len = strlen(label);
p_value = buf + label_len;
if (NULL != guard)
{
guard_len = strlen(guard);
if (0 > (pos = ftell(f)))
return FAIL;
}
while (NULL != fgets(buf, (int)sizeof(buf), f))
{
if (NULL != guard)
{
if (0 == strncmp(buf, guard, guard_len))
{
if (0 != fseek(f, pos, SEEK_SET))
ret = FAIL;
break;
}
if (0 > (pos = ftell(f)))
{
ret = FAIL;
break;
}
}
if (0 != strncmp(buf, label, label_len))
continue;
if (NULL == (p_unit = strrchr(p_value, ' ')))
{
ret = FAIL;
break;
}
*p_unit++ = '\0';
while (' ' == *p_value)
p_value++;
if (FAIL == zbx_is_uint64(p_value, bytes))
{
ret = FAIL;
break;
}
convert_to_bytes(p_unit, bytes);
ret = SUCCEED;
break;
}
return ret;
}
static int get_total_memory(zbx_uint64_t *total_memory)
{
FILE *f;
int ret = FAIL;
if (NULL != (f = fopen("/proc/meminfo", "r")))
{
ret = byte_value_from_proc_file(f, "MemTotal:", NULL, total_memory);
zbx_fclose(f);
}
return ret;
}
int proc_mem(AGENT_REQUEST *request, AGENT_RESULT *result)
{
#define ZBX_SIZE 0
#define ZBX_RSS 1
#define ZBX_VSIZE 2
#define ZBX_PMEM 3
#define ZBX_VMPEAK 4
#define ZBX_VMSWAP 5
#define ZBX_VMLIB 6
#define ZBX_VMLCK 7
#define ZBX_VMPIN 8
#define ZBX_VMHWM 9
#define ZBX_VMDATA 10
#define ZBX_VMSTK 11
#define ZBX_VMEXE 12
#define ZBX_VMPTE 13
char tmp[MAX_STRING_LEN], *procname, *proccomm, *param;
DIR *dir;
struct dirent *entries;
struct passwd *usrinfo;
zbx_regexp_t *proccomm_rxp = NULL;
FILE *f_cmd = NULL, *f_stat = NULL;
zbx_uint64_t mem_size = 0, byte_value = 0, total_memory;
double pct_size = 0.0, pct_value = 0.0;
int do_task, res, mem_type_code, mem_type_tried = 0, proccount = 0, invalid_user = 0,
invalid_read = 0, ret = SYSINFO_RET_OK;
char *mem_type = NULL, *rxp_error = NULL;
const char *mem_type_search = NULL;
if (5 < request->nparam)
{
SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
ret = SYSINFO_RET_FAIL;
goto clean_re;
}
procname = get_rparam(request, 0);
param = get_rparam(request, 1);
if (NULL != param && '\0' != *param)
{
errno = 0;
if (NULL == (usrinfo = getpwnam(param)))
{
if (0 != errno)
{
SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot obtain user information: %s",
zbx_strerror(errno)));
ret = SYSINFO_RET_FAIL;
goto clean_re;
}
invalid_user = 1;
}
}
else
usrinfo = NULL;
param = get_rparam(request, 2);
if (NULL == param || '\0' == *param || 0 == strcmp(param, "sum"))
do_task = ZBX_DO_SUM;
else if (0 == strcmp(param, "avg"))
do_task = ZBX_DO_AVG;
else if (0 == strcmp(param, "max"))
do_task = ZBX_DO_MAX;
else if (0 == strcmp(param, "min"))
do_task = ZBX_DO_MIN;
else
{
SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid third parameter."));
ret = SYSINFO_RET_FAIL;
goto clean_re;
}
proccomm = get_rparam(request, 3);
if (NULL != proccomm && '\0' != *proccomm)
{
if (SUCCEED != zbx_regexp_compile(proccomm, &proccomm_rxp, &rxp_error))
{
SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Invalid regular expression in fourth parameter: "
"%s", rxp_error));
zbx_free(rxp_error);
ret = SYSINFO_RET_FAIL;
goto clean_re;
}
}
mem_type = get_rparam(request, 4);
/* Comments for process memory types were compiled from: */
/* man 5 proc */
/* https://www.kernel.org/doc/Documentation/filesystems/proc.txt */
/* Himanshu Arora, Linux Processes explained - Part II, http://mylinuxbook.com/linux-processes-part2/ */
if (NULL == mem_type || '\0' == *mem_type || 0 == strcmp(mem_type, "vsize"))
{
mem_type_code = ZBX_VSIZE; /* current virtual memory size (total program size) */
mem_type_search = "VmSize:\t";
}
else if (0 == strcmp(mem_type, "rss"))
{
mem_type_code = ZBX_RSS; /* current resident set size (size of memory portions) */
mem_type_search = "VmRSS:\t";
}
else if (0 == strcmp(mem_type, "pmem"))
{
mem_type_code = ZBX_PMEM; /* percentage of real memory used by process */
}
else if (0 == strcmp(mem_type, "size"))
{
mem_type_code = ZBX_SIZE; /* size of process (code + data + stack) */
}
else if (0 == strcmp(mem_type, "peak"))
{
mem_type_code = ZBX_VMPEAK; /* peak virtual memory size */
mem_type_search = "VmPeak:\t";
}
else if (0 == strcmp(mem_type, "swap"))
{
mem_type_code = ZBX_VMSWAP; /* size of swap space used */
mem_type_search = "VmSwap:\t";
}
else if (0 == strcmp(mem_type, "lib"))
{
mem_type_code = ZBX_VMLIB; /* size of shared libraries */
mem_type_search = "VmLib:\t";
}
else if (0 == strcmp(mem_type, "lck"))
{
mem_type_code = ZBX_VMLCK; /* size of locked memory */
mem_type_search = "VmLck:\t";
}
else if (0 == strcmp(mem_type, "pin"))
{
mem_type_code = ZBX_VMPIN; /* size of pinned pages, they are never swappable */
mem_type_search = "VmPin:\t";
}
else if (0 == strcmp(mem_type, "hwm"))
{
mem_type_code = ZBX_VMHWM; /* peak resident set size ("high water mark") */
mem_type_search = "VmHWM:\t";
}
else if (0 == strcmp(mem_type, "data"))
{
mem_type_code = ZBX_VMDATA; /* size of data segment */
mem_type_search = "VmData:\t";
}
else if (0 == strcmp(mem_type, "stk"))
{
mem_type_code = ZBX_VMSTK; /* size of stack segment */
mem_type_search = "VmStk:\t";
}
else if (0 == strcmp(mem_type, "exe"))
{
mem_type_code = ZBX_VMEXE; /* size of text (code) segment */
mem_type_search = "VmExe:\t";
}
else if (0 == strcmp(mem_type, "pte"))
{
mem_type_code = ZBX_VMPTE; /* size of page table entries */
mem_type_search = "VmPTE:\t";
}
else
{
SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid fifth parameter."));
ret = SYSINFO_RET_FAIL;
goto clean_re;
}
if (1 == invalid_user) /* handle 0 for non-existent user after all parameters have been parsed and validated */
goto out;
if (ZBX_PMEM == mem_type_code)
{
if (SUCCEED != get_total_memory(&total_memory))
{
SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot obtain amount of total memory: %s",
zbx_strerror(errno)));
ret = SYSINFO_RET_FAIL;
goto clean_re;
}
if (0 == total_memory) /* this should never happen but anyway - avoid crash due to dividing by 0 */
{
SET_MSG_RESULT(result, zbx_strdup(NULL, "Total memory reported is 0."));
ret = SYSINFO_RET_FAIL;
goto clean_re;
}
}
if (NULL == (dir = opendir("/proc")))
{
SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot open /proc: %s", zbx_strerror(errno)));
ret = SYSINFO_RET_FAIL;
goto clean_re;
}
while (NULL != (entries = readdir(dir)))
{
zbx_fclose(f_cmd);
zbx_fclose(f_stat);
if (0 == atoi(entries->d_name))
continue;
zbx_snprintf(tmp, sizeof(tmp), "/proc/%s/cmdline", entries->d_name);
if (NULL == (f_cmd = fopen(tmp, "r")))
continue;
zbx_snprintf(tmp, sizeof(tmp), "/proc/%s/status", entries->d_name);
if (NULL == (f_stat = fopen(tmp, "r")))
continue;
if (FAIL == check_procname(f_cmd, f_stat, procname))
continue;
if (FAIL == check_user(f_stat, usrinfo))
continue;
if (FAIL == check_proccomm(f_cmd, proccomm_rxp))
continue;
rewind(f_stat);
if (0 == mem_type_tried)
mem_type_tried = 1;
switch (mem_type_code)
{
case ZBX_VSIZE:
case ZBX_RSS:
case ZBX_VMPEAK:
case ZBX_VMSWAP:
case ZBX_VMLIB:
case ZBX_VMLCK:
case ZBX_VMPIN:
case ZBX_VMHWM:
case ZBX_VMDATA:
case ZBX_VMSTK:
case ZBX_VMEXE:
case ZBX_VMPTE:
res = byte_value_from_proc_file(f_stat, mem_type_search, NULL, &byte_value);
if (NOTSUPPORTED == res)
continue;
if (FAIL == res)
{
invalid_read = 1;
goto clean;
}
break;
case ZBX_SIZE:
{
zbx_uint64_t m;
/* VmData, VmStk and VmExe follow in /proc/PID/status file in that order. */
/* Therefore we do not rewind f_stat between calls. */
mem_type_search = "VmData:\t";
if (SUCCEED == (res = byte_value_from_proc_file(f_stat, mem_type_search, NULL,
&byte_value)))
{
mem_type_search = "VmStk:\t";
if (SUCCEED == (res = byte_value_from_proc_file(f_stat, mem_type_search,
NULL, &m)))
{
byte_value += m;
mem_type_search = "VmExe:\t";
if (SUCCEED == (res = byte_value_from_proc_file(f_stat,
mem_type_search, NULL, &m)))
{
byte_value += m;
}
}
}
if (SUCCEED != res)
{
if (NOTSUPPORTED == res)
{
/* NOTSUPPORTED - at least one of data strings not found in */
/* the /proc/PID/status file */
continue;
}
else /* FAIL */
{
invalid_read = 1;
goto clean;
}
}
}
break;
case ZBX_PMEM:
mem_type_search = "VmRSS:\t";
res = byte_value_from_proc_file(f_stat, mem_type_search, NULL, &byte_value);
if (SUCCEED == res)
{
pct_value = ((double)byte_value / (double)total_memory) * 100.0;
}
else if (NOTSUPPORTED == res)
{
continue;
}
else /* FAIL */
{
invalid_read = 1;
goto clean;
}
break;
}
if (ZBX_PMEM != mem_type_code)
{
if (0 != proccount++)
{
if (ZBX_DO_MAX == do_task)
mem_size = MAX(mem_size, byte_value);
else if (ZBX_DO_MIN == do_task)
mem_size = MIN(mem_size, byte_value);
else
mem_size += byte_value;
}
else
mem_size = byte_value;
}
else
{
if (0 != proccount++)
{
if (ZBX_DO_MAX == do_task)
pct_size = MAX(pct_size, pct_value);
else if (ZBX_DO_MIN == do_task)
pct_size = MIN(pct_size, pct_value);
else
pct_size += pct_value;
}
else
pct_size = pct_value;
}
}
clean:
zbx_fclose(f_cmd);
zbx_fclose(f_stat);
closedir(dir);
if ((0 == proccount && 0 != mem_type_tried) || 0 != invalid_read)
{
char *s;
s = zbx_strdup(NULL, mem_type_search);
zbx_rtrim(s, ":\t");
SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot get amount of \"%s\" memory.", s));
zbx_free(s);
ret = SYSINFO_RET_FAIL;
goto clean_re;
}
out:
if (ZBX_PMEM != mem_type_code)
{
if (ZBX_DO_AVG == do_task)
SET_DBL_RESULT(result, 0 == proccount ? 0 : (double)mem_size / (double)proccount);
else
SET_UI64_RESULT(result, mem_size);
}
else
{
if (ZBX_DO_AVG == do_task)
SET_DBL_RESULT(result, 0 == proccount ? 0 : pct_size / (double)proccount);
else
SET_DBL_RESULT(result, pct_size);
}
clean_re:
if (NULL != proccomm_rxp)
zbx_regexp_free(proccomm_rxp);
return ret;
#undef ZBX_SIZE
#undef ZBX_RSS
#undef ZBX_VSIZE
#undef ZBX_PMEM
#undef ZBX_VMPEAK
#undef ZBX_VMSWAP
#undef ZBX_VMLIB
#undef ZBX_VMLCK
#undef ZBX_VMPIN
#undef ZBX_VMHWM
#undef ZBX_VMDATA
#undef ZBX_VMSTK
#undef ZBX_VMEXE
#undef ZBX_VMPTE
}
int proc_num(AGENT_REQUEST *request, AGENT_RESULT *result)
{
char tmp[MAX_STRING_LEN], *procname, *proccomm, *param, *rxp_error = NULL;
DIR *dir;
struct dirent *entries;
struct passwd *usrinfo;
zbx_regexp_t *proccomm_rxp = NULL;
FILE *f_cmd = NULL, *f_stat = NULL;
int proccount = 0, invalid_user = 0, zbx_proc_stat, ret = SYSINFO_RET_OK;
if (4 < request->nparam)
{
SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
ret = SYSINFO_RET_FAIL;
goto clean;
}
procname = get_rparam(request, 0);
param = get_rparam(request, 1);
if (NULL != param && '\0' != *param)
{
errno = 0;
if (NULL == (usrinfo = getpwnam(param)))
{
if (0 != errno)
{
SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot obtain user information: %s",
zbx_strerror(errno)));
ret = SYSINFO_RET_FAIL;
goto clean;
}
invalid_user = 1;
}
}
else
usrinfo = NULL;
param = get_rparam(request, 2);
if (NULL == param || '\0' == *param || 0 == strcmp(param, "all"))
zbx_proc_stat = ZBX_PROC_STAT_ALL;
else if (0 == strcmp(param, "run"))
zbx_proc_stat = ZBX_PROC_STAT_RUN;
else if (0 == strcmp(param, "sleep"))
zbx_proc_stat = ZBX_PROC_STAT_SLEEP;
else if (0 == strcmp(param, "zomb"))
zbx_proc_stat = ZBX_PROC_STAT_ZOMB;
else if (0 == strcmp(param, "disk"))
zbx_proc_stat = ZBX_PROC_STAT_DISK;
else if (0 == strcmp(param, "trace"))
zbx_proc_stat = ZBX_PROC_STAT_TRACE;
else
{
SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid third parameter."));
ret = SYSINFO_RET_FAIL;
goto clean;
}
proccomm = get_rparam(request, 3);
if (NULL != proccomm && '\0' != *proccomm)
{
if (SUCCEED != zbx_regexp_compile(proccomm, &proccomm_rxp, &rxp_error))
{
SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Invalid regular expression in fourth parameter: "
"%s", rxp_error));
zbx_free(rxp_error);
ret = SYSINFO_RET_FAIL;
goto clean;
}
}
if (1 == invalid_user) /* handle 0 for non-existent user after all parameters have been parsed and validated */
goto out;
if (NULL == (dir = opendir("/proc")))
{
SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot open /proc: %s", zbx_strerror(errno)));
ret = SYSINFO_RET_FAIL;
goto clean;
}
while (NULL != (entries = readdir(dir)))
{
zbx_fclose(f_cmd);
zbx_fclose(f_stat);
if (0 == atoi(entries->d_name))
continue;
zbx_snprintf(tmp, sizeof(tmp), "/proc/%s/cmdline", entries->d_name);
if (NULL == (f_cmd = fopen(tmp, "r")))
continue;
zbx_snprintf(tmp, sizeof(tmp), "/proc/%s/status", entries->d_name);
if (NULL == (f_stat = fopen(tmp, "r")))
continue;
if (FAIL == check_procname(f_cmd, f_stat, procname))
continue;
if (FAIL == check_user(f_stat, usrinfo))
continue;
if (FAIL == check_proccomm(f_cmd, proccomm_rxp))
continue;
if (FAIL == check_procstate(f_stat, zbx_proc_stat))
continue;
proccount++;
}
zbx_fclose(f_cmd);
zbx_fclose(f_stat);
closedir(dir);
out:
SET_UI64_RESULT(result, proccount);
clean:
if (NULL != proccomm_rxp)
zbx_regexp_free(proccomm_rxp);
return ret;
}
/******************************************************************************
* *
* Purpose: returns process name *
* *
* Parameters: pid - [IN] *
* procname - [OUT] *
* *
* Return value: SUCCEED *
* FAIL *
* *
* Comments: The process name is allocated by this function and must be freed *
* by the caller. *
* *
******************************************************************************/
static int proc_get_process_name(pid_t pid, char **procname)
{
int n, fd;
char tmp[MAX_STRING_LEN], *pend, *pstart;
zbx_snprintf(tmp, sizeof(tmp), "/proc/%d/stat", (int)pid);
if (-1 == (fd = open(tmp, O_RDONLY)))
return FAIL;
n = read(fd, tmp, sizeof(tmp));
close(fd);
if (-1 == n)
return FAIL;
for (pend = tmp + n - 1; ')' != *pend && pend > tmp; pend--)
;
*pend = '\0';
if (NULL == (pstart = strchr(tmp, '(')))
return FAIL;
*procname = zbx_strdup(NULL, pstart + 1);
return SUCCEED;
}
/******************************************************************************
* *
* Purpose: returns process command line *
* *
* Parameters: pid - [IN] *
* cmdline - [OUT] process command line *
* cmdline_nbytes - [OUT] number of bytes in command line *
* *
* Return value: SUCCEED *
* FAIL *
* *
* Comments: The command line is allocated by this function and must be freed *
* by the caller. *
* *
******************************************************************************/
static int proc_get_process_cmdline(pid_t pid, char **cmdline, size_t *cmdline_nbytes)
{
char tmp[MAX_STRING_LEN];
int fd, n;
size_t cmdline_alloc = ZBX_KIBIBYTE;
*cmdline_nbytes = 0;
zbx_snprintf(tmp, sizeof(tmp), "/proc/%d/cmdline", (int)pid);
if (-1 == (fd = open(tmp, O_RDONLY)))
return FAIL;
*cmdline = (char *)zbx_malloc(NULL, cmdline_alloc);
while (0 < (n = read(fd, *cmdline + *cmdline_nbytes, cmdline_alloc - *cmdline_nbytes)))
{
*cmdline_nbytes += n;
if (*cmdline_nbytes == cmdline_alloc)
{
cmdline_alloc *= 2;
*cmdline = (char *)zbx_realloc(*cmdline, cmdline_alloc);
}
}
close(fd);
if (0 < *cmdline_nbytes)
{
/* add terminating NUL if it is missing due to processes setting their titles or other reasons */
if ('\0' != (*cmdline)[*cmdline_nbytes - 1])
{
if (*cmdline_nbytes == cmdline_alloc)
{
cmdline_alloc += 1;
*cmdline = (char *)zbx_realloc(*cmdline, cmdline_alloc);
}
(*cmdline)[*cmdline_nbytes] = '\0';
*cmdline_nbytes += 1;
}
}
else
{
zbx_free(*cmdline);
}
return SUCCEED;
}
/******************************************************************************
* *
* Purpose: returns process user identifier *
* *
* Parameters: pid - [IN] *
* uid - [OUT] *
* *
* Return value: SUCCEED *
* FAIL *
* *
******************************************************************************/
static int proc_get_process_uid(pid_t pid, uid_t *uid)
{
char tmp[MAX_STRING_LEN];
zbx_stat_t st;
zbx_snprintf(tmp, sizeof(tmp), "/proc/%d", (int)pid);
if (0 != zbx_stat(tmp, &st))
return FAIL;
*uid = st.st_uid;
return SUCCEED;
}
/******************************************************************************
* *
* Purpose: Reads 64 bit unsigned space or zero character terminated integer *
* from a text string. *
* *
* Parameters: ptr - [IN] text string *
* value - [OUT] parsed value *
* *
* Return value: The length of the parsed text or FAIL if parsing failed. *
* *
******************************************************************************/
static int proc_read_value(const char *ptr, zbx_uint64_t *value)
{
const char *start = ptr;
int len;
while (' ' != *ptr && '\0' != *ptr)
ptr++;
len = ptr - start;
if (SUCCEED == zbx_is_uint64_n(start, len, value))
return len;
return FAIL;
}
/******************************************************************************
* *
* Purpose: Reads process cpu utilization values from /proc/[pid]/stat file. *
* *
* Parameters: procutil - [IN/OUT] process cpu utilization data *
* *
* Return value: SUCCEED - process cpu utilization data was read *
* successfully *
* <0 - otherwise, -errno code is returned *
* *
******************************************************************************/
static int proc_read_cpu_util(zbx_procstat_util_t *procutil)
{
int n, offset, fd, ret = SUCCEED;
char tmp[MAX_STRING_LEN], *ptr;
zbx_snprintf(tmp, sizeof(tmp), "/proc/%d/stat", (int)procutil->pid);
if (-1 == (fd = open(tmp, O_RDONLY)))
return -errno;
if (-1 == (n = read(fd, tmp, sizeof(tmp) - 1)))
{
ret = -errno;
goto out;
}
tmp[n] = '\0';
/* skip to the end of process name to avoid dealing with possible spaces in process name */
if (NULL == (ptr = strrchr(tmp, ')')))
{
ret = -EFAULT;
goto out;
}
n = 0;
while ('\0' != *ptr)
{
if (' ' != *ptr++)
continue;
switch (++n)
{
case 12:
if (FAIL == (offset = proc_read_value(ptr, &procutil->utime)))
{
ret = -EINVAL;
goto out;
}
ptr += offset;
break;
case 13:
if (FAIL == (offset = proc_read_value(ptr, &procutil->stime)))
{
ret = -EINVAL;
goto out;
}
ptr += offset;
break;
case 20:
if (FAIL == proc_read_value(ptr, &procutil->starttime))
{
ret = -EINVAL;
goto out;
}
goto out;
}
}
ret = -ENODATA;
out:
close(fd);
return ret;
}
/******************************************************************************
* *
* Purpose: checks if process name matches filter *
* *
******************************************************************************/
static int proc_match_name(const zbx_sysinfo_proc_t *proc, const char *procname)
{
if (NULL == procname)
return SUCCEED;
if (NULL != proc->name && 0 == strcmp(procname, proc->name))
return SUCCEED;
if (NULL != proc->name_arg0 && 0 == strcmp(procname, proc->name_arg0))
return SUCCEED;
return FAIL;
}
/******************************************************************************
* *
* Purpose: checks if process user matches filter *
* *
******************************************************************************/
static int proc_match_user(const zbx_sysinfo_proc_t *proc, const struct passwd *usrinfo)
{
if (NULL == usrinfo)
return SUCCEED;
if (proc->uid == usrinfo->pw_uid)
return SUCCEED;
return FAIL;
}
/******************************************************************************
* *
* Purpose: checks if process command line matches filter *
* *
******************************************************************************/
static int proc_match_cmdline(const zbx_sysinfo_proc_t *proc, const char *cmdline)
{
if (NULL == cmdline)
return SUCCEED;
if (NULL != proc->cmdline && NULL != zbx_regexp_match(proc->cmdline, cmdline, NULL))
return SUCCEED;
return FAIL;
}
/******************************************************************************
* *
* Purpose: gets process cpu utilization data *
* *
* Parameters: procs - [IN/OUT] array of process utilization data *
* procs_num - [IN] number of items in procs array *
* *
******************************************************************************/
void zbx_proc_get_process_stats(zbx_procstat_util_t *procs, int procs_num)
{
zabbix_log(LOG_LEVEL_TRACE, "In %s() procs_num:%d", __func__, procs_num);
for (int i = 0; i < procs_num; i++)
procs[i].error = proc_read_cpu_util(&procs[i]);
zabbix_log(LOG_LEVEL_TRACE, "End of %s()", __func__);
}
/******************************************************************************
* *
* Purpose: creates process object with specified properties *
* *
* Parameters: pid - [IN] *
* flags - [IN] flags specifying properties to set *
* *
* Return value: The created process object or NULL if property reading *
* failed. *
* *
******************************************************************************/
static zbx_sysinfo_proc_t *proc_create(int pid, unsigned int flags)
{
char *procname = NULL, *cmdline = NULL, *name_arg0 = NULL;
uid_t uid = (uid_t)-1;
zbx_sysinfo_proc_t *proc = NULL;
int ret = FAIL;
size_t cmdline_nbytes;
if (0 != (flags & ZBX_SYSINFO_PROC_USER) && SUCCEED != proc_get_process_uid(pid, &uid))
goto out;
if (0 != (flags & (ZBX_SYSINFO_PROC_CMDLINE | ZBX_SYSINFO_PROC_NAME)) &&
SUCCEED != proc_get_process_cmdline(pid, &cmdline, &cmdline_nbytes))
{
goto out;
}
if (0 != (flags & ZBX_SYSINFO_PROC_NAME) && SUCCEED != proc_get_process_name(pid, &procname))
goto out;
if (NULL != cmdline)
{
char *ptr;
unsigned int i;
if (0 != (flags & ZBX_SYSINFO_PROC_NAME))
{
if (NULL == (ptr = strrchr(cmdline, '/')))
name_arg0 = zbx_strdup(NULL, cmdline);
else
name_arg0 = zbx_strdup(NULL, ptr + 1);
}
/* according to proc(5) the arguments are separated by '\0' */
for (i = 0; i < cmdline_nbytes - 1; i++)
if ('\0' == cmdline[i])
cmdline[i] = ' ';
}
ret = SUCCEED;
out:
if (SUCCEED == ret)
{
proc = (zbx_sysinfo_proc_t *)zbx_malloc(NULL, sizeof(zbx_sysinfo_proc_t));
proc->pid = pid;
proc->uid = uid;
proc->name = procname;
proc->cmdline = cmdline;
proc->name_arg0 = name_arg0;
}
else
{
zbx_free(procname);
zbx_free(cmdline);
zbx_free(name_arg0);
}
return proc;
}
/******************************************************************************
* *
* Purpose: gets system processes *
* *
* Parameters: processes - [OUT] system processes *
* flags - [IN] flags specifying process properties that must *
* be returned *
* *
* Return value: SUCCEED - system processes were retrieved successfully *
* FAIL - failed to open /proc directory *
* *
******************************************************************************/
int zbx_proc_get_processes(zbx_vector_ptr_t *processes, unsigned int flags)
{
DIR *dir;
struct dirent *entries;
int ret = FAIL, pid;
zbx_sysinfo_proc_t *proc;
zabbix_log(LOG_LEVEL_TRACE, "In %s()", __func__);
if (NULL == (dir = opendir("/proc")))
goto out;
while (NULL != (entries = readdir(dir)))
{
/* skip entries not containing pids */
if (FAIL == zbx_is_uint32(entries->d_name, &pid))
continue;
if (NULL == (proc = proc_create(pid, flags)))
continue;
zbx_vector_ptr_append(processes, proc);
}
closedir(dir);
ret = SUCCEED;
out:
zabbix_log(LOG_LEVEL_TRACE, "End of %s(): %s, processes:%d", __func__, zbx_result_string(ret),
processes->values_num);
return ret;
}
/******************************************************************************
* *
* Purpose: frees process vector read by zbx_proc_get_processes function *
* *
* Parameters: processes - [IN/OUT] process vector to free *
* *
******************************************************************************/
void zbx_proc_free_processes(zbx_vector_ptr_t *processes)
{
zbx_vector_ptr_clear_ext(processes, (zbx_mem_free_func_t)zbx_sysinfo_proc_free);
}
/******************************************************************************
* *
* Purpose: gets pids matching specified process name, user name and *
* command line *
* *
* Parameters: processes - [IN] list of system processes *
* procname - [IN] NULL - all *
* username - [IN] ... *
* cmdline - [IN] ... *
* pids - [OUT] vector of matching pids *
* *
* Return value: SUCCEED - pids were read successfully *
* -errno - failed to read pids *
* *
******************************************************************************/
void zbx_proc_get_matching_pids(const zbx_vector_ptr_t *processes, const char *procname, const char *username,
const char *cmdline, zbx_uint64_t flags, zbx_vector_uint64_t *pids)
{
struct passwd *usrinfo;
zbx_sysinfo_proc_t *proc;
zabbix_log(LOG_LEVEL_TRACE, "In %s() procname:%s username:%s cmdline:%s flags:" ZBX_FS_UI64, __func__,
ZBX_NULL2EMPTY_STR(procname), ZBX_NULL2EMPTY_STR(username), ZBX_NULL2EMPTY_STR(cmdline), flags);
if (NULL != username)
{
/* in the case of invalid user there are no matching processes, return empty vector */
if (NULL == (usrinfo = getpwnam(username)))
goto out;
}
else
usrinfo = NULL;
for (int i = 0; i < processes->values_num; i++)
{
proc = (zbx_sysinfo_proc_t *)processes->values[i];
if (SUCCEED != proc_match_user(proc, usrinfo))
continue;
if (SUCCEED != proc_match_name(proc, procname))
continue;
if (SUCCEED != proc_match_cmdline(proc, cmdline))
continue;
zbx_vector_uint64_append(pids, (zbx_uint64_t)proc->pid);
}
out:
zabbix_log(LOG_LEVEL_TRACE, "End of %s()", __func__);
}
int proc_cpu_util(AGENT_REQUEST *request, AGENT_RESULT *result)
{
const char *procname, *username, *cmdline, *tmp;
char *errmsg = NULL;
int period, type;
double value;
zbx_timespec_t ts_timeout, ts;
/* proc.cpu.util[,,(user|system),,(avg1|avg5|avg15)] */
/* 0 1 2 3 4 */
if (5 < request->nparam)
{
SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
return SYSINFO_RET_FAIL;
}
/* zbx_procstat_get_* functions expect NULL for default values - */
/* convert empty procname, username and cmdline strings to NULL values */
if (NULL != (procname = get_rparam(request, 0)) && '\0' == *procname)
procname = NULL;
if (NULL != (username = get_rparam(request, 1)) && '\0' == *username)
username = NULL;
if (NULL != (cmdline = get_rparam(request, 3)) && '\0' == *cmdline)
cmdline = NULL;
/* utilization type parameter (user|system) */
if (NULL == (tmp = get_rparam(request, 2)) || '\0' == *tmp || 0 == strcmp(tmp, "total"))
{
type = ZBX_PROCSTAT_CPU_TOTAL;
}
else if (0 == strcmp(tmp, "user"))
{
type = ZBX_PROCSTAT_CPU_USER;
}
else if (0 == strcmp(tmp, "system"))
{
type = ZBX_PROCSTAT_CPU_SYSTEM;
}
else
{
SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid third parameter."));
return SYSINFO_RET_FAIL;
}
/* mode parameter (avg1|avg5|avg15) */
if (NULL == (tmp = get_rparam(request, 4)) || '\0' == *tmp || 0 == strcmp(tmp, "avg1"))
{
period = SEC_PER_MIN;
}
else if (0 == strcmp(tmp, "avg5"))
{
period = SEC_PER_MIN * 5;
}
else if (0 == strcmp(tmp, "avg15"))
{
period = SEC_PER_MIN * 15;
}
else
{
SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid fifth parameter."));
return SYSINFO_RET_FAIL;
}
if (SUCCEED != zbx_procstat_collector_started())
{
SET_MSG_RESULT(result, zbx_strdup(NULL, "Collector is not started."));
return SYSINFO_RET_FAIL;
}
zbx_timespec(&ts_timeout);
ts_timeout.sec += sysinfo_get_config_timeout();
while (SUCCEED != zbx_procstat_get_util(procname, username, cmdline, 0, period, type, &value, &errmsg))
{
/* zbx_procstat_get_* functions will return FAIL when either a collection */
/* error was registered or if less than 2 data samples were collected. */
/* In the first case the errmsg will contain error message. */
if (NULL != errmsg)
{
SET_MSG_RESULT(result, errmsg);
return SYSINFO_RET_FAIL;
}
zbx_timespec(&ts);
if (0 > zbx_timespec_compare(&ts_timeout, &ts))
{
SET_MSG_RESULT(result, zbx_strdup(NULL, "Timeout while waiting for collector data."));
return SYSINFO_RET_FAIL;
}
sleep(1);
}
SET_DBL_RESULT(result, value);
return SYSINFO_RET_OK;
}
static proc_data_t *proc_get_data(FILE *f_status, FILE *f_stat, int zbx_proc_mode)
{
#define READ_STATUS_VALUE_NUM(fld, type, value) \
do \
{ \
if (SUCCEED != read_value_from_proc_file(f_status, 0, fld, type, value, NULL)) \
*value = ZBX_MAX_UINT64; \
} while(0)
zbx_uint64_t val;
char buf[MAX_STRING_LEN], *ptr, *state;
int offset, n = 0;
long hz;
proc_data_t *proc_data;
proc_data = (proc_data_t *)zbx_malloc(NULL, sizeof(proc_data_t));
if (ZBX_PROC_MODE_SUMMARY != zbx_proc_mode)
READ_STATUS_VALUE_NUM("PPid", PROC_VAL_TYPE_NUM, &proc_data->ppid);
if (ZBX_PROC_MODE_THREAD != zbx_proc_mode)
{
READ_STATUS_VALUE_NUM("VmSize", PROC_VAL_TYPE_BYTE, &proc_data->vsize);
READ_STATUS_VALUE_NUM("VmLck", PROC_VAL_TYPE_BYTE, &proc_data->lck);
READ_STATUS_VALUE_NUM("VmPin", PROC_VAL_TYPE_BYTE, &proc_data->pin);
READ_STATUS_VALUE_NUM("VmData", PROC_VAL_TYPE_BYTE, &proc_data->data);
READ_STATUS_VALUE_NUM("VmStk", PROC_VAL_TYPE_BYTE, &proc_data->stk);
READ_STATUS_VALUE_NUM("VmExe", PROC_VAL_TYPE_BYTE, &proc_data->exe);
READ_STATUS_VALUE_NUM("VmLib", PROC_VAL_TYPE_BYTE, &proc_data->lib);
READ_STATUS_VALUE_NUM("VmPTE", PROC_VAL_TYPE_BYTE, &proc_data->pte);
READ_STATUS_VALUE_NUM("VmSwap", PROC_VAL_TYPE_BYTE, &proc_data->swap);
READ_STATUS_VALUE_NUM("Threads", PROC_VAL_TYPE_NUM, &proc_data->threads);
if (ZBX_MAX_UINT64 == proc_data->exe || ZBX_MAX_UINT64 == proc_data->data ||
ZBX_MAX_UINT64 == proc_data->stk)
{
proc_data->size = ZBX_MAX_UINT64;
}
else
proc_data->size = proc_data->exe + proc_data->data + proc_data->stk;
if (SUCCEED == read_value_from_proc_file(f_status, 0, "VmRSS", PROC_VAL_TYPE_BYTE, &proc_data->rss,
NULL))
{
proc_data->pmem = SUCCEED == get_total_memory(&val) && 0 != val ?
(double)proc_data->rss / (double)val * 100.0 : -1.0;
}
else
{
proc_data->rss = ZBX_MAX_UINT64;
proc_data->pmem = -1.0;
}
proc_data->tname = NULL;
}
else if (SUCCEED != read_value_from_proc_file(f_status, 0, "Name", PROC_VAL_TYPE_TEXT, NULL, &proc_data->tname))
{
proc_data->tname = NULL;
}
if (ZBX_PROC_MODE_PROCESS == zbx_proc_mode)
{
READ_STATUS_VALUE_NUM("VmPeak", PROC_VAL_TYPE_BYTE, &proc_data->peak);
READ_STATUS_VALUE_NUM("VmHWM", PROC_VAL_TYPE_BYTE, &proc_data->hwm);
}
READ_STATUS_VALUE_NUM("voluntary_ctxt_switches", PROC_VAL_TYPE_NUM, &proc_data->ctx_switches);
READ_STATUS_VALUE_NUM("nonvoluntary_ctxt_switches", PROC_VAL_TYPE_NUM, &val);
if (ZBX_MAX_UINT64 != proc_data->ctx_switches && ZBX_MAX_UINT64 != val)
proc_data->ctx_switches += val;
else if (ZBX_MAX_UINT64 != proc_data->ctx_switches)
proc_data->ctx_switches = ZBX_MAX_UINT64;
if (ZBX_PROC_MODE_SUMMARY != zbx_proc_mode && SUCCEED ==
read_value_from_proc_file(f_status, 0, "State", PROC_VAL_TYPE_TEXT, NULL, &state))
{
if (NULL != (ptr = strchr(state, '(')))
{
zbx_rtrim(++ptr, ")");
if ('\0' != *ptr)
proc_data->state = zbx_strdup(NULL, ptr);
else
proc_data->state = NULL;
zbx_free(state);
}
else
proc_data->state = state;
}
else
proc_data->state = NULL;
proc_data->page_faults = ZBX_MAX_UINT64;
proc_data->cputime_user = -1.0;
proc_data->cputime_system = -1.0;
if (NULL == fgets(buf, (int)sizeof(buf), f_stat) || NULL == (ptr = strrchr(buf, ')')))
goto out;
hz = sysconf(_SC_CLK_TCK);
while ('\0' != *ptr)
{
if (' ' != *ptr++)
continue;
switch (++n)
{
case 10:
if (FAIL == (offset = proc_read_value(ptr, &proc_data->page_faults)))
goto out;
ptr += offset;
break;
case 12:
if (0 >= hz || FAIL == (offset = proc_read_value(ptr, &val)))
goto out;
proc_data->cputime_user = (double)val / (double)hz;
ptr += offset;
break;
case 13:
if (FAIL != proc_read_value(ptr, &val))
proc_data->cputime_system = (double)val / (double)hz;
goto out;
}
}
out:
return proc_data;
#undef READ_STATUS_VALUE_NUM
}
static proc_data_t *proc_read_data(char *path, int zbx_proc_mode)
{
char tmp[MAX_STRING_LEN];
FILE *f_status, *f_stat;
proc_data_t *proc_data;
zbx_snprintf(tmp, sizeof(tmp), "%s/status", path);
if (NULL == (f_status = fopen(tmp, "r")))
return NULL;
zbx_snprintf(tmp, sizeof(tmp), "%s/stat", path);
if (NULL == (f_stat = fopen(tmp, "r")))
{
zbx_fclose(f_status);
return NULL;
}
proc_data = proc_get_data(f_status, f_stat, zbx_proc_mode);
zbx_fclose(f_status);
zbx_fclose(f_stat);
return proc_data;
}
int proc_get(AGENT_REQUEST *request, AGENT_RESULT *result)
{
#define SUM_PROC_VALUE(param) \
do \
{ \
if (ZBX_MAX_UINT64 != pdata->param && ZBX_MAX_UINT64 != pdata_cmp->param) \
pdata->param += pdata_cmp->param; \
else if (ZBX_MAX_UINT64 != pdata->param) \
pdata->param = ZBX_MAX_UINT64; \
} while(0)
#define SUM_PROC_VALUE_DBL(param) \
do \
{ \
if (0.0 <= pdata->param && 0.0 <= pdata_cmp->param) \
pdata->param += pdata_cmp->param; \
else if (0.0 <= pdata->param) \
pdata->param = -1.0; \
} while(0)
#define JSON_ADD_PROC_VALUE(name, value) \
do \
{ \
if (ZBX_MAX_UINT64 != value) \
zbx_json_adduint64(&j, name, value); \
else \
zbx_json_addint64(&j, name, -1); \
} while(0)
char *procname, *proccomm, *param, *prname = NULL, *cmdline = NULL, *user = NULL,
*group = NULL, *rxp_error = NULL;
int invalid_user = 0, zbx_proc_mode;
DIR *dir;
FILE *f_cmd = NULL, *f_status = NULL, *f_stat = NULL;
struct dirent *entries;
struct passwd *usrinfo;
struct zbx_json j;
zbx_regexp_t *proccomm_rxp = NULL;
zbx_vector_proc_data_ptr_t proc_data_ctx;
if (4 < request->nparam)
{
SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
return SYSINFO_RET_FAIL;
}
procname = get_rparam(request, 0);
param = get_rparam(request, 1);
if (NULL != param && '\0' != *param)
{
errno = 0;
if (NULL == (usrinfo = getpwnam(param)))
{
if (0 != errno)
{
SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot obtain user information: %s",
zbx_strerror(errno)));
return SYSINFO_RET_FAIL;
}
invalid_user = 1;
}
}
else
usrinfo = NULL;
proccomm = get_rparam(request, 2);
param = get_rparam(request, 3);
if (NULL == param || '\0' == *param || 0 == strcmp(param, "process"))
{
zbx_proc_mode = ZBX_PROC_MODE_PROCESS;
}
else if (0 == strcmp(param, "thread"))
{
zbx_proc_mode = ZBX_PROC_MODE_THREAD;
}
else if (0 == strcmp(param, "summary") && (NULL == proccomm || '\0' == *proccomm))
{
zbx_proc_mode = ZBX_PROC_MODE_SUMMARY;
}
else
{
SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid fourth parameter."));
return SYSINFO_RET_FAIL;
}
if (NULL != proccomm && '\0' != *proccomm)
{
if (SUCCEED != zbx_regexp_compile(proccomm, &proccomm_rxp, &rxp_error))
{
SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Invalid regular expression in third parameter: %s",
rxp_error));
zbx_free(rxp_error);
return SYSINFO_RET_FAIL;
}
}
if (1 == invalid_user)
{
zbx_json_initarray(&j, ZBX_JSON_STAT_BUF_LEN);
goto out;
}
if (NULL == (dir = opendir("/proc")))
{
SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot open /proc: %s", zbx_strerror(errno)));
if (NULL != proccomm_rxp)
zbx_regexp_free(proccomm_rxp);
return SYSINFO_RET_FAIL;
}
zbx_vector_proc_data_ptr_create(&proc_data_ctx);
while (NULL != (entries = readdir(dir)))
{
char tmp[MAX_STRING_LEN];
unsigned int pid;
zbx_uint64_t uid, gid;
size_t l;
int ret_uid;
proc_data_t *proc_data;
zbx_fclose(f_cmd);
zbx_fclose(f_status);
zbx_free(cmdline);
zbx_free(prname);
zbx_free(user);
zbx_free(group);
if (FAIL == zbx_is_uint32(entries->d_name, &pid))
continue;
zbx_snprintf(tmp, sizeof(tmp), "/proc/%s/cmdline", entries->d_name);
if (NULL == (f_cmd = fopen(tmp, "r")))
continue;
zbx_snprintf(tmp, sizeof(tmp), "/proc/%s/status", entries->d_name);
if (NULL == (f_status = fopen(tmp, "r")))
continue;
if (SUCCEED != get_cmdline(f_cmd, &cmdline, &l))
continue;
if (SUCCEED != read_value_from_proc_file(f_status, 0, "Name", PROC_VAL_TYPE_TEXT, NULL, &prname))
continue;
if ('\0' != *cmdline)
{
char *p, *pend, sep = 0;
size_t len;
if (NULL != (pend = strpbrk(cmdline, " :")))
{
sep = *pend;
*pend = '\0';
}
if (NULL == (p = strrchr(cmdline, '/')))
p = cmdline;
else
p++;
if (NULL == prname || (strlen(p) > (len = strlen(prname)) && 0 == strncmp(p, prname, len)))
prname = zbx_strdup(prname, p);
if (NULL != pend)
*pend = sep;
for (len = 0, l -= 2; len < l; len++)
{
if ('\0' == cmdline[len])
cmdline[len] = ' ';
}
}
if (NULL == prname || (NULL != procname && '\0' != *procname && 0 != strcmp(prname, procname)))
continue;
ret_uid = proc_read_id(f_status, PROC_ID_TYPE_USER, &uid);
if (NULL != usrinfo && (SUCCEED != ret_uid || usrinfo->pw_uid != uid))
continue;
if (NULL != proccomm && '\0' != *proccomm && 0 != zbx_regexp_match_precompiled(cmdline, proccomm_rxp))
continue;
if (ZBX_PROC_MODE_SUMMARY != zbx_proc_mode)
{
struct group *grp;
struct passwd *usr;
if (SUCCEED == ret_uid)
{
user = NULL != (usr = getpwuid((uid_t)uid)) ?
zbx_strdup(NULL, usr->pw_name) :
zbx_dsprintf(NULL, ZBX_FS_UI64, uid);
}
else
{
uid = ZBX_MAX_UINT64;
user = zbx_strdup(NULL, "-1");
}
if (SUCCEED == proc_read_id(f_status, PROC_ID_TYPE_GROUP, &gid))
{
gid = (zbx_uint64_t)gid;
group = NULL != (grp = getgrgid((gid_t)gid)) ?
zbx_strdup(NULL, grp->gr_name) :
zbx_dsprintf(NULL, ZBX_FS_UI64, gid);
}
else
{
gid = ZBX_MAX_UINT64;
group = zbx_strdup(NULL, "-1");
}
}
if (ZBX_PROC_MODE_THREAD == zbx_proc_mode)
{
DIR *taskdir;
zbx_snprintf(tmp, sizeof(tmp), "/proc/%s/task", entries->d_name);
if (NULL != (taskdir = opendir(tmp)))
{
struct dirent *threads;
char path[MAX_STRING_LEN];
unsigned int tid;
while (NULL != (threads = readdir(taskdir)))
{
if (FAIL == zbx_is_uint32(threads->d_name, &tid))
continue;
zbx_snprintf(path, sizeof(path), "%s/%s", tmp, threads->d_name);
if (NULL != (proc_data = proc_read_data(path, zbx_proc_mode)))
{
proc_data->pid = pid;
proc_data->tid = tid;
proc_data->cmdline = NULL;
proc_data->name = zbx_strdup(NULL, prname);
proc_data->uid = uid;
proc_data->gid = gid;
proc_data->user = zbx_strdup(NULL, user);
proc_data->group = zbx_strdup(NULL, group);
zbx_vector_proc_data_ptr_append(&proc_data_ctx, proc_data);
}
}
closedir(taskdir);
}
}
else
{
zbx_fclose(f_stat);
zbx_snprintf(tmp, sizeof(tmp), "/proc/%s/stat", entries->d_name);
if (NULL == (f_stat = fopen(tmp, "r")))
continue;
if (NULL != (proc_data = proc_get_data(f_status, f_stat, zbx_proc_mode)))
{
if (ZBX_PROC_MODE_PROCESS == zbx_proc_mode)
{
proc_data->pid = pid;
proc_data->uid = uid;
proc_data->gid = gid;
}
else
{
zbx_free(cmdline);
zbx_free(user);
zbx_free(group);
}
proc_data->name = prname;
proc_data->cmdline = cmdline;
proc_data->user = user;
proc_data->group = group;
get_pid_mem_stats(entries->d_name, &proc_data->memory);
zbx_vector_proc_data_ptr_append(&proc_data_ctx, proc_data);
cmdline = prname = user = group = NULL;
}
}
}
zbx_fclose(f_cmd);
zbx_fclose(f_status);
zbx_fclose(f_stat);
closedir(dir);
zbx_free(cmdline);
zbx_free(prname);
zbx_free(user);
zbx_free(group);
if (ZBX_PROC_MODE_SUMMARY == zbx_proc_mode)
{
for (int i = 0; i < proc_data_ctx.values_num; i++)
{
proc_data_t *pdata = proc_data_ctx.values[i];
pdata->processes = 1;
for (int k = i + 1; k < proc_data_ctx.values_num; k++)
{
proc_data_t *pdata_cmp = proc_data_ctx.values[k];
if (0 == strcmp(pdata->name, pdata_cmp->name))
{
pdata->processes++;
SUM_PROC_VALUE(vsize);
SUM_PROC_VALUE(rss);
SUM_PROC_VALUE(data);
SUM_PROC_VALUE(exe);
SUM_PROC_VALUE(lck);
SUM_PROC_VALUE(lib);
SUM_PROC_VALUE(pin);
SUM_PROC_VALUE(pte);
SUM_PROC_VALUE(size);
SUM_PROC_VALUE(stk);
SUM_PROC_VALUE(swap);
SUM_PROC_VALUE(ctx_switches);
SUM_PROC_VALUE(threads);
SUM_PROC_VALUE(page_faults);
SUM_PROC_VALUE_DBL(pmem);
SUM_PROC_VALUE_DBL(cputime_user);
SUM_PROC_VALUE_DBL(cputime_system);
SUM_PROC_VALUE(memory);
proc_data_free(pdata_cmp);
zbx_vector_proc_data_ptr_remove(&proc_data_ctx, k--);
}
}
}
}
zbx_json_initarray(&j, ZBX_JSON_STAT_BUF_LEN);
for (int i = 0; i < proc_data_ctx.values_num; i++)
{
proc_data_t *pdata;
pdata = proc_data_ctx.values[i];
zbx_json_addobject(&j, NULL);
if (ZBX_PROC_MODE_PROCESS == zbx_proc_mode)
{
zbx_json_adduint64(&j, "pid", pdata->pid);
JSON_ADD_PROC_VALUE("ppid", pdata->ppid);
zbx_json_addstring(&j, "name", pdata->name, ZBX_JSON_TYPE_STRING);
zbx_json_addstring(&j, "cmdline", pdata->cmdline, ZBX_JSON_TYPE_STRING);
zbx_json_addstring(&j, "user", pdata->user, ZBX_JSON_TYPE_STRING);
zbx_json_addstring(&j, "group", pdata->group, ZBX_JSON_TYPE_STRING);
JSON_ADD_PROC_VALUE("uid", pdata->uid);
JSON_ADD_PROC_VALUE("gid", pdata->gid);
JSON_ADD_PROC_VALUE("vsize", pdata->vsize);
zbx_json_addfloat(&j, "pmem", pdata->pmem);
JSON_ADD_PROC_VALUE("rss", pdata->rss);
JSON_ADD_PROC_VALUE("data", pdata->data);
JSON_ADD_PROC_VALUE("exe", pdata->exe);
JSON_ADD_PROC_VALUE("hwm", pdata->hwm);
JSON_ADD_PROC_VALUE("lck", pdata->lck);
JSON_ADD_PROC_VALUE("lib", pdata->lib);
JSON_ADD_PROC_VALUE("peak", pdata->peak);
JSON_ADD_PROC_VALUE("pin", pdata->pin);
JSON_ADD_PROC_VALUE("pte", pdata->pte);
JSON_ADD_PROC_VALUE("size", pdata->size);
JSON_ADD_PROC_VALUE("stk", pdata->stk);
JSON_ADD_PROC_VALUE("swap", pdata->swap);
zbx_json_addfloat(&j, "cputime_user", pdata->cputime_user);
zbx_json_addfloat(&j, "cputime_system", pdata->cputime_system);
zbx_json_addstring(&j, "state", pdata->state, ZBX_JSON_TYPE_STRING);
JSON_ADD_PROC_VALUE("ctx_switches", pdata->ctx_switches);
JSON_ADD_PROC_VALUE("threads", pdata->threads);
JSON_ADD_PROC_VALUE("page_faults", pdata->page_faults);
JSON_ADD_PROC_VALUE("pss", pdata->memory);
}
else if (ZBX_PROC_MODE_THREAD == zbx_proc_mode)
{
zbx_json_adduint64(&j, "pid", pdata->pid);
JSON_ADD_PROC_VALUE("ppid", pdata->ppid);
zbx_json_addstring(&j, "name", pdata->name, ZBX_JSON_TYPE_STRING);
zbx_json_addstring(&j, "user", pdata->user, ZBX_JSON_TYPE_STRING);
zbx_json_addstring(&j, "group", pdata->group, ZBX_JSON_TYPE_STRING);
JSON_ADD_PROC_VALUE("uid", pdata->uid);
JSON_ADD_PROC_VALUE("gid", pdata->gid);
zbx_json_adduint64(&j, "tid", pdata->tid);
zbx_json_addstring(&j, "tname", pdata->tname, ZBX_JSON_TYPE_STRING);
zbx_json_addfloat(&j, "cputime_user", pdata->cputime_user);
zbx_json_addfloat(&j, "cputime_system", pdata->cputime_system);
zbx_json_addstring(&j, "state", pdata->state, ZBX_JSON_TYPE_STRING);
JSON_ADD_PROC_VALUE("ctx_switches", pdata->ctx_switches);
JSON_ADD_PROC_VALUE("page_faults", pdata->page_faults);
}
else
{
zbx_json_addstring(&j, "name", pdata->name, ZBX_JSON_TYPE_STRING);
zbx_json_adduint64(&j, "processes", pdata->processes);
JSON_ADD_PROC_VALUE("vsize", pdata->vsize);
zbx_json_addfloat(&j, "pmem", pdata->pmem);
JSON_ADD_PROC_VALUE("rss", pdata->rss);
JSON_ADD_PROC_VALUE("data", pdata->data);
JSON_ADD_PROC_VALUE("exe", pdata->exe);
JSON_ADD_PROC_VALUE("lck", pdata->lck);
JSON_ADD_PROC_VALUE("lib", pdata->lib);
JSON_ADD_PROC_VALUE("pin", pdata->pin);
JSON_ADD_PROC_VALUE("pte", pdata->pte);
JSON_ADD_PROC_VALUE("size", pdata->size);
JSON_ADD_PROC_VALUE("stk", pdata->stk);
JSON_ADD_PROC_VALUE("swap", pdata->swap);
zbx_json_addfloat(&j, "cputime_user", pdata->cputime_user);
zbx_json_addfloat(&j, "cputime_system", pdata->cputime_system);
JSON_ADD_PROC_VALUE("ctx_switches", pdata->ctx_switches);
JSON_ADD_PROC_VALUE("threads", pdata->threads);
JSON_ADD_PROC_VALUE("page_faults", pdata->page_faults);
JSON_ADD_PROC_VALUE("pss", pdata->memory);
}
zbx_json_close(&j);
}
zbx_vector_proc_data_ptr_clear_ext(&proc_data_ctx, proc_data_free);
zbx_vector_proc_data_ptr_destroy(&proc_data_ctx);
out:
zbx_json_close(&j);
SET_STR_RESULT(result, zbx_strdup(NULL, j.buffer));
zbx_json_free(&j);
if (NULL != proccomm_rxp)
zbx_regexp_free(proccomm_rxp);
return SYSINFO_RET_OK;
#undef SUM_PROC_VALUE
#undef SUM_PROC_VALUE_DBL
#undef JSON_ADD_PROC_VALUE
}