fixed issue 342
[public/netxms.git] / src / agent / subagents / linux / proc.cpp
CommitLineData
5039dede
AK
1/*
2** NetXMS subagent for GNU/Linux
3** Copyright (C) 2004 Alex Kirhenshtein
4**
5** This program is free software; you can redistribute it and/or modify
6** it under the terms of the GNU General Public License as published by
7** the Free Software Foundation; either version 2 of the License, or
8** (at your option) any later version.
9**
10** This program is distributed in the hope that it will be useful,
11** but WITHOUT ANY WARRANTY; without even the implied warranty of
12** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13** GNU General Public License for more details.
14**
15** You should have received a copy of the GNU General Public License
16** along with this program; if not, write to the Free Software
17** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18**
19**/
20
3ed83850 21#include "linux_subagent.h"
5039dede 22
aa0fd637
VK
23/**
24 * Filter for reading only pid directories from /proc
25 */
5039dede
AK
26static int ProcFilter(const struct dirent *pEnt)
27{
28 char *pTmp;
29
30 if (pEnt == NULL)
31 {
32 return 0; // ignore file
33 }
34
35 pTmp = (char *)pEnt->d_name;
36 while (*pTmp != 0)
37 {
38 if (*pTmp < '0' || *pTmp > '9')
39 {
40 return 0; // skip
41 }
42 pTmp++;
43 }
44
45 return 1;
46}
47
aa0fd637
VK
48/**
49 * Read process information from /proc system
50 * Parameters:
51 * pEnt - If not NULL, ProcRead() will return pointer to dynamically
52 * allocated array of process information structures for
53 * matched processes. Caller should free it with free().
54 * szProcName - If not NULL, only processes with matched name will
55 * be counted and read. If szCmdLine is NULL, then exact
56 * match required to pass filter; otherwise szProcName can
57 * be a regular expression.
58 * szCmdLine - If not NULL, only processes with command line matched to
59 * regular expression will be counted and read.
60 * Return value: number of matched processes or -1 in case of error.
61 */
5039dede
AK
62int ProcRead(PROC_ENT **pEnt, char *szProcName, char *szCmdLine)
63{
64 struct dirent **pNameList;
65 int nCount, nFound;
66 BOOL bProcFound, bCmdFound;
5039dede
AK
67
68 nFound = -1;
906befb5
VK
69 if (pEnt != NULL)
70 *pEnt = NULL;
9459538e 71 AgentWriteDebugLog(5, _T("ProcRead(%p,\"%hs\",\"%hs\")"), pEnt, CHECK_NULL_A(szProcName), CHECK_NULL_A(szCmdLine));
5039dede
AK
72
73 nCount = scandir("/proc", &pNameList, &ProcFilter, alphasort);
74 // if everything is null we can simply return nCount!!!
75 if (nCount < 0 )
76 {
77 //perror("scandir");
78 }
79 else if( pEnt == NULL && szProcName == NULL && szCmdLine == NULL ) // get process count, we can skip long loop
80 {
81 nFound = nCount;
82 // clean up
83 while(nCount--)
84 {
85 free(pNameList[nCount]);
86 }
87 free(pNameList);
88 }
89 else
90 {
91 nFound = 0;
92
93 if (nCount > 0 && pEnt != NULL)
94 {
95 *pEnt = (PROC_ENT *)malloc(sizeof(PROC_ENT) * (nCount + 1));
96
97 if (*pEnt == NULL)
98 {
99 nFound = -1;
100 // cleanup
101 while(nCount--)
102 {
103 free(pNameList[nCount]);
104 }
105 }
106 else
107 {
108 memset(*pEnt, 0, sizeof(PROC_ENT) * (nCount + 1));
109 }
110 }
111
112 while(nCount--)
113 {
114 bProcFound = bCmdFound = FALSE;
115 char szFileName[512];
116 FILE *hFile;
3ed83850 117 char szProcStat[1024] = {0};
5039dede 118 char szBuff[1024] = {0};
3ed83850 119 char *pProcName = NULL, *pProcStat = NULL;
5039dede
AK
120 unsigned long nPid;
121
122 snprintf(szFileName, sizeof(szFileName),
123 "/proc/%s/stat", pNameList[nCount]->d_name);
124 hFile = fopen(szFileName, "r");
125 if (hFile != NULL)
126 {
3ed83850 127 if (fgets(szProcStat, sizeof(szProcStat), hFile) != NULL)
5039dede 128 {
3ed83850 129 if (sscanf(szProcStat, "%lu ", &nPid) == 1)
5039dede 130 {
3ed83850 131 pProcName = strchr(szProcStat, ')');
5039dede
AK
132 if (pProcName != NULL)
133 {
3ed83850
VK
134 pProcStat = pProcName + 1;
135 *pProcName = 0;
136 pProcName = strchr(szProcStat, '(');
5039dede
AK
137 if (pProcName != NULL)
138 {
139 pProcName++;
3ed83850 140 if ((szProcName != NULL) && (*szProcName != 0))
5039dede
AK
141 {
142 if (szCmdLine == NULL) // use old style compare
3ed83850 143 bProcFound = strcmp(pProcName, szProcName) == 0;
5039dede 144 else
b847803c 145 bProcFound = RegexpMatchA(pProcName, szProcName, FALSE);
5039dede
AK
146 }
147 else
148 {
149 bProcFound = TRUE;
150 }
151 }
152 } // pProcName != NULL
153 }
154 } // fgets
155 fclose(hFile);
156 } // hFile
157
3ed83850 158 if ((szCmdLine != NULL) && (*szCmdLine != 0))
5039dede
AK
159 {
160 snprintf(szFileName, sizeof(szFileName),
161 "/proc/%s/cmdline", pNameList[nCount]->d_name);
162 hFile = fopen(szFileName, "r");
163 if (hFile != NULL)
164 {
3ed83850
VK
165 char processCmdLine[1024];
166 memset(processCmdLine, 0, sizeof(processCmdLine));
5039dede 167
3ed83850 168 int len = fread(processCmdLine, 1, sizeof(processCmdLine) - 1, hFile);
5039dede
AK
169 if (len > 0) // got a valid record in format: argv[0]\x00argv[1]\x00...
170 {
171 int j;
3ed83850 172 //char *pArgs;
5039dede 173
3ed83850
VK
174 /* Commented out by victor: to behave identicaly on different platforms,
175 argv[0] should be matched as well
5039dede
AK
176 j = strlen(szBuff) + 1;
177 pArgs = szBuff + j; // skip first (argv[0])
3ed83850 178 len -= j;*/
5039dede 179 // replace 0x00 with spaces
3ed83850 180 for(j = 0; j < len - 1; j++)
5039dede 181 {
3ed83850 182 if (processCmdLine[j] == 0)
5039dede 183 {
3ed83850 184 processCmdLine[j] = ' ';
5039dede
AK
185 }
186 }
b847803c 187 bCmdFound = RegexpMatchA(processCmdLine, szCmdLine, TRUE);
5039dede
AK
188 }
189 else
190 {
b847803c 191 bCmdFound = RegexpMatchA("", szCmdLine, TRUE);
5039dede
AK
192 }
193 fclose(hFile);
194 } // hFile != NULL
195 else
196 {
b847803c 197 bCmdFound = RegexpMatchA("", szCmdLine, TRUE);
5039dede
AK
198 }
199 } // szCmdLine
200 else
201 {
202 bCmdFound = TRUE;
203 }
204
205 if (bProcFound && bCmdFound)
206 {
3ed83850 207 if (pEnt != NULL && pProcName != NULL)
5039dede
AK
208 {
209 (*pEnt)[nFound].nPid = nPid;
b847803c 210 strncpy((*pEnt)[nFound].szProcName, pProcName, sizeof((*pEnt)[nFound].szProcName));
3ed83850
VK
211
212 // Parse rest of /proc/pid/stat file
213 if (pProcStat != NULL)
214 {
215 if (sscanf(pProcStat, " %c %d %d %*d %*d %*d %*u %lu %*u %lu %*u %lu %lu %*u %*u %*d %*d %ld %*d %*u %lu %ld ",
216 &(*pEnt)[nFound].state, &(*pEnt)[nFound].parent, &(*pEnt)[nFound].group,
217 &(*pEnt)[nFound].minflt, &(*pEnt)[nFound].majflt,
218 &(*pEnt)[nFound].utime, &(*pEnt)[nFound].ktime, &(*pEnt)[nFound].threads,
219 &(*pEnt)[nFound].vmsize, &(*pEnt)[nFound].rss) != 10)
220 {
b847803c 221 AgentWriteDebugLog(2, _T("Error parsing /proc/%d/stat"), nPid);
3ed83850
VK
222 }
223 }
5039dede 224 }
3ed83850 225 nFound++;
5039dede
AK
226 }
227
228 free(pNameList[nCount]);
229 }
230 free(pNameList);
231 }
232
906befb5
VK
233 if ((nFound < 0) && (pEnt != NULL))
234 {
235 safe_free(*pEnt);
236 *pEnt = NULL;
237 }
238
5039dede
AK
239 return nFound;
240}
241
aa0fd637
VK
242/**
243 * Handler for System.ProcessCount, Process.Count() and Process.CountEx() parameters
244 */
b847803c 245LONG H_ProcessCount(const TCHAR *pszParam, const TCHAR *pArg, TCHAR *pValue)
3ed83850
VK
246{
247 int nRet = SYSINFO_RC_ERROR;
248 char szArg[128] = "", szCmdLine[128] = "";
249 int nCount = -1;
5039dede 250
b847803c 251 if (*pArg != _T('T'))
3ed83850 252 {
b847803c
VK
253 AgentGetParameterArgA(pszParam, 1, szArg, sizeof(szArg));
254 if (*pArg == _T('E'))
3ed83850 255 {
b847803c 256 AgentGetParameterArgA(pszParam, 2, szCmdLine, sizeof(szCmdLine));
3ed83850
VK
257 }
258 }
5039dede 259
b847803c 260 nCount = ProcRead(NULL, (*pArg != _T('T')) ? szArg : NULL, (*pArg == _T('E')) ? szCmdLine : NULL);
5039dede 261
3ed83850
VK
262 if (nCount >= 0)
263 {
264 ret_int(pValue, nCount);
265 nRet = SYSINFO_RC_SUCCESS;
266 }
5039dede 267
3ed83850
VK
268 return nRet;
269}
5039dede 270
aa0fd637
VK
271/**
272 * Handler for System.ThreadCount parameter
273 */
b847803c 274LONG H_ThreadCount(const TCHAR *param, const TCHAR *arg, TCHAR *value)
3ed83850
VK
275{
276 int i, sum, count, ret = SYSINFO_RC_ERROR;
277 PROC_ENT *procList;
5039dede 278
3ed83850
VK
279 count = ProcRead(&procList, NULL, NULL);
280 if (count >= 0)
281 {
282 for(i = 0, sum = 0; i < count; i++)
283 sum += procList[i].threads;
284 ret_int(value, sum);
285 ret = SYSINFO_RC_SUCCESS;
906befb5 286 free(procList);
3ed83850
VK
287 }
288
289 return ret;
290}
291
aa0fd637
VK
292/**
293 * Handler for Process.xxx() parameters
294 * Parameter has the following syntax:
295 * Process.XXX(<process>,<type>,<cmdline>)
296 * where
297 * XXX - requested process attribute (see documentation for list of valid attributes)
298 * <process> - process name (same as in Process.Count() parameter)
299 * <type> - representation type (meaningful when more than one process with the same
300 * name exists). Valid values are:
301 * min - minimal value among all processes named <process>
302 * max - maximal value among all processes named <process>
303 * avg - average value for all processes named <process>
304 * sum - sum of values for all processes named <process>
305 * <cmdline> - command line
306 */
b847803c 307LONG H_ProcessDetails(const TCHAR *param, const TCHAR *arg, TCHAR *value)
3ed83850
VK
308{
309 int i, count, type;
3ed83850
VK
310 INT64 currVal, finalVal;
311 PROC_ENT *procList;
312 char procName[MAX_PATH], cmdLine[MAX_PATH], buffer[256];
313 static const char *typeList[]={ "min", "max", "avg", "sum", NULL };
314
315 // Get parameter type arguments
b847803c 316 AgentGetParameterArgA(param, 2, buffer, 256);
3ed83850
VK
317 if (buffer[0] == 0) // Omited type
318 {
319 type = INFOTYPE_SUM;
320 }
321 else
322 {
323 for(type = 0; typeList[type] != NULL; type++)
324 if (!stricmp(typeList[type], buffer))
325 break;
326 if (typeList[type] == NULL)
327 return SYSINFO_RC_UNSUPPORTED; // Unsupported type
328 }
329
c6491611 330 // Get process name
b847803c
VK
331 AgentGetParameterArgA(param, 1, procName, MAX_PATH);
332 AgentGetParameterArgA(param, 3, cmdLine, MAX_PATH);
333 StrStripA(cmdLine);
3ed83850
VK
334
335 count = ProcRead(&procList, procName, (cmdLine[0] != 0) ? cmdLine : NULL);
b847803c 336 AgentWriteDebugLog(5, _T("H_ProcessDetails(\"%hs\"): ProcRead() returns %d"), param, count);
3ed83850
VK
337 if (count == -1)
338 return SYSINFO_RC_ERROR;
339
aa0fd637
VK
340 long pageSize = getpagesize();
341 long ticksPerSecond = sysconf(_SC_CLK_TCK);
3ed83850
VK
342 for(i = 0, finalVal = 0; i < count; i++)
343 {
344 switch(CAST_FROM_POINTER(arg, int))
345 {
346 case PROCINFO_CPUTIME:
347 currVal = (procList[i].ktime + procList[i].utime) * 1000 / ticksPerSecond;
348 break;
349 case PROCINFO_KTIME:
350 currVal = procList[i].ktime * 1000 / ticksPerSecond;
351 break;
352 case PROCINFO_UTIME:
353 currVal = procList[i].utime * 1000 / ticksPerSecond;
354 break;
355 case PROCINFO_PAGEFAULTS:
356 currVal = procList[i].majflt + procList[i].minflt;
357 break;
358 case PROCINFO_THREADS:
359 currVal = procList[i].threads;
360 break;
361 case PROCINFO_VMSIZE:
362 currVal = procList[i].vmsize;
363 break;
364 case PROCINFO_WKSET:
365 currVal = procList[i].rss * pageSize;
366 break;
367 default:
368 currVal = 0;
369 break;
370 }
371
372 switch(type)
373 {
374 case INFOTYPE_SUM:
375 case INFOTYPE_AVG:
376 finalVal += currVal;
377 break;
378 case INFOTYPE_MIN:
379 finalVal = min(currVal, finalVal);
380 break;
381 case INFOTYPE_MAX:
382 finalVal = max(currVal, finalVal);
383 break;
384 }
385 }
386
387 safe_free(procList);
388 if (type == INFOTYPE_AVG)
389 finalVal /= count;
390
391 ret_int64(value, finalVal);
392 return SYSINFO_RC_SUCCESS;
393}
5039dede 394
aa0fd637
VK
395/**
396 * Handler for System.ProcessList list
397 */
398LONG H_ProcessList(const TCHAR *pszParam, const TCHAR *pArg, StringList *pValue)
399{
400 int nRet = SYSINFO_RC_ERROR;
401
402 PROC_ENT *plist;
403 int nCount = ProcRead(&plist, NULL, NULL);
404 if (nCount >= 0)
405 {
406 nRet = SYSINFO_RC_SUCCESS;
407
408 for(int i = 0; i < nCount; i++)
409 {
410 TCHAR szBuff[128];
411 _sntprintf(szBuff, sizeof(szBuff), _T("%d %hs"), plist[i].nPid, plist[i].szProcName);
412 pValue->add(szBuff);
413 }
414 }
415 safe_free(plist);
416 return nRet;
417}
418
419/**
420 * Handler for System.Processes table
421 */
422LONG H_ProcessTable(const TCHAR *cmd, const TCHAR *arg, Table *value)
423{
424 value->addColumn(_T("PID"), DCI_DT_UINT, _T("PID"), true);
425 value->addColumn(_T("NAME"), DCI_DT_STRING, _T("Name"));
426 value->addColumn(_T("THREADS"), DCI_DT_UINT, _T("Threads"));
427 value->addColumn(_T("KTIME"), DCI_DT_UINT64, _T("Kernel Time"));
428 value->addColumn(_T("UTIME"), DCI_DT_UINT64, _T("User Time"));
429 value->addColumn(_T("VMSIZE"), DCI_DT_UINT64, _T("VM Size"));
430 value->addColumn(_T("RSS"), DCI_DT_UINT64, _T("RSS"));
431 value->addColumn(_T("PAGE_FAULTS"), DCI_DT_UINT64, _T("Page Faults"));
432 value->addColumn(_T("CMDLINE"), DCI_DT_STRING, _T("Command Line"));
433
434 int rc = SYSINFO_RC_ERROR;
435
436 PROC_ENT *plist;
437 int nCount = ProcRead(&plist, NULL, NULL);
438 if (nCount >= 0)
439 {
440 rc = SYSINFO_RC_SUCCESS;
441
c88761c1
VK
442 UINT64 pageSize = getpagesize();
443 UINT64 ticksPerSecond = sysconf(_SC_CLK_TCK);
aa0fd637
VK
444 for(int i = 0; i < nCount; i++)
445 {
446 value->addRow();
447 value->set(0, plist[i].nPid);
448#ifdef UNICODE
449 value->setPreallocated(1, WideStringFromMBString(plist[i].szProcName));
450#else
451 value->set(1, plist[i].szProcName);
452#endif
c88761c1
VK
453 value->set(2, (UINT32)plist[i].threads);
454 value->set(3, (UINT64)plist[i].ktime * 1000 / ticksPerSecond);
455 value->set(4, (UINT64)plist[i].utime * 1000 / ticksPerSecond);
456 value->set(5, (UINT64)plist[i].vmsize);
aa0fd637 457 value->set(6, (UINT64)plist[i].rss * pageSize);
c88761c1 458 value->set(7, (UINT64)plist[i].minflt + (UINT64)plist[i].majflt);
aa0fd637
VK
459 }
460 }
461 safe_free(plist);
462 return rc;
463}