Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Pre-release 0.9 commit of the Sysmon project

  • Loading branch information...
commit 2b3d8743f849c3d7743b186cd8992221a51c7639 0 parents
Ari Gesher arigesher authored
Showing with 6,438 additions and 0 deletions.
  1. +14 −0 .classpath
  2. +3 −0  .gitignore
  3. +13 −0 .project
  4. +9 −0 .settings/org.eclipse.jdt.core.prefs
  5. +13 −0 LICENSE
  6. +41 −0 README.md
  7. +76 −0 example/com/palantir/opensource/sysmon/example/SimpleSysmonExample.java
  8. +177 −0 pom.xml
  9. +38 −0 src/com/palantir/opensource/sysmon/Monitor.java
  10. +274 −0 src/com/palantir/opensource/sysmon/SysmonDaemon.java
  11. +41 −0 src/com/palantir/opensource/sysmon/SysmonException.java
  12. +60 −0 src/com/palantir/opensource/sysmon/SystemMonitor.java
  13. +641 −0 src/com/palantir/opensource/sysmon/linux/LinuxDiskspaceJMXWrapper.java
  14. +38 −0 src/com/palantir/opensource/sysmon/linux/LinuxEntropyLevel.java
  15. +233 −0 src/com/palantir/opensource/sysmon/linux/LinuxEntropyLevelJMXWrapper.java
  16. +23 −0 src/com/palantir/opensource/sysmon/linux/LinuxEntropyLevelMBean.java
  17. +131 −0 src/com/palantir/opensource/sysmon/linux/LinuxFileSystem.java
  18. +43 −0 src/com/palantir/opensource/sysmon/linux/LinuxFileSystemMBean.java
  19. +241 −0 src/com/palantir/opensource/sysmon/linux/LinuxIOStat.java
  20. +559 −0 src/com/palantir/opensource/sysmon/linux/LinuxIOStatJMXWrapper.java
  21. +48 −0 src/com/palantir/opensource/sysmon/linux/LinuxIOStatMBean.java
  22. +57 −0 src/com/palantir/opensource/sysmon/linux/LinuxLoadAverage.java
  23. +232 −0 src/com/palantir/opensource/sysmon/linux/LinuxLoadAverageJMXWrapper.java
  24. +27 −0 src/com/palantir/opensource/sysmon/linux/LinuxLoadAverageMBean.java
  25. +188 −0 src/com/palantir/opensource/sysmon/linux/LinuxMonitor.java
  26. +43 −0 src/com/palantir/opensource/sysmon/linux/LinuxMonitoringException.java
  27. +336 −0 src/com/palantir/opensource/sysmon/linux/LinuxNetStatJMXWrapper.java
  28. +329 −0 src/com/palantir/opensource/sysmon/linux/LinuxNetworkInterface.java
  29. +62 −0 src/com/palantir/opensource/sysmon/linux/LinuxNetworkInterfaceMBean.java
  30. +205 −0 src/com/palantir/opensource/sysmon/linux/LinuxVMStat.java
  31. +396 −0 src/com/palantir/opensource/sysmon/linux/LinuxVMStatJMXWrapper.java
  32. +58 −0 src/com/palantir/opensource/sysmon/linux/LinuxVMStatMBean.java
  33. +47 −0 src/com/palantir/opensource/sysmon/linux/package-info.java
  34. +31 −0 src/com/palantir/opensource/sysmon/package-info.java
  35. +77 −0 src/com/palantir/opensource/sysmon/util/InterruptTimerTask.java
  36. +103 −0 src/com/palantir/opensource/sysmon/util/JMXUtils.java
  37. +60 −0 src/com/palantir/opensource/sysmon/util/PropertiesUtils.java
  38. +18 −0 src/com/palantir/opensource/sysmon/util/package-info.java
  39. +236 −0 test/com/palantir/opensource/sysmon/BaseTest.java
  40. +38 −0 test/com/palantir/opensource/sysmon/ErrorDetechingAppenderTest.java
  41. +35 −0 test/com/palantir/opensource/sysmon/linux/AllLinuxTests.java
  42. +183 −0 test/com/palantir/opensource/sysmon/linux/DiskspaceTest.java
  43. +85 −0 test/com/palantir/opensource/sysmon/linux/EntropyLevelTest.java
  44. +246 −0 test/com/palantir/opensource/sysmon/linux/IOStatTest.java
  45. +36 −0 test/com/palantir/opensource/sysmon/linux/LinuxBaseTest.java
  46. +76 −0 test/com/palantir/opensource/sysmon/linux/LinuxFileSystemTest.java
  47. +86 −0 test/com/palantir/opensource/sysmon/linux/LoadAverageTest.java
  48. +199 −0 test/com/palantir/opensource/sysmon/linux/NetStatTest.java
  49. +112 −0 test/com/palantir/opensource/sysmon/linux/VMStatTest.java
  50. +121 −0 util/com/palantir/opensource/sysmon/util/JavadocConfigGenerator.java
