/* ** Copyright (C) 2001-2025 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 <https://www.gnu.org/licenses/>. **/ #include "zbxsysinfo.h" #include "../sysinfo.h" #include "zbxjson.h" #include "zbxalgo.h" #include "zbxstr.h" #include "zbxwin32.h" #include <tlhelp32.h> #include <sddl.h> /* ConvertSidToStringSid */ #define MAX_NAME 256 typedef struct { unsigned int pid; unsigned int ppid; unsigned int tid; char *name; zbx_uint64_t processes; zbx_uint64_t threads; zbx_int64_t handles; char *user; char *sid; double cputime_user; double cputime_system; double page_faults; double io_read_b; double io_write_b; double io_other_b; double io_read_op; double io_write_op; double io_other_op; double vmsize; double wkset; } proc_data_t; ZBX_PTR_VECTOR_DECL(proc_data_ptr, proc_data_t *) ZBX_PTR_VECTOR_IMPL(proc_data_ptr, proc_data_t *) /* function 'zbx_get_process_username' require 'userName' with size 'MAX_NAME' */ static int zbx_get_process_username(HANDLE hProcess, char *userName, char *sid) { HANDLE tok; TOKEN_USER *ptu = NULL; DWORD sz = 0, nlen, dlen; wchar_t name[MAX_NAME], dom[MAX_NAME], *sid_string; int iUse, res = FAIL; /* clean result; */ *userName = '\0'; if (NULL != sid) *sid = '\0'; /* open the processes token */ if (0 == OpenProcessToken(hProcess, TOKEN_QUERY, &tok)) return res; /* Get required buffer size and allocate the TOKEN_USER buffer */ if (0 == GetTokenInformation(tok, (TOKEN_INFORMATION_CLASS)1, (LPVOID)ptu, 0, &sz)) { if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) goto lbl_err; ptu = (PTOKEN_USER)zbx_malloc(ptu, sz); } /* Get the token user information from the access token. */ if (0 == GetTokenInformation(tok, (TOKEN_INFORMATION_CLASS)1, (LPVOID)ptu, sz, &sz)) goto lbl_err; /* get the account/domain name of the SID */ nlen = MAX_NAME; dlen = MAX_NAME; if (0 == LookupAccountSid(NULL, ptu->User.Sid, name, &nlen, dom, &dlen, (PSID_NAME_USE)&iUse)) goto lbl_err; if (NULL != sid) { if (TRUE == ConvertSidToStringSid(ptu->User.Sid, &sid_string)) { zbx_unicode_to_utf8_static(sid_string, sid, MAX_NAME); LocalFree(sid_string); } else goto lbl_err; } zbx_unicode_to_utf8_static(name, userName, MAX_NAME); res = SUCCEED; lbl_err: zbx_free(ptu); CloseHandle(tok); return res; } int proc_num(AGENT_REQUEST *request, AGENT_RESULT *result) { HANDLE hProcessSnap, hProcess; PROCESSENTRY32 pe32; DWORD access; const OSVERSIONINFOEX *vi; int proccount, proc_ok; char *procName, *userName, baseName[MAX_PATH], uname[MAX_NAME]; if (2 < request->nparam) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters.")); return SYSINFO_RET_FAIL; } procName = get_rparam(request, 0); userName = get_rparam(request, 1); if (INVALID_HANDLE_VALUE == (hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0))) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot obtain system information.")); return SYSINFO_RET_FAIL; } if (NULL == (vi = zbx_win_getversion())) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot retrieve system version.")); return SYSINFO_RET_FAIL; } if (6 > vi->dwMajorVersion) { /* PROCESS_QUERY_LIMITED_INFORMATION is not supported on Windows Server 2003 and XP */ access = PROCESS_QUERY_INFORMATION; } else access = PROCESS_QUERY_LIMITED_INFORMATION; pe32.dwSize = sizeof(PROCESSENTRY32); if (FALSE == Process32First(hProcessSnap, &pe32)) { CloseHandle(hProcessSnap); SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot obtain system information.")); return SYSINFO_RET_FAIL; } proccount = 0; do { proc_ok = 1; if (NULL != procName && '\0' != *procName) { zbx_unicode_to_utf8_static(pe32.szExeFile, baseName, MAX_NAME); if (0 != stricmp(baseName, procName)) proc_ok = 0; } if (0 != proc_ok && NULL != userName && '\0' != *userName) { hProcess = OpenProcess(access, FALSE, pe32.th32ProcessID); if (NULL == hProcess || SUCCEED != zbx_get_process_username(hProcess, uname, NULL) || 0 != stricmp(uname, userName)) { proc_ok = 0; } if (NULL != hProcess) CloseHandle(hProcess); } if (0 != proc_ok) proccount++; } while (TRUE == Process32Next(hProcessSnap, &pe32)); CloseHandle(hProcessSnap); SET_UI64_RESULT(result, proccount); return SYSINFO_RET_OK; } /************ PROC INFO ****************/ /* * Convert process time from FILETIME structure (100-nanosecond units) to double (milliseconds) */ static double ConvertProcessTime(FILETIME *lpft) { /* Convert 100-nanosecond units to milliseconds */ return (double)((((__int64)lpft->dwHighDateTime << 32) | lpft->dwLowDateTime) / 10000); } /* * Get specific process attribute */ static int GetProcessAttribute(HANDLE hProcess, int attr, int type, int count, double *lastValue) { double value; PROCESS_MEMORY_COUNTERS mc; IO_COUNTERS ioCounters; FILETIME ftCreate, ftExit, ftKernel, ftUser; /* Get value for current process instance */ switch (attr) { case 0: /* vmsize */ GetProcessMemoryInfo(hProcess, &mc, sizeof(PROCESS_MEMORY_COUNTERS)); value = (double)mc.PagefileUsage / 1024; /* Convert to Kbytes */ break; case 1: /* wkset */ GetProcessMemoryInfo(hProcess, &mc, sizeof(PROCESS_MEMORY_COUNTERS)); value = (double)mc.WorkingSetSize / 1024; /* Convert to Kbytes */ break; case 2: /* pf */ GetProcessMemoryInfo(hProcess, &mc, sizeof(PROCESS_MEMORY_COUNTERS)); value = (double)mc.PageFaultCount; break; case 3: /* ktime */ case 4: /* utime */ GetProcessTimes(hProcess, &ftCreate, &ftExit, &ftKernel, &ftUser); value = ConvertProcessTime(3 == attr ? &ftKernel : &ftUser); break; case 5: /* gdiobj */ case 6: /* userobj */ if (NULL == zbx_get_GetGuiResources()) return SYSINFO_RET_FAIL; value = (double)(*zbx_get_GetGuiResources())(hProcess, 5 == attr ? 0 : 1); break; case 7: /* io_read_b */ if (NULL == zbx_get_GetProcessIoCounters()) return SYSINFO_RET_FAIL; (*zbx_get_GetProcessIoCounters())(hProcess, &ioCounters); value = (double)((__int64)ioCounters.ReadTransferCount); break; case 8: /* io_read_op */ if (NULL == zbx_get_GetProcessIoCounters()) return SYSINFO_RET_FAIL; (*zbx_get_GetProcessIoCounters())(hProcess, &ioCounters); value = (double)((__int64)ioCounters.ReadOperationCount); break; case 9: /* io_write_b */ if (NULL == zbx_get_GetProcessIoCounters()) return SYSINFO_RET_FAIL; (*zbx_get_GetProcessIoCounters())(hProcess, &ioCounters); value = (double)((__int64)ioCounters.WriteTransferCount); break; case 10: /* io_write_op */ if (NULL == zbx_get_GetProcessIoCounters()) return SYSINFO_RET_FAIL; (*zbx_get_GetProcessIoCounters())(hProcess, &ioCounters); value = (double)((__int64)ioCounters.WriteOperationCount); break; case 11: /* io_other_b */ if (NULL == zbx_get_GetProcessIoCounters()) return SYSINFO_RET_FAIL; (*zbx_get_GetProcessIoCounters())(hProcess, &ioCounters); value = (double)((__int64)ioCounters.OtherTransferCount); break; case 12: /* io_other_op */ if (NULL == zbx_get_GetProcessIoCounters()) return SYSINFO_RET_FAIL; (*zbx_get_GetProcessIoCounters())(hProcess, &ioCounters); value = (double)((__int64)ioCounters.OtherOperationCount); break; default: /* Unknown attribute */ return SYSINFO_RET_FAIL; } /* Recalculate final value according to selected type */ switch (type) { case 0: /* min */ if (0 == count || value < *lastValue) *lastValue = value; break; case 1: /* max */ if (0 == count || value > *lastValue) *lastValue = value; break; case 2: /* avg */ *lastValue = (*lastValue * count + value) / (count + 1); break; case 3: /* sum */ *lastValue += value; break; default: return SYSINFO_RET_FAIL; } return SYSINFO_RET_OK; } /* * Get process-specific information * Parameter has the following syntax: * proc_info[<process>,<attribute>,<type>] * where * <process> - process name (same as in proc_cnt[] parameter) * <attribute> - requested process attribute (see documentation for list of valid attributes) * <type> - representation type (meaningful when more than one process with the same * name exists). Valid values are: * min - minimal value among all processes named <process> * max - maximal value among all processes named <process> * avg - average value for all processes named <process> * sum - sum of values for all processes named <process> */ int proc_info(AGENT_REQUEST *request, AGENT_RESULT *result) { HANDLE hProcessSnap, hProcess; PROCESSENTRY32 pe32; char *proc_name, *attr, *type, baseName[MAX_PATH]; const char *attrList[] = {"vmsize", "wkset", "pf", "ktime", "utime", "gdiobj", "userobj", "io_read_b", "io_read_op", "io_write_b", "io_write_op", "io_other_b", "io_other_op", NULL}, *typeList[] = {"min", "max", "avg", "sum", NULL}; double value; DWORD access; const OSVERSIONINFOEX *vi; int counter, attr_id, type_id, ret = SYSINFO_RET_OK; if (3 < request->nparam) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters.")); return SYSINFO_RET_FAIL; } proc_name = get_rparam(request, 0); attr = get_rparam(request, 1); type = get_rparam(request, 2); if (NULL == proc_name || '\0' == *proc_name) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid first parameter.")); return SYSINFO_RET_FAIL; } /* Get attribute code from string */ if (NULL == attr || '\0' == *attr) { for (attr_id = 0; NULL != attrList[attr_id] && 0 != strcmp(attrList[attr_id], "vmsize"); attr_id++) ; } else { for (attr_id = 0; NULL != attrList[attr_id] && 0 != strcmp(attrList[attr_id], attr); attr_id++) ; } if (NULL == attrList[attr_id]) /* Unsupported attribute */ { SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid second parameter.")); return SYSINFO_RET_FAIL; } /* Get type code from string */ if (NULL == type || '\0' == *type) { for (type_id = 0; NULL != typeList[type_id] && 0 != strcmp(typeList[type_id], "avg"); type_id++) ; } else { for (type_id = 0; NULL != typeList[type_id] && 0 != strcmp(typeList[type_id], type); type_id++) ; } if (NULL == typeList[type_id]) /* Unsupported type */ { SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid third parameter.")); return SYSINFO_RET_FAIL; } if (INVALID_HANDLE_VALUE == (hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0))) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot obtain system information.")); return SYSINFO_RET_FAIL; } if (NULL == (vi = zbx_win_getversion())) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot retrieve system version.")); return SYSINFO_RET_FAIL; } if (6 > vi->dwMajorVersion) { /* PROCESS_QUERY_LIMITED_INFORMATION is not supported on Windows Server 2003 and XP */ access = PROCESS_QUERY_INFORMATION; } else access = PROCESS_QUERY_LIMITED_INFORMATION; pe32.dwSize = sizeof(PROCESSENTRY32); if (FALSE == Process32First(hProcessSnap, &pe32)) { CloseHandle(hProcessSnap); SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot obtain system information.")); return SYSINFO_RET_FAIL; } counter = 0; value = 0; do { zbx_unicode_to_utf8_static(pe32.szExeFile, baseName, MAX_NAME); if (0 == stricmp(baseName, proc_name)) { if (NULL != (hProcess = OpenProcess(access, FALSE, pe32.th32ProcessID))) { ret = GetProcessAttribute(hProcess, attr_id, type_id, counter++, &value); CloseHandle(hProcess); if (SYSINFO_RET_OK != ret) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot obtain process information.")); break; } } } } while (TRUE == Process32Next(hProcessSnap, &pe32)); CloseHandle(hProcessSnap); if (SYSINFO_RET_OK == ret) SET_DBL_RESULT(result, value); else SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot obtain process information.")); return ret; } static void proc_data_free(proc_data_t *proc_data) { zbx_free(proc_data->name); zbx_free(proc_data->user); zbx_free(proc_data->sid); zbx_free(proc_data); } int proc_get(AGENT_REQUEST *request, AGENT_RESULT *result) { #define SUM_PROC_VALUE_DBL(param) \ do \ { \ if (0.0 <= proc_data->param && 0.0 <= pdata_cmp->param) \ proc_data->param += pdata_cmp->param; \ else if (0.0 <= proc_data->param) \ proc_data->param = -1.0; \ } while(0) int zbx_proc_mode; struct zbx_json j; HANDLE hProcessSnap, hThreadSnap; PROCESSENTRY32 pe32; DWORD access; const OSVERSIONINFOEX *vi; char *param, *procName, *userName, *procComm, baseName[MAX_PATH], uname[MAX_NAME], sid[MAX_NAME]; proc_data_t *proc_data; 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); userName = get_rparam(request, 1); procComm = get_rparam(request, 2); param = get_rparam(request, 3); if (NULL != procComm && '\0' != *procComm) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid third parameter.")); return SYSINFO_RET_FAIL; } 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")) { zbx_proc_mode = ZBX_PROC_MODE_SUMMARY; } else { SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid fourth parameter.")); return SYSINFO_RET_FAIL; } if (NULL == (vi = zbx_win_getversion())) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot retrieve system version.")); return SYSINFO_RET_FAIL; } if (6 > vi->dwMajorVersion) { /* PROCESS_QUERY_LIMITED_INFORMATION is not supported on Windows Server 2003 and XP */ access = PROCESS_QUERY_INFORMATION; } else access = PROCESS_QUERY_LIMITED_INFORMATION; if (INVALID_HANDLE_VALUE == (hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0))) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot obtain system information.")); return SYSINFO_RET_FAIL; } pe32.dwSize = sizeof(PROCESSENTRY32); if (FALSE == Process32First(hProcessSnap, &pe32) || (ZBX_PROC_MODE_THREAD == zbx_proc_mode && INVALID_HANDLE_VALUE == (hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0)))) { CloseHandle(hProcessSnap); SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot obtain system information.")); return SYSINFO_RET_FAIL; } zbx_vector_proc_data_ptr_create(&proc_data_ctx); do { HANDLE hProcess; int ret_usr; zbx_unicode_to_utf8_static(pe32.szExeFile, baseName, MAX_NAME); if (NULL != procName && '\0' != *procName && 0 != stricmp(baseName, procName)) continue; if (NULL == (hProcess = OpenProcess(access, FALSE, pe32.th32ProcessID))) continue; ret_usr = zbx_get_process_username(hProcess, uname, sid); if (NULL != userName && '\0' != *userName && (SUCCEED != ret_usr || 0 != stricmp(uname, userName))) goto next; if (ZBX_PROC_MODE_THREAD == zbx_proc_mode) { THREADENTRY32 te32; te32.dwSize = sizeof(THREADENTRY32); if (FALSE == Thread32First(hThreadSnap, &te32)) goto next; do { if (te32.th32OwnerProcessID == pe32.th32ProcessID) { proc_data = (proc_data_t *)zbx_malloc(NULL, sizeof(proc_data_t)); proc_data->pid = pe32.th32ProcessID; proc_data->ppid = pe32.th32ParentProcessID; proc_data->name = zbx_strdup(NULL, baseName); proc_data->tid = te32.th32ThreadID; proc_data->sid = zbx_strdup(NULL, SUCCEED == ret_usr ? sid : "-1"); proc_data->user = zbx_strdup(NULL, SUCCEED == ret_usr ? uname : "-1"); zbx_vector_proc_data_ptr_append(&proc_data_ctx, proc_data); } } while (TRUE == Thread32Next(hThreadSnap, &te32)); } else { DWORD handleCount; PROCESS_MEMORY_COUNTERS mc; IO_COUNTERS ioCounters; FILETIME ftCreate, ftExit, ftKernel, ftUser; proc_data = (proc_data_t *)zbx_malloc(NULL, sizeof(proc_data_t)); proc_data->pid = pe32.th32ProcessID; proc_data->ppid = pe32.th32ParentProcessID; proc_data->name = zbx_strdup(NULL, baseName); proc_data->threads = pe32.cntThreads; proc_data->sid = zbx_strdup(NULL, SUCCEED == ret_usr ? sid : "-1"); proc_data->user = zbx_strdup(NULL, SUCCEED == ret_usr ? uname : "-1"); if (FALSE != GetProcessHandleCount(hProcess, &handleCount)) proc_data->handles = (zbx_uint64_t)handleCount; else proc_data->handles = -1; if (FALSE != GetProcessMemoryInfo(hProcess, &mc, sizeof(PROCESS_MEMORY_COUNTERS))) { proc_data->vmsize = (double)mc.PagefileUsage / 1024; proc_data->wkset = (double)mc.WorkingSetSize / 1024; proc_data->page_faults = (double)mc.PageFaultCount; } else proc_data->vmsize = proc_data->wkset = proc_data->page_faults = -1.0; if (FALSE != GetProcessTimes(hProcess, &ftCreate, &ftExit, &ftKernel, &ftUser)) { proc_data->cputime_system = ConvertProcessTime(&ftKernel) / 1000.0; proc_data->cputime_user = ConvertProcessTime(&ftUser) / 1000.0; } else proc_data->cputime_system = proc_data->cputime_user = -1.0; if (NULL != zbx_get_GetProcessIoCounters() && FALSE != (*zbx_get_GetProcessIoCounters())(hProcess, &ioCounters)) { proc_data->io_read_b = (double)((__int64)ioCounters.ReadTransferCount); proc_data->io_read_op = (double)((__int64)ioCounters.ReadOperationCount); proc_data->io_write_b = (double)((__int64)ioCounters.WriteTransferCount); proc_data->io_write_op = (double)((__int64)ioCounters.WriteOperationCount); proc_data->io_other_b = (double)((__int64)ioCounters.OtherTransferCount); proc_data->io_other_op = (double)((__int64)ioCounters.OtherOperationCount); } else { proc_data->io_read_b = proc_data->io_read_op = proc_data->io_write_b = proc_data->io_write_op = proc_data->io_other_b = proc_data->io_other_op = -1.0; } zbx_vector_proc_data_ptr_append(&proc_data_ctx, proc_data); } next: CloseHandle(hProcess); } while (TRUE == Process32Next(hProcessSnap, &pe32)); if (ZBX_PROC_MODE_THREAD == zbx_proc_mode) CloseHandle(hThreadSnap); CloseHandle(hProcessSnap); if (ZBX_PROC_MODE_SUMMARY == zbx_proc_mode) { for (int i = 0; i < proc_data_ctx.values_num; i++) { proc_data = proc_data_ctx.values[i]; proc_data->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(proc_data->name, pdata_cmp->name)) { proc_data->processes++; proc_data->threads += pdata_cmp->threads; SUM_PROC_VALUE_DBL(vmsize); SUM_PROC_VALUE_DBL(wkset); SUM_PROC_VALUE_DBL(cputime_user); SUM_PROC_VALUE_DBL(cputime_system); SUM_PROC_VALUE_DBL(page_faults); SUM_PROC_VALUE_DBL(io_read_b); SUM_PROC_VALUE_DBL(io_write_b); SUM_PROC_VALUE_DBL(io_other_b); SUM_PROC_VALUE_DBL(io_read_op); SUM_PROC_VALUE_DBL(io_write_op); SUM_PROC_VALUE_DBL(io_other_op); if (0 <= proc_data->handles && 0 <= pdata_cmp->handles) proc_data->handles += pdata_cmp->handles; else if (0 <= proc_data->handles) proc_data->handles = -1; 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 = proc_data_ctx.values[i]; zbx_json_addobject(&j, NULL); if (ZBX_PROC_MODE_SUMMARY != zbx_proc_mode) { zbx_json_adduint64(&j, "pid", proc_data->pid); zbx_json_adduint64(&j, "ppid", proc_data->ppid); } zbx_json_addstring(&j, "name", proc_data->name, ZBX_JSON_TYPE_STRING); if (ZBX_PROC_MODE_SUMMARY != zbx_proc_mode) { zbx_json_addstring(&j, "user", proc_data->user, ZBX_JSON_TYPE_STRING); zbx_json_addstring(&j, "sid", proc_data->sid, ZBX_JSON_TYPE_STRING); } else zbx_json_adduint64(&j, "processes", proc_data->processes); if (ZBX_PROC_MODE_THREAD != zbx_proc_mode) { zbx_json_addint64(&j, "vmsize", (zbx_uint64_t)proc_data->vmsize); zbx_json_addint64(&j, "wkset", (zbx_uint64_t)proc_data->wkset); zbx_json_addfloat(&j, "cputime_user", proc_data->cputime_user); zbx_json_addfloat(&j, "cputime_system", proc_data->cputime_system); zbx_json_adduint64(&j, "threads", proc_data->threads); zbx_json_addint64(&j, "page_faults", (zbx_uint64_t)proc_data->page_faults); zbx_json_addint64(&j, "handles", (zbx_uint64_t)proc_data->handles); zbx_json_addint64(&j, "io_read_b", (zbx_uint64_t)proc_data->io_read_b); zbx_json_addint64(&j, "io_write_b", (zbx_uint64_t)proc_data->io_write_b); zbx_json_addint64(&j, "io_read_op", (zbx_uint64_t)proc_data->io_read_op); zbx_json_addint64(&j, "io_write_op", (zbx_uint64_t)proc_data->io_write_op); zbx_json_addint64(&j, "io_other_b", (zbx_uint64_t)proc_data->io_other_b); zbx_json_addint64(&j, "io_other_op", (zbx_uint64_t)proc_data->io_other_op); } else zbx_json_adduint64(&j, "tid", proc_data->tid); zbx_json_close(&j); } 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); SET_STR_RESULT(result, zbx_strdup(NULL, j.buffer)); zbx_json_free(&j); return SYSINFO_RET_OK; #undef SUM_PROC_VALUE_DBL }