|
StatsCollector |
|
1 package com.mccrory.scott.spumoni;
2
3 ////////////////////////////////////////////////////////////////////////////////
4 // Copyright (C) 2002 Scott McCrory
5 //
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 ////////////////////////////////////////////////////////////////////////////////
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.ObjectInputStream;
24 import java.io.ObjectOutputStream;
25 import java.util.HashMap;
26 import java.util.Iterator;
27 import java.util.NoSuchElementException;
28 import java.util.StringTokenizer;
29 import java.util.Vector;
30
31 import org.apache.log4j.Category;
32 import org.apache.log4j.NDC;
33 import org.apache.log4j.PropertyConfigurator;
34 import org.apache.oro.text.regex.MalformedPatternException;
35 import org.apache.oro.text.regex.MatchResult;
36 import org.apache.oro.text.regex.Pattern;
37 import org.apache.oro.text.regex.PatternCompiler;
38 import org.apache.oro.text.regex.PatternMatcher;
39 import org.apache.oro.text.regex.PatternMatcherInput;
40 import org.apache.oro.text.regex.Perl5Compiler;
41 import org.apache.oro.text.regex.Perl5Matcher;
42 import org.dom4j.Attribute;
43 import org.dom4j.Document;
44 import org.dom4j.DocumentException;
45 import org.dom4j.Element;
46 import org.dom4j.io.SAXReader;
47 import org.opennms.protocols.snmp.SnmpAgentSession;
48 import org.opennms.protocols.snmp.SnmpInt32;
49 import org.opennms.protocols.snmp.SnmpOctetString;
50 import org.opennms.protocols.snmp.SnmpVarBind;
51
52 import com.mccrory.scott.base.ConcreteFilenameFilter;
53 import com.mccrory.scott.base.Dom4jHelper;
54 import com.mccrory.scott.base.ExecRunner;
55 import fr.dyade.jdring.AlarmEntry;
56 import fr.dyade.jdring.AlarmListener;
57 import fr.dyade.jdring.AlarmManager;
58 import fr.dyade.jdring.PastDateException;
59
60 /**
61 * <P>Responsible for obtaining system statistics and storing them in a data file.
62 * This is typically done using our main() entry point or manually as such:
63 * <pre>
64 * try {
65 * StatsCollector sc = new StatsCollector(args);
66 * sc.scheduleRuns();
67 * }
68 * catch (Exception e) {
69 * e.printStackTrace();
70 * System.exit(1);
71 * }
72 * </pre></P>
73 *
74 * @author <a href="mailto:smccrory@users.sourceforge.net">Scott McCrory</a>.
75 * @version CVS $Id: StatsCollector.java,v 1.30 2002/08/04 22:04:53 smccrory Exp $
76 */
77 public class StatsCollector implements AlarmListener, OidHandler {
78
79 /** The name of this class to log context without introspection **/
80 private static final String CLASS_NAME = "StatsCollector";
81
82 /** The version of this class (filled in by CVS) **/
83 private static final String VERSION = "CVS $Revision: 1.30 $";
84
85 /** Default preferences directory **/
86 private static final String DEFAULT_PREFS_DIR = "conf/";
87
88 /** Default run mode **/
89 private static final String DEFAULT_RUN_MODE = "once";
90
91 /** Default global preferences filename **/
92 private static final String DEFAULT_GLOBAL_PREFS_FILENAME = "global.xml";
93
94 /** Default output data filename **/
95 private static final String DEFAULT_DATA_FILENAME = "spumoni.dat";
96
97 /** Default log4j configuration filename **/
98 private static final String DEFAULT_LOG4J_CONFIG_FILENAME = "log4j.properties";
99
100 /** Default module list **/
101 private static final String DEFAULT_MODULE_LIST = "linux";
102
103 /** Default run interval in minutes **/
104 private static final String PREFS_DIR_CMD_SWITCH = "--prefsdir";
105
106 /** Output data file header message **/
107 private static final String STATS_HEADER_MESSAGE =
108 "This is the Spumoni stats data file";
109
110 /** Key for last run date string in data file **/
111 private static final String LAST_RUN_DATE_STRING_KEY = "lastRunDateString";
112
113 /** End of line terminator (system dependant) **/
114 private static final String CRLF =
115 System.getProperties().getProperty("line.separator");
116
117 /** Who wrote Spumoni **/
118 private static final String AUTHOR =
119 "Scott McCrory and the Spumoni development team";
120
121 /** Contact info for author **/
122 private static final String CONTACT_INFO = "smccrory@users.sourceforge.net";
123
124 /** Some more program info for the help screen **/
125 private static final String SPUMONI_INFO =
126 "Distributed as part of the Spumoni project - http://spumoni.sourceforge.net";
127
128 /** Some more program info for the help screen **/
129 private static final String CONFIG_INFO =
130 "If you haven't done so, you need to configure Spumoni before running this!";
131
132 /** Header string for help screen **/
133 private static final String HEADER =
134 "StatsCollector.java, " + VERSION + ", part of the Spumoni project";
135
136 /** The usage message string built up from the other constants **/
137 private static final String USAGE =
138 "Obtains statistics from the host system and outputs them for easy lookup."
139 + CRLF
140 + "Written by "
141 + AUTHOR
142 + ", "
143 + CONTACT_INFO
144 + CRLF
145 + SPUMONI_INFO
146 + CRLF
147 + CONFIG_INFO
148 + CRLF
149 + "usage: java com.mccrory.scott.spumoni.StatsCollector [options]"
150 + CRLF
151 + " "
152 + PREFS_DIR_CMD_SWITCH
153 + "=dir : defaults to `conf/'"
154 + CRLF;
155
156 /** Where we hold arguments passed to us from the command line **/
157 private HashMap argsMap = new HashMap();
158
159 /** Where we hold the stats program XML document objects prior to parsing **/
160 private HashMap statsProgDocMap = new HashMap();
161
162 /** Where we hold the StatsProgram objects **/
163 private StatsProgramList statsProgramList = new StatsProgramList();
164
165 /** The final preferences directory after being modified by command-line args **/
166 private String prefsDir = new String();
167
168 /** The final run interval; defaults to 5 **/
169 private int interval = 5;
170
171 /** The final run mode after being modified by global.xml settings **/
172 private boolean runMode = false;
173
174 /** A Vector of modules we're allowed to run **/
175 private Vector moduleList = new Vector();
176
177 /** Whether SNMP is enabled at all or not **/
178 private boolean snmpReceiveEnabled = true;
179
180 /** What version of SNMP to use if enabled **/
181 private int snmpReceiveVersion = 2;
182
183 /** What SNMP community string to receive if enabled **/
184 private String snmpReceiveCommunityString = "public";
185
186 /** What port to run the SNMP listener on if enabled **/
187 private int snmpReceivePort = 161;
188
189 /** The final run mode after being modified by global.xml settings **/
190 private boolean snmpTrapEnabled = true;
191
192 /** What version of SNMP to use for traps if enabled **/
193 private int snmpTrapVersion = 2;
194
195 /** What SNMP target hostname to send traps to if enabled **/
196 private String snmpTrapTargetIp = "localhost";
197
198 /** What SNMP community name to use for traps if enabled **/
199 private String snmpTrapCommunityString = "public";
200
201 /** What port to send SNMP traps to if enabled **/
202 private int snmpTrapTargetPort = 162;
203
204 /** Our log4j object. I think "log" is more intuitive than "cat"! **/
205 private static Category log;
206
207 /**
208 * StatsCollector constructor.
209 * We do the following things because they have to only be performed once
210 * and are tightly coupled to the creation of the object.
211 *
212 * @param args an array of command-line arguments
213 * @throws ExceptionInInitializerError thrown if a problem occurs
214 */
215 public StatsCollector(java.lang.String[] args)
216 throws ExceptionInInitializerError {
217
218 super();
219
220 final String METHOD_NAME = "StatsCollector(S)";
221 final String LOG_SOURCE = CLASS_NAME + "." + METHOD_NAME;
222
223 try {
224
225 // Tell the User that we're Spumoni. We use System.out instead of
226 // log() so that the user knows what failed if the log creation goes kaput.
227 System.out.println(
228 "--------------------------------------------------------------------------");
229 System.out.println(HEADER);
230 System.out.println(
231 "--------------------------------------------------------------------------");
232
233 // Parse args into the form of a HashMap for quick lookup
234 boolean parseError = parseArgs(args);
235 if (parseError) {
236 System.out.println(USAGE);
237 System.exit(1);
238 }
239
240 // Set up log4j using the referenced config file
241 log = Category.getInstance(CLASS_NAME);
242 PropertyConfigurator.configure(prefsDir + "/" + DEFAULT_LOG4J_CONFIG_FILENAME);
243
244 // Parse the preferences files
245 parsePrefsXmlFiles();
246
247 }
248 catch (Exception e) {
249 e.printStackTrace();
250 throw new ExceptionInInitializerError("Couldn't initialize: " + e.getMessage());
251 }
252
253 }
254
255 /**
256 * We override the <code>clone</code> method here to prevent cloning of our class.
257 *
258 * @throws CloneNotSupportedException To indicate cloning is not allowed
259 * @return Nothing ever really returned since we throw a CloneNotSupportedException
260 **/
261 public final Object clone() throws CloneNotSupportedException {
262
263 throw new CloneNotSupportedException();
264
265 }
266
267 /**
268 * Collect all of the stats and write them to the data file.
269 * This is typically performed by our main() method, either once or
270 * periodically as specified by global.xml. See main() for usage.
271 */
272 public synchronized void collectStats() {
273
274 // Set up our log source using log4j's nested diagnostic context
275 final String METHOD_NAME = "collectStats()";
276 final String LOG_SOURCE = CLASS_NAME + "." + METHOD_NAME;
277 NDC.push(LOG_SOURCE);
278 log.debug("-------------------------------------");
279
280 // Unset our collected stats to keep from retaining the old values
281 // when the current run doesn't get new results (i.e. let the old
282 // results fall out like they should). THIS MAY BE BEHAVIOR WHICH
283 // SHOULD BE LEFT UP TO THE USER AS DEFINED IN EACH STATS PROGRAM(?)
284 //statsProgramList.unsetValues();
285
286 // Step through all of the stats programs
287 for (int i = 0; i < statsProgramList.size(); i++) {
288
289 // Get the current StatsProgram and its attributes, including
290 // its Vector of StatsValue objects
291 StatsProgram sProg = (StatsProgram) statsProgramList.get(i);
292 String program = sProg.getProgram();
293 String regexp = sProg.getRegexp();
294 int maxRunTime = sProg.getMaxRunTime();
295 Vector requiredModules = sProg.getRequiredModules();
296
297 ///////////////////////////////////////////////////////////////////////////
298 // OK, we're now ready to run the program and obtain the results
299 // (provided the required modules are in the modules list).
300 if (moduleList.containsAll(requiredModules)) {
301
302 // We'll be modifying this...
303 StatsValueList statsValueList = sProg.getStatsValueList();
304
305 // Execute the program and grab the results
306 String out = "";
307 String err = "";
308 try {
309
310 ExecRunner er = new ExecRunner();
311 if (maxRunTime > 0) {
312 er.setMaxRunTimeSecs(maxRunTime);
313 }
314
315 log.info("Running '" + program + "' for max of " + maxRunTime + " seconds...");
316
317 er.exec(program);
318 if (!er.getMaxRunTimeExceeded()) {
319 out = er.getOutString();
320 err = er.getErrString();
321 }
322 else {
323 log.error("Maximum run time exceeded!");
324 continue;
325 }
326
327 }
328 catch (Exception e) {
329 log.error("Error executing stats program " + program + ": " + e.getMessage());
330 continue;
331 }
332
333 /////////////////////////////////////////////////////////////
334 // Parse the results using the specified regular expression
335 MatchResult mr = matchResult(regexp, out);
336
337 // Retrieve the number of matched groups. A group corresponds to a
338 // parenthesized set in a pattern, or in other words, each one of
339 // the values we're extracting from the stats program's output.
340 if (mr != null) {
341 int valuesMatched = mr.groups();
342 for (int group = 1; group < valuesMatched; group++) {
343
344 // Save the data extracted
345 StatsValue sValue = (StatsValue) statsValueList.get(group - 1);
346 String valueString = mr.group(group);
347 sValue.setValue(valueString);
348
349 // Check to see if the min or max values were exceeded,
350 // and if so, send an SNMP Trap
351 try {
352
353 String message = "";
354 int min = sValue.getSnmpTrapMin();
355 int max = sValue.getSnmpTrapMax();
356 boolean sendIt = false;
357 int value = Integer.parseInt(valueString);
358 if (min != -2147483648 && value < min) {
359 message = "Collected value " + value + " was below threshold of " + min;
360 sendIt = true;
361 }
362 if (max != 2147483647 && value > max) {
363 message = "Collected value " + value + " was above threshold of " + max;
364 sendIt = true;
365 }
366 if (sendIt) {
367 sendSnmpTrap(sValue.getSnmpOid(), message);
368 }
369 }
370 catch (NumberFormatException e) {
371 // Something wrong happened while parsing
372 // the value string (it must not be a number)
373 // So we silently ignore it and carry on
374 }
375 }
376 }
377 else {
378 log.warn("No matches with regexp '" + regexp + "'");
379 }
380
381 }
382
383 }
384
385 // Report the information collected to the log
386 log.info(getCollectedStats());
387
388 // Pop our nested diagnostic context off the stack
389 NDC.pop();
390
391 }
392
393 /**
394 * Returns an object with only the COLLECTED statistics.
395 * This is done by returning a StatsProgramList object
396 * which in turn contains StatsProgram (containing StatsValue) objects.
397 *
398 * @return a StatsProgramList object containing the collected statistics.
399 */
400 public StatsProgramList getCollectedStats() {
401
402 // Our Vector of COLLECTED StatsProgram objects
403 StatsProgramList collected = new StatsProgramList();
404
405 // Iterate through every stats program
406 for (int i = 0; i < statsProgramList.size(); i++) {
407
408 StatsProgram sProg = (StatsProgram) statsProgramList.get(i);
409 StatsValueList statsValueList = sProg.getStatsValueList();
410 boolean isSet = false;
411
412 // Go through each StatsValue and see if it is in a "set" state
413 for (int j = 0; j < statsValueList.size(); j++) {
414 StatsValue sValue = (StatsValue) statsValueList.get(j);
415 if (!sValue.isUnset()) {
416 isSet = true;
417 break;
418 }
419 }
420
421 // If this program's values were set, then add it to the
422 // Vector that we'll be returning to our caller.
423 if (isSet) {
424 collected.add(sProg);
425 }
426
427 }
428
429 return collected;
430 }
431
432 /**
433 * Invoked when a JDring alarm is triggered.
434 *
435 * @param entry The JDring AlarmEntry which has been triggered.
436 */
437 public void handleAlarm(AlarmEntry entry) {
438
439 collectStats();
440
441 }
442
443 /**
444 * Starts the Spumoni stats collection according to the prefs files' specs.
445 *
446 * @param args an array of command-line arguments
447 */
448 public static void main(java.lang.String[] args) {
449
450 try {
451
452 StatsCollector sc = new StatsCollector(args);
453 sc.scheduleRuns();
454
455 }
456 catch (Exception e) {
457 e.printStackTrace();
458 System.exit(1);
459 }
460
461 }
462
463 /**
464 * Performs regexp pattern matching and returns results.
465 * Code was taken from Apache's ORO examples; please see
466 * http://jakarta.apache.org/oro/index.html for more info.
467 *
468 * @param patternString The regexp pattern string
469 * @param inputString The string to throw at the regexp pattern
470 * @return The regexp match result
471 */
472 private static MatchResult matchResult(
473 String patternString,
474 String inputString) {
475
476 int groups = 0;
477 PatternMatcher matcher = null;
478 PatternCompiler compiler = null;
479 Pattern pattern = null;
480 PatternMatcherInput input = null;
481 MatchResult result = null;
482
483 // Must have at least two arguments, else exit.
484 if (patternString == null
485 || patternString.length() < 1
486 || inputString == null
487 || inputString.length() < 1) {
488 return null;
489 }
490
491 // Create Perl5Compiler and Perl5Matcher instances.
492 compiler = new Perl5Compiler();
493 matcher = new Perl5Matcher();
494
495 // Attempt to compile the pattern. If the pattern is not valid,
496 // report the error and exit.
497 try {
498 pattern = compiler.compile(patternString);
499 }
500 catch (MalformedPatternException e) {
501 System.out.println("Bad pattern.");
502 System.out.println(e.getMessage());
503 return null;
504 }
505
506 // Create a PatternMatcherInput instance to keep track of the position
507 // where the last match finished, so that the next match search will
508 // start from there. You always create a PatternMatcherInput instance
509 // when you want to search a string for all of the matches it contains,
510 // and not just the first one.
511 input = new PatternMatcherInput(inputString);
512
513 // Loop until there are no more matches left.
514 if (matcher.contains(input, pattern)) {
515 // Since we're still in the loop, fetch match that was found.
516 result = matcher.getMatch();
517 return result;
518 }
519 else {
520 return null;
521 }
522
523 }
524
525 /**
526 * Parses the command-line args into our static HashMap called argsMap.
527 *
528 * @param args an array of command-line arguments
529 * @return true is there was a problem parsing the args
530 * @throws IllegalArgumentException thrown if a problem occurs
531 */
532 private boolean parseArgs(java.lang.String[] args)
533 throws IllegalArgumentException {
534
535 // Set up our log source using log4j's nested diagnostic context
536 final String METHOD_NAME = "parseArgs(S[])";
537 final String LOG_SOURCE = CLASS_NAME + "." + METHOD_NAME;
538 NDC.push(LOG_SOURCE);
539
540 if (args == null || args.length < 1) {
541 System.out.println("No args detected - running with defaults");
542 }
543 else {
544 for (int i = 0; i < args.length; i++) {
545
546 // Do we hear cries for help?
547 String arg = args[i];
548 if (arg == null
549 || arg.length() < 1
550 || arg.equals("--help")
551 || arg.equals("--h")
552 || arg.equals("help")
553 || arg.equals("--?")
554 || arg.equals("-?")
555 || arg.equals("?")
556 || arg.equals("help")
557 || arg.equals("--version")
558 || arg.equals("-version")
559 || arg.equals("-v")) {
560 NDC.pop();
561 return true;
562 }
563
564 // All of the arguments should either be switches or key/value pairs
565 if (arg.indexOf("=") > 0) {
566
567 // One or more "=" was found, so let's try to parse the key/value pair
568 Vector pair = new Vector();
569 StringTokenizer st = new StringTokenizer(arg, "=");
570 while (st.hasMoreTokens()) {
571 pair.add(st.nextToken());
572 }
573
574 // There should only ever be one "=", which is to say
575 // two strings: the key and the value. If there's more
576 // than one "=" then return null to indicate a problem.
577 if (pair.size() == 2) {
578 argsMap.put((String) pair.get(0), (String) pair.get(1));
579 }
580 else {
581 NDC.pop();
582 return true;
583 }
584
585 }
586 else {
587 // It's just a switch - store it on the HashMap with a null value
588 argsMap.put(arg, null);
589 }
590 }
591
592 /////////////////////////////////////////////////////////////////////////////
593 // All done parsing, now set the object's values based on what we found
594
595 // Grab our default interval if it wasn't specified on the command line
596 if (argsMap.containsKey(PREFS_DIR_CMD_SWITCH)) {
597 prefsDir = (String) argsMap.get(PREFS_DIR_CMD_SWITCH);
598 }
599 else {
600 prefsDir = DEFAULT_PREFS_DIR;
601 System.out.println(
602 "No \"" + PREFS_DIR_CMD_SWITCH + "\", running with defaults");
603 }
604 }
605
606 NDC.pop();
607 return false;
608
609 }
610
611 /**
612 * Parses a module list and returns them as individual elements in a Vector.
613 * We assume that the comma (",") is the delimiter
614 *
615 * @param moduleList A list of modules separated by commas
616 * @return The list of modules parsed from the comma-delimited string
617 */
618 private static Vector parseModuleList(String moduleList) {
619
620 if (moduleList == null || moduleList.length() < 1) {
621 return null;
622 }
623
624 Vector tempModuleList = new Vector();
625
626 StringTokenizer st = new StringTokenizer(moduleList, ",");
627 while (st.hasMoreTokens()) {
628 tempModuleList.add(st.nextElement());
629 }
630
631 return tempModuleList;
632
633 }
634
635 /**
636 * Parses all of the XML preferences files.
637 * Once parsed, we process all of global.xml's contents here but don't
638 * actually process what is in the stats program XML files until each time
639 * the schedule kicks off (or once right after parsing if specified).
640 *
641 * @throws IOException thrown if a problem occurs
642 * @throws DocumentException thrown if a parsing problem occurs
643 */
644 private void parsePrefsXmlFiles() throws IOException, DocumentException {
645
646 // Set up our log source using log4j's nested diagnostic context
647 final String METHOD_NAME = "parsePrefsXmlFiles()";
648 final String LOG_SOURCE = CLASS_NAME + "." + METHOD_NAME;
649 NDC.push(LOG_SOURCE);
650 log.debug("Begins");
651
652 // Get the user's prefs directory or else the default one
653 String prefsDirString;
654 if (argsMap.containsKey(PREFS_DIR_CMD_SWITCH)) {
655 prefsDirString = (String) argsMap.get(PREFS_DIR_CMD_SWITCH);
656 }
657 else {
658 prefsDirString = DEFAULT_PREFS_DIR;
659 }
660
661 // Create a handle to the directory and make sure it is legit
662 File prefsDir = new File(prefsDirString);
663 if (!prefsDir.isDirectory()) {
664 NDC.pop();
665 throw new IOException("'" + prefsDirString + "' is not a valid directory!");
666 }
667
668 // Get a list of XML files in that directory
669 ConcreteFilenameFilter xmlFilter = new ConcreteFilenameFilter(".xml");
670 File fileList[] = prefsDir.listFiles(xmlFilter);
671 if (fileList == null || fileList.length < 1) {
672 NDC.pop();
673 throw new IOException("'" + prefsDirString + "' has no .xml files in it!");
674 }
675
676 ///////////////////////////////////////////////////////////////////////////////
677 // Now iterate through each XML file and parse it into a separate Document, then
678 // attach each to a master HashMap. This makes it easier to process each's
679 // content later.
680 for (int i = 0; i < fileList.length; i++) {
681
682 String fileName = fileList[i].getName();
683 log.debug("Parsing: " + fileName);
684
685 SAXReader reader = new SAXReader();
686 Document prefsDocument = reader.read(fileList[i]);
687 Element root = prefsDocument.getRootElement();
688
689 // If we're working with the global.xml file, then we process it here,
690 // otherwise it contains stats program information (see 'else' below)
691 if (fileName.equalsIgnoreCase(DEFAULT_GLOBAL_PREFS_FILENAME)) {
692
693 // It is the global.xml file, so process it.
694 processGlobalXmlFile(root);
695
696 }
697 else {
698
699 // It wasn't the global.xml file, which means it contains
700 // stats program information, so process the data and store
701 // it into a StatsProgram object then accumulate it onto our
702 // Vector for later handling.
703 processStatsProgramFile(root, fileName);
704
705 }
706
707 }
708
709 // Check for OID conflicts. This is critical because we're going to
710 // be using OID as a primary key to report the collected values via SNMP
711 StatsProgramList oidConflicts = statsProgramList.getOidConflicts();
712 /* if (oidConflicts.size() > 0) {
713 log.error(
714 "OID Conflicts detected! Please correct the problems below before running Spumoni again:");
715 for (int col = 0; col < oidConflicts.size(); col++) {
716 log.error(oidConflicts.get(col));
717 }
718 NDC.pop();
719 throw new IOException("OID Conflicts detected!");
720 }
721 */
722 // Pop our nested diagnostic context off the stack
723 log.debug("Ends");
724 NDC.pop();
725
726 }
727
728 /**
729 * Processes the parsed global.xml document and saves its data into
730 * our class variables.
731 * @param root org.dom4j.Element - The root element of our global.xml document
732 */
733 private void processGlobalXmlFile(Element root) {
734
735 for (Iterator rootAttributesIterator = root.attributeIterator();
736 rootAttributesIterator.hasNext();
737 ) {
738
739 Attribute attribute = (Attribute) rootAttributesIterator.next();
740 String attributeName = attribute.getName();
741
742 //////// Check for the fundamental elements ////////
743 if (attributeName.equals("runMode")) {
744 String runModeString = (String) attribute.getData();
745 if (!runModeString.equalsIgnoreCase("once")) {
746 runMode = true;
747 }
748 }
749 if (attributeName.equals("interval")) {
750 interval = Integer.parseInt((String) attribute.getData());
751 }
752 if (attributeName.equals("moduleList")) {
753 String moduleListString = (String) attribute.getData();
754 moduleList = parseModuleList(moduleListString);
755 }
756
757 //////// Check for SNMP Receive elements ////////
758 if (attributeName.equals("snmpReceiveEnabled")) {
759 String s = (String) attribute.getData();
760 if (s.equalsIgnoreCase("true")) {
761 snmpReceiveEnabled = true;
762 }
763 else {
764 snmpReceiveEnabled = false;
765 }
766 }
767 if (attributeName.equals("snmpReceiveVersion")) {
768 snmpReceiveVersion = Integer.parseInt((String) attribute.getData());
769 }
770 if (attributeName.equals("snmpReceiveCommunityString")) {
771 snmpReceiveCommunityString = (String) attribute.getData();
772 }
773 if (attributeName.equals("snmpReceivePort")) {
774 snmpReceivePort = Integer.parseInt((String) attribute.getData());
775 }
776
777 //////// Check for SNMP Trap elements ////////
778 if (attributeName.equals("snmpTrapEnabled")) {
779 String s = (String) attribute.getData();
780 if (s.equalsIgnoreCase("true")) {
781 snmpTrapEnabled = true;
782 }
783 else {
784 snmpTrapEnabled = false;
785 }
786 }
787 if (attributeName.equals("snmpTrapVersion")) {
788 snmpTrapVersion = Integer.parseInt((String) attribute.getData());
789 }
790 if (attributeName.equals("snmpTrapTargetIp")) {
791 snmpTrapTargetIp = (String) attribute.getData();
792 }
793 if (attributeName.equals("snmpTrapCommunityString")) {
794 snmpTrapCommunityString = (String) attribute.getData();
795 }
796 if (attributeName.equals("snmpTrapTargetPort")) {
797 snmpTrapTargetPort = Integer.parseInt((String) attribute.getData());
798 }
799
800 }
801
802 }
803
804 /**
805 * Processes a single parsed stats program document and stores the data in
806 * the form of a StatsProgram objects on our class-wide Vector.
807 * @param root org.dom4j.Element - The root element of our document
808 * @param filename The filename of the file we're parsing
809 */
810 private void processStatsProgramFile(Element root, String filename) {
811
812 // Iterate through all stats programs defined in this file
813 for (Iterator statsProgIterator = root.elementIterator();
814 statsProgIterator.hasNext();
815 ) {
816
817 Element statsProgElement = (Element) statsProgIterator.next();
818 HashMap statsProgAttribs = Dom4jHelper.getAttributes(statsProgElement);
819
820 // Build our StatsProgram with what we know so far
821 StatsProgram program = new StatsProgram();
822 program.setXmlFilename(filename);
823 program.setProgram((String) statsProgAttribs.get("program"));
824 program.setRegexp((String) statsProgAttribs.get("regexp"));
825 String requiredModulesString = (String) statsProgAttribs.get("requiredModules");
826 program.setRequiredModules(parseModuleList(requiredModulesString));
827 program.setMaxRunTime(
828 Integer.parseInt((String) statsProgAttribs.get("maxRunTime")));
829
830 // Iterate through all the child elements, which will be
831 // the value names and attributes.
832 for (Iterator valueIterator = statsProgElement.elementIterator();
833 valueIterator.hasNext();
834 ) {
835
836 Element valueElement = (Element) valueIterator.next();
837 String valueName = (String) valueElement.getData();
838 HashMap valueAttribs = Dom4jHelper.getAttributes(valueElement);
839
840 // Build our StatsValue with the details
841 StatsValue value = new StatsValue();
842 value.setValueName(valueName);
843
844 // Get the oid
845 String oid = (String) valueAttribs.get("snmpOid");
846 if (oid.startsWith(".")) {
847 oid = oid.substring(1);
848 }
849 value.setSnmpOid(oid);
850
851 String snmpTrapMin = (String) valueAttribs.get("snmpTrapMin");
852 if (snmpTrapMin != null && snmpTrapMin.length() > 0) {
853 value.setSnmpTrapMin(Integer.parseInt(snmpTrapMin));
854 }
855 else {
856 value.setSnmpTrapMin(0);
857 }
858
859 String snmpTrapMax = (String) valueAttribs.get("snmpTrapMax");
860 if (snmpTrapMax != null && snmpTrapMax.length() > 0) {
861 value.setSnmpTrapMax(Integer.parseInt(snmpTrapMax));
862 }
863 else {
864 value.setSnmpTrapMax(0);
865 }
866
867 // Add the completed StatsValue object to our StatsProgram
868 program.addStatsValue(value);
869
870 }
871
872 // Add the completed StatsProgram object to our class-wide Vector
873 statsProgramList.add(program);
874
875 }
876
877 }
878
879 /**
880 * We override the <code>readObject</code> method here to prevent
881 * deserialization of our class for security reasons.
882 *
883 * @param in java.io.ObjectInputStream
884 * @throws IOException thrown if a problem occurs
885 **/
886 private final void readObject(ObjectInputStream in) throws IOException {
887
888 throw new IOException("Object cannot be deserialized");
889
890 }
891
892 /**
893 * Run once or schedule ourself on a new alarm manager to run periodically.
894 */
895 public void scheduleRuns() {
896
897 // Set up our log source using log4j's nested diagnostic context
898 final String METHOD_NAME = "scheduleRuns()";
899 final String LOG_SOURCE = CLASS_NAME + "." + METHOD_NAME;
900
901 // Create a new alarm manager that will hold our "cronned" events
902 AlarmManager mgr = new AlarmManager();
903
904 if (runMode && interval > 0) {
905
906 // Set up alarms to occur every INTERVAL minutes
907 try {
908
909 for (int i = 0; i < 60; i += interval) {
910 mgr.addAlarm(i, -1, -1, -1, -1, -1, this);
911 }
912
913 }
914 catch (PastDateException e) {
915
916 NDC.push(LOG_SOURCE);
917 log.fatal("Couldn't schedule ourself: " + e.getMessage());
918 e.printStackTrace();
919 NDC.pop();
920
921 return;
922 }
923
924 // Spawn the SNMP agent daemon and register our known OIDs
925 // if the user wants this )native SNMP agent) functionality.
926 if (snmpReceiveEnabled
927 && snmpReceiveCommunityString != null
928 && snmpReceiveCommunityString.length() > 0) {
929
930 try {
931
932 NDC.push(LOG_SOURCE);
933
934 // Initialize the daemon and register our OIDs
935 SnmpD snmpd = new SnmpD();
936 Vector oids = statsProgramList.getOids();
937 for (int i = 0; i < oids.size(); i++) {
938 String thisOid = (String) oids.get(i);
939 snmpd.registerOidHandler(thisOid, this);
940 }
941
942 SnmpAgentSession daemonSession = new SnmpAgentSession(snmpd, snmpReceivePort);
943 log.debug("SNMP Agent Started");
944
945 // Wait for daemon to complete
946 synchronized (daemonSession) {
947 //daemonSession.wait();
948 }
949
950 //User wants to exit...
951 //log.debug("SNMP Agent Exiting");
952 //daemonSession.close();
953
954 NDC.pop();
955
956 }
957 catch (Exception e) {
958
959 NDC.push(LOG_SOURCE);
960 log.fatal("Problem with SNMP daemon: " + e.getMessage());
961 e.printStackTrace();
962 NDC.pop();
963
964 System.exit(0);
965 }
966
967 }
968
969 // Give a brief report to the user to let them know what's coming
970 NDC.push(LOG_SOURCE);
971 log.info("Collecting stats every " + interval + " min(s)");
972 log.debug("-------------------------------------");
973 NDC.pop();
974
975 }
976 else {
977
978 // Run the stats collector once (useful for diagnostics)
979 collectStats();
980 System.exit(0);
981 }
982
983 }
984
985 /**
986 * Sends an SNMP trap if all of the required data and conditions are OK.
987 *
988 * @param oid The SNMP OID of the triggering value
989 * @param message A message about the trap
990 */
991 public void sendSnmpTrap(String oid, String message) {
992
993 final String METHOD_NAME = "sendSnmpTrap()";
994 final String LOG_SOURCE = CLASS_NAME + "." + METHOD_NAME;
995 NDC.push(LOG_SOURCE);
996
997 if (// Check parameters
998 oid != null
999 && oid.length() > 0
1000 && message != null
1001 && message.length() > 0
1002 && // Check object conditions
1003 snmpTrapEnabled == true
1004 && snmpTrapTargetIp != null
1005 && snmpTrapTargetIp.length() > 0
1006 && snmpTrapCommunityString != null
1007 && snmpTrapCommunityString.length() > 0) {
1008
1009 try {
1010
1011 SnmpTrapAgent sa = new SnmpTrapAgent(snmpTrapTargetIp, snmpTrapTargetPort, oid);
1012
1013 if (snmpTrapVersion == 1) {
1014 sa.sendV1Trap(message);
1015 }
1016 else {
1017 sa.sendV2Trap(message);
1018 }
1019 sa.close();
1020
1021 log.info(
1022 "Trap sent to '"
1023 + snmpTrapTargetIp
1024 + ":"
1025 + snmpTrapTargetPort
1026 + "' "
1027 + "for OID '"
1028 + oid
1029 + "' "
1030 + "with message '"
1031 + message
1032 + "' ");
1033
1034 }
1035 catch (Exception e) {
1036 log.warn(
1037 "Trouble sending trap to '"
1038 + snmpTrapTargetIp
1039 + ":"
1040 + snmpTrapTargetPort
1041 + "' "
1042 + "for OID '"
1043 + oid
1044 + "' "
1045 + "with message '"
1046 + message
1047 + "': "
1048 + e.getMessage());
1049 }
1050
1051 }
1052
1053 // Pop our nested diagnostic context off the stack
1054 NDC.pop();
1055
1056 }
1057
1058 /**
1059 * We override the <code>writeObject</code> method here to prevent
1060 * serialization of our class for security reasons.
1061 *
1062 * @param out java.io.ObjectOutputStream
1063 * @throws IOException thrown if a problem occurs
1064 **/
1065 private final void writeObject(ObjectOutputStream out) throws IOException {
1066
1067 throw new IOException("Object cannot be serialized");
1068
1069 }
1070
1071 /**
1072 * Handles SNMP GET PDUs.
1073 *
1074 * @param varBind The request SnmpVarBind
1075 * @return The response SnmpVarBind.
1076 *
1077 */
1078 public SnmpVarBind handleGet(SnmpVarBind varBind) {
1079
1080 // Set up our log source using log4j's nested diagnostic context
1081 final String METHOD_NAME = "handleGet()";
1082 final String LOG_SOURCE = CLASS_NAME + "." + METHOD_NAME;
1083 NDC.push(LOG_SOURCE);
1084
1085 // Get the oid
1086 String oid = varBind.getName().toString();
1087 if (oid.startsWith(".")) {
1088 oid = oid.substring(1);
1089 }
1090
1091 // Start assembling the response value
1092 String value = null;
1093 try {
1094 value = statsProgramList.getValueByOid(oid);
1095 }
1096 catch (NoSuchElementException e) {
1097 log.info(oid + " NOT FOUND!");
1098 value = oid + " NOT FOUND!";
1099 }
1100
1101 // If we can turn this into an integer then do so, otherwise use a String
1102 try {
1103 SnmpInt32 si = new SnmpInt32(Integer.parseInt(value));
1104 varBind.setValue(si);
1105 log.debug("Setting oid " + oid + " with integer " + value);
1106 }
1107 catch (NumberFormatException e) {
1108 SnmpOctetString sos = new SnmpOctetString();
1109 sos.setString(value);
1110 varBind.setValue(sos);
1111 log.debug("Setting oid " + oid + " with String " + value);
1112 }
1113
1114 NDC.pop();
1115
1116 return varBind;
1117
1118 }
1119
1120 /**
1121 * Handles SNMP GETBULK PDUs.
1122 *
1123 * @param varBind The request SnmpVarBind
1124 * @return The response SnmpVarBind.
1125 *
1126 */
1127 public SnmpVarBind handleGetbulk(SnmpVarBind varBind) {
1128
1129 // Set up our log source using log4j's nested diagnostic context
1130 final String METHOD_NAME = "handleGetbulk()";
1131 final String LOG_SOURCE = CLASS_NAME + "." + METHOD_NAME;
1132 NDC.push(LOG_SOURCE);
1133
1134 log.debug("GETBULK received");
1135
1136 NDC.pop();
1137
1138 return varBind;
1139
1140 }
1141
1142 /**
1143 * Handles SNMP GETNEXT PDUs.
1144 *
1145 * @param varBind The request SnmpVarBind
1146 * @return The response SnmpVarBind.
1147 *
1148 */
1149 public SnmpVarBind handleGetnext(SnmpVarBind varBind) {
1150
1151 // Set up our log source using log4j's nested diagnostic context
1152 final String METHOD_NAME = "handleGetnext()";
1153 final String LOG_SOURCE = CLASS_NAME + "." + METHOD_NAME;
1154 NDC.push(LOG_SOURCE);
1155
1156 log.debug("GETNEXT received");
1157
1158 NDC.pop();
1159
1160 return varBind;
1161
1162 }
1163
1164 /**
1165 * Handles SNMP INFORM PDUs.
1166 *
1167 * @param varBind The request SnmpVarBind
1168 * @return The response SnmpVarBind.
1169 *
1170 */
1171 public SnmpVarBind handleInform(SnmpVarBind varBind) {
1172
1173 // Set up our log source using log4j's nested diagnostic context
1174 final String METHOD_NAME = "handleInform()";
1175 final String LOG_SOURCE = CLASS_NAME + "." + METHOD_NAME;
1176 NDC.push(LOG_SOURCE);
1177
1178 log.debug("INFORM received");
1179
1180 NDC.pop();
1181
1182 return varBind;
1183
1184 }
1185
1186 /**
1187 * Handles SNMP REPORT PDUs.
1188 *
1189 * @param varBind The request SnmpVarBind
1190 * @return The response SnmpVarBind.
1191 *
1192 */
1193 public SnmpVarBind handleReport(SnmpVarBind varBind) {
1194
1195 // Set up our log source using log4j's nested diagnostic context
1196 final String METHOD_NAME = "handleReport()";
1197 final String LOG_SOURCE = CLASS_NAME + "." + METHOD_NAME;
1198 NDC.push(LOG_SOURCE);
1199
1200 log.debug("REPORT received");
1201
1202 NDC.pop();
1203
1204 return varBind;
1205
1206 }
1207
1208 /**
1209 * Handles SNMP RESPONSE PDUs.
1210 *
1211 * @param varBind The request SnmpVarBind
1212 * @return The response SnmpVarBind.
1213 *
1214 */
1215 public SnmpVarBind handleResponse(SnmpVarBind varBind) {
1216
1217 // Set up our log source using log4j's nested diagnostic context
1218 final String METHOD_NAME = "handleResponse()";
1219 final String LOG_SOURCE = CLASS_NAME + "." + METHOD_NAME;
1220 NDC.push(LOG_SOURCE);
1221
1222 log.debug("RESPONSE received");
1223
1224 NDC.pop();
1225
1226 return varBind;
1227
1228 }
1229
1230 /**
1231 * Handles SNMP SET PDUs.
1232 *
1233 * @param varBind The request SnmpVarBind
1234 * @return The response SnmpVarBind.
1235 *
1236 */
1237 public SnmpVarBind handleSet(SnmpVarBind varBind) {
1238
1239 // Set up our log source using log4j's nested diagnostic context
1240 final String METHOD_NAME = "handleSet()";
1241 final String LOG_SOURCE = CLASS_NAME + "." + METHOD_NAME;
1242 NDC.push(LOG_SOURCE);
1243
1244 log.debug("SET received");
1245
1246 NDC.pop();
1247
1248 return varBind;
1249
1250 }
1251
1252 /**
1253 * Handles SNMP TRAP PDUs.
1254 *
1255 * @param varBind The request SnmpVarBind
1256 * @return The response SnmpVarBind.
1257 *
1258 */
1259 public SnmpVarBind handleTrap(SnmpVarBind varBind) {
1260
1261 // Set up our log source using log4j's nested diagnostic context
1262 final String METHOD_NAME = "handleTrap()";
1263 final String LOG_SOURCE = CLASS_NAME + "." + METHOD_NAME;
1264 NDC.push(LOG_SOURCE);
1265
1266 log.debug("TRAP received");
1267
1268 NDC.pop();
1269
1270 return varBind;
1271
1272 }
1273
1274 /**
1275 * Handles SNMP V2TRAP PDUs.
1276 *
1277 * @param varBind The request SnmpVarBind
1278 * @return The response SnmpVarBind.
1279 *
1280 */
1281 public SnmpVarBind handleV2Trap(SnmpVarBind varBind) {
1282
1283 // Set up our log source using log4j's nested diagnostic context
1284 final String METHOD_NAME = "handleV2Trap()";
1285 final String LOG_SOURCE = CLASS_NAME + "." + METHOD_NAME;
1286 NDC.push(LOG_SOURCE);
1287
1288 log.debug("V2TRAP received");
1289
1290 NDC.pop();
1291
1292 return varBind;
1293
1294 }
1295
1296}
|
StatsCollector |
|