1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package it.jnrpe.plugin;
17
18 import it.jnrpe.ICommandLine;
19 import it.jnrpe.ReturnValue;
20 import it.jnrpe.Status;
21 import it.jnrpe.plugin.utils.ShellUtils;
22 import it.jnrpe.plugins.PluginBase;
23 import it.jnrpe.plugins.annotations.Option;
24 import it.jnrpe.plugins.annotations.Plugin;
25 import it.jnrpe.plugins.annotations.PluginOptions;
26 import it.jnrpe.utils.BadThresholdException;
27 import it.jnrpe.utils.ThresholdUtil;
28
29 import java.io.ByteArrayOutputStream;
30 import java.io.InputStream;
31 import java.math.BigDecimal;
32 import java.util.ArrayList;
33 import java.util.HashMap;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Map.Entry;
37 import java.util.regex.Pattern;
38
39 import org.apache.commons.io.IOUtils;
40
41
42
43
44
45
46
47 @Plugin(name = "CHECK_PROCS", description = "Checks system processes and does check against metrics. Default metrics is number of processes.")
48 @PluginOptions({
49
50 @Option(shortName = "w", longName = "warning", description = "Warning value if metric is out of range", required = false, hasArgs = true, argName = "warning", optionalArgs = false, option = "warning"),
51 @Option(shortName = "c", longName = "critical", description = "Critical value if metric is out of range", required = false, hasArgs = true, argName = "critical", optionalArgs = false, option = "critical"),
52
53 @Option(shortName = "m", longName = "metric", description = "Metric type. Valid values are: PROCS - number of processes"
54 + "; VSZ - virtual memory size (unix only); RSS - resident set memory size (unix only); MEM - memory usage in KB (Windows only); CPU - CPU percentage; "
55 + "ELAPSED - elapsed time in seconds (unix only)", required = false, hasArgs = true, argName = "metric", optionalArgs = false, option = "metric"),
56
57 @Option(shortName = "a", longName = "argument-array", description = "Only scan for processes with args that contain STRING. (unix only). Use instead of ereg-argument-array.", required = false, hasArgs = true, argName = "argument-array", optionalArgs = false, option = "argument-array"),
58 @Option(shortName = "e", longName = "ereg-argument-array", description = "Only scan for processes with args that contain the regex. (unix only). Use instead of argument-array.", required = false, hasArgs = true, argName = "ereg-argument-array", optionalArgs = false, option = "ereg-argument-array"),
59
60 @Option(shortName = "p", longName = "ppid", description = "Only scan for children of the parent process ID indicated (unix only).", required = false, hasArgs = true, argName = "ppid", optionalArgs = false, option = "ppid"),
61
62 @Option(shortName = "z", longName = "vsz", description = "Only scan for processes with VSZ higher than indicated (unix only).", required = false, hasArgs = true, argName = "vsz", optionalArgs = false, option = "vsz"),
63 @Option(shortName = "r", longName = "rss", description = "Only scan for processes with RSS higher than indicated (unix only).", required = false, hasArgs = true, argName = "rss", optionalArgs = false, option = "rss"),
64 @Option(shortName = "M", longName = "memory", description = "Only scan for processes with memory usage higher than indicated (windows only).", required = false, hasArgs = true, argName = "memory", optionalArgs = false, option = "memory"),
65
66 @Option(shortName = "C", longName = "command", description = "Only scan for exact matches of COMMAND (without path).", required = false, hasArgs = true, argName = "command", optionalArgs = false, option = "command"),
67 @Option(shortName = "u", longName = "user", description = "Only scan for exact matches of USER", required = false, hasArgs = true, argName = "user", optionalArgs = false, option = "user") })
68 public class CheckProcs extends PluginBase {
69
70 private final static int SECONDS_IN_MINUTE = 60;
71 private final static int SECONDS_IN_HOUR = SECONDS_IN_MINUTE * 60;
72 private final static int SECONDS_IN_DAY = SECONDS_IN_HOUR * 24;
73
74 private final static String[] DEFAULT_WINDOWS_CMD = new String[] { "cmd",
75 "/C", "tasklist /FO CSV /V" };
76
77 private final static String[] DEFAULT_UNIX_CMD = new String[] { "/bin/ps",
78 "-eo", "comm,pid,ppid,user,c,rss,vsz,time,args" };
79
80 private final static String METRIC_PROCS = "PROCS";
81
82 private final static String METRIC_RSS = "RSS";
83
84 private final static String METRIC_VSZ = "VSZ";
85
86 private final static String METRIC_CPU = "CPU";
87
88 private final static String METRIC_ELAPSED = "ELAPSED";
89
90 private final static String METRIC_MEMORY = "MEMORY";
91
92 private final static String FILTER_COMMAND = "command";
93
94 private final static String FILTER_PPID = "ppid";
95
96 private final static String FILTER_VSZ = "vsz";
97
98 private final static String FILTER_RSS = "rss";
99
100 private final static String FILTER_USER = "user";
101
102 private final static String FILTER_ARG_ARRAY = "argument-array";
103
104 private final static String FILTER_EREG_ARG_ARRAY = "ereg-argument-array";
105
106 private final static String FILTER_MEMORY = "memory";
107
108 private final static String[] FILTERS = new String[] { FILTER_COMMAND,
109 FILTER_PPID, FILTER_VSZ, FILTER_RSS, FILTER_USER, FILTER_ARG_ARRAY,
110 FILTER_EREG_ARG_ARRAY };
111
112 private final static String[] UNIX_ONLY = new String[] { FILTER_ARG_ARRAY,
113 FILTER_RSS, FILTER_PPID, FILTER_VSZ, FILTER_EREG_ARG_ARRAY,
114
115 };
116
117
118
119 @Override
120 protected String getPluginName() {
121 return "CHECK_PROCS";
122 }
123
124
125
126
127
128
129
130
131 @Override
132 public ReturnValue execute(final ICommandLine cl)
133 throws BadThresholdException {
134 boolean windows = ShellUtils.isWindows();
135 try {
136 String metric = cl.getOptionValue("metric");
137 if (metric == null) {
138 metric = METRIC_PROCS;
139 }
140 metric = metric.toUpperCase();
141
142 validateArguments(cl, windows, metric);
143
144 String output = exec(!windows);
145
146 List<Map<String, String>> result = windows ? parseWindowsOutput(output)
147 : parseUnixOutput(output);
148 return analyze(result, cl, metric);
149 } catch (Exception e) {
150 e.printStackTrace();
151 throw new BadThresholdException(e);
152 }
153 }
154
155
156
157
158
159
160
161
162
163
164 private void validateArguments(ICommandLine cl, boolean windows,
165 String metric) throws Exception {
166 if (windows) {
167 if (metric.equals(METRIC_VSZ) || metric.equals(METRIC_RSS)
168 || metric.endsWith(METRIC_ELAPSED)) {
169 throw new Exception("Metric " + metric
170 + " not supported in Wndows.");
171 } else {
172 for (String opt : UNIX_ONLY) {
173 if (cl.getOptionValue("opt") != null) {
174 throw new Exception("Option " + opt
175 + " is not supported in Windows.");
176 }
177 }
178 }
179 } else {
180 if (metric.equals(METRIC_MEMORY)) {
181 throw new Exception("Metric " + metric
182 + " not supported in unix.");
183 }
184 }
185 }
186
187
188
189
190
191
192
193
194
195 private ReturnValue analyze(List<Map<String, String>> output,
196 ICommandLine cl, String metric) {
197 Map<String, String> filterAndValue = getFilterAndValue(cl);
198 output = applyFilters(output, filterAndValue);
199 String message = getMessage(filterAndValue);
200 String critical = cl.getOptionValue("critical");
201 String warning = cl.getOptionValue("warning");
202
203 ReturnValue retVal = null;
204 try {
205 if (metric.equals(METRIC_PROCS)) {
206 retVal = analyzeProcMetrics(output, cl, critical, warning,
207 message);
208 } else {
209 retVal = analyzeMetrics(output, cl, critical, warning, message,
210 metric);
211 }
212 } catch (Exception e) {
213 e.printStackTrace();
214 }
215 return retVal;
216 }
217
218 private ReturnValue analyzeProcMetrics(List<Map<String, String>> output,
219 ICommandLine cl, String critical, String warning, String message)
220 throws Exception {
221 int size = output.size();
222 if (critical != null) {
223 if (ThresholdUtil.isValueInRange(critical, new BigDecimal(size))) {
224 return new ReturnValue(Status.CRITICAL, "PROCS CRITICAL: "
225 + message + " " + size + " processes.");
226 }
227 }
228 if (warning != null) {
229 if (ThresholdUtil.isValueInRange(warning, new BigDecimal(size))) {
230 return new ReturnValue(Status.WARNING, "PROCS WARNING: "
231 + message + " " + size + " processes.");
232 }
233 }
234 return new ReturnValue(Status.OK, "PROCS OK: " + message + " " + size
235 + " processes.");
236 }
237
238
239
240
241
242
243
244
245
246
247
248
249 private ReturnValue analyzeMetrics(List<Map<String, String>> output,
250 ICommandLine cl, String critical, String warning, String message,
251 String metric) throws Exception {
252
253 if (critical != null) {
254 int checkCritical = compareMetric(output, critical,
255 metric.toUpperCase());
256 if (checkCritical > 0) {
257 return new ReturnValue(Status.CRITICAL, metric.toUpperCase()
258 + " CRITCAL: " + message + " "
259 + (output.size() - checkCritical) + " critical out of "
260 + output.size() + " processes.");
261 }
262 }
263 if (warning != null) {
264 int checkWarning = compareMetric(output, warning, metric);
265 if (checkWarning > 0) {
266 return new ReturnValue(Status.WARNING, metric.toUpperCase()
267 + " WARNING: " + message + " "
268 + (output.size() - checkWarning) + " warning out of "
269 + output.size() + " processes.");
270 }
271 }
272 return new ReturnValue(Status.OK, metric.toUpperCase() + " OK: "
273 + message + " " + output.size() + " processes.");
274 }
275
276 @SuppressWarnings("deprecation")
277 private int compareMetric(List<Map<String, String>> output, String value,
278 String metric) throws BadThresholdException {
279 List<Map<String, String>> list = new ArrayList<Map<String, String>>();
280 for (Map<String, String> values : output) {
281 int procValue = Integer.parseInt(values.get(metric.toLowerCase()));
282 if (ThresholdUtil.isValueInRange(value, procValue)) {
283 list.add(values);
284 }
285 }
286 return list.size();
287 }
288
289
290
291
292
293
294
295 private String getMessage(Map<String, String> filterAndValue) {
296
297 String msgString = "";
298 if (!filterAndValue.isEmpty()) {
299 StringBuilder msg = new StringBuilder();
300 msg.append("with ");
301
302 for (Entry<String, String> entry : filterAndValue.entrySet()) {
303 msg.append(entry.getKey()).append(" = ")
304 .append(entry.getValue()).append(", ");
305 }
306
307 msgString = msg.toString().trim();
308 if (msgString.endsWith(", ")) {
309 msgString = msgString.substring(0, msgString.length() - 2);
310 }
311 }
312 return msgString;
313 }
314
315
316
317
318
319
320
321 private String exec(boolean unix) throws Exception {
322 String output = null;
323 InputStream input = null;
324 String[] command = null;
325 if (unix) {
326
327 command = DEFAULT_UNIX_CMD;
328 Process p = Runtime.getRuntime().exec(command);
329 input = p.getInputStream();
330
331 ByteArrayOutputStream bout = new ByteArrayOutputStream();
332 IOUtils.copy(input, bout);
333 output = getFormattedOutput(new String(bout.toByteArray(), "UTF-8"));
334 input.close();
335
336 } else {
337 command = DEFAULT_WINDOWS_CMD;
338 output = ShellUtils.executeSystemCommandAndGetOutput(command,
339 "CP437");
340 }
341
342 return output;
343 }
344
345
346
347
348 private String getFormattedOutput(String output) {
349 String out = "";
350 StringBuilder lines = new StringBuilder();
351 String[] splittedLines = output.split("\n");
352 for (int i = 0; i < splittedLines.length; i++) {
353 if (i == 0) {
354 continue;
355 }
356 String splittedLine = splittedLines[i];
357 if (splittedLine.contains("<defunct>")) {
358 continue;
359 }
360
361 String line = splittedLine.replaceAll("\\s+", ",");
362
363 lines.append(line).append('\n');
364 }
365 out = lines.toString();
366 return out;
367 }
368
369
370
371
372
373
374
375
376 private List<Map<String, String>> parseWindowsOutput(String output) {
377 List<Map<String, String>> info = new ArrayList<Map<String, String>>();
378 String[] lines = output.split("\n");
379
380 int totalRunTime = 0;
381 String cpu = METRIC_CPU.toLowerCase();
382 int count = 0;
383 for (String l : lines) {
384 if (count == 0) {
385 count++;
386 continue;
387 }
388
389 Map<String, String> values = new HashMap<String, String>();
390 String[] line = l.replaceAll("\"", "").split(",");
391 values.put(FILTER_COMMAND, line[0]);
392 values.put("pid", line[1]);
393 values.put(FILTER_MEMORY, "" + convertToMemoryInt(line[4]));
394 values.put(FILTER_USER, line[6]);
395 int seconds = 0;
396 if (!ShellUtils.isWindowsIdleProc(line[0])) {
397 seconds = convertToSeconds(line[7].trim());
398 }
399 totalRunTime += seconds;
400 values.put(cpu, Integer.toString(seconds));
401 info.add(values);
402 }
403
404 for (Map<String, String> map : info) {
405 int secs = Integer.parseInt(map.get(cpu));
406 log.debug("secs " + secs);
407 double perc = ((double) secs / (double) totalRunTime) * 100.0;
408 map.put(cpu, Integer.toString((int) perc));
409 }
410
411 log.debug(info + "");
412 return info;
413 }
414
415
416
417
418
419
420
421 private List<Map<String, String>> parseUnixOutput(String output) {
422 List<Map<String, String>> info = new ArrayList<Map<String, String>>();
423 output = output.replaceAll("\"", "");
424 String[] lines = output.split("\n");
425 for (String l : lines) {
426 if (l.startsWith("PID")) {
427 continue;
428 }
429
430 String[] line = l.split(",", 9);
431
432
433
434
435
436
437 Map<String, String> values = new HashMap<String, String>();
438 values.put(FILTER_COMMAND, line[0].trim());
439 values.put("pid", line[1].trim());
440 values.put("ppid", line[2].trim());
441 values.put(FILTER_USER, line[3].trim());
442 values.put(METRIC_CPU.toLowerCase(), line[4].trim());
443 values.put(METRIC_RSS.toLowerCase(), line[5].trim());
444 values.put(METRIC_VSZ.toLowerCase(), line[6].trim());
445 values.put(METRIC_ELAPSED.toLowerCase(),
446 convertToSeconds(line[7].trim()) + "");
447 values.put(FILTER_ARG_ARRAY, line[8]);
448
449 info.add(values);
450 }
451 return info;
452 }
453
454 private int convertToMemoryInt(String mem) {
455 mem = mem.replaceAll("[^0-9]", "");
456
457 return Integer.parseInt(mem);
458 }
459
460
461
462
463
464
465
466
467 private List<Map<String, String>> applyFilters(
468 List<Map<String, String>> values, Map<String, String> filterAndValue) {
469 if (filterAndValue == null || filterAndValue.size() == 0) {
470 return values;
471 }
472 List<Map<String, String>> filtered = new ArrayList<Map<String, String>>();
473 for (Map<String, String> map : values) {
474 boolean matchesAll = true;
475 for (Entry<String, String> entry : filterAndValue.entrySet()) {
476 String filter = entry.getKey();
477 String filterValue = entry.getValue();
478 if (filter.contains(FILTER_COMMAND)
479 || filter.contains(FILTER_USER)
480 || filter.equals(FILTER_ARG_ARRAY)
481 || filter.contains(FILTER_PPID)) {
482 if (!map.get(filter).contains(filterValue)) {
483 matchesAll = false;
484 break;
485 }
486 } else if (filter.contains(FILTER_EREG_ARG_ARRAY)
487 && !Pattern.matches(filterValue,
488 map.get(FILTER_ARG_ARRAY))) {
489 matchesAll = false;
490 break;
491 } else if (filter.contains(FILTER_VSZ)
492 || filter.contains(FILTER_RSS)
493 || filter.contains(FILTER_MEMORY)) {
494 int filterval = Integer.parseInt(filterValue);
495 int value = Integer.parseInt(map.get(filter));
496 if (value < filterval) {
497 matchesAll = false;
498 break;
499 }
500 }
501 }
502 if (matchesAll) {
503 filtered.add(map);
504 }
505 }
506 return filtered;
507 }
508
509 private Map<String, String> getFilterAndValue(ICommandLine cl) {
510 Map<String, String> map = new HashMap<String, String>();
511 for (String filter : FILTERS) {
512 if (cl.getOptionValue(filter) != null) {
513 map.put(filter, cl.getOptionValue(filter));
514 }
515 }
516 return map;
517 }
518
519
520
521
522
523
524
525 private int convertToSeconds(final String input) {
526 int days = 0;
527 int hours = 0;
528 int minutes = 0;
529 int seconds = 0;
530
531 String remainingTokens = input;
532
533 if (input.indexOf('-') != -1) {
534 String[] parts = remainingTokens.split("-");
535 days = Integer.parseInt(parts[0]);
536 remainingTokens = parts[1];
537 }
538
539 String[] timeParts = remainingTokens.split(":");
540
541 for (int i = timeParts.length - 1, partType = 0; i >= 0; i--, partType++) {
542 switch (partType) {
543 case 0:
544 seconds = Integer.parseInt(timeParts[i]);
545 break;
546 case 1:
547 minutes = Integer.parseInt(timeParts[i]);
548 break;
549 case 2:
550 hours = Integer.parseInt(timeParts[i]);
551 break;
552 default:
553 }
554 }
555
556 return (days * SECONDS_IN_DAY) + (hours * SECONDS_IN_HOUR)
557 + (minutes * SECONDS_IN_MINUTE) + seconds;
558 }
559 }