|
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 |
|