14 .classpath
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry including="**/*.java" kind="src" output="target/test-classes" path="test"/>
+ <classpathentry including="**/*.java" kind="src" path="src"/>
+ <classpathentry including="**/*.java" kind="src" path="example"/>
+ <classpathentry kind="src" path="util"/>
+ <classpathentry kind="var" path="M2_REPO/commons-io/commons-io/1.3.2/commons-io-1.3.2.jar"/>
+ <classpathentry kind="var" path="M2_REPO/commons-lang/commons-lang/2.3/commons-lang-2.3.jar"/>
+ <classpathentry kind="var" path="M2_REPO/com/google/guava/guava/r09/guava-r09.jar"/>
+ <classpathentry kind="var" path="M2_REPO/junit/junit/3.8.1/junit-3.8.1.jar"/>
+ <classpathentry kind="var" path="M2_REPO/log4j/log4j/1.2.13/log4j-1.2.13.jar"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry kind="output" path="target/classes"/>
+</classpath>
3  .gitignore
@@ -0,0 +1,3 @@
+target/
+.*.swp
+*~
13 .project
@@ -0,0 +1,13 @@
+<projectDescription>
+ <name>ptoss-sysmon</name>
+ <comment>NO_M2ECLIPSE_SUPPORT: Project files created with the maven-eclipse-plugin are not supported in M2Eclipse.</comment>
+ <projects/>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
9 .settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,9 @@
+#Sat Sep 17 15:52:09 PDT 2011
+encoding//src=UTF-8
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
+eclipse.preferences.version=1
+encoding//src/test/resources=UTF-8
+encoding//test=UTF-8
+org.eclipse.jdt.core.compiler.source=1.5
+encoding//src/main/resources=UTF-8
+org.eclipse.jdt.core.compiler.compliance=1.5
13 LICENSE
@@ -0,0 +1,13 @@
+Copyright 2011 Palantir Technologies
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
41 README.md
@@ -0,0 +1,41 @@
+![Palantir Logo](/palantir/Sysmon/wiki/palantir-logo.png)
+# Sysmon - lightweight platform monitoring for Java VMs #
+
+---
+
+### About #
+
+Sysmon is a lightweight platform monitoring tool. It's designed to gather performance data (CPU, disks, network, etc.) from the host running the Java VM. This data is gathered, packaged, and published via Java Management Extensions (JMX) for access using the JMX APIs and standard tools (such as [jconsole](http://download.oracle.com/javase/6/docs/technotes/guides/management/jconsole.html)).
+
+Sysmon can be run as a standalone daemon or as a library to add platform monitoring to any application.
+
+Currently, Linux is the only platform for which monitoring has been implemented. However, building new or porting existing monitors to other Unix or Unix-like platforms should be pretty straightforward.
+
+### Project Resources #
+
+* The [Wiki](Sysmon/wiki) has all the project documentation.
+* API docs are available [here](http://palantir.github.com/Sysmon/apidocs)
+* Mailing lists are hosted on Google Groups:
+ * [Announce](http://groups.google.com/ptoss-sysmon-announce)
+ * [General](http://groups.google.com/ptoss-sysmon)
+* Please file issues on the [Github Issue Tracker](/palantir/Sysmon/issues)
+* Email project admin: [Ari Gesher](mailto:agesher@palantir.com)
+
+
+## License #
+
+Sysmon is made available under the Apache 2.0 License.
+
+>Copyright 2011 Palantir Technologies
+>
+>Licensed under the Apache License, Version 2.0 (the "License");
+>you may not use this file except in compliance with the License.
+>You may obtain a copy of the License at
+>
+><http://www.apache.org/licenses/LICENSE-2.0>
+>
+>Unless required by applicable law or agreed to in writing, software
+>distributed under the License is distributed on an "AS IS" BASIS,
+>WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+>See the License for the specific language governing permissions and
+>limitations under the License.
76 example/com/palantir/opensource/sysmon/example/SimpleSysmonExample.java
@@ -0,0 +1,76 @@
+// Copyright 2011 Palantir Technologies
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.palantir.opensource.sysmon.example;
+
+import java.lang.management.ManagementFactory;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Set;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+import com.palantir.opensource.sysmon.SysmonDaemon;
+import com.palantir.opensource.sysmon.SystemMonitor;
+import com.palantir.opensource.sysmon.util.JMXUtils;
+
+
+/**
+ * Runs the {@link SysmonDaemon} for twenty seconds, reading all of the available measurements
+ * every two seconds and printing them out to console.
+ *
+ *
+ *
+ */
+public class SimpleSysmonExample {
+
+ public static final void main(String[] args) throws Exception {
+
+ SysmonDaemon.configureDefaultLogging();
+
+ // empty config picks up the defaults
+ SysmonDaemon daemon = new SysmonDaemon(null);
+
+ // daemon is now running
+ final MBeanServer server = ManagementFactory.getPlatformMBeanServer();
+ ObjectName allSysmonObjectsPattern = new ObjectName(SystemMonitor.DEFAULT_JMX_BEAN_PATH + ".*:*");
+
+ // take data every two seconds, ten times
+ for (int i = 0; i < 10; i++){
+ Thread.sleep(2000);
+ String timestamp = getTimestamp();
+ System.out.println("------------------- START " + timestamp + " -------------------");
+ Set<ObjectName> sysmonObjects = server.queryNames(allSysmonObjectsPattern, null);
+ for(ObjectName mbean : sysmonObjects) {
+ JMXUtils.prettyPrintMbean(mbean);
+ }
+ System.out.println("-------------------- END " + timestamp + " --------------------\n\n");
+ }
+ System.out.println("Took ten samples across 20 seconds.");
+ System.out.println("Shutting down daemon");
+ long shutdownStart = System.currentTimeMillis();
+ daemon.shutdown();
+ long shutdownDuration = System.currentTimeMillis() - shutdownStart;
+ System.out.println("Shutdown completed in " + shutdownDuration + "ms");
+
+ }
+
+
+ static String getTimestamp(){
+ DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
+ return df.format(new Date());
+ }
+
+}
177 pom.xml
@@ -0,0 +1,177 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>com.palantir.opensource</groupId>
+ <artifactId>ptoss-sysmon</artifactId>
+ <packaging>jar</packaging>
+ <version>1.0-SNAPSHOT</version>
+ <name>Palantir Technologies Platform Monitoring Daemon</name>
+ <url>https://github.com/palantir/Sysmon</url>
+ <organization>
+ <name>Palantir Technologies</name>
+ <url>http://palantir.com/</url>
+ </organization>
+
+ <properties>
+ <skipTests>true</skipTests>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ </properties>
+
+ <build>
+ <defaultGoal>package</defaultGoal>
+ <directory>${basedir}/target</directory>
+ <finalName>${project.artifactId}-${project.version}</finalName>
+ <sourceDirectory>${basedir}/src</sourceDirectory>
+ <testSourceDirectory>${basedir}/test</testSourceDirectory>
+ <outputDirectory>${basedir}/target/classes</outputDirectory>
+ <testOutputDirectory>${basedir}/target/test-classes</testOutputDirectory>
+
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <encoding>UTF-8</encoding>
+ <!-- http://maven.apache.org/plugins/maven-compiler-plugin/ -->
+ <source>1.5</source>
+ <target>1.5</target>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-resources-plugin</artifactId>
+ <configuration>
+ <encoding>UTF-8</encoding>
+ </configuration>
+ </plugin>
+
+ </plugins>
+ </pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ <version>2.1.2</version>
+ <executions>
+ <execution>
+ <id>copy-src</id>
+ <phase>package</phase>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${project.build.directory}/dist/src/</outputDirectory>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <version>2.3.1</version>
+ <configuration>
+ <outputDirectory>${project.build.directory}/dist/</outputDirectory>
+ <archive>
+ <manifest>
+ <addClasspath>true</addClasspath>
+ <classpathPrefix>lib/</classpathPrefix>
+ <mainClass>com.palantir.opensource.sysmon.SysmonDaemon</mainClass>
+ </manifest>
+ </archive>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>copy-dependencies</id>
+ <phase>package</phase>
+ <goals>
+ <goal>copy-dependencies</goal>
+ </goals>
+ <configuration>
+ <overWriteIfNewer>true</overWriteIfNewer>
+ <outputDirectory>${project.build.directory}/dist/lib</outputDirectory>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <version>2.7.2</version>
+ <configuration>
+ <redirectTestOutputToFile>true</redirectTestOutputToFile>
+ </configuration>
+ </plugin>
+
+ </plugins>
+
+ </build>
+ <reporting>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <version>2.8</version>
+ <configuration>
+ <show>private</show>
+ <nohelp>true</nohelp>
+ </configuration>
+ </plugin>
+ </plugins>
+ </reporting>
+ <profiles>
+ <!-- only turn on running unit tests on Linux -->
+ <profile>
+ <id>linux-unit-tests</id>
+ <activation>
+ <os>
+ <name>Linux</name>
+ </os>
+ </activation>
+ <properties>
+ <skipTests>false</skipTests>
+ </properties>
+ </profile>
+ </profiles>
+
+ <dependencies>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>3.8.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <version>r09</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-lang</groupId>
+ <artifactId>commons-lang</artifactId>
+ <version>2.3</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <version>1.3.2</version>
+ </dependency>
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <version>1.2.13</version>
+ </dependency>
+ </dependencies>
+ <issueManagement>
+ <system>GitHub</system>
+ <url>https://github.com/palantir/Sysmon/issues</url>
+ </issueManagement>
+ <scm>
+ <url>https://github.com/palantir/Sysmon</url>
+ </scm>
+</project>
38 src/com/palantir/opensource/sysmon/Monitor.java
@@ -0,0 +1,38 @@
+// Copyright 2011 Palantir Technologies
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.palantir.opensource.sysmon;
+
+import com.palantir.opensource.sysmon.linux.LinuxIOStatJMXWrapper;
+
+/**
+ * <p>
+ * An interface for metric-specific montiors, each reading and publishing values
+ * from a source on the machine.
+ * </p><p>
+ * Construction of a Monitor object must not start any
+ * background processes. {@link #startMonitoring()} must be called to
+ * start up the background routines.
+ * </p><p>
+ * A call to {@link #stopMonitoring()} will shut down and clean up any background processes and
+ * data processing threads.
+ * </p><p>
+ * A Monitor object may not be restarted. Construct a new instance rather
+ * calling {@link #startMonitoring()} after calling {@link #stopMonitoring()}.
+ * </p>
+ *
+ */
+public interface Monitor {
+ public void startMonitoring() throws SysmonException;
+ public void stopMonitoring() throws InterruptedException;
+}
274 src/com/palantir/opensource/sysmon/SysmonDaemon.java
@@ -0,0 +1,274 @@
+// Copyright 2011 Palantir Technologies
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.palantir.opensource.sysmon;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Properties;
+import java.util.concurrent.Semaphore;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.ConsoleAppender;
+import org.apache.log4j.Level;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+import org.apache.log4j.PatternLayout;
+
+import com.palantir.opensource.sysmon.linux.LinuxMonitor;
+
+/**
+ * <p>
+ * {@link SysmonDaemon} can be used either as a component of a larger program or as standalone
+ * process (by invoking {@link #main(String[])}).
+ * </p><p>
+ * In standalone mode, it is run as a normal Java class. It will look for a config file to
+ * override defaults by either consulting a the System property
+ * {@value #SYSTEM_PROPERTY_CONFIG_FILE} or from it's first non-VM command-line argument. Although
+ * it's called a daemon, it will not disassociate from the controlling terminal and run in the
+ * background - this capability is not provided by default by the Java VM. Pressing Ctrl-C will
+ * cause the process to exit.
+ * </p><p>
+ * To use the {@link SysmonDaemon} as a component of another program, construct a new instance and
+ * pass it a {@link Properties} object with any custom configuration - null is fine to pick up
+ * defaults. Construction will start a non-daemon background thread that can be shut down by
+ * {@link #shutdown()}
+ * </p>
+ * <xmp>
+ * Properties config = new Properties();
+ * // ...
+ * // load config file if desired
+ * // ...
+ * //
+ * SysmonDaemon daemon = new SysmonDaemon(config);
+ * // background thread is now running
+ * //
+ * // ...
+ * // Do some other stuff
+ * // ...
+ * //
+ * daemon.shutdown();
+ * </xmp>
+ */
+public class SysmonDaemon extends Thread {
+
+ static final Logger log = LogManager.getLogger(SysmonDaemon.class);
+
+ /**
+ * used to communicate between threads in order to shutdown the non-daemon
+ * thread used to keep the VM running when this used in pure-daemon mode.
+ */
+ private final Semaphore shutdown = new Semaphore(0);
+
+ /**
+ * Platform specific monitor to be used on this platform.
+ *
+ * @see #determinePlatformMonitor() for information about how this value is obtained.
+ */
+ private final SystemMonitor sysmon;
+
+ private final Properties config;
+
+ /**
+ * Constructs a new {@link SysmonDaemon} object and determines its platform-specific
+ * {@link SystemMonitor} but does not start any monitoring.
+ *
+ * @throws SysmonException upon error figuring out which class will be used as the
+ * {@link SystemMonitor}, initializing an instance of that class, or that instance's
+ * verification of the execution environment in this VM.
+ */
+ public SysmonDaemon(Properties config) throws SysmonException {
+ super(SysmonDaemon.class.getName());
+ setDaemon(false); // keep the VM alive until this thread dies
+ if(config != null) {
+ this.config = (Properties)config.clone(); // cloned for safety
+ } else {
+ this.config = new Properties();
+ }
+ sysmon = determinePlatformMonitor();
+ sysmon.verifyExecutionEnvironment((Properties)this.config.clone()); // cloned for safety
+ start(); // jump off into a background thread
+ }
+
+ /**
+ * Since the number of platforms currently supported is small (one, to be exact), this
+ * is just a static method that must produce a {@link SystemMonitor} object to be run by
+ * the daemon. A second and third platform could be trivially added to this method &mdash;
+ * after that, perhaps some sort of classpath-scanning reflective mechanism should be
+ * implemented to allow new platforms to be added to the system without changing this class.
+ *
+ * @return the object to be run by this daemon to monitor the current platform.
+ */
+ public static final SystemMonitor determinePlatformMonitor() throws SysmonException {
+
+ final String osName = System.getProperty("os.name","UNKNOWN"); // use default to avoid NPE
+ final String osVersion = System.getProperty("os.version","UNKNOWN"); // use default to avoid NPE
+
+ String platformId = osName + " " + osVersion;
+ try {
+ if(osName.equals("Linux") && osVersion.startsWith("2.6")) {
+ return LinuxMonitor.class.newInstance();
+ }
+ } catch (Exception e) {
+ throw new SysmonException("Error instantiating platform monitor for " + platformId,e);
+ }
+
+ throw new SysmonException("No platform monitor found for platform " + platformId);
+ }
+
+ /**
+ * Starts platform specific monitoring and then blocks in a non-daemon thread, waiting
+ * for a call to {@link #shutdown()} to unblock it (by releasing a semaphore) and allowing
+ * the thread to exit. This will keep the VM alive even as the platform monitors themselves
+ * are only running daemon threads.
+ *
+ * @see Thread#setDaemon(boolean)
+ */
+ @Override
+ public void run() {
+ try {
+ try {
+ sysmon.startPlatformSpecificMonitoring((Properties)config.clone()); // cloned for safety
+ } catch (SysmonException e) {
+ log.error("Error while starting monitoring. Exiting.",e);
+ return;
+ }
+ // just block until someone calls shutdown.
+ shutdown.acquire();
+ log.info("Shutdown signalled. Exiting.");
+ } catch (InterruptedException e) {
+ log.warn("Thread was interrupted while waiting for shutdown signal. Daemon may now exit.",e);
+ }
+ log.info("Exiting " + getName() + " thread.");
+ }
+
+
+ /**
+ * Shuts down this {@link SysmonDaemon} instance. Shutdown depends on all platform monitors to
+ * only start daemon threads.
+ * @see Thread#setDaemon(boolean)
+ */
+ public void shutdown(){
+ sysmon.stopPlatformSpecificMonitoring();
+ shutdown.release(1);
+ }
+
+ /**
+ * Sets up log4j logging in the case where no external configuration has been specified.
+ *
+ * This is only called when this class is invoked via {@link #main(String[])}.
+ *
+ * It sets up a single {@link ConsoleAppender}, thresholded at {@link Level#WARN}, and adds
+ * it to the root logger.
+ */
+ public static void configureDefaultLogging() {
+
+ Logger rootLogger = LogManager.getRootLogger();
+ @SuppressWarnings("unchecked")
+ Enumeration<Appender> appenders = rootLogger.getAllAppenders();
+ int appenderCount = 0;
+ while(appenders.hasMoreElements()) {
+ appenders.nextElement();
+ appenderCount++;
+ }
+
+ if(appenderCount == 0) {
+ PatternLayout consoleLayout = new PatternLayout("%d{ISO8601} [%p] %c{1} %m%n");
+ ConsoleAppender ca = new ConsoleAppender(consoleLayout);
+ ca.setThreshold(Level.WARN);
+ rootLogger.addAppender(ca);
+ LogManager.getLogger("com.palantir").setLevel(Level.WARN);
+ }
+ }
+
+ /**
+ * System property used to specify the config file path. Can be overridden by command line
+ * argument.
+ * Property: {@value}
+ * @see System#getProperties()
+ */
+ public static final String SYSTEM_PROPERTY_CONFIG_FILE = "sysmon.configPath";
+ /**
+ * Runs the SysmonDaemon as a process. Config file specified on command line takes precedence
+ * over {@link #SYSTEM_PROPERTY_CONFIG_FILE}.
+ * @param args takes single argument: the path to the config file.
+ */
+ public static void main(String[] args) {
+
+ configureDefaultLogging();
+
+ // use passed configuration file
+ final String configFileParam;
+ if(args.length > 0){
+ configFileParam = args[0];
+ } else {
+ configFileParam = System.getProperty("sysmon.configPath",null);
+ }
+
+ // start with empty config for defaults behavior
+ final Properties config = new Properties();
+ try {
+ if(configFileParam != null){
+ // load from path
+ File configFile = new File(configFileParam);
+ String configMsg = "Using config file: " + configFile.getAbsolutePath();
+ System.out.println(configMsg);
+ log.info(configMsg);
+ config.load(new FileReader(configFile));
+ } else {
+ String configMsg = "No config file specified. Using platform defaults.";
+ System.out.println(configMsg);
+ log.info(configMsg);
+ }
+ new SysmonDaemon(config); // starts new thread
+ System.out.println("Sysmon daemon started. Ctrl-C to quit.");
+ } catch (IOException e) {
+ String msg = "Error while loading config file: " + configFileParam;
+ log.error(msg,e);
+ System.err.println(msg);
+ e.printStackTrace(System.err);
+ } catch (SysmonException e) {
+ String msg = "Error while starting daemon with config file: " + configFileParam;
+ log.error(msg,e);
+ System.err.println(msg);
+ e.printStackTrace(System.err);
+ }
+
+ }
+
+ /**
+ * Simple task for parallel shutdown of monitors. Helpfully manipulates thread names to
+ * show state of shutdown.
+ */
+ public static final class ShutdownTask implements Runnable {
+
+ private final Monitor service;
+
+ public ShutdownTask(Monitor service) {
+ this.service = service;
+ }
+
+ public void run() {
+ try {
+ Thread.currentThread().setName("Shutdown thread: " + service.getClass().getSimpleName());
+ this.service.stopMonitoring();
+ } catch (InterruptedException e) {
+ log.warn("Shutdown interrupted",e);
+ } finally {
+ Thread.currentThread().setName(service.getClass().getSimpleName() + " Shutdown");
+ }
+ }
+ }
+}
41 src/com/palantir/opensource/sysmon/SysmonException.java
@@ -0,0 +1,41 @@
+// Copyright 2011 Palantir Technologies
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.palantir.opensource.sysmon;
+
+
+/**
+ * Generic exception for errors in the Sysmon framework.
+ *
+ *
+ *
+ */
+public class SysmonException extends Exception {
+
+ public SysmonException() {
+ super();
+ }
+
+ public SysmonException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public SysmonException(String message) {
+ super(message);
+ }
+
+ public SysmonException(Throwable cause) {
+ super(cause);
+ }
+
+}
60 src/com/palantir/opensource/sysmon/SystemMonitor.java
@@ -0,0 +1,60 @@
+// Copyright 2011 Palantir Technologies
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.palantir.opensource.sysmon;
+
+import java.util.Properties;
+
+
+/**
+ * Interface for platform monitors.
+ *
+ *
+ */
+public interface SystemMonitor {
+
+ /**
+ * Root of Sysmon JMX beans in the bean server hierarchy.
+ * Bean root: {@value}
+ */
+ public static final String DEFAULT_JMX_BEAN_PATH = "sysmon";
+ /**
+ * Prefix for configuration keys found in the {@link Properties} objects passed to
+ * {@link #startPlatformSpecificMonitoring(Properties)}. Implementations should use
+ * this constant to build up their own config prefixes so as to avoid conflicts in the
+ * configuration namespace.
+ * Config prefix: {@value}
+ */
+ public static final String CONFIG_KEY_PREFIX = "sysmon";
+
+ /**
+ * Start monitoring for this specific platform (as implemented by instances of this interface).
+ * @param config configuration information for the specific monitor implementations. Must empty
+ * and/or null {@link Properties} objects as a way of signalling that default values should be
+ * used.
+ */
+ public void startPlatformSpecificMonitoring(Properties config) throws SysmonException;
+ /**
+ * Stop monitoring for this specific platform (as implemented by instances of this interface).
+ */
+ public void stopPlatformSpecificMonitoring();
+
+ /**
+ * Tells the platform specific monitor to check that it believes that it is compatible
+ * with the current VM's execution environments. This is where the platform monitor
+ * might check versions and paths that it expects to exist.
+ * @throws Exception to specify errors with execution environment.
+ */
+ public void verifyExecutionEnvironment(Properties config) throws SysmonException;
+
+}
641 src/com/palantir/opensource/sysmon/linux/LinuxDiskspaceJMXWrapper.java
@@ -0,0 +1,641 @@
+// Copyright 2011 Palantir Technologies
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.palantir.opensource.sysmon.linux;
+
+import java.io.*;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.management.JMException;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+
+import com.palantir.opensource.sysmon.Monitor;
+import com.palantir.opensource.sysmon.util.JMXUtils;
+import com.palantir.opensource.sysmon.util.PropertiesUtils;
+
+
+/**
+ * <p>
+ * Monitors disk space on a Linux system. Requires the
+ * <a href='http://linux.die.net/man/1/df'>df</a> utility (part of the coreutils package
+ * on Redhat based systems.
+ * </p><p>
+ * Reads the output from 'df -P -B M' that looks like this:
+ *
+ * <pre>
+ * Filesystem 1048576-blocks Used Available Capacity Mounted on
+ * /dev/md0 19689M 5275M 13414M 29% /
+ * none 4021M 0M 4021M 0% /dev/shm
+ * /dev/md1 51376M 48506M 262M 100% /u1
+ * </pre>
+ *
+ * It then looks up the filesystem type by reading /proc/self/mounts (for maximum portability).
+ * </p>
+ *
+ * <h3>JMX Data Path</h3>
+ * Each device will be at:<br/>
+ * <code>sysmon.linux.beanpath:type=filesystem,devicename=&lt;devicename&gt;</code>
+ *
+ * <h3>Configuration parameters</h3>
+ * <em>Note that any value not set in the config file will use the default value.</em>
+ * <table cellspacing=5 cellpadding=5><tr><th>Config Key</th><th>Description</th><th>Default Value</th><th>Constant</th></tr>
+ * <tr><td>sysmon.linux.df.df.path</td>
+ * <td>path to <code>df</code> binary</td>
+ * <td><code>df</code></td>
+ * <td>{@link #CONFIG_KEY_DF_PATH}</td></tr>
+ * <tr><td>sysmon.linux.df.df.block.opts</td>
+ * <td>options passed to <code>df</code> when checking free space</td>
+ * <td><code>-P -B M</code></td>
+ * <td>{@link #CONFIG_KEY_DF_OPTIONS}</td></tr>
+ * <tr><td>sysmon.linux.df.df.inode.opts</td>
+ * <td>options passed to <code>df</code> when checking inodes</td>
+ * <td><code>-P -i</code></td>
+ * <td>{@link #CONFIG_KEY_DF_INODE_OPTIONS}</td></tr>
+ * <tr><td>sysmon.linux.df.df.period</td>
+ * <td>Period between checks, in seconds</td>
+ * <td><code>10</code></td>
+ * <td>{@link #CONFIG_KEY_DF_PERIOD}</td></tr>
+ * <tr><td>sysmon.linux.df.df.deviceNameFilter</td>
+ * <td>Comma-separated list of device names (full path) to ignore when making free space calculations.</td>
+ * <td><code></code></td>
+ * <td>{@link #CONFIG_KEY_DF_DEVICE_NAME_FILTER}</td></tr>
+ * <tr><td>sysmon.linux.df.df.fsTypeFilter</td>
+ * <td>Comma-separated list of filesystem types to ignore when making free space calculations.</td>
+ * <td><code>iso9660,proc,sysfs,tmpfs</code></td>
+ * <td>{@link #CONFIG_KEY_DF_FS_TYPE_FILTER}</td></tr>
+ * <tr><td>sysmon.linux.df.mtab.path</td>
+ * <td>path to <code>mtab</code> file</td>
+ * <td><code>/etc/mtab</code></td>
+ * <td>{@link #CONFIG_KEY_MTAB_PATH}</td></tr>
+ * </tr></table>
+*
+ * @see Monitor Lifecycle documentation
+ *
+ */
+public class LinuxDiskspaceJMXWrapper extends Thread implements Monitor {
+
+ private static final Logger log = LogManager.getLogger(LinuxDiskspaceJMXWrapper.class);
+
+ static final String CONFIG_KEY_PREFIX = LinuxMonitor.CONFIG_KEY_PREFIX + ".df";
+
+ /**
+ * Path to the df executable. Defaults to "df" (uses $PATH to find executable).
+ * Set this config value in the to override where to find df.
+ *
+ * Config key: {@value}
+ * @see LinuxDiskspaceJMXWrapper#DEFAULT_DF_PATH default value for this config parameter
+ * @see <a href='http://linux.die.net/man/1/df'>df(1) on your local linux box</a>
+ */
+ public static final String CONFIG_KEY_DF_PATH = CONFIG_KEY_PREFIX + ".df.path";
+ /**
+ * Options passed to df.
+ * Set this key in config file to override defaults options.
+ * Config key: {@value}
+ * @see #DEFAULT_DF_OPTIONS
+ * @see <a href='http://linux.die.net/man/1/df'>df(1) on your local linux box</a>
+ */
+ public static final String CONFIG_KEY_DF_OPTIONS = CONFIG_KEY_PREFIX + ".df.block.opts";
+ /**
+ * Options passed to df when calculating free inodes.
+ * Set this key in config file to override defaults options.
+ * Config key: {@value}
+ * @see #DEFAULT_DF_INODE_OPTIONS
+ * @see <a href='http://linux.die.net/man/1/df'>df(1) on your local linux box</a>
+ */
+ public static final String CONFIG_KEY_DF_INODE_OPTIONS = CONFIG_KEY_PREFIX + ".df.inode.opts";
+ /**
+ * How often to run space calculations, in seconds.
+ * Set this key in config file to override defaults options.
+ * Config key: {@value}
+ * @see #DEFAULT_DF_PERIOD
+ * @see <a href='http://linux.die.net/man/1/df'>df(1) on your local linux box</a>
+ */
+ public static final String CONFIG_KEY_DF_PERIOD = CONFIG_KEY_PREFIX + ".df.period";
+ /**
+ * Comma-separated list of device names (full path) to ignore when
+ * making free space calculations.
+ *
+ * Set this key in config file to override defaults options.
+ * Config key: {@value}
+ * @see #DEFAULT_DF_DEVICE_NAME_FILTER
+ * @see <a href='http://linux.die.net/man/1/df'>df(1) on your local linux box</a>
+ */
+ public static final String CONFIG_KEY_DF_DEVICE_NAME_FILTER = CONFIG_KEY_PREFIX + ".df.deviceNameFilter";
+ /**
+ * Comma-separated list of file system types to ignore when making free space calculations.
+ *
+ * Set this key in config file to override defaults options.
+ * Config key: {@value}
+ * @see #DEFAULT_DF_FS_TYPE_FILTER
+ * @see <a href='http://linux.die.net/man/1/df'>df(1) on your local linux box</a>
+ */
+ public static final String CONFIG_KEY_DF_FS_TYPE_FILTER = CONFIG_KEY_PREFIX + ".df.fsTypeFilter";
+ /**
+ * Path to the mtab file.
+ *
+ * Set this key in config file to override defaults options.
+ * Config key: {@value}
+ * @see #DEFAULT_MTAB_PATH
+ * @see <a href='http://linux.die.net/man/8/mount'>mount(8) on your local linux box</a>
+ */
+ public static final String CONFIG_KEY_MTAB_PATH = CONFIG_KEY_PREFIX + ".mtab.path";
+ /**
+ * For each device name and the passed JMX bean path, the a bean will be mounted
+ * with the name '{@value}&lt;devicename&gt;'
+ */
+ public static final String OBJECT_NAME_PREFIX = ":type=filesystem,devicename=";
+ /**
+ * Default's to using a bare path to allow use of $PATH to find the df executable.
+ * Default value: {@value}
+ * @see #CONFIG_KEY_DF_PATH for information on overriding the default value.
+ */
+ public static final String DEFAULT_DF_PATH = "df"; // let the shell figure it out
+ /**
+ * Default options passed to df when making disk space calculations.
+ * Default value: {@value}
+ * @see #CONFIG_KEY_DF_OPTIONS for information on overriding the default value.
+ */
+ public static final String DEFAULT_DF_OPTIONS = "-P -B M";
+ /**
+ * Default options passed to df when making inode calculations.
+ * Default value: {@value}
+ * @see #CONFIG_KEY_DF_INODE_OPTIONS for information on overriding the default value.
+ */
+ public static final String DEFAULT_DF_INODE_OPTIONS = "-P -i";
+ /**
+ * Default period, in seconds.
+ * Default value: {@value}
+ * @see #CONFIG_KEY_DF_PERIOD for information on overriding the default value.
+ */
+ public static final int DEFAULT_DF_PERIOD = 10;
+ /**
+ * Default device filter.
+ * Default value: {@value}
+ * @see #CONFIG_KEY_DF_DEVICE_NAME_FILTER for information on overriding the default value.
+ */
+ public static final String DEFAULT_DF_DEVICE_NAME_FILTER = "";
+ /**
+ * Default filesystem type filter.
+ * Default value: {@value}
+ * @see #CONFIG_KEY_DF_FS_TYPE_FILTER for information on overriding the default value.
+ */
+ public static final String DEFAULT_DF_FS_TYPE_FILTER = "iso9660,proc,sysfs,tmpfs";
+ /**
+ * Default path to mtab file
+ * Default value: {@value}
+ * @see #CONFIG_KEY_MTAB_PATH for information on overriding the default value.
+ */
+ public static final String DEFAULT_MTAB_PATH = "/etc/mtab";
+ /**
+ * {@link Pattern} for recognizing the df header.
+ */
+ private static final Pattern DF_HEADER_PATTERN =
+ Pattern.compile("^\\s*Filesystem\\s+\\d+-blocks\\s+Used\\s+Available\\s+Capacity\\s+Mounted on\\s*$");
+ /**
+ * {@link Pattern} for parsing df data.
+ */
+ private static final Pattern DF_INODE_DATA_PATTERN =
+ Pattern.compile("^\\s*(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S(.*?\\S)?)\\s*$");
+ /**
+ * {@link Pattern} for recognizing the df inode header.
+ */
+ private static final Pattern DF_DATA_PATTERN =
+ Pattern.compile("^\\s*(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S(.*?\\S)?)\\s*$");
+ /**
+ * {@link Pattern} for parsing df inode data.
+ */
+ private static final Pattern DF_INODE_HEADER_PATTERN =
+ Pattern.compile("^\\s*Filesystem\\s+Inodes\\s+IUsed\\s+IFree\\s+IUse%\\s+Mounted on\\s*$");
+ /**
+ * {@link Pattern} for parsing of mtab data.
+ */
+ private static final Pattern MTAB_DATA =
+ Pattern.compile("^\\s*(\\S+)\\s+\\S(.*?\\S)?\\s+(\\S+)\\s+\\S+\\s+\\d+\\s+\\d+\\s*$");
+
+
+ // instance variables to hold all the config options
+ final String dfCmd[];
+ final String dfInodeCmd[];
+ /**
+ * Stored in millis, but configured in seconds.
+ */
+ final long period;
+ final String dfPath;
+ final String dfOptions;
+ final String dfInodeOptions;
+ final File mtabPath;
+ final Set<String> dfDeviceNameFilter = new HashSet<String>();
+ final Set<String> dfFsTypeFilter = new HashSet<String>();
+ /**
+ * volatile - to be used for simple inter-thread communication
+ */
+ volatile boolean shutdown = false;
+ /**
+ * Where to put the data in the JMX tree.
+ */
+ final String beanPathPrefix;
+ /**
+ * Current set of beans, for update purposes. JMX requires update of the beans in place, so
+ * we hold a reference to them here.
+ */
+ final Map<String, LinuxFileSystem> filesystems = new HashMap<String, LinuxFileSystem>();
+
+ private Process process = null; // Any currently running process, protected by synchronized(this).
+
+ public LinuxDiskspaceJMXWrapper(Properties config) throws LinuxMonitoringException {
+ // initialize thread
+ super("LinuxDiskspaceMonitor");
+ this.setDaemon(true);
+
+ // configure
+ this.beanPathPrefix = config.getProperty(LinuxMonitor.CONFIG_KEY_JMX_BEAN_PATH,
+ LinuxMonitor.DEFAULT_JMX_BEAN_PATH) +
+ OBJECT_NAME_PREFIX;
+ this.dfPath = config.getProperty(CONFIG_KEY_DF_PATH,DEFAULT_DF_PATH);
+ this.dfOptions = config.getProperty(CONFIG_KEY_DF_OPTIONS, DEFAULT_DF_OPTIONS);
+ this.dfInodeOptions = config.getProperty(CONFIG_KEY_DF_INODE_OPTIONS, DEFAULT_DF_INODE_OPTIONS);
+ try {
+ int periodSeconds = PropertiesUtils.extractInteger(config,
+ CONFIG_KEY_DF_PERIOD,
+ DEFAULT_DF_PERIOD);
+ this.period = periodSeconds * 1000; // millis
+ } catch (NumberFormatException e) {
+ throw new LinuxMonitoringException("Invalid config value for " + CONFIG_KEY_DF_PERIOD,e);
+ }
+ this.mtabPath = new File(config.getProperty(CONFIG_KEY_MTAB_PATH, DEFAULT_MTAB_PATH));
+
+ // configure filters
+ String filters = config.getProperty(CONFIG_KEY_DF_DEVICE_NAME_FILTER,
+ DEFAULT_DF_DEVICE_NAME_FILTER);
+ for (String filter : filters.split(",")) {
+ if ("".equals(filter)) {
+ continue;
+ }
+ dfDeviceNameFilter.add(filter);
+ }
+ filters = config.getProperty(CONFIG_KEY_DF_FS_TYPE_FILTER,DEFAULT_DF_FS_TYPE_FILTER);
+ for (String filter : filters.split(",")) {
+ if ("".equals(filter)) {
+ continue;
+ }
+ dfFsTypeFilter.add(filter);
+ }
+
+ // build command line
+ String[] opts = dfOptions.split("\\s+");
+ dfCmd = new String[opts.length + 1];
+ dfCmd[0] = dfPath;
+ System.arraycopy(opts, 0, dfCmd, 1, opts.length);
+
+ // build command line (inode)
+ opts = dfInodeOptions.split("\\s+");
+ dfInodeCmd = new String[opts.length + 1];
+ dfInodeCmd[0] = dfPath;
+ System.arraycopy(opts, 0, dfInodeCmd, 1, opts.length);
+
+ // read once to throw config exceptions on calling thread
+ // QA-28918: Previously used InterruptTimerTask, which wasn't properly interrupting the df process. Switching to a process kill.
+ KillTimerTask timer = setKillTimer(4 * period);
+ try {
+ readData();
+ } finally {
+ timer.cancel();
+ }
+ }
+
+ /**
+ * Starts background thread that will actually be performing monitoring runs.
+ */
+ public void startMonitoring() {
+ this.start();
+ }
+
+ /**
+ * Signals shutdown to background thread and then waits for thread to die.
+ * @throws InterruptedException if interrupted while waiting for thread to die.
+ */
+ public void stopMonitoring() throws InterruptedException {
+ this.shutdown = true;
+ this.interrupt();
+ this.join(this.period * 2);
+ }
+
+ @Override
+ public void run() {
+ try {
+ do {
+ readData();
+ Thread.sleep(this.period);
+ } while(!shutdown);
+ } catch (Exception e) {
+ if(!shutdown) {
+ log.error("Shutting down filesytem monitoring due to error.",e);
+ }
+ }
+ }
+
+ public void kill() {
+ synchronized(this) {
+ if (process != null) {
+ process.destroy();
+ process = null;
+ }
+ }
+ }
+
+
+
+ private void readData() throws LinuxMonitoringException {
+ Map<String, String> fsTypeMap = readFileSystemTypes();
+ Map<String, DfData> dfDataMap = readDfData(dfCmd, DF_HEADER_PATTERN, DF_DATA_PATTERN);
+ Map<String, DfData> dfInodeDataMap = readDfData(dfInodeCmd, DF_INODE_HEADER_PATTERN, DF_INODE_DATA_PATTERN);
+ updateBeans(fsTypeMap, dfDataMap, dfInodeDataMap);
+ }
+
+ /**
+ * Here's where the sausage gets made - run df in separate process and read its output into
+ * {@link DfData} structures.
+ *
+ * @param cmd
+ * @param headerPattern
+ * @param dataPattern
+ * @return parsed output of the df command
+ * @throws LinuxMonitoringException
+ */
+ private Map<String, DfData> readDfData(String[] cmd, Pattern headerPattern, Pattern dataPattern) throws LinuxMonitoringException {
+ Map<String, DfData> result = new HashMap<String, DfData>();
+ BufferedReader stdout = null;
+ InputStream stderr = null;
+ OutputStream stdin = null;
+ try {
+ synchronized(this) {
+ process = Runtime.getRuntime().exec(cmd); // (authorized)
+ stdout = new BufferedReader(new InputStreamReader(process.getInputStream()));
+ stderr = process.getErrorStream();
+ stdin = process.getOutputStream();
+ }
+ String line = stdout.readLine();
+ if (line == null) {
+ throw new LinuxMonitoringException("No data read from df process!");
+ }
+ // Check header.
+ Matcher m = headerPattern.matcher(line);
+ if (!m.matches()) {
+ throw new LinuxMonitoringException("Unexpected header from df process: " + line + ". " +
+ "Did not mach with regex: " + headerPattern.pattern());
+ }
+ // Read data.
+ do {
+ line = stdout.readLine();
+ if (line != null) {
+ m = dataPattern.matcher(line);
+ if (m.matches()) {
+ DfData dfData = new DfData(
+ m.group(1), // device name
+ m.group(6), // mount point
+ parseLongIgnoreAlpha(m.group(2)), // total
+ parseLongIgnoreAlpha(m.group(3)), // used
+ parseLongIgnoreAlpha(m.group(4)), // available
+ parseByteIgnorePcnt(m.group(5))); // percentage used
+ result.put(dfData.getDeviceName(), dfData);
+ } else {
+ String msg = "Df data line did not match: " + line + ". Pattern: " + dataPattern.pattern();
+ log.warn(msg);
+ }
+ }
+ } while (line != null);
+ } catch (IOException e) {
+ throw new LinuxMonitoringException("Error while reading data from df process",e);
+ } finally {
+ IOUtils.closeQuietly(stdout);
+ IOUtils.closeQuietly(stderr);
+ IOUtils.closeQuietly(stdin);
+ kill();
+ }
+ return result;
+ }
+
+ private void updateBeans(Map<String, String> fsTypeMap,
+ Map<String, DfData> dfDataMap,
+ Map<String, DfData> dfInodeDataMap) throws LinuxMonitoringException {
+
+ // these are maps df output data, keyed by devicename
+ for (String deviceName : dfDataMap.keySet()) {
+ String fsType = fsTypeMap.get(deviceName);
+ DfData dfData = dfDataMap.get(deviceName);
+ DfData dfInodeData = dfInodeDataMap.get(deviceName);
+
+ // Check filters, skipping if necessary
+ if (dfDeviceNameFilter.contains(deviceName)) {
+ continue;
+ }
+ if (dfFsTypeFilter.contains(fsType)) {
+ continue;
+ }
+
+ // May have null values in the case of parse errors.
+ if (fsType == null) {
+ log.warn("Unexpected null 'fsType' for device '" + deviceName + "'.");
+ }
+ if (dfData == null) {
+ log.warn("Unexpected null 'dfData' for device '" + deviceName + "'. Will update with empty values.");
+ dfData = new DfData(deviceName, null, null, null, null, null);
+ }
+ if (dfInodeData == null) {
+ log.warn("Unexpected null 'dfInodeData' for device '" + deviceName + "'. Will update with empty values.");
+ dfInodeData = new DfData(deviceName, null, null, null, null, null);
+ }
+
+ LinuxFileSystem newBean = new LinuxFileSystem(beanPathPrefix + dfData.getMountPoint(),
+ deviceName,
+ fsType,
+ dfData.getMountPoint(),
+ dfData.getTotal(),
+ dfData.getUsed(),
+ dfData.getAvailable(),
+ dfData.getPercentageUsed(),
+ dfInodeData.getTotal(),
+ dfInodeData.getUsed(),
+ dfInodeData.getAvailable(),
+ dfInodeData.getPercentageUsed());
+ updateBean(newBean);
+ }
+ }
+
+ private void updateBean(LinuxFileSystem fs) throws LinuxMonitoringException {
+ LinuxFileSystem jmxBean = filesystems.get(fs.objectName);
+ if(jmxBean == null) { // new device
+ try {
+ JMXUtils.registerMBean(fs, fs.objectName);
+ filesystems.put(fs.objectName, fs);
+ } catch (JMException e) {
+ log.error("Error registering bean for " + fs.objectName,e);
+ }
+ } else {
+ // updates bean in place
+ jmxBean.takeValues(fs);
+ log.debug(jmxBean.toString());
+ }
+ }
+
+ private Map<String, String> readFileSystemTypes() throws LinuxMonitoringException {
+ BufferedReader mounts = null;
+ final Map<String,String> fsTypeMap = new HashMap<String, String>();
+ try {
+ mounts = new BufferedReader(new InputStreamReader(new FileInputStream(mtabPath)));
+
+ String line = null;
+ do {
+ line = mounts.readLine();
+ if(line != null) {
+ // parse out the fields
+ // sample line: /dev/sda2 / ext3 rw 0 0
+ Matcher m = MTAB_DATA.matcher(line);
+ if(m.matches()) {
+ String fsName = m.group(1);
+ String fsType = m.group(3);
+ fsTypeMap.put(fsName, fsType);
+ }
+ }
+ } while(line != null);
+ } catch (IOException e) {
+ throw new LinuxMonitoringException("Error while reading data from " +
+ mtabPath.getAbsolutePath(),
+ e);
+ } finally {
+ IOUtils.closeQuietly(mounts);
+ }
+ return fsTypeMap;
+ }
+
+
+ /**
+ * Data container representing a device/mount point and all its attendant data.
+ * Values are unitless and are used to represent either megabytes or inodes - the output
+ * of a df line.
+ */
+ private static class DfData {
+ private final String deviceName;
+ private final String mountPoint;
+ private final Long total;
+ private final Long used;
+ private final Long available;
+ private final Byte percentageUsed;
+ public DfData(
+ String deviceName,
+ String mountPoint,
+ Long total,
+ Long used,
+ Long available,
+ Byte percentageUsed
+ ) {
+ this.deviceName = deviceName;
+ this.mountPoint = mountPoint;
+ this.total = total;
+ this.used = used;
+ this.available = available;
+ this.percentageUsed = percentageUsed;
+ }
+ public String getDeviceName() {
+ return deviceName;
+ }
+ public String getMountPoint() {
+ return mountPoint;
+ }
+ /**
+ * @return Size of this device in megabytes or inodes.
+ */
+ public Long getTotal() {
+ return total;
+ }
+ /**
+ * @return used resource (megabytes or inodes) on this device.
+ */
+ public Long getUsed() {
+ return used;
+ }
+ /**
+ * @return megabytes or inodes free on this device.
+ */
+ public Long getAvailable() {
+ return available;
+ }
+ /**
+ * @return Percentage of megabytes or in use.
+ */
+ public Byte getPercentageUsed() {
+ return percentageUsed;
+ }
+ }
+
+ private static final Timer TIMER = new Timer("DiskspaceMonitor Kill Timer", true);
+
+ /**
+ * Sets a background task to kill the child process of this monitor
+ * if it's taking too long to produce data. Caller holds reference to passed
+ * object and calls {@link KillTimerTask#cancel()} once background work has completed.
+ *
+ * @param delay millis to wait before killing background process
+ * @see Timer
+ * @return {@link KillTimerTask} object used to cancel timer.
+ */
+ private KillTimerTask setKillTimer(long delay) {
+ KillTimerTask tt = new KillTimerTask();
+ TIMER.schedule(tt, delay);
+ return tt;
+ }
+
+ /**
+ * Class used to kill background tasks that are taking too long to execute.
+ *
+ *
+ */
+ private class KillTimerTask extends TimerTask {
+ @Override
+ public void run() {
+ if (log.isDebugEnabled()) {
+ log.debug("Killing DiskspaceMonitor child process.");
+ }
+ // kill destroys the child process of the thread, not the thread itself
+ LinuxDiskspaceJMXWrapper.this.kill();
+ }
+ }
+
+
+ private static Long parseLongIgnoreAlpha(String longValue) {
+ try {
+ return Long.parseLong(longValue.replaceAll("[A-Za-z]", ""));
+ } catch(NumberFormatException e) {
+ if (log.isDebugEnabled()) {
+ log.debug("Error parsing value: " + longValue,e);
+ }
+ }
+ return null;
+ }
+
+ private static Byte parseByteIgnorePcnt(String byteValue) {
+ try {
+ return Byte.parseByte(byteValue.replaceAll("%", ""));
+ } catch(NumberFormatException e) {
+ if (log.isDebugEnabled()) {
+ log.debug("Error parsing value: " + byteValue,e);
+ }
+ }
+ return null;
+ }
+}
38 src/com/palantir/opensource/sysmon/linux/LinuxEntropyLevel.java
@@ -0,0 +1,38 @@
+// Copyright 2011 Palantir Technologies
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.palantir.opensource.sysmon.linux;
+
+/**
+ * Data container and JMX MBean implementation for entropy level measurements used by {@link LinuxEntropyLevelJMXWrapper}.
+ *
+ * @see <a href='http://linux.die.net/man/4/random'>the random(4) man page for more information on entropy pools</a>
+ *
+ */
+public class LinuxEntropyLevel implements LinuxEntropyLevelMBean {
+
+ private Integer entropyLevel;
+
+ public LinuxEntropyLevel(Integer entropyLevel) {
+ this.entropyLevel = entropyLevel;
+ }
+
+ public synchronized Integer getEntropyLevel() {
+ return entropyLevel;
+ }
+
+ public synchronized void updateValue(Integer entropyLevel) {
+ this.entropyLevel = entropyLevel;
+ }
+
+}
233 src/com/palantir/opensource/sysmon/linux/LinuxEntropyLevelJMXWrapper.java
@@ -0,0 +1,233 @@
+// Copyright 2011 Palantir Technologies
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.palantir.opensource.sysmon.linux;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Properties;
+
+import javax.management.JMException;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+
+import com.palantir.opensource.sysmon.Monitor;
+import com.palantir.opensource.sysmon.util.InterruptTimerTask;
+import com.palantir.opensource.sysmon.util.JMXUtils;
+import com.palantir.opensource.sysmon.util.PropertiesUtils;
+
+/**
+ * Monitors entropy pools that feed secure random number generation.
+ * <p>
+ * Entropy pool size is reported in bytes
+ * and is read out of the /proc filesystem on a configurable period.
+ * </p>
+ * <h3>JMX Data Path</h3>
+ * <code>sysmon.linux.beanpath:type=EntropyLevel</code>
+ * <h3>Configuration parameters</h3>
+ * <em>Note that any value not set in the config file will use the default value.</em>
+ * <table cellspacing=5 cellpadding=5><tr><th>Config Key</th><th>Description</th><th>Default Value</th><th>Constant</th></tr>
+ * <tr><td>sysmon.linux.entropyLevel.period</td>
+ * <td>Period, in seconds, between checks of the entropy pool</td>
+ * <td><code>10</code></td>
+ * <td>{@link #CONFIG_KEY_ENTROPY_LEVEL_PERIOD}</td></tr>
+ * </tr></table>
+ *
+ * @see Monitor Lifecycle documentation
+ * @see <a href='http://linux.die.net/man/4/random'>the random(4) man page for more information on entropy pools</a>
+ *
+ */
+public class LinuxEntropyLevelJMXWrapper extends Thread implements Monitor {
+
+ static final Logger log = LogManager.getLogger(LinuxEntropyLevelJMXWrapper.class);
+
+ static final String CONFIG_KEY_PREFIX = LinuxMonitor.CONFIG_KEY_PREFIX + ".entropyLevel";
+
+ /**
+ * Set this value in the configuration file to set how often (in seconds) the entropy
+ * levels are checked.
+ * Key: {@value}
+ * @see #DEFAULT_ENTROPY_LEVEL_PERIOD for default value for the configuration parameter.
+ */
+ public static final String CONFIG_KEY_ENTROPY_LEVEL_PERIOD = CONFIG_KEY_PREFIX + ".period";
+ /**
+ * Default value for how often (in seconds) the entropy
+ * levels are checked.
+ * Default: {@value}
+ * @see #CONFIG_KEY_ENTROPY_LEVEL_PERIOD for instructions on overriding the default value.
+ */
+ public static final int DEFAULT_ENTROPY_LEVEL_PERIOD = 10;
+ /**
+ * Path to check in proc for entropy pool status.
+ * Path: {@value}
+ */
+ static final File DATA_PATH = new File("/proc/sys/kernel/random/entropy_avail");
+ /**
+ * Path where this bean publishes its values.
+ * Path: {@value}
+ */
+ public static final String OBJECT_NAME = ":type=EntropyLevel";
+ /**
+ * Start background thread to monitor entropy levels.
+ *
+ * @throws LinuxMonitoringException
+ */
+ public void startMonitoring() {
+ // Start monitoring on a separate thread.
+ start();
+ }
+ /**
+ * Signals for the background thread to shutdown and waits for its execution to finish.
+ * @throws InterruptedException if the calling thread is interrupted while waiting for
+ * the background thread to {@link Thread#join()}.
+ */
+ public void stopMonitoring() throws InterruptedException {
+ this.shutdown = true;
+ this.join();
+ }
+
+ /**
+ * MX Bean holding entropy pool size. Defaults to 0 before any readings.
+ *
+ */
+ final LinuxEntropyLevel bean = new LinuxEntropyLevel(0);
+
+ /**
+ * How long to sleep between reads of {@link #DATA_PATH}.
+ */
+ final int period;
+ /**
+ * Cross-thread shutdown flag.
+ */
+ volatile boolean shutdown = false;
+
+ /**
+ * Path to place the entropy pool data.
+ */
+ final String beanPath;
+
+ /**
+ * Constructs a new {@link LinuxEntropyLevel} object. You must call
+ * {@link #startMonitoring()} to start the background thread that will
+ * continually update the entropy pool values.
+ *
+ * Note that the constructor will do a single read of the entropy to verify that everything
+ * will work in the background thread. Therefore, constructing one of these object is a valid
+ * way to take a one-time reading of the entropy pool size.
+ *
+ * @param config configuration for this JMX wrapper. Passing null or an empty
+ * {@link Properties} object will just use the default config values.
+ * @throws LinuxMonitoringException on configuration or data read error.
+ *
+ */
+ public LinuxEntropyLevelJMXWrapper(Properties config) throws LinuxMonitoringException {
+ super(LinuxEntropyLevelJMXWrapper.class.getSimpleName());
+ this.setDaemon(true);
+
+ // use empty properties to avoid NPE and pick up defaults
+ if(config == null) {
+ config = new Properties();
+ }
+
+ try {
+ String beanPathPrefix = config.getProperty(LinuxMonitor.CONFIG_KEY_JMX_BEAN_PATH,
+ LinuxMonitor.DEFAULT_JMX_BEAN_PATH);
+ this.beanPath = beanPathPrefix + OBJECT_NAME;
+ this.period = PropertiesUtils.extractInteger(config,
+ CONFIG_KEY_ENTROPY_LEVEL_PERIOD,
+ DEFAULT_ENTROPY_LEVEL_PERIOD);
+ } catch (NumberFormatException e) {
+ throw new LinuxMonitoringException("Invalid config parameter for " + CONFIG_KEY_ENTROPY_LEVEL_PERIOD, e);
+ }
+
+ // Check to make sure it will all work.
+ if(!DATA_PATH.exists()) {
+ throw new LinuxMonitoringException("No such path: " + DATA_PATH.getAbsolutePath() +
+ ". Can't read entropy level. (Is /proc mounted?)");
+ }
+ if(!DATA_PATH.canRead()) {
+ throw new LinuxMonitoringException("Permission denied: " + DATA_PATH.getAbsolutePath());
+ }
+
+
+ // Tick once to detect any errors. Convert seconds to milliseconds.
+ InterruptTimerTask timer = InterruptTimerTask.setInterruptTimer(4000L * period);
+ try {
+ // take a reading
+ readData();
+ JMXUtils.registerMBean(bean, beanPath);
+ } catch(JMException e){
+ final String msg = "Error while registering bean to path " + beanPath;
+ throw new LinuxMonitoringException(msg,e);
+ }
+ finally {
+ timer.cancel();
+ }
+ }
+
+ @Override
+ public void run() {
+ try {
+ do {
+ readData();
+ // Convert seconds to milliseconds.
+ Thread.sleep(1000L * this.period);
+ } while(!shutdown);
+ } catch (Exception e) {
+ log.error("Shutting down entropy level monitoring due to error.",e);
+ }
+ }
+
+ /**
+ * Reads the entropy pool out of the /proc file system and publishes value to a JMX MXBean.
+ *
+ * @throws LinuxMonitoringException on error with reading value.
+ */
+ void readData() throws LinuxMonitoringException {
+ ByteArrayOutputStream baos = null;
+ InputStream data = null;
+ BufferedReader lines = null;
+
+ try {
+ data = new FileInputStream(DATA_PATH);
+ baos = new ByteArrayOutputStream();
+ IOUtils.copy(data, baos);
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ InputStreamReader inputStreamReader = new InputStreamReader(bais);
+ lines = new BufferedReader(inputStreamReader);
+
+ Integer entropyLevel = null;
+ String entropyValue = lines.readLine();
+ try {
+ entropyLevel = Integer.parseInt(entropyValue);
+ } catch (NumberFormatException e) {
+ log.warn("Error parsing value: " + entropyValue, e);
+ }
+ bean.updateValue(entropyLevel);
+ } catch (IOException e) {
+ throw new LinuxMonitoringException("Unexpected IOException during processing.",e);
+ } finally {
+ IOUtils.closeQuietly(data);
+ IOUtils.closeQuietly(baos);
+ IOUtils.closeQuietly(lines);
+ }
+ }
+}
23 src/com/palantir/opensource/sysmon/linux/LinuxEntropyLevelMBean.java
@@ -0,0 +1,23 @@
+// Copyright 2011 Palantir Technologies
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.palantir.opensource.sysmon.linux;
+
+/**
+ * MBean interface for {@link LinuxEntropyLevelJMXWrapper}.
+ */
+public interface LinuxEntropyLevelMBean {
+
+ public abstract Integer getEntropyLevel();
+
+}
131 src/com/palantir/opensource/sysmon/linux/LinuxFileSystem.java
@@ -0,0 +1,131 @@
+// Copyright 2011 Palantir Technologies
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.palantir.opensource.sysmon.linux;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
+/**
+ * <p>
+ * Data container and JMX MBean implementation for file system storage measurements used by {@link LinuxDiskspaceJMXWrapper}.
+ * </p>
+ *
+ * @see <a href='http://linux.die.net/man/1/df'>df(1) for detailed information on each measurement</a>
+ */
+public class LinuxFileSystem implements LinuxFileSystemMBean {
+
+ /**
+ * JMX path for this object.
+ */
+ public final String objectName;
+
+ private final String deviceName;
+ private final String filesytemType;
+ private final String mountPoint;
+
+ private Long totalMegabytes;
+ private Long usedMegabytes;
+ private Long availableMegabytes;
+ private Byte percentageSpaceUsed;
+
+ private Long totalInodes;
+ private Long usedInodes;
+ private Long availableInodes;
+ private Byte percentageInodesUsed;
+
+ public LinuxFileSystem(
+ String objectName,
+ String filesystemName,
+ String filesytemType,
+ String mountPoint,
+ Long totalMegabytes,
+ Long usedMegabytes,
+ Long availableMegabytes,
+ Byte percentageSpaceUsed,
+ Long totalInodes,
+ Long usedInodes,
+ Long availableInodes,
+ Byte percentageInodesUsed
+ ) {
+ this.objectName = objectName;
+ this.deviceName = filesystemName;
+ this.filesytemType = filesytemType;
+ this.mountPoint = mountPoint;
+ this.totalMegabytes = totalMegabytes;
+ this.usedMegabytes = usedMegabytes;
+ this.availableMegabytes = availableMegabytes;
+ this.percentageSpaceUsed = percentageSpaceUsed;
+ this.totalInodes = totalInodes;
+ this.usedInodes = usedInodes;
+ this.availableInodes = availableInodes;
+ this.percentageInodesUsed = percentageInodesUsed;
+ }
+
+ public String getDevicName() {
+ return deviceName;
+ }
+
+ public String getFilesytemType() {
+ return filesytemType;
+ }
+
+ public String getMountPoint() {
+ return mountPoint;
+ }
+
+ public Long getTotalMegabytes() {
+ return totalMegabytes;
+ }
+
+ public Long getUsedMegabytes() {
+ return usedMegabytes;
+ }
+
+ public Long getAvailableMegabytes() {
+ return availableMegabytes;
+ }
+
+ public Byte getPercentageSpaceUsed() {
+ return percentageSpaceUsed;
+ }
+
+ public Long getTotalInodes() {
+ return totalInodes;
+ }
+
+ public Long getUsedInodes() {
+ return usedInodes;
+ }
+
+ public Long getAvailableInodes() {
+ return availableInodes;
+ }
+
+ public Byte getPercentageInodesUsed() {
+ return percentageInodesUsed;
+ }
+
+ public synchronized void takeValues(LinuxFileSystemMBean dataBean) throws LinuxMonitoringException {
+ try {
+ Field[] fields = dataBean.getClass().getDeclaredFields();
+ for(Field f : fields ) {
+ if((f.getModifiers() & Modifier.FINAL) != Modifier.FINAL) {
+ f.set(this, f.get(dataBean));
+ }
+ }
+ } catch (Exception e) {
+ throw new LinuxMonitoringException("Error while reflectively copying fields",e);
+ }
+ }
+}
43 src/com/palantir/opensource/sysmon/linux/LinuxFileSystemMBean.java
@@ -0,0 +1,43 @@
+// Copyright 2011 Palantir Technologies
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.palantir.opensource.sysmon.linux;
+
+/**
+ * MBean interface for {@link LinuxDiskspaceJMXWrapper}.
+ */
+public interface LinuxFileSystemMBean {
+
+ public abstract String getDevicName();
+
+ public abstract String getFilesytemType();
+
+ public abstract String getMountPoint();
+
+ public abstract Long getTotalMegabytes();
+
+ public abstract Long getUsedMegabytes();
+
+ public abstract Long getAvailableMegabytes();
+
+ public abstract Byte getPercentageSpaceUsed();
+
+ public abstract Long getTotalInodes();
+
+ public abstract Long getUsedInodes();
+
+ public abstract Long getAvailableInodes();
+
+ public abstract Byte getPercentageInodesUsed();
+
+}
241 src/com/palantir/opensource/sysmon/linux/LinuxIOStat.java
@@ -0,0 +1,241 @@
+// Copyright 2011 Palantir Technologies
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.palantir.opensource.sysmon.linux;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+
+/**
+ * <p>
+ * Data container and JMX MBean implementation for I/O statistics used by
+ * {@link LinuxIOStatJMXWrapper}.
+ * </p>
+ * <p>
+ * iostat output looks like this:
+ * <pre>
+ * Linux 2.6.26.5-28.fc8 (nosuchhost.palantir.com) 11/12/2008
+ *
+ *
+ * Device: rrqm/s wrqm/s r/s w/s rMB/s wMB/s avgrq-sz avgqu-sz await svctm %util
+ * sda 0.14 0.68 0.41 0.72 0.01 0.03 62.52 0.06 55.69 3.15 0.35
+ * sda1 0.00 0.00 0.00 0.00 0.00 0.00 21.42 0.00 13.80 10.32 0.00
+ * sda2 0.04 0.54 0.30 0.54 0.00 0.00 19.19 0.04 43.17 3.10 0.26
+ * sda3 0.00 0.00 0.00 0.00 0.00 0.00 30.38 0.00 40.20 34.95 0.00
+ * sda4 0.00 0.00 0.00 0.00 0.00 0.00 2.00 0.00 13.33 13.33 0.00
+ * sda5 0.10 0.14 0.10 0.18 0.00 0.02 189.29 0.03 92.34 3.94 0.11
+ * </pre>
+ * @see <a href='http://linux.die.net/man/1/iostat'>iostat(1) for more on the meaning
+ * of each measurement</a>
+ */
+public class LinuxIOStat implements LinuxIOStatMBean {
+
+ static final Logger log = LogManager.getLogger(LinuxIOStat.class);
+
+ volatile long timestamp;
+
+ volatile String device;
+
+ final String objectName;
+
+
+ /**
+ * Over what period the aveages were computed, in seconds;
+ */
+ volatile int samplePeriodInSeconds;
+
+ /**
+ * The number of read requests merged per second that were queued to the device.
+ */
+ volatile float mergedReadRequestsPerSecond;
+
+ /**
+ * The number of write requests merged per second that were queued to the device.
+ */
+ volatile float mergedWriteRequestsPerSecond;
+
+ /**
+ *The number of read requests that were issued to the device per second.
+ */
+ volatile float readRequestsPerSecond;
+ /**
+ * The number of write requests that were issued to the device per second.
+ */
+ volatile float writeRequestsPerSecond;
+ /**
+ * The number of megabytes read from the device per second.
+ */
+ volatile float kilobytesReadPerSecond;
+ /**
+ * The number of megabytes written to the device per second.
+ */
+ volatile float kilobytesWrittenPerSecond;
+
+ /**
+ * The average size (in sectors) of the requests that were issued to the device.
+ */
+ volatile float averageRequestSizeInSectors;
+ /**
+ * The average queue length of the requests that were issued to the device.
+ */
+ volatile float averageQueueLengthInSectors;
+ /**
+ * The average time (in milliseconds) for I/O requests issued to the device to be served.
+ * This includes the time spent by the requests in queue and the time spent servicing them.
+ *
+ */
+ volatile float averageWaitTimeInMillis;
+ /**
+ * The average service time (in milliseconds) for I/O requests that were issued to the device.
+ */
+ volatile float averageServiceTimeInMillis;
+ /**
+ * Percentage of CPU time during which I/O requests were issued to the device (bandwidth
+ * utilization for the device). Device saturation occurs when this value is close to 100%.
+ */
+ volatile float bandwidthUtilizationPercentage;
+
+
+ public LinuxIOStat(String objectName) {
+ this.objectName = objectName;
+ }
+
+ public synchronized void takeValues(LinuxIOStatMBean dataBean) throws LinuxMonitoringException {
+ try {
+ Field[] fields = dataBean.getClass().getDeclaredFields();
+ for(Field f : fields ) {
+ if((f.getModifiers() & Modifier.FINAL) != Modifier.FINAL) {
+ f.set(this, f.get(dataBean));
+ }
+ }
+ } catch (Exception e) {
+ throw new LinuxMonitoringException("Error while refletively copying fields",e);
+ }
+ }
+
+
+ public synchronized final String getDevice() {
+ return device;
+ }
+
+
+ public synchronized final int getSamplePeriodInSeconds() {
+ return samplePeriodInSeconds;
+ }
+
+
+ public synchronized final float getMergedReadRequestsPerSecond() {
+ return mergedReadRequestsPerSecond;
+ }
+
+
+ public synchronized final float getMergedWriteRequestsPerSecond() {
+ return mergedWriteRequestsPerSecond;
+ }
+
+
+ public synchronized final float getReadRequestsPerSecond() {
+ return readRequestsPerSecond;
+ }
+
+
+ public synchronized final float getWriteRequestsPerSecond() {
+ return writeRequestsPerSecond;
+ }
+
+
+ public synchronized final float getKilobytesReadPerSecond() {
+ // QA-26161: PEM: Weird spike in stat
+ if (kilobytesReadPerSecond > 1000000000000.0f) {
+ log.error("Unexpected value for kilobytesReadPerSecond, " + kilobytesReadPerSecond);
+ }
+ return kilobytesReadPerSecond;
+ }
+
+
+ public synchronized final float getKilobytesWrittenPerSecond() {
+ // QA-26161: PEM: Weird spike in stat
+ if (kilobytesWrittenPerSecond > 1000000000000.0f) {
+ log.error("Unexpected value for kilobytesWrittenPerSecond, " + kilobytesWrittenPerSecond);
+ }
+ return kilobytesWrittenPerSecond;
+ }
+
+
+ public synchronized final float getAverageRequestSizeInSectors() {
+ // QA-26161: PEM: Weird spike in stat
+ if (averageRequestSizeInSectors > 1000000000000.0f) {
+ log.error("Unexpected value for averageRequestSizeInSectors, " + averageRequestSizeInSectors);
+ }
+ return averageRequestSizeInSectors;
+ }
+
+
+ public synchronized final float getAverageQueueLengthInSectors() {
+ return averageQueueLengthInSectors;
+ }
+
+
+ public synchronized final float getAverageWaitTimeInMillis() {
+ // QA-26161: PEM: Weird spike in stat
+ if (averageWaitTimeInMillis > 1000000000000.0f) {
+ log.error("Unexpected value for averageWaitTimeInMillis, " + averageWaitTimeInMillis);
+ }
+ return averageWaitTimeInMillis;
+ }
+
+
+ public synchronized final float getAverageServiceTimeInMillis() {
+ return averageServiceTimeInMillis;
+ }
+
+
+ public synchronized final float getBandwidthUtilizationPercentage() {
+ // QA-26161: PEM: Weird spike in stat
+ if (bandwidthUtilizationPercentage > 1000000000000.0f) {
+ log.error("Unexpected value for bandwidthUtilizationPercentage, " + bandwidthUtilizationPercentage);
+ }
+ return bandwidthUtilizationPercentage;
+ }
+
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((objectName == null) ? 0 : objectName.hashCode());
+ return result;
+ }
+
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ final LinuxIOStat other = (LinuxIOStat) obj;
+ if (objectName == null) {
+ if (other.objectName != null)
+ return false;
+ } else if (!objectName.equals(other.objectName))
+ return false;
+ return true;
+ }
+
+}
559 src/com/palantir/opensource/sysmon/linux/LinuxIOStatJMXWrapper.java
@@ -0,0 +1,559 @@
+// Copyright 2011 Palantir Technologies
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.palantir.opensource.sysmon.linux;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.management.JMException;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+
+import com.palantir.opensource.sysmon.Monitor;
+import com.palantir.opensource.sysmon.util.InterruptTimerTask;
+import com.palantir.opensource.sysmon.util.JMXUtils;
+import com.palantir.opensource.sysmon.util.PropertiesUtils;
+
+
+/**
+ * <p>Monitors I/O statistics as reported by <a href='http://linux.die.net/man/1/iostat'>iostat</a></p>
+ * <p>
+ * This class that fires up <a href='http://linux.die.net/man/1/iostat'>iostat</a>
+ * in a background process, reads its output, and publishes it via JMX MBeans.
+ * </p>
+ *
+ * <h3>JMX Data Path</h3>
+ * Each device will be placed at:
+ * <code>sysmon.linux.beanpath:type=io-device,devicename=&lt;devicename&gt;</code>
+ *
+ * <h3>Configuration parameters</h3>
+ * <em>Note that any value not set in the config file will use the default value.</em>
+ * <table cellspacing=5 cellpadding=5><tr><th>Config Key</th><th>Description</th><th>Default Value</th><th>Constant</th></tr>
+ * <tr><td>sysmon.linux.iostat.path</td>
+ * <td>path to <code>iostat</code> binary</td>
+ * <td><code>iostat</code></td>
+ * <td>{@link #CONFIG_KEY_IOSTAT_PATH}</td></tr>
+ * <tr><td>sysmon.linux.iostat.opts</td>
+ * <td>options passed to iostat</td>
+ * <td><code>-d -x -k</code></td>
+ * <td>{@link #CONFIG_KEY_IOSTAT_OPTIONS}</td></tr>
+ * <tr><td>sysmon.linux.iostat.period</td>
+ * <td>period, in seconds, between iostat reports</td>
+ * <td><code>60</code></td>
+ * <td>{@link #CONFIG_KEY_IOSTAT_PERIOD}</td></tr>
+ * </tr></table>
+ * @see Monitor Lifecycle documentation
+ * @see <a href='http://linux.die.net/man/1/iostat'>iostat(1)</a> for more information on <code>iostat</code>.
+ */
+public class LinuxIOStatJMXWrapper extends Thread implements Monitor {
+
+ static final Logger log = LogManager.getLogger(LinuxIOStatJMXWrapper.class);
+
+ static final String CONFIG_KEY_PREFIX = LinuxMonitor.CONFIG_KEY_PREFIX + ".iostat";
+
+ /**
+ * Path to iostat executable. Defaults to "iostat" (uses $PATH to find executable).
+ * Set this config value in the to override where to find iostat.
+ *
+ * Config key: {@value}
+ * @see LinuxIOStatJMXWrapper#DEFAULT_IOSTAT_PATH default value for this config parameter
+ * @see <a href='http://linux.die.net/man/1/iostat'>iostat(1) on your local linux box</a>
+ */
+ public static final String CONFIG_KEY_IOSTAT_PATH = CONFIG_KEY_PREFIX + ".path";
+
+ /**
+ * <p>
+ * Options passed to <code>iostat</code> (other than period argument).
+ * </p>
+ * <p>
+ * Note that passing config values that
+ * change the format of the output from <code>iostat</code> may break this monitor. Proceed
+ * with caution.
+ * </p><p>
+ * Set this key in the config file to override default values.
+ * </p>
+ * Config key: {@value}
+ * @see LinuxIOStatJMXWrapper#DEFAULT_IOSTAT_OPTIONS default value for this config parameter
+ * @see <a href='http://linux.die.net/man/1/iostat'>iostat(1) on your local linux box</a>
+ */
+ public static final String CONFIG_KEY_IOSTAT_OPTIONS = CONFIG_KEY_PREFIX + ".opts";
+
+ /**
+ * Period for iostat. Set this config value to override how often iostat is outputting values.
+ *
+ * Config key: {@value}
+ * @see LinuxIOStatJMXWrapper#DEFAULT_IOSTAT_PERIOD default value for this config parameter
+ * @see <a href='http://linux.die.net/man/1/iostat'>iostat(1) on your local linux box</a>
+ */
+ public static final String CONFIG_KEY_IOSTAT_PERIOD = CONFIG_KEY_PREFIX + ".period";
+
+ /**
+ * Default path to iostat executable. Defaults to "iostat" (uses $PATH to find executable).
+ *
+ * Config key: {@value}
+ * @see LinuxIOStatJMXWrapper#CONFIG_KEY_IOSTAT_PATH instructions on overriding this value.
+ * @see <a href='http://linux.die.net/man/1/iostat'>iostat(1) on your local linux box</a>
+ */
+ public static final String DEFAULT_IOSTAT_PATH = "iostat"; // let the shell figure it out
+
+ /**
+ * Default options passed to iostat executable.
+ *
+ * Config key: {@value}
+ * @see LinuxIOStatJMXWrapper#CONFIG_KEY_IOSTAT_OPTIONS instructions on overriding this value.
+ * @see <a href='http://linux.die.net/man/1/iostat'>iostat(1) on your local linux box</a>
+ */
+ public static final String DEFAULT_IOSTAT_OPTIONS = "-d -x -k";
+
+ /**
+ * Default period between iostat output (in seconds).
+ *
+ * Config key: {@value}
+ * @see LinuxIOStatJMXWrapper#CONFIG_KEY_IOSTAT_PERIOD Instructions on overriding this value.
+ * @see <a href='http://linux.die.net/man/1/iostat'>iostat(1) on your local linux box</a>
+ */
+ public static final Integer DEFAULT_IOSTAT_PERIOD = Integer.valueOf(60);
+
+ /**
+ * Relative JMX data path where this monitor publishes its data. This will have the
+ * individual device name appended to the end in the JMX tree.
+ * Path: {@value}
+ */
+ public static final String OBJECT_NAME_PREFIX = ":type=io-device,devicename=" ;
+
+
+ static final String FIRST_LINE_PREFIX = "Linux 2.6";
+
+ /**
+ * iostat likes to sometimes break things across two lines. This detects that situation.
+ * Pattern: {@value}
+ */
+ static final Pattern DEVICE_ONLY = Pattern.compile("^\\s*\\S+\\s*$");
+ /**
+ * regex to match version 7.x of iostat.
+ * {@value}
+ */
+ static final String HEADER_V7_RE =
+ "^\\s*Device:\\s+rrqm/s\\s+wrqm/s\\s+r/s\\s+w/s\\s+rkB/s\\s+wkB/s\\s+avgrq-sz\\s+" +
+ "avgqu-sz\\s+await\\s+svctm\\s+%util\\s*$";
+ /**
+ * regex to match version 5.x of iostat.
+ * Pattern: {@value}
+ */
+ static final String HEADER_V5_RE =
+ "^\\s*Device:\\s+rrqm/s\\s+wrqm/s\\s+r/s\\s+w/s\\s+rsec/s\\s+wsec/s\\s+rkB/s\\s+" +
+ "wkB/s\\s+avgrq-sz\\s+avgqu-sz\\s+await\\s+svctm\\s+%util\\s*$";
+ /**
+ * {@link Pattern} to match version 7.x of iostat header output.
+ * Pattern: {@value}
+ */
+ static final Pattern HEADER_V7_PAT = Pattern.compile(HEADER_V7_RE);
+ /**
+ * {@link Pattern} to match version 5.x of iostat header output.
+ * Pattern: {@value}
+ */
+ static final Pattern HEADER_V5_PAT = Pattern.compile(HEADER_V5_RE);
+ /**
+ * {@link Pattern} to match version 7.x of iostat data output.
+ * Pattern: {@value}
+ */
+ static final Pattern DATA_V7_PAT = buildWhitespaceDelimitedRegex(12);
+
+ /**
+ * Pattern for version 5 of iostat. It has three additional fields that we ignore in our
+ * parsing. Because of that, we don't build it programmatically, but use this specially
+ * rolled regex. The upshot is that it's group index compatible with the version 7 regex.
+ */
+ static final String DATA_V5_RE = "^\\s*(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)" +
+ "\\s+\\S+\\s+\\S+\\s+" + // skipped fields
+ "(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)" +
+ "\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s*$";
+ /**
+ * {@link Pattern} to match version 5.x of iostat data output.
+ * Pattern: {@value}
+ */
+ static final Pattern DATA_V5_PAT = Pattern.compile(DATA_V5_RE);
+
+ long freshnessTimestamp = System.currentTimeMillis();
+ final String iostatCmd[];
+ final int period;
+ final String iostatPath;
+ final String beanPath;
+ volatile boolean shutdown = false;
+ Process iostat = null;
+ BufferedReader iostatStdout = null;
+ InputStream iostatStderr = null;
+ OutputStream iostatStdin = null;
+ Pattern dataPattern = null;
+ Pattern headerPattern = null;
+
+ final Map<String,LinuxIOStat> beans = new HashMap<String, LinuxIOStat>();
+
+ /**
+ * Constructs a new iostat JMX wrapper. Does not start monitoring. Call
+ * {@link #startMonitoring()} to start monitoring and publishing
+ * JMX data.
+ *
+ * @param config configuration for this service
+ * @see #CONFIG_KEY_IOSTAT_OPTIONS
+ * @see #CONFIG_KEY_IOSTAT_PATH
+ * @see #CONFIG_KEY_IOSTAT_PERIOD
+ * @throws LinuxMonitoringException upon error in setting up this service.
+ */
+ public LinuxIOStatJMXWrapper(Properties config) throws LinuxMonitoringException {
+ super(LinuxIOStatJMXWrapper.class.getSimpleName());
+ this.setDaemon(true);
+
+ if(config == null) {
+ // blank one to get all the defaults
+ config = new Properties();
+ }
+
+ try {
+ final String beanPathPrefix = config.getProperty(LinuxMonitor.CONFIG_KEY_JMX_BEAN_PATH,
+ LinuxMonitor.DEFAULT_JMX_BEAN_PATH);
+ beanPath = beanPathPrefix + OBJECT_NAME_PREFIX;
+
+ iostatPath = config.getProperty(CONFIG_KEY_IOSTAT_PATH,DEFAULT_IOSTAT_PATH);
+ period = PropertiesUtils.extractInteger(config,
+ CONFIG_KEY_IOSTAT_PERIOD,
+ DEFAULT_IOSTAT_PERIOD);
+ String iostatOpts = config.getProperty(CONFIG_KEY_IOSTAT_OPTIONS,
+ DEFAULT_IOSTAT_OPTIONS);
+ String cmd = iostatPath + " " + iostatOpts + " " + period;
+ this.iostatCmd = (cmd).split("\\s+");
+ log.info("iostat cmd: " + cmd);
+
+ } catch (NumberFormatException e) {
+ throw new LinuxMonitoringException("Invalid config Parameter for " +
+ CONFIG_KEY_IOSTAT_PERIOD,e);
+ }
+ }
+
+ /**
+ * Start iostat as a background process and makes sure header output
+ * parses correctly. If no errors are encountered, starts this instance's Thread
+ * to read the data from the iotstat process in the background.
+ *
+ * @throws LinuxMonitoringException upon error with iostat startup.
+ */
+ public void startMonitoring() throws LinuxMonitoringException {
+ if(shutdown){
+ throw new LinuxMonitoringException("Do not reuse " + getClass().getSimpleName() + " objects");
+ }
+ try {
+ // check that we can start iostat in the background
+ startIOStat();
+ // jump off into thread land
+ start();
+ } catch (LinuxMonitoringException e) {
+ cleanup();
+ throw e;
+ }
+ }
+
+ /**
+ * Fires up iostat in the background and verifies that the header data parses
+ * as expected.
+ *
+ * @throws LinuxMonitoringException upon error starting iostat or parsing output.
+ */
+ private void startIOStat() throws LinuxMonitoringException {
+ // Convert seconds to milliseconds for setInterruptTimer.
+ InterruptTimerTask timer = InterruptTimerTask.setInterruptTimer(1000L * period);
+ try {
+
+ iostat = Runtime.getRuntime().exec(iostatCmd);
+ iostatStdout = new BufferedReader(new InputStreamReader(iostat.getInputStream()));
+ iostatStderr = iostat.getErrorStream();
+ iostatStdin = iostat.getOutputStream();
+
+ // first line is discarded
+ String firstLine = iostatStdout.readLine();
+ if(firstLine == null) {
+ throw new LinuxMonitoringException("Unexpected end of input from iostat: " +
+ "null first line");
+ }
+ if(!firstLine.trim().startsWith(FIRST_LINE_PREFIX)) {
+ log.warn("iostat returned unexpected first line: " + firstLine +
+ ". Expected something that started with: " + FIRST_LINE_PREFIX);
+ }
+
+ String secondLine = iostatStdout.readLine();
+ if(secondLine == null) {
+ throw new LinuxMonitoringException("Unexpected end of input from iostat: " +
+ "null second line");
+ }
+ if(!(secondLine.trim().length() == 0)) {
+ throw new LinuxMonitoringException("Missing blank second line. Found this instead: " +
+ secondLine);
+ }
+ // make sure we're getting the fields we expect
+ String headerLine = iostatStdout.readLine();
+ if(headerLine == null) {
+ throw new LinuxMonitoringException("Unexpected end of input from iostat: " +
+ "null header line");
+ }
+
+ if(HEADER_V5_PAT.matcher(headerLine).matches()){
+ log.info("Detected iostats version 5.");
+ headerPattern = HEADER_V5_PAT;
+ dataPattern = DATA_V5_PAT;
+ } else if(HEADER_V7_PAT.matcher(headerLine).matches()) {
+ log.info("Detected iostats version 7.");
+ headerPattern = HEADER_V7_PAT;
+ dataPattern = DATA_V7_PAT;
+ } else {
+ final String msg = "Header line does match expected header! Expected: " +
+ HEADER_V7_PAT.pattern() + "\nGot: " + headerLine + "\n";
+ throw new LinuxMonitoringException(msg);