Overview
metrics-sampler is a java program which regularly queries metrics from a configured set of inputs, selects and renames them using regular expressions and sends them to a configured set of outputs. It supports JMX and JDBC as inputs and Graphite as output out of the box. Writing new extensions containing new inputs, outputs, samplers and selectors is pretty straight-forward.
Example Configuration
Check out the following configuration as a quick-start:
<configuration>
<includes>
<include location="selectors/*.xml" />
</includes>
<shared-resources>
<thread-pool name="samplers" size="10" />
<thread-pool name="custom.samplers" size="2" />
<jdbc-connection-pool name="oracle01" url="jdbc:oracle:thin:@//oracle1.metrics-sampler.org:1521/EXAMPLE" username="user" password="password" driver="oracle.jdbc.OracleDriver" min-size="1" max-size="5" />
</shared-resources>
<inputs>
<jmx name="wls-template" template="true" username="admin" password="weblogic1" provider-packages="weblogic.management.remote" persistent-connection="true">
<ignore-object-names>
<ignore-object-name regexp="^com\.oracle\.jrockit:type=Flight.+" />
</ignore-object-names>
<connection-properties>
<entry key="jmx.remote.x.request.waiting.timeout" value="100" />
</connection-properties>
<socket-options connect-timeout="100" so-timeout="200" keep-alive="false" send-buffer-size="16384" receive-buffer-size="16384" />
<variables>
<string name="tomcat.port" value="8080" />
</variables>
</jmx>
<jmx name="wls01" url="service:jmx:t3://weblogic1.metrics-sampler.org:6001/jndi/weblogic.management.mbeanservers.runtime" parent="wls-template" />
<jmx name="wls02" url="service:jmx:t3://weblogic2.metrics-sampler.org:6001/jndi/weblogic.management.mbeanservers.runtime" parent="wls-template" />
<jmx name="tomcat01" url="service:jmx:rmi:///jndi/rmi://tomcat.metrics-sampler.org:7001/jmxrmi" persistent-connection="true" />
<jdbc name="oracle01" pool="oracle01">
<query>select replace(T2.host_name||'.'||T2.instance_name||'.'||replace(replace(replace(replace(metric_name,'/',''),'%','Perc'),'(',''),')',''),' ','_') as metric, value, (25200 + round((end_time - to_date('01-JAN-1970','DD-MON-YYYY')) * (86400),0))*1000 as dt from gv$sysmetric T1, gv$instance T2 where T1.intsize_csec between 1400 and 1600 and T1.inst_id = T2.INST_ID</query>
</jdbc>
<apache-status name="apache01" url="http://apache1.metrics-sampler.org:80/qos-viewer?auto" username="user" password="pass" />
<redis name="redis" host="redis.metrics-sampler.org" port="6379">
<commands>
<redis-hlen database="0" key="hash1" />
<redis-llen key="list1" />
</commands>
</redis>
<oracle-nosql name="oracle-nosql" hosts="kv1.metrics-sampler.org:5000 kv2.metrics-sampler.org:5000 kv3.metrics-sampler.org:5000 kv4.metrics-sampler.org:5000" />
<webmethods name="webmethods1" url="http://webmethods1.example.com:1234/invoke/wm.server.admin/getDiagnosticData" username="user" password="pass" max-entry-size="10000000" date-format="yyyy-MM-dd HH:mm:ss z" />
<self name="self" />
<exec name="exec1" command="cmd">
<arguments>
<argument>/C</argument>
<argument>echo %METRIC%=28</argument>
</arguments>
<environment>
<entry key="METRIC" value="a.b.c" />
</environment>
</exec>
<http name="http1" url="http://localhost/query_metrics.php?format=csv" username="user" password="pass">
<regexp-response-parser>
<regexp-line-format expression="\s*(\S+)\s*=\s*(\S+)\s*" name-index="1" value-index="2" />
<regexp-line-format expression="\s*(\S+)\s*:\s*(\S+)\s*" name-index="1" value-index="2" />
</regexp-response-parser>
</http>
</inputs>
<outputs>
<console name="console" />
<graphite name="graphite" host="graphite.metrics-sampler.org" port="2003" default="true" />
</outputs>
<variables>
<string name="tomcat.port" value="8080" />
</variables>
<selector-groups>
<selector-group name="wls">
<regexp from-name="com\.bea:Name=DataSource_(.+),ServerRuntime=.+,Type=JDBCOracleDataSourceRuntime\.(ActiveConnectionsAverageCount|ActiveConnectionsCurrentCount|ActiveConnectionsHighCount|ConnectionDelayTime|ConnectionsTotalCount|CurrCapacity|CurrCapacityHighCount|FailuresToReconnectCount|HighestNumAvailable|HighestNumUnavailable|LeakedConnectionCount|NumAvailable|NumUnavailable|ReserveRequestCountWaitSecondsHighCount|WaitingForConnection.*)" to-name="${prefix}.jdbc.${name[1]}.${name[2]}" />
<regexp from-name="com\.bea:Name=JTARuntime,ServerRuntime=.*,Type=JTARuntime\.(.*TotalCount)" to-name="${prefix}.jta.${name[1]}" />
<regexp from-name="com\.bea:Name=ThreadPoolRuntime,ServerRuntime=.*,Type=ThreadPoolRuntime\.(CompletedRequestCount|ExecuteThreadIdleCount|ExecuteThreadTotalCount|HoggingThreadCount|MinThreadsConstraintsCompleted|MinThreadsConstraintsPending|PendingUserRequestCount|QueueLength|SharedCapacityForWorkManagers|StandbyThreadCount|Throughput)" to-name="${prefix}.threads.${name[1]}"/>
<regexp from-name="com\.bea:Name=.*,ServerRuntime=.*,Type=JRockitRuntime\.(JvmProcessorLoad|TotalGarbageCollectionCount|TotalGarbageCollectionTime|FreePhysicalMemory|UsedPhysicalMemory|Uptime)" to-name="${prefix}.jrockit.${name[1]}" />
</selector-group>
<selector-group name="tomcat">
<regexp from-name="Catalina:type=GlobalRequestProcessor,name=http-${tomcat.port}.\.(requestCount|bytesSent|bytesReceived)" to-name="${prefix}.http.${name[1]}"/>
</selector-group>
<selector-group name="mod_qos">
<regexp from-name=".*,metric=([^,]+),path=/([^.]+)\.(current|limit)" to-name="${prefix}.${name[2]}.${name[1]}.${name[3]}"/>
<regexp from-name=".*,metric=([^,]+)$" to-name="${prefix}.${name[1]}"/>
<regexp from-name=".*,metric=([^,]+)\.(current|limit)" to-name="${prefix}.${name[1]}.${name[2]}"/>
</selector-group>
</selector-groups>
<samplers>
<sampler name="wls" template="true" interval="10" reset-timeout="60">
<selectors>
<use-group name="wls" />
</selectors>
<variables>
<string name="prefix" value="backend.${input.name}" />
</variables>
<value-transformers>
<el-value-transformer name=".*\.jrockit\.gc.*\.(pauseTime|time)" expression="value / 1000000" />
</value-transformers>
</sampler>
<sampler input="wls01" parent="wls" />
<sampler input="wls02" parent="wls" />
<sampler input="tomcat01" interval="10">
<variables>
<string name="prefix" value="frontend.${input.name}" />
<string name="tomcat.port" value="9080" />
</variables>
<selectors>
<use-group name="tomcat" />
</selectors>
</sampler>
<sampler input="apache01" interval="10" quiet="true">
<variables>
<string name="prefix" value="frontend.${input.name}" />
</variables>
<selectors>
<use-group name="mod_qos" />
</selectors>
</sampler>
<sampler input="oracle01" interval="10" ignored="true" pool="custom.samplers">
<variables>
<string name="prefix" value="database.${input.name}" />
</variables>
<selectors>
<regexp from-name="(.*)" to-name="${name[1]}"/>
</selectors>
</sampler>
<sampler input="redis" interval="10">
<selectors>
<regexp from-name="(.*)" to-name="${name[1]}" />
</selectors>
</sampler>
<sampler input="oracle-nosql" interval="10">
<selectors>
<regexp from-name="(.*)" to-name="${name[1]}" />
</selectors>
</sampler>
<sampler input="webmethods1" interval="60">
<selectors>
<regexp from-name="ServerStats\.Memory\.(.+)" to-name="${input.name}.memory.${name[1]}" />
</selectors>
</sampler>
<sampler input="exec1" interval="10">
<selectors>
<regexp from-name="(.*)" to-name="${name[1]}" />
</selectors>
</sampler>
<sampler input="http1" interval="10">
<selectors>
<regexp from-name="(.*)" to-name="${name[1]}" />
</selectors>
</sampler>
</samplers>
</configuration>
Shared Resources
- JDBC connection pools to use with e.g. the JDBC input. c3p0 used under the hood.
- Thread pools for the samplers that make it possible to distribute the samplers on different thread pools. You will need to define at least one called "samplers".
Supported Inputs
- Java Management Extensions (JMX) - queries object names and attributes from a remote JMX server. The reader caches all meta-data until a reconnect. The name of the metrics consist of the canonicalized object name + '#' + attribute name.
- jdbc - sequentially execute a list of SQL queries and interpret the returned rows as metrics. Queries must return either two or three columns - the first one is the metric's name and the second one is its value. The optional third one is a timestamp (in milliseconds since epoch start).
- apache-status - parses the output of the apache and mod_qos status page (with option ?auto) and exposes the values in a more usable format. The reader uses non-persistent HTTP connection and queries both metadata and data when opened.
- oracle-nosql - fetches the perfmap from a list of hosts/ports running in an Oralce NoSQL (KVStore) cluster and exposes the values in a more usable format as metrics. The reader caches the RMI connections and only reloads them in case of failures.
- redis - executes the info command using jedis and exposes the parsed values as metrics. Keeps the connection until a failure is detected.
- self - expose metrics on the samplers and the input readers
- webmethods - fetch diagnostics data over HTTP from a running webmethods instances, parse the runtime files in it and expose the data as metrics
- exec - execute process and parse its standard output / error looking for metrics in the form [:]=
- http - fetch remote URLs and parse the response using regular expressions
- memcached - fetch memcached stats
Supported Selectors
- Regular expressions selector
Matches metrics by their names using regular expressions. Each metric can then be renamed using expressions which can refer to the input's name and the matching groups of the regular expressions.
Supported Outputs
Variables
Variables can be defined in the global context, in the inputs and in the samplers. Additionally there are some variables that are automatically generated by the inputs like input.name. If a variable with the same name is defined in multiple contexts, its value will be taken from the definition in the most specific context - global variables will be overridden by variables defined in the inputs and in the samplers. Variables defined in an input will be overridden by variables defined in the samplers.
Quick start
- Download the metrics-sampler-distribution-[version]-all.tar.gz
- Unpack it into a directory of your choice, e.g. metrics-sampler-[version]
- Create a configuration in config/config.xml using config/config.xml.example and this readme as starting point and reference
- If you want to list all the metrics from your configured inputs you can call "bin/metrics-sampler.sh metadata". This will output all names and descriptions of the available metrics for each input. You can use those to define your regexp selectors.
- If you have to tune some startup parameter create the file bin/local.sh and override the environment variables there (e.g. JAVA or JAVA_OPTS)
- Run "bin/metrics-sampler.sh check" to verify that each selector of each enabled sampler matches at least one metric. You can also run the script with "test" to fetch just one sample.
- Start the daemon using "bin/metrics-sampler.sh start". Logs are located in logs/metrics-sampler.log and in logs/console.out
- To check whether the daemon is running execute "bin/metrics-sampler.sh status". The output should be clear enough. If you want to process the result - exit code 0 means running, anything else means stopped.
- You can stop the daemon using "bin/metrics-sampler.sh stop"
- Additional configuration
- if you want to use a JVM that is not on the path and/or change the startup parameters you can create an executable bash script in bin/local.sh and set the JAVA and JAVA_OPTS variables. This file (if existing and executable) automatically gets sources into the startup script. Using this will help you keep customizations and default startup separate and thus ease up the upgrade.
- if you need additional JARs on your classpath (e.g. for JDBC drivers, T3 protocol, etc). you should create a directory lib.local and put them there. This way you can safely delete all JARs in lib before upgrading.
- if you want to tune the logging configuration then save it in the file config/logback.xml (config/logback-console.xml for the non-daemon commands like check, metadata, etc.)
Extensions
It should be pretty easy to extend the program with new inputs, outputs, samplers and selectors. For this you will need to create a new module/project like this (you could also check out the extensions-* modules which use the same mechanism):
- Add metrics-sampler-core to the classpath of your program/module (e.g. maven dependency)
- Create the file "META-INF/services/org.metricssampler.service.Extension" in src/main/resources (or in any location that lands in your compiled JAR) containing the fully qualified class name of a class that implements org.metricssampler.service.Extension
- Your org.metricssampler.service.Extension implementation will return your custom XBeans (XML configuration beans) which are just normal POJOs with XStream annotations
- You will have to implement an org.metricssampler.service.LocalObjectFactory (e.g. by extending org.metricssampler.service.AbstractLocalObjectFactory) so that you can create the actual input readers, output writers etc. from their configurations
- Put the resulting JAR file on your classpath and you are ready to go (e.g. copy it to the lib/ directory of your installation)
- If you think the extension might be of any use to anyone else - please share it.
- If you are going to fetch and parse data from HTTP consider using org.metricssampler.reader.HttpMetricsReader as base class (and its config and xbeans)
Internals
- I chose to use slf4j in all classes with logback under the hood as it is pretty simple to configure
- The graphite writer currently disconnects on each sampling but could be improved to keep the connection (or even better let that be configurable)
- XStream is used to load the XML configuration. The XML is mapped to *XBean instances which are basically POJOs with the some added abilities like validating their data and converting themselves to the configuration format independent *Config POJOs. The *Config POJOs are value objects used by the rest of the system (e.g. samplers, readers, writers, selectors).
- You will need to install some artifacts in your maven repository to be able to build using maven because some of the required artifacts (e.g. the oracle nosql kvstore jars)
Publishing new versions to maven central
- Release the project using mvn release:prepare, mvn release:perform
- Switch to the released tag using git checkout v[version]
- Make sure that you have your sonatype credentials in your maven settings as server id
sonatype-nexus-releases
otherwise you will get HTTP 401 when trying to upload - Depending on your local setup, you might need to help GPG know how to ask you for the passphrase - run this
export GPG_TTY=$(tty)
- Build and deploy the artifacts to sonatype
mvn clean deploy -Dgpg.keyname="<name>" -Dgpg.passphrase="<passphrase>" -Dmaven.test.skip=true -P publish
- Switch back to master using
git checkout master
- Close and release the repository at oss.sonatype.org
- Push the changes to github. Also push the tags (
git push --tags
).
Compatibility
- Tested with Hotspot JVM 1.8
- Tested with Tomcat 7 and Weblogic Server 12c (provided that wlfullclient.jar (or the jmx client and t3 protocol JARs) is on the classpath)
- You might need to add -Dsun.lang.ClassLoader.allowArraySyntax=true as JVM parameter in the metrics-sampler.sh script if you are connecting using JVM 1.7 client to a JVM 1.5 server