1 /******************************************************************************
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; either version 2 of the License, or
6 (at your option) any later version.
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
13 You should have received a copy of the GNU General Public License
14 along with this program; if not, write to the Free Software
15 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 ******************************************************************************/
19 const char *progname = "check_procs";
20 const char *revision = "$Revision$";
21 const char *copyright = "2000-2003";
22 const char *email = "nagiosplug-devel@lists.sourceforge.net";
24 #include "common.h"
25 #include "popen.h"
26 #include "utils.h"
27 #include <pwd.h>
29 int process_arguments (int, char **);
30 int validate_arguments (void);
31 int check_thresholds (int);
32 void print_help (void);
33 void print_usage (void);
35 int wmax = -1;
36 int cmax = -1;
37 int wmin = -1;
38 int cmin = -1;
40 int options = 0; /* bitmask of filter criteria to test against */
41 #define ALL 1
42 #define STAT 2
43 #define PPID 4
44 #define USER 8
45 #define PROG 16
46 #define ARGS 32
47 #define VSZ 64
48 #define RSS 128
49 #define PCPU 256
51 /* Different metrics */
52 char *metric_name;
53 enum metric {
54 METRIC_PROCS,
55 METRIC_VSZ,
56 METRIC_RSS,
57 METRIC_CPU
58 };
59 enum metric metric = METRIC_PROCS;
61 int verbose = 0;
62 int uid;
63 int ppid;
64 int vsz;
65 int rss;
66 float pcpu;
67 char *statopts;
68 char *prog;
69 char *args;
70 char *fmt;
71 char *fails;
72 char tmp[MAX_INPUT_BUFFER];
78 \f
79 int
80 main (int argc, char **argv)
81 {
82 char input_buffer[MAX_INPUT_BUFFER];
84 int procuid = 0;
85 int procppid = 0;
86 int procvsz = 0;
87 int procrss = 0;
88 float procpcpu = 0;
89 char procstat[8];
90 char procprog[MAX_INPUT_BUFFER];
91 char *procargs;
92 char *temp_string;
94 const char *zombie = "Z";
96 int resultsum = 0; /* bitmask of the filter criteria met by a process */
97 int found = 0; /* counter for number of lines returned in `ps` output */
98 int procs = 0; /* counter for number of processes meeting filter criteria */
99 int pos; /* number of spaces before 'args' in `ps` output */
100 int cols; /* number of columns in ps output */
101 int expected_cols = PS_COLS - 1;
102 int warn = 0; /* number of processes in warn state */
103 int crit = 0; /* number of processes in crit state */
104 int i = 0;
106 int result = STATE_UNKNOWN;
108 setlocale (LC_ALL, "");
109 bindtextdomain (PACKAGE, LOCALEDIR);
110 textdomain (PACKAGE);
112 asprintf (&metric_name, "PROCS");
113 metric = METRIC_PROCS;
115 if (process_arguments (argc, argv) == ERROR)
116 usage (_("Unable to parse command line\n"));
118 if (verbose >= 2)
119 printf (_("CMD: %s\n"), PS_COMMAND);
121 child_process = spopen (PS_COMMAND);
122 if (child_process == NULL) {
123 printf (_("Could not open pipe: %s\n"), PS_COMMAND);
124 return STATE_UNKNOWN;
125 }
127 child_stderr = fdopen (child_stderr_array[fileno (child_process)], "r");
128 if (child_stderr == NULL)
129 printf (_("Could not open stderr for %s\n"), PS_COMMAND);
131 fgets (input_buffer, MAX_INPUT_BUFFER - 1, child_process);
133 while (fgets (input_buffer, MAX_INPUT_BUFFER - 1, child_process)) {
134 strcpy (procprog, "");
135 asprintf (&procargs, "%s", "");
137 cols = sscanf (input_buffer, PS_FORMAT, PS_VARLIST);
139 /* Zombie processes do not give a procprog command */
140 if ( cols == (expected_cols - 1) && strstr(procstat, zombie) ) {
141 cols = expected_cols;
142 /* Set some value for procargs for the strip command further below
143 Seen to be a problem on some Solaris 7 and 8 systems */
144 input_buffer[pos] = '\n';
145 input_buffer[pos+1] = 0x0;
146 }
147 if ( cols >= expected_cols ) {
148 resultsum = 0;
149 asprintf (&procargs, "%s", input_buffer + pos);
150 strip (procargs);
152 /* Some ps return full pathname for command. This removes path */
153 temp_string = strtok ((char *)procprog, "/");
154 while (temp_string) {
155 strcpy(procprog, temp_string);
156 temp_string = strtok (NULL, "/");
157 }
159 if (verbose >= 3)
160 printf ("%d %d %d %d %d %.2f %s %s %s\n",
161 procs, procuid, procvsz, procrss,
162 procppid, procpcpu, procstat, procprog, procargs);
164 /* Ignore self */
165 if (strcmp (procprog, progname) == 0) {
166 continue;
167 }
169 if ((options & STAT) && (strstr (statopts, procstat)))
170 resultsum |= STAT;
171 if ((options & ARGS) && procargs && (strstr (procargs, args) != NULL))
172 resultsum |= ARGS;
173 if ((options & PROG) && procprog && (strcmp (prog, procprog) == 0))
174 resultsum |= PROG;
175 if ((options & PPID) && (procppid == ppid))
176 resultsum |= PPID;
177 if ((options & USER) && (procuid == uid))
178 resultsum |= USER;
179 if ((options & VSZ) && (procvsz >= vsz))
180 resultsum |= VSZ;
181 if ((options & RSS) && (procrss >= rss))
182 resultsum |= RSS;
183 if ((options & PCPU) && (procpcpu >= pcpu))
184 resultsum |= PCPU;
186 found++;
188 /* Next line if filters not matched */
189 if (!(options == resultsum || options == ALL))
190 continue;
192 procs++;
194 if (metric == METRIC_VSZ)
195 i = check_thresholds (procvsz);
196 else if (metric == METRIC_RSS)
197 i = check_thresholds (procrss);
198 /* TODO? float thresholds for --metric=CPU */
199 else if (metric == METRIC_CPU)
200 i = check_thresholds ((int)procpcpu);
202 if (metric != METRIC_PROCS) {
203 if (i == STATE_WARNING) {
204 warn++;
205 }
206 if (i == STATE_CRITICAL) {
207 crit++;
208 }
209 asprintf (&fails, "%s%s%s", fails, (strcmp(fails,"") ? ", " : ""), procprog);
210 result = max_state (result, i);
211 }
212 }
213 /* This should not happen */
214 else if (verbose) {
215 printf(_("Not parseable: %s"), input_buffer);
216 }
217 }
219 /* If we get anything on STDERR, at least set warning */
220 while (fgets (input_buffer, MAX_INPUT_BUFFER - 1, child_stderr)) {
221 if (verbose)
222 printf (_("STDERR: %s"), input_buffer);
223 result = max_state (result, STATE_WARNING);
224 printf (_("System call sent warnings to stderr\n"));
225 }
227 (void) fclose (child_stderr);
229 /* close the pipe */
230 if (spclose (child_process)) {
231 printf (_("System call returned nonzero status\n"));
232 result = max_state (result, STATE_WARNING);
233 }
235 if (found == 0) { /* no process lines parsed so return STATE_UNKNOWN */
236 printf (_("Unable to read output\n"));
237 return result;
238 }
240 if ( result == STATE_UNKNOWN )
241 result = STATE_OK;
243 /* Needed if procs found, but none match filter */
244 if ( metric == METRIC_PROCS ) {
245 result = max_state (result, check_thresholds (procs) );
246 }
248 if ( result == STATE_OK ) {
249 printf ("%s %s: ", metric_name, _("OK"));
250 } else if (result == STATE_WARNING) {
251 printf ("%s %s: ", metric_name, _("WARNING"));
252 if ( metric != METRIC_PROCS ) {
253 printf (_("%d warn out of "), warn);
254 }
255 } else if (result == STATE_CRITICAL) {
256 printf ("%s %s: ", metric_name, _("CRITICAL"));
257 if (metric != METRIC_PROCS) {
258 printf (_("%d crit, %d warn out of "), crit, warn);
259 }
260 }
261 printf (ngettext ("%d process", "%d processes", procs), procs);
263 if (strcmp(fmt,"") != 0) {
264 printf (_(" with %s"), fmt);
265 }
267 if ( verbose >= 1 && strcmp(fails,"") )
268 printf (" [%s]", fails);
270 printf ("\n");
271 return result;
272 }
278 \f
279 /* process command-line arguments */
280 int
281 process_arguments (int argc, char **argv)
282 {
283 int c = 1;
284 char *user;
285 struct passwd *pw;
286 int option = 0;
287 static struct option longopts[] = {
288 {"warning", required_argument, 0, 'w'},
289 {"critical", required_argument, 0, 'c'},
290 {"metric", required_argument, 0, 'm'},
291 {"timeout", required_argument, 0, 't'},
292 {"status", required_argument, 0, 's'},
293 {"ppid", required_argument, 0, 'p'},
294 {"command", required_argument, 0, 'C'},
295 {"vsz", required_argument, 0, 'z'},
296 {"rss", required_argument, 0, 'r'},
297 {"pcpu", required_argument, 0, 'P'},
298 {"argument-array", required_argument, 0, 'a'},
299 {"help", no_argument, 0, 'h'},
300 {"version", no_argument, 0, 'V'},
301 {"verbose", no_argument, 0, 'v'},
302 {0, 0, 0, 0}
303 };
305 for (c = 1; c < argc; c++)
306 if (strcmp ("-to", argv[c]) == 0)
307 strcpy (argv[c], "-t");
309 while (1) {
310 c = getopt_long (argc, argv, "Vvht:c:w:p:s:u:C:a:z:r:m:P:",
311 longopts, &option);
313 if (c == -1 || c == EOF)
314 break;
316 switch (c) {
317 case '?': /* help */
318 print_usage ();
319 exit (STATE_UNKNOWN);
320 case 'h': /* help */
321 print_help ();
322 exit (STATE_OK);
323 case 'V': /* version */
324 print_revision (progname, revision);
325 exit (STATE_OK);
326 case 't': /* timeout period */
327 if (!is_integer (optarg))
328 usage (_("Timeout Interval must be an integer!\n\n"));
329 else
330 timeout_interval = atoi (optarg);
331 break;
332 case 'c': /* critical threshold */
333 if (is_integer (optarg))
334 cmax = atoi (optarg);
335 else if (sscanf (optarg, ":%d", &cmax) == 1)
336 break;
337 else if (sscanf (optarg, "%d:%d", &cmin, &cmax) == 2)
338 break;
339 else if (sscanf (optarg, "%d:", &cmin) == 1)
340 break;
341 else
342 usage (_("Critical Process Count must be an integer!\n\n"));
343 break;
344 case 'w': /* warning time threshold */
345 if (is_integer (optarg))
346 wmax = atoi (optarg);
347 else if (sscanf (optarg, ":%d", &wmax) == 1)
348 break;
349 else if (sscanf (optarg, "%d:%d", &wmin, &wmax) == 2)
350 break;
351 else if (sscanf (optarg, "%d:", &wmin) == 1)
352 break;
353 else
354 usage (_("%s: Warning Process Count must be an integer!\n\n"));
355 break;
356 case 'p': /* process id */
357 if (sscanf (optarg, "%d%[^0-9]", &ppid, tmp) == 1) {
358 asprintf (&fmt, "%s%sPPID = %d", (fmt ? fmt : "") , (options ? ", " : ""), ppid);
359 options |= PPID;
360 break;
361 }
362 printf (_("%s: Parent Process ID must be an integer!\n\n"),
363 progname);
364 print_usage ();
365 exit (STATE_UNKNOWN);
366 case 's': /* status */
367 if (statopts)
368 break;
369 else
370 statopts = optarg;
371 asprintf (&fmt, _("%s%sSTATE = %s"), (fmt ? fmt : ""), (options ? ", " : ""), statopts);
372 options |= STAT;
373 break;
374 case 'u': /* user or user id */
375 if (is_integer (optarg)) {
376 uid = atoi (optarg);
377 pw = getpwuid ((uid_t) uid);
378 /* check to be sure user exists */
379 if (pw == NULL) {
380 printf (_("UID %d was not found\n"), uid);
381 print_usage ();
382 exit (STATE_UNKNOWN);
383 }
384 }
385 else {
386 pw = getpwnam (optarg);
387 /* check to be sure user exists */
388 if (pw == NULL) {
389 printf (_("User name %s was not found\n"), optarg);
390 print_usage ();
391 exit (STATE_UNKNOWN);
392 }
393 /* then get uid */
394 uid = pw->pw_uid;
395 }
396 user = pw->pw_name;
397 asprintf (&fmt, _("%s%sUID = %d (%s)"), (fmt ? fmt : ""), (options ? ", " : ""),
398 uid, user);
399 options |= USER;
400 break;
401 case 'C': /* command */
402 if (prog)
403 break;
404 else
405 prog = optarg;
406 asprintf (&fmt, _("%s%scommand name '%s'"), (fmt ? fmt : ""), (options ? ", " : ""),
407 prog);
408 options |= PROG;
409 break;
410 case 'a': /* args (full path name with args) */
411 if (args)
412 break;
413 else
414 args = optarg;
415 asprintf (&fmt, _("%s%sargs '%s'"), (fmt ? fmt : ""), (options ? ", " : ""), args);
416 options |= ARGS;
417 break;
418 case 'r': /* RSS */
419 if (sscanf (optarg, "%d%[^0-9]", &rss, tmp) == 1) {
420 asprintf (&fmt, _("%s%sRSS >= %d"), (fmt ? fmt : ""), (options ? ", " : ""), rss);
421 options |= RSS;
422 break;
423 }
424 printf (_("%s: RSS must be an integer!\n\n"),
425 progname);
426 print_usage ();
427 exit (STATE_UNKNOWN);
428 case 'z': /* VSZ */
429 if (sscanf (optarg, "%d%[^0-9]", &vsz, tmp) == 1) {
430 asprintf (&fmt, _("%s%sVSZ >= %d"), (fmt ? fmt : ""), (options ? ", " : ""), vsz);
431 options |= VSZ;
432 break;
433 }
434 printf (_("%s: VSZ must be an integer!\n\n"),
435 progname);
436 print_usage ();
437 exit (STATE_UNKNOWN);
438 case 'P': /* PCPU */
439 /* TODO: -P 1.5.5 is accepted */
440 if (sscanf (optarg, "%f%[^0-9.]", &pcpu, tmp) == 1) {
441 asprintf (&fmt, _("%s%sPCPU >= %.2f"), (fmt ? fmt : ""), (options ? ", " : ""), pcpu);
442 options |= PCPU;
443 break;
444 }
445 printf (_("%s: PCPU must be a float!\n\n"),
446 progname);
447 print_usage ();
448 exit (STATE_UNKNOWN);
449 case 'm':
450 asprintf (&metric_name, "%s", optarg);
451 if ( strcmp(optarg, "PROCS") == 0) {
452 metric = METRIC_PROCS;
453 break;
454 }
455 else if ( strcmp(optarg, "VSZ") == 0) {
456 metric = METRIC_VSZ;
457 break;
458 }
459 else if ( strcmp(optarg, "RSS") == 0 ) {
460 metric = METRIC_RSS;
461 break;
462 }
463 else if ( strcmp(optarg, "CPU") == 0 ) {
464 metric = METRIC_CPU;
465 break;
466 }
467 printf (_("%s: metric must be one of PROCS, VSZ, RSS, CPU!\n\n"),
468 progname);
469 print_usage ();
470 exit (STATE_UNKNOWN);
471 case 'v': /* command */
472 verbose++;
473 break;
474 }
475 }
477 c = optind;
478 if (wmax == -1 && argv[c])
479 wmax = atoi (argv[c++]);
480 if (cmax == -1 && argv[c])
481 cmax = atoi (argv[c++]);
482 if (statopts == NULL && argv[c]) {
483 asprintf (&statopts, "%s", argv[c++]);
484 asprintf (&fmt, _("%s%sSTATE = %s"), (fmt ? fmt : ""), (options ? ", " : ""), statopts);
485 options |= STAT;
486 }
488 return validate_arguments ();
489 }
494 int
495 validate_arguments ()
496 {
498 if (wmax >= 0 && wmin == -1)
499 wmin = 0;
500 if (cmax >= 0 && cmin == -1)
501 cmin = 0;
502 if (wmax >= wmin && cmax >= cmin) { /* standard ranges */
503 if (wmax > cmax && cmax != -1) {
504 printf (_("wmax (%d) cannot be greater than cmax (%d)\n"), wmax, cmax);
505 return ERROR;
506 }
507 if (cmin > wmin && wmin != -1) {
508 printf (_("wmin (%d) cannot be less than cmin (%d)\n"), wmin, cmin);
509 return ERROR;
510 }
511 }
513 /* if (wmax == -1 && cmax == -1 && wmin == -1 && cmin == -1) { */
514 /* printf ("At least one threshold must be set\n"); */
515 /* return ERROR; */
516 /* } */
518 if (options == 0)
519 options = ALL;
521 if (statopts==NULL)
522 statopts = strdup("");
524 if (prog==NULL)
525 prog = strdup("");
527 if (args==NULL)
528 args = strdup("");
530 if (fmt==NULL)
531 fmt = strdup("");
533 if (fails==NULL)
534 fails = strdup("");
536 return options;
537 }
543 \f
544 /* Check thresholds against value */
545 int
546 check_thresholds (int value)
547 {
548 if (wmax == -1 && cmax == -1 && wmin == -1 && cmin == -1) {
549 return OK;
550 }
551 else if (cmax >= 0 && cmin >= 0 && cmax < cmin) {
552 if (value > cmax && value < cmin)
553 return STATE_CRITICAL;
554 }
555 else if (cmax >= 0 && value > cmax) {
556 return STATE_CRITICAL;
557 }
558 else if (cmin >= 0 && value < cmin) {
559 return STATE_CRITICAL;
560 }
562 if (wmax >= 0 && wmin >= 0 && wmax < wmin) {
563 if (value > wmax && value < wmin) {
564 return STATE_WARNING;
565 }
566 }
567 else if (wmax >= 0 && value > wmax) {
568 return STATE_WARNING;
569 }
570 else if (wmin >= 0 && value < wmin) {
571 return STATE_WARNING;
572 }
573 return STATE_OK;
574 }
580 \f
581 void
582 print_help (void)
583 {
584 print_revision (progname, revision);
586 printf (_("Copyright (c) 1999 Ethan Galstad <nagios@nagios.org>"));
587 printf (_(COPYRIGHT), copyright, email);
589 printf(_("\
590 Checks all processes and generates WARNING or CRITICAL states if the specified\n\
591 metric is outside the required threshold ranges. The metric defaults to number\n\
592 of processes. Search filters can be applied to limit the processes to check.\n\n"));
594 print_usage ();
596 printf(_("\n\
597 Required Arguments:\n\
598 -w, --warning=RANGE\n\
599 Generate warning state if metric is outside this range\n\
600 -c, --critical=RANGE\n\
601 Generate critical state if metric is outside this range\n"));
603 printf(_("\n\
604 Optional Arguments:\n\
605 -m, --metric=TYPE\n\
606 Check thresholds against metric. Valid types:\n\
607 PROCS - number of processes (default)\n\
608 VSZ - virtual memory size\n\
609 RSS - resident set memory size\n\
610 CPU - percentage cpu\n\
611 -v, --verbose\n\
612 Extra information. Up to 3 verbosity levels\n"));
614 printf(_("\n\
615 Optional Filters:\n\
616 -s, --state=STATUSFLAGS\n\
617 Only scan for processes that have, in the output of `ps`, one or\n\
618 more of the status flags you specify (for example R, Z, S, RS,\n\
619 RSZDT, plus others based on the output of your 'ps' command).\n\
620 -p, --ppid=PPID\n\
621 Only scan for children of the parent process ID indicated.\n\
622 -z, --vsz=VSZ\n\
623 Only scan for processes with vsz higher than indicated.\n\
624 -r, --rss=RSS\n\
625 Only scan for processes with rss higher than indicated.\n"));
627 printf(_("\
628 -P, --pcpu=PCPU\n\
629 Only scan for processes with pcpu higher than indicated.\n\
630 -u, --user=USER\n\
631 Only scan for processes with user name or ID indicated.\n\
632 -a, --argument-array=STRING\n\
633 Only scan for processes with args that contain STRING.\n\
634 -C, --command=COMMAND\n\
635 Only scan for exact matches of COMMAND (without path).\n"));
637 printf(_("\n\
638 RANGEs are specified 'min:max' or 'min:' or ':max' (or 'max'). If\n\
639 specified 'max:min', a warning status will be generated if the\n\
640 count is inside the specified range\n\n"));
642 printf(_("\
643 This plugin checks the number of currently running processes and\n\
644 generates WARNING or CRITICAL states if the process count is outside\n\
645 the specified threshold ranges. The process count can be filtered by\n\
646 process owner, parent process PID, current state (e.g., 'Z'), or may\n\
647 be the total number of running processes\n\n"));
649 printf(_("\
650 Examples:\n\
651 check_procs -w 2:2 -c 2:1024 -C portsentry\n\
652 Warning if not two processes with command name portsentry. Critical\n\
653 if < 2 or > 1024 processes\n\n\
654 check_procs -w 10 -a '/usr/local/bin/perl' -u root\n\
655 Warning alert if > 10 processes with command arguments containing \n\
656 '/usr/local/bin/perl' and owned by root\n\n\
657 check_procs -w 50000 -c 100000 --metric=VSZ\n\
658 Alert if vsz of any processes over 50K or 100K\n\n"));
660 printf (_(UT_SUPPORT));
661 }
663 void
664 print_usage (void)
665 {
666 printf ("\
667 Usage: %s -w <range> -c <range> [-m metric] [-s state] [-p ppid]\n\
668 [-u user] [-r rss] [-z vsz] [-P %%cpu] [-a argument-array]\n\
669 [-C command] [-v]\n", progname);
670 printf (_(UT_HLP_VRS), progname, progname);
671 }