├── .gitignore ├── JMX-Config.png ├── JVM-Dashboard.png ├── LICENSE ├── README.md ├── graylog-plugin-input-jmx.iml ├── pom.xml └── src └── main ├── java ├── com │ └── googlecode │ │ └── jmxtrans │ │ ├── jmx │ │ ├── JmxQueryProcessor.java │ │ └── JmxResultProcessor.java │ │ └── model │ │ ├── PropertyResolver.java │ │ ├── Query.java │ │ ├── Result.java │ │ └── Server.java └── org │ └── graylog │ └── inputs │ └── jmx │ ├── JMXInput.java │ ├── JMXInputMetaData.java │ ├── JMXInputPlugin.java │ ├── JMXInputPluginModule.java │ ├── JMXTransport.java │ └── model │ ├── GLAttribute.java │ ├── GLQuery.java │ └── GLQueryConfig.java └── resources ├── META-INF └── services │ └── org.graylog2.plugin.Plugin ├── jvm.json ├── kafka.json └── tomcat.json /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | pom.xml.tag 3 | pom.xml.releaseBackup 4 | pom.xml.versionsBackup 5 | pom.xml.next 6 | release.properties 7 | dependency-reduced-pom.xml 8 | buildNumber.properties 9 | .mvn/timing.properties 10 | .idea/ -------------------------------------------------------------------------------- /JMX-Config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sivasamyk/graylog-plugin-input-jmx/b8ccb5aa034bb5c40074c74b00315a09aebd378e/JMX-Config.png -------------------------------------------------------------------------------- /JVM-Dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sivasamyk/graylog-plugin-input-jmx/b8ccb5aa034bb5c40074c74b00315a09aebd378e/JVM-Dashboard.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Sivasamy Kaliappan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Graylog JMX Input Plugin 2 | 3 | Graylog input plugin to monitor JMX end points with built-in support for JVM and Tomcat endpoints 4 | 5 | Features 6 | -------- 7 | 8 | * No agent required in the machines to be monitored 9 | * Support for Authentication 10 | * Support for SSL 11 | * Built-in support for monitoring JVM and Tomcat servers (more to come) 12 | * Monitor multiple servers from single input plugin instance 13 | * Support for monitoring custom JMX endpoints 14 | * Tested with Graylog 2.0.0 15 | 16 | Setup 17 | ----- 18 | 19 | Download the plugin [jar](https://github.com/sivasamyk/graylog-plugin-input-jmx/releases/download/v1.0.2/graylog-plugin-input-jmx-1.0.2-SNAPSHOT.jar) and copy to graylog plugin directory (restart the graylog server for the changes to take effect). 20 | From Graylog UI, launch System->Input and select "JMX" input type 21 | 22 | Following parameters can be configured 23 | 24 | * Servers to monitor - Comma separated value of list of server IP Address or names to be monitored e.g. (10.220.5.123,webserver ) 25 | * Port - Port on which the JMX endpoint is listening ( firewall should be configured for bidirectional access to this port) 26 | * JMX Object type - List of built-in JMX Object Types available. Select 'Custom' for monitoring custom endpoints. 27 | In this case the json config file path has to be specified in 'Config File Path' parameter 28 | * Username - Username configured in JMX access file (applicable when JMX authentication is enabled) 29 | * Password - Password configured in JMX password file (applicable when JMX authentication is enabled) 30 | * Polling Interval - Interval to poll JMX endpoints (recommend to set the interval > 30 secs) 31 | * Polling Interval time unit - Polling interval time unit 32 | 33 | 34 | To enable JMX monitoring in your Java application you need to pass certain command options to the application. 35 | e.g. To enable bare minimum JMX monitoring without security: 36 | 37 | ``` 38 | java \ 39 | -Dcom.sun.management.jmxremote \ 40 | -Dcom.sun.management.jmxremote.port=12345 \ 41 | -Dcom.sun.management.jmxremote.authenticate=false \ 42 | -Dcom.sun.management.jmxremote.ssl=false \ 43 | -jar /usr/share/doc/openjdk-6-jre-headless/demo/jfc/Notepad/Notepad.jar 44 | ``` 45 | 46 | For more info on authentication and options refer 47 | [http://docs.oracle.com/javase/8/docs/technotes/guides/management/agent.html](http://docs.oracle.com/javase/8/docs/technotes/guides/management/agent.html) 48 | 49 | Custom Configuration 50 | -------------------- 51 | 52 | To monitor custom JMX object types ( and to extend existing JMX type), a custom config file can be writtern and 53 | specified while launching the plugin. Example config file 54 | 55 | ``` 56 | { 57 | "type": "jvm", /* Type of the endpoint */ 58 | "queries": [ 59 | { 60 | "object": "java.lang:type=Memory", /* JMX ObjectName */ 61 | "attributes": [ 62 | { 63 | "name": "HeapMemoryUsage", /* JMX MBean Attribute Name */ 64 | "key": "used", /* JMX Attribute Key if applicable */ 65 | "label": "jvm.mem.heap.used" /* Maps to a field in the graylog message. Allowed characters are A-Z,a-z,0-9,.,_ */ 66 | } 67 | ] 68 | }, 69 | { 70 | "object": "java.lang:type=GarbageCollector,name=*", 71 | "attributes": [ 72 | { 73 | "name": "CollectionCount", 74 | "label": "jvm.gc.{name}.count" /* Support for dynamic field names based on object name property values */ 75 | } 76 | ] 77 | } 78 | ] 79 | } 80 | ``` 81 | 82 | This plugin uses the JMX Query code from [JMXTrans](https://github.com/jmxtrans/jmxtrans) project 83 | 84 | Enabling SSL 85 | ------------ 86 | 87 | * To enable SSL generate keystore and truststore using keytool. Refer to following like for detailed steps. 88 | [https://pubs.vmware.com/continuent/tungsten-replicator-3.0/deployment-ssl-stores.html](https://pubs.vmware.com/continuent/tungsten-replicator-3.0/deployment-ssl-stores.html) 89 | 90 | * Start the application to be monitored using following command line arguments: 91 | ``` 92 | java -Dcom.sun.management.jmxremote.port=1234 93 | -Djavax.net.ssl.keyStore=/home/user/Documents/keystore.jks 94 | -Djavax.net.ssl.keyStorePassword=keystorepass 95 | -Djavax.net.ssl.trustStore=/home/user/Documents/truststore.ts 96 | -Djavax.net.ssl.trustStorePassword=truststorepass 97 | -Dcom.sun.management.jmxremote.registry.ssl=true 98 | -Dcom.sun.management.jmxremote.authenticate=false 99 | MainClass 100 | ``` 101 | * Configure Truststore path and password in Graylog plugin configuration window. 102 | * If you are using self-signed or untrusted certificates, remember to add them to trusted certificates in JRE lib/security 103 | For more info refer to [https://www.mkyong.com/webservices/jax-ws/suncertpathbuilderexception-unable-to-find-valid-certification-path-to-requested-target/](https://www.mkyong.com/webservices/jax-ws/suncertpathbuilderexception-unable-to-find-valid-certification-path-to-requested-target/) 104 | 105 | Screenshots 106 | ----------- 107 | 108 | Configuration Window 109 | 110 | ![Configuration Window](https://raw.githubusercontent.com/sivasamyk/graylog-plugin-input-jmx/master/JMX-Config.png) 111 | 112 | JVM Dashboard 113 | 114 | ![JVM Dashboard](https://raw.githubusercontent.com/sivasamyk/graylog-plugin-input-jmx/master/JVM-Dashboard.png) 115 | -------------------------------------------------------------------------------- /graylog-plugin-input-jmx.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.graylog 8 | graylog-plugin-input-jmx 9 | 1.0.2-SNAPSHOT 10 | jar 11 | 12 | ${project.artifactId} 13 | Graylog ${project.artifactId} plugin. 14 | https://www.graylog.org 15 | 16 | 17 | UTF-8 18 | 1.7 19 | 1.7 20 | 1.0.0 21 | /usr/share/graylog-server/plugin 22 | 23 | 24 | 25 | 26 | org.graylog2 27 | graylog2-plugin 28 | ${graylog2.version} 29 | provided 30 | 31 | 32 | org.graylog2 33 | graylog2-inputs 34 | ${graylog2.version} 35 | provided 36 | 37 | 38 | log4j 39 | log4j 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | org.apache.maven.plugins 49 | maven-shade-plugin 50 | 2.3 51 | 52 | false 53 | 54 | 55 | 56 | package 57 | 58 | shade 59 | 60 | 61 | 62 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | jdeb 72 | org.vafer 73 | 1.3 74 | 75 | ${project.build.directory}/${project.artifactId}-${project.version}.deb 76 | 77 | 78 | ${project.build.directory}/${project.build.finalName}.jar 79 | file 80 | 81 | perm 82 | ${graylog2.plugin-dir} 83 | 644 84 | root 85 | root 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | org.codehaus.mojo 94 | rpm-maven-plugin 95 | 2.1.2 96 | 97 | Application/Internet 98 | 99 | /usr 100 | 101 | 102 | _unpackaged_files_terminate_build 0 103 | _binaries_in_noarch_packages_terminate_build 0 104 | 105 | 644 106 | 755 107 | root 108 | root 109 | 110 | 111 | ${graylog2.plugin-dir} 112 | 113 | 114 | ${project.build.directory}/ 115 | 116 | ${project.build.finalName}.jar 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jmxtrans/jmx/JmxQueryProcessor.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jmxtrans.jmx; 2 | 3 | import com.google.common.collect.HashMultimap; 4 | import com.google.common.collect.ImmutableList; 5 | import com.googlecode.jmxtrans.model.Query; 6 | import com.googlecode.jmxtrans.model.Result; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import javax.management.*; 11 | import java.io.IOException; 12 | import java.rmi.UnmarshalException; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | public class JmxQueryProcessor { 17 | private final Logger log = LoggerFactory.getLogger(getClass()); 18 | 19 | /** 20 | * Responsible for processing individual Queries. 21 | */ 22 | public HashMultimap processQuery(MBeanServerConnection mbeanServer, Query query) throws Exception { 23 | HashMultimap objectResults = HashMultimap.create(); 24 | ObjectName oName = new ObjectName(query.getObj()); 25 | for (ObjectName queryName : mbeanServer.queryNames(oName, null)) { 26 | ImmutableList results = fetchResults(mbeanServer, query, queryName); 27 | for (Result result : results) { 28 | objectResults.put(queryName, result); 29 | } 30 | } 31 | return objectResults; 32 | } 33 | 34 | private ImmutableList fetchResults(MBeanServerConnection mbeanServer, Query query, ObjectName queryName) throws InstanceNotFoundException, IntrospectionException, ReflectionException, IOException { 35 | MBeanInfo info = mbeanServer.getMBeanInfo(queryName); 36 | ObjectInstance oi = mbeanServer.getObjectInstance(queryName); 37 | 38 | List attributes; 39 | if (query.getAttr().isEmpty()) { 40 | attributes = new ArrayList<>(); 41 | for (MBeanAttributeInfo attrInfo : info.getAttributes()) { 42 | attributes.add(attrInfo.getName()); 43 | } 44 | } else { 45 | attributes = query.getAttr(); 46 | } 47 | 48 | ImmutableList results = ImmutableList.of(); 49 | try { 50 | if (attributes.size() > 0) { 51 | log.debug("Executing queryName [{}] from query [{}]", queryName.getCanonicalName(), query); 52 | 53 | AttributeList al = mbeanServer.getAttributes(queryName, attributes.toArray(new String[attributes.size()])); 54 | 55 | results = new JmxResultProcessor(query, oi, al.asList(), info.getClassName(), queryName.getDomain()).getResults(); 56 | } 57 | } catch (UnmarshalException ue) { 58 | if ((ue.getCause() != null) && (ue.getCause() instanceof ClassNotFoundException)) { 59 | log.debug("Bad unmarshall, continuing. This is probably ok and due to something like this: " 60 | + "http://ehcache.org/xref/net/sf/ehcache/distribution/RMICacheManagerPeerListener.html#52", ue.getMessage()); 61 | } 62 | } 63 | return results; 64 | } 65 | 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jmxtrans/jmx/JmxResultProcessor.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jmxtrans.jmx; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.google.common.collect.ImmutableMap; 5 | import com.googlecode.jmxtrans.model.Query; 6 | import com.googlecode.jmxtrans.model.Result; 7 | 8 | import javax.management.Attribute; 9 | import javax.management.ObjectInstance; 10 | import javax.management.ObjectName; 11 | import javax.management.openmbean.CompositeData; 12 | import javax.management.openmbean.CompositeDataSupport; 13 | import javax.management.openmbean.CompositeType; 14 | import javax.management.openmbean.TabularDataSupport; 15 | import java.lang.reflect.Array; 16 | import java.util.Collections; 17 | import java.util.List; 18 | import java.util.Map; 19 | import java.util.Set; 20 | 21 | import static com.google.common.collect.ImmutableList.Builder; 22 | import static com.google.common.collect.Maps.newHashMap; 23 | 24 | public class JmxResultProcessor { 25 | 26 | private final Query query; 27 | private final ObjectInstance objectInstance; 28 | private final String className; 29 | private final String objDomain; 30 | private final List attributes; 31 | private static final String SEPERATOR = "_"; 32 | 33 | public JmxResultProcessor(Query query, ObjectInstance objectInstance, List attributes, String className, String objDomain) { 34 | this.query = query; 35 | this.objectInstance = objectInstance; 36 | this.className = className; 37 | this.objDomain = objDomain; 38 | this.attributes = attributes; 39 | } 40 | 41 | public ImmutableList getResults() { 42 | Builder accumulator = ImmutableList.builder(); 43 | for (Attribute attribute : attributes) { 44 | getResult(accumulator, attribute); 45 | } 46 | return accumulator.build(); 47 | } 48 | 49 | /** 50 | * Used when the object is effectively a java type 51 | */ 52 | private void getResult(Builder accumulator, Attribute attribute) { 53 | Object value = attribute.getValue(); 54 | if (value == null) { 55 | return; 56 | } 57 | 58 | if (value instanceof CompositeData) { 59 | getResult(accumulator, attribute.getName(), (CompositeData) value); 60 | } else if (value instanceof CompositeData[]) { 61 | for (CompositeData cd : (CompositeData[]) value) { 62 | getResult(accumulator, attribute.getName(), cd); 63 | } 64 | } else if (value instanceof ObjectName[]) { 65 | Map values = newHashMap(); 66 | for (ObjectName obj : (ObjectName[]) value) { 67 | values.put(obj.getCanonicalName(), obj.getKeyPropertyListString()); 68 | } 69 | Result r = getNewResultObject(attribute.getName(), values); 70 | accumulator.add(r); 71 | } else if (value.getClass().isArray()) { 72 | // OMFG: this is nutty. some of the items in the array can be 73 | // primitive! great interview question! 74 | Map values = newHashMap(); 75 | for (int i = 0; i < Array.getLength(value); i++) { 76 | Object val = Array.get(value, i); 77 | values.put(attribute.getName() + SEPERATOR + i, val); 78 | } 79 | accumulator.add(getNewResultObject(attribute.getName(), values)); 80 | } else if (value instanceof TabularDataSupport) { 81 | TabularDataSupport tds = (TabularDataSupport) value; 82 | Map values = Collections.emptyMap(); 83 | Result r = getNewResultObject(attribute.getName(), values); 84 | processTabularDataSupport(accumulator, attribute.getName(), tds); 85 | accumulator.add(r); 86 | } else if (value instanceof Map) { 87 | Result r = getNewResultObject(attribute.getName(), convertKeysToString((Map) value)); 88 | accumulator.add(r); 89 | } else { 90 | Map values = newHashMap(); 91 | values.put(attribute.getName(), value); 92 | Result r = getNewResultObject(attribute.getName(), values); 93 | accumulator.add(r); 94 | } 95 | } 96 | 97 | private ImmutableMap convertKeysToString(Map value) { 98 | ImmutableMap.Builder values = ImmutableMap.builder(); 99 | for (Map.Entry entry : value.entrySet()) { 100 | values.put(entry.getKey().toString(), entry.getValue()); 101 | } 102 | return values.build(); 103 | } 104 | 105 | /** 106 | * Populates the Result objects. This is a recursive function. Query 107 | * contains the keys that we want to get the values of. 108 | */ 109 | private void getResult(Builder accumulator, String attributeName, CompositeData cds) { 110 | CompositeType t = cds.getCompositeType(); 111 | 112 | Map values = newHashMap(); 113 | 114 | Set keys = t.keySet(); 115 | for (String key : keys) { 116 | Object value = cds.get(key); 117 | if (value instanceof TabularDataSupport) { 118 | TabularDataSupport tds = (TabularDataSupport) value; 119 | processTabularDataSupport(accumulator, attributeName + SEPERATOR + key, tds); 120 | values.put(key, value); 121 | } else if (value instanceof CompositeDataSupport) { 122 | // now recursively go through everything. 123 | CompositeDataSupport cds2 = (CompositeDataSupport) value; 124 | getResult(accumulator, attributeName, cds2); 125 | return; // because we don't want to add to the list yet. 126 | } else { 127 | values.put(key, value); 128 | } 129 | } 130 | Result r = getNewResultObject(attributeName, values); 131 | accumulator.add(r); 132 | } 133 | 134 | private void processTabularDataSupport( 135 | Builder accumulator, String attributeName, 136 | TabularDataSupport tds) { 137 | Set> entries = tds.entrySet(); 138 | for (Map.Entry entry : entries) { 139 | Object entryKeys = entry.getKey(); 140 | if (entryKeys instanceof List) { 141 | // ie: attributeName=LastGcInfo.Par Survivor Space 142 | // i haven't seen this be smaller or larger than List<1>, but 143 | // might as well loop it. 144 | StringBuilder sb = new StringBuilder(); 145 | for (Object entryKey : (List) entryKeys) { 146 | sb.append(SEPERATOR); 147 | sb.append(entryKey); 148 | } 149 | String attributeName2 = sb.toString(); 150 | Object entryValue = entry.getValue(); 151 | if (entryValue instanceof CompositeDataSupport) { 152 | getResult(accumulator, attributeName + attributeName2, (CompositeDataSupport) entryValue); 153 | } else { 154 | throw new RuntimeException("!!!!!!!!!! Please file a bug: https://github.com/jmxtrans/jmxtrans/issues entryValue is: " 155 | + entryValue.getClass().getCanonicalName()); 156 | } 157 | } else { 158 | throw new RuntimeException("!!!!!!!!!! Please file a bug: https://github.com/jmxtrans/jmxtrans/issues entryKeys is: " 159 | + entryKeys.getClass().getCanonicalName()); 160 | } 161 | } 162 | } 163 | 164 | /** 165 | * Builds up the base Result object 166 | */ 167 | private Result getNewResultObject(String attributeName, Map values) { 168 | return new Result(System.currentTimeMillis(), attributeName, className, objDomain, query.getResultAlias(), objectInstance.getObjectName().getCanonicalKeyPropertyListString(), values); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jmxtrans/model/PropertyResolver.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jmxtrans.model; 2 | 3 | import com.google.common.base.Function; 4 | import com.google.common.collect.ImmutableList; 5 | import com.google.common.collect.ImmutableMap; 6 | 7 | 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | import static com.google.common.collect.FluentIterable.from; 12 | import static com.google.common.collect.ImmutableMap.copyOf; 13 | import static com.google.common.collect.Maps.transformValues; 14 | 15 | /*** 16 | * 17 | * Property Resolver 18 | * 19 | * @author henri 20 | * 21 | */ 22 | public class PropertyResolver { 23 | 24 | private static PropertyResolverFunc RESOLVE_PROPERTIES = new PropertyResolverFunc(); 25 | 26 | private static ObjectPropertyResolverFunc RESOLVE_OBJECT_PROPERTIES = new ObjectPropertyResolverFunc(); 27 | 28 | /** 29 | * Resolve a property from System Properties (aka ${key}) key:defval is 30 | * supported and if key not found on SysProps, defval will be returned 31 | * 32 | * @param s 33 | * @return resolved string or null if not found in System Properties and no 34 | * defval 35 | */ 36 | private static String resolveString(String s) { 37 | 38 | int pos = s.indexOf(":", 0); 39 | 40 | if (pos == -1) 41 | return (System.getProperty(s)); 42 | 43 | String key = s.substring(0, pos); 44 | String defval = s.substring(pos + 1); 45 | 46 | String val = System.getProperty(key); 47 | 48 | if (val != null) 49 | return val; 50 | else 51 | return defval; 52 | } 53 | 54 | /** 55 | * Parse a String and replace vars a la ant (${key} from System Properties 56 | * Support complex Strings like : 57 | * 58 | * "${myhost}" "${myhost:w2}" "${mybean:defbean}.${mybean2:defbean2}" 59 | * 60 | * @param s 61 | * @return resolved String 62 | */ 63 | public static String resolveProps( String s) { 64 | if (s == null) { 65 | return null; 66 | } 67 | 68 | int ipos = 0; 69 | int pos = s.indexOf("${", ipos); 70 | 71 | if (pos == -1) 72 | return s; 73 | 74 | StringBuilder sb = new StringBuilder(); 75 | 76 | while (ipos < s.length()) { 77 | pos = s.indexOf("${", ipos); 78 | 79 | if (pos < 0) { 80 | sb.append(s.substring(ipos)); 81 | break; 82 | } 83 | 84 | if (pos != ipos) 85 | sb.append(s.substring(ipos, pos)); 86 | 87 | int end = s.indexOf('}', pos); 88 | 89 | if (end < 0) 90 | break; 91 | 92 | int start = pos + 2; 93 | pos = end + 1; 94 | 95 | String key = s.substring(start, end); 96 | String val = resolveString(key); 97 | 98 | if (val != null) 99 | sb.append(val); 100 | else 101 | sb.append("${").append(key).append("}"); 102 | 103 | ipos = end + 1; 104 | } 105 | 106 | return (sb.toString()); 107 | } 108 | 109 | /** 110 | * Parse Map and resolve Strings value with resolveProps 111 | */ 112 | 113 | public static ImmutableMap resolveMap(Map map) { 114 | return copyOf(transformValues(map, RESOLVE_OBJECT_PROPERTIES)); 115 | } 116 | 117 | /** 118 | * Parse List and resolve Strings value with resolveProps 119 | */ 120 | 121 | public static ImmutableList resolveList(List list) { 122 | return from(list) 123 | .transform(RESOLVE_PROPERTIES) 124 | .toList(); 125 | } 126 | 127 | private static class PropertyResolverFunc implements Function { 128 | 129 | @Override 130 | public String apply( String input) { 131 | return resolveProps(input); 132 | } 133 | } 134 | 135 | private static class ObjectPropertyResolverFunc implements Function { 136 | 137 | @Override 138 | public Object apply( Object input) { 139 | if (input instanceof String) { 140 | return resolveProps((String) input); 141 | } 142 | return input; 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jmxtrans/model/Query.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jmxtrans.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.fasterxml.jackson.annotation.JsonPropertyOrder; 6 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 7 | import com.google.common.collect.ImmutableList; 8 | import com.google.common.collect.ImmutableSet; 9 | 10 | 11 | import java.util.Collections; 12 | import java.util.List; 13 | import java.util.Set; 14 | 15 | import static com.fasterxml.jackson.databind.annotation.JsonSerialize.Inclusion.NON_NULL; 16 | import static com.google.common.base.MoreObjects.firstNonNull; 17 | import static com.google.common.collect.Lists.newArrayList; 18 | import static com.google.common.collect.Sets.newHashSet; 19 | import static com.googlecode.jmxtrans.model.PropertyResolver.resolveList; 20 | import static java.util.Arrays.asList; 21 | 22 | /** 23 | * Represents a JMX Query to ask for obj, attr and one or more keys. 24 | * 25 | * @author jon 26 | */ 27 | @JsonSerialize(include = NON_NULL) 28 | @JsonPropertyOrder(value = {"obj", "attr", "typeNames", "resultAlias", "keys", "allowDottedKeys", "outputWriters"}) 29 | public class Query { 30 | 31 | private final String obj; 32 | private final ImmutableList keys; 33 | private final ImmutableList attr; 34 | private final ImmutableSet typeNames; 35 | private final String resultAlias; 36 | private final boolean useObjDomainAsKey; 37 | private final boolean allowDottedKeys; 38 | 39 | @JsonCreator 40 | public Query( 41 | @JsonProperty("obj") String obj, 42 | @JsonProperty("keys") List keys, 43 | @JsonProperty("attr") List attr, 44 | @JsonProperty("typeNames") Set typeNames, 45 | @JsonProperty("alias") String resultAlias, 46 | @JsonProperty("useObjDomainAsKey") boolean useObjDomainAsKey, 47 | @JsonProperty("allowDottedKeys") boolean allowDottedKeys 48 | ) { 49 | this.obj = obj; 50 | this.attr = resolveList(firstNonNull(attr, Collections.emptyList())); 51 | this.resultAlias = resultAlias; 52 | this.useObjDomainAsKey = firstNonNull(useObjDomainAsKey, false); 53 | this.keys = resolveList(firstNonNull(keys, Collections.emptyList())); 54 | this.allowDottedKeys = allowDottedKeys; 55 | this.typeNames = ImmutableSet.copyOf(firstNonNull(typeNames, Collections.emptySet())); 56 | } 57 | 58 | /** 59 | * The JMX object representation: java.lang:type=Memory 60 | */ 61 | public String getObj() { 62 | return obj; 63 | } 64 | 65 | /** 66 | * The alias allows you to specify what you would like the results of the 67 | * query to go into. 68 | */ 69 | public String getResultAlias() { 70 | return resultAlias; 71 | } 72 | 73 | /** 74 | * The useObjDomainAsKey property allows you to specify the use of the Domain portion of the Object Name 75 | * as part of the output key instead of using the ClassName of the MBean which is the default behavior. 76 | */ 77 | public boolean isUseObjDomainAsKey() { 78 | return useObjDomainAsKey; 79 | } 80 | 81 | /** 82 | * The list of type names used in a JMX bean string when querying with a 83 | * wildcard which is used to expose the actual type name value to the key 84 | * string. e.g. for this JMX name 85 | *

86 | * typeName=name=PS Eden Space,type=MemoryPool 87 | *

88 | * If you add a typeName("name"), then it'll retrieve 'PS Eden Space' from 89 | * the string 90 | */ 91 | public ImmutableSet getTypeNames() { 92 | return typeNames; 93 | } 94 | 95 | public ImmutableList getAttr() { 96 | return attr; 97 | } 98 | 99 | public ImmutableList getKeys() { 100 | return keys; 101 | } 102 | 103 | public boolean isAllowDottedKeys() { 104 | return allowDottedKeys; 105 | } 106 | 107 | 108 | @Override 109 | public String toString() { 110 | return "Query [obj=" + obj + ", useObjDomainAsKey:" + useObjDomainAsKey + 111 | ", resultAlias=" + resultAlias + ", attr=" + attr + "]"; 112 | } 113 | 114 | public static Builder builder() { 115 | return new Builder(); 116 | } 117 | 118 | public static final class Builder { 119 | 120 | private String obj; 121 | private final List attr = newArrayList(); 122 | private String resultAlias; 123 | private final List keys = newArrayList(); 124 | private boolean useObjDomainAsKey; 125 | private boolean allowDottedKeys; 126 | private final Set typeNames = newHashSet(); 127 | 128 | private Builder() {} 129 | 130 | public Builder setObj(String obj) { 131 | this.obj = obj; 132 | return this; 133 | } 134 | 135 | public Builder addAttr(String... attr) { 136 | this.attr.addAll(asList(attr)); 137 | return this; 138 | } 139 | 140 | public Builder setResultAlias(String resultAlias) { 141 | this.resultAlias = resultAlias; 142 | return this; 143 | } 144 | 145 | public Builder setUseObjDomainAsKey(boolean useObjDomainAsKey) { 146 | this.useObjDomainAsKey = useObjDomainAsKey; 147 | return this; 148 | } 149 | 150 | public Builder addKey(String keys) { 151 | return addKeys(keys); 152 | } 153 | 154 | public Builder addKeys(String... keys) { 155 | this.keys.addAll(asList(keys)); 156 | return this; 157 | } 158 | 159 | public Builder setAllowDottedKeys(boolean allowDottedKeys) { 160 | this.allowDottedKeys = allowDottedKeys; 161 | return this; 162 | } 163 | 164 | public Builder setTypeNames(Set typeNames) { 165 | this.typeNames.addAll(typeNames); 166 | return this; 167 | } 168 | 169 | public Query build() { 170 | return new Query( 171 | this.obj, 172 | this.keys, 173 | this.attr, 174 | this.typeNames, 175 | this.resultAlias, 176 | this.useObjDomainAsKey, 177 | this.allowDottedKeys 178 | ); 179 | } 180 | 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jmxtrans/model/Result.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jmxtrans.model; 2 | 3 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 4 | import com.google.common.collect.ImmutableMap; 5 | 6 | import java.util.Map; 7 | 8 | import static com.fasterxml.jackson.databind.annotation.JsonSerialize.Inclusion.NON_NULL; 9 | 10 | /** 11 | * Represents the result of a query. 12 | * 13 | * @author jon 14 | */ 15 | @JsonSerialize(include = NON_NULL) 16 | public class Result { 17 | private final String attributeName; 18 | private final String className; 19 | private final String objDomain; 20 | private final String typeName; 21 | private final ImmutableMap values; 22 | private final long epoch; 23 | private final String keyAlias; 24 | 25 | public Result(long epoch, String attributeName, String className, String objDomain, String keyAlias, String typeName, Map values) { 26 | this.className = className; 27 | this.objDomain = objDomain; 28 | this.typeName = typeName; 29 | this.values = ImmutableMap.copyOf(values); 30 | this.epoch = epoch; 31 | this.attributeName = attributeName; 32 | this.keyAlias = keyAlias; 33 | } 34 | 35 | public String getClassName() { 36 | return className; 37 | } 38 | 39 | public String getObjDomain() { 40 | return objDomain; 41 | } 42 | 43 | /** 44 | * Specified as part of the query. 45 | */ 46 | public String getKeyAlias() { 47 | return keyAlias; 48 | } 49 | 50 | public String getTypeName() { 51 | return typeName; 52 | } 53 | 54 | public ImmutableMap getValues() { 55 | return values; 56 | } 57 | 58 | public String getAttributeName() { 59 | return attributeName; 60 | } 61 | 62 | public long getEpoch() { 63 | return this.epoch; 64 | } 65 | 66 | @Override 67 | public String toString() { 68 | return "Result [attributeName=" + attributeName + ", className=" + className + ", objDomain=" + objDomain + ", typeName=" + typeName + ", values=" + values + ", epoch=" 69 | + epoch + "]"; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jmxtrans/model/Server.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jmxtrans.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonIgnore; 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | import com.fasterxml.jackson.annotation.JsonPropertyOrder; 7 | import com.fasterxml.jackson.databind.ObjectMapper; 8 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 9 | import com.google.common.collect.ImmutableSet; 10 | import com.googlecode.jmxtrans.jmx.JmxQueryProcessor; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | import javax.management.MBeanServer; 15 | import javax.management.MBeanServerConnection; 16 | import javax.management.remote.JMXConnector; 17 | import javax.management.remote.JMXConnectorFactory; 18 | import javax.management.remote.JMXServiceURL; 19 | import javax.net.SocketFactory; 20 | import javax.net.ssl.SSLContext; 21 | import javax.net.ssl.TrustManager; 22 | import javax.net.ssl.TrustManagerFactory; 23 | import javax.net.ssl.X509TrustManager; 24 | import javax.rmi.ssl.SslRMIClientSocketFactory; 25 | import java.io.FileInputStream; 26 | import java.io.IOException; 27 | import java.lang.management.ManagementFactory; 28 | import java.net.MalformedURLException; 29 | import java.net.Socket; 30 | import java.security.GeneralSecurityException; 31 | import java.security.KeyStore; 32 | import java.security.cert.CertificateException; 33 | import java.security.cert.X509Certificate; 34 | import java.util.*; 35 | 36 | import static com.fasterxml.jackson.databind.annotation.JsonSerialize.Inclusion.NON_NULL; 37 | import static com.google.common.collect.ImmutableSet.copyOf; 38 | import static com.googlecode.jmxtrans.model.PropertyResolver.resolveProps; 39 | import static java.util.Arrays.asList; 40 | import static javax.management.remote.JMXConnectorFactory.PROTOCOL_PROVIDER_PACKAGES; 41 | import static javax.naming.Context.SECURITY_CREDENTIALS; 42 | import static javax.naming.Context.SECURITY_PRINCIPAL; 43 | 44 | /** 45 | * Represents a jmx server that we want to connect to. This also stores the 46 | * queries that we want to execute against the server. 47 | * 48 | * @author jon 49 | */ 50 | @JsonSerialize(include = NON_NULL) 51 | @JsonPropertyOrder(value = { 52 | "alias", 53 | "local", 54 | "host", 55 | "port", 56 | "username", 57 | "password", 58 | "cronExpression", 59 | "numQueryThreads", 60 | "protocolProviderPackages" 61 | }) 62 | public class Server { 63 | 64 | private static final String FRONT = "service:jmx:rmi:///jndi/rmi://"; 65 | private static final String BACK = "/jmxrmi"; 66 | private static final Logger LOGGER = LoggerFactory.getLogger(Server.class); 67 | 68 | private final String alias; 69 | private final String host; 70 | private final String port; 71 | private final String username; 72 | private final String password; 73 | private final String protocolProviderPackages; 74 | private final String url; 75 | private final String cronExpression; 76 | private final Integer numQueryThreads; 77 | private String trustStorePath; 78 | private String trustStorePass; 79 | 80 | public String getTrustStorePath() { 81 | return trustStorePath; 82 | } 83 | 84 | public void setTrustStorePath(String trustStorePath) { 85 | this.trustStorePath = trustStorePath; 86 | } 87 | 88 | public String getTrustStorePass() { 89 | return trustStorePass; 90 | } 91 | 92 | public void setTrustStorePass(String trustStorePass) { 93 | this.trustStorePass = trustStorePass; 94 | } 95 | 96 | // if using local JMX to embed JmxTrans to query the local MBeanServer 97 | private final boolean local; 98 | 99 | private final ImmutableSet queries; 100 | 101 | @JsonCreator 102 | public Server( 103 | @JsonProperty("alias") String alias, 104 | @JsonProperty("host") String host, 105 | @JsonProperty("port") String port, 106 | @JsonProperty("username") String username, 107 | @JsonProperty("password") String password, 108 | @JsonProperty("protocolProviderPackages") String protocolProviderPackages, 109 | @JsonProperty("url") String url, 110 | @JsonProperty("cronExpression") String cronExpression, 111 | @JsonProperty("numQueryThreads") Integer numQueryThreads, 112 | @JsonProperty("local") boolean local, 113 | @JsonProperty("queries") List queries, 114 | @JsonProperty("trustStorePath") String trustStorePath, 115 | @JsonProperty("trustStorePass") String trustStorePass) { 116 | this.alias = resolveProps(alias); 117 | this.host = resolveProps(host); 118 | this.port = resolveProps(port); 119 | this.username = resolveProps(username); 120 | this.password = resolveProps(password); 121 | this.protocolProviderPackages = protocolProviderPackages; 122 | this.url = resolveProps(url); 123 | this.cronExpression = cronExpression; 124 | this.numQueryThreads = numQueryThreads; 125 | this.local = local; 126 | this.queries = copyOf(queries); 127 | this.trustStorePath = trustStorePath; 128 | this.trustStorePass = trustStorePass; 129 | } 130 | 131 | /** 132 | * Generates the proper username/password environment for JMX connections. 133 | */ 134 | @JsonIgnore 135 | public Map getEnvironment() { 136 | Map environment = new HashMap<>(); 137 | if (getProtocolProviderPackages() != null && getProtocolProviderPackages().contains("weblogic")) { 138 | if ((username != null) && (password != null)) { 139 | environment.put(PROTOCOL_PROVIDER_PACKAGES, getProtocolProviderPackages()); 140 | environment.put(SECURITY_PRINCIPAL, username); 141 | environment.put(SECURITY_CREDENTIALS, password); 142 | } 143 | } 144 | 145 | if ((username != null) && (password != null)) { 146 | String[] credentials = new String[]{ 147 | username, 148 | password 149 | }; 150 | environment.put(JMXConnector.CREDENTIALS, credentials); 151 | } 152 | 153 | if (trustStorePath != null && trustStorePath.trim().length() > 0) { 154 | environment.put("com.sun.jndi.rmi.factory.socket", createSslRMIClientSocketFactory()); 155 | } 156 | 157 | return environment; 158 | } 159 | 160 | private SslRMIClientSocketFactory createSslRMIClientSocketFactory() { 161 | return new SslRMIClientSocketFactory() { 162 | @Override 163 | public Socket createSocket(String host, int port) throws IOException { 164 | try { 165 | final SocketFactory sslSocketFactory = getSslConetext().getSocketFactory(); 166 | return sslSocketFactory.createSocket(host, port); 167 | } catch (GeneralSecurityException e) { 168 | throw new IOException("Cannot create socket", e); 169 | } 170 | } 171 | }; 172 | } 173 | 174 | private SSLContext getSslConetext() throws GeneralSecurityException,IOException { 175 | TrustManager[] myTMs = new TrustManager[]{ 176 | new JMXX509TrustManager(trustStorePath, trustStorePass.toCharArray())}; 177 | SSLContext ctx = SSLContext.getInstance("TLS"); 178 | ctx.init(null, myTMs, null); 179 | return ctx; 180 | } 181 | 182 | /** 183 | * Helper method for connecting to a Server. You need to close the resulting 184 | * connection. 185 | */ 186 | @JsonIgnore 187 | public JMXConnector getServerConnection() throws Exception { 188 | JMXServiceURL url = new JMXServiceURL(getUrl()); 189 | return JMXConnectorFactory.connect(url, this.getEnvironment()); 190 | } 191 | 192 | @JsonIgnore 193 | public MBeanServer getLocalMBeanServer() { 194 | // Getting the platform MBean server is cheap (expect for th first call) no need to cache it. 195 | return ManagementFactory.getPlatformMBeanServer(); 196 | } 197 | 198 | /** 199 | * Some writers (GraphiteWriter) use the alias in generation of the unique 200 | * key which references this server. 201 | */ 202 | public String getAlias() { 203 | return this.alias; 204 | } 205 | 206 | public String getHost() { 207 | if (host == null && url == null) { 208 | // TODO: shouldn't we just return a null in this case ? 209 | throw new IllegalStateException("host is null and url is null. Cannot construct host dynamically."); 210 | } 211 | 212 | if (host != null) { 213 | return host; 214 | } 215 | 216 | // removed the caching of the extracted host as it is a very minor 217 | // optimization we should probably pre compute it in the builder and 218 | // throw exception at construction if both url and host are set 219 | // we might also be able to use java.net.URI to parse the URL, but I'm 220 | // not familiar enough with JMX URLs to think of the test cases ... 221 | return url.substring(url.lastIndexOf("//") + 2, url.lastIndexOf(':')); 222 | } 223 | 224 | public String getPort() { 225 | if (port == null && url == null) { 226 | throw new IllegalStateException("port is null and url is null. Cannot construct port dynamically."); 227 | } 228 | if (this.port != null) { 229 | return port; 230 | } 231 | 232 | return extractPortFromUrl(url); 233 | } 234 | 235 | private static String extractPortFromUrl(String url) { 236 | String computedPort = url.substring(url.lastIndexOf(':') + 1); 237 | if (computedPort.contains("/")) { 238 | computedPort = computedPort.substring(0, computedPort.indexOf('/')); 239 | } 240 | return computedPort; 241 | } 242 | 243 | public String getUsername() { 244 | return this.username; 245 | } 246 | 247 | public String getPassword() { 248 | return this.password; 249 | } 250 | 251 | /** 252 | * Whether the current local Java process should be used or not (useful for 253 | * polling the embedded JVM when using JmxTrans inside a JVM to poll JMX 254 | * stats and push them remotely) 255 | */ 256 | public boolean isLocal() { 257 | return local; 258 | } 259 | 260 | public ImmutableSet getQueries() { 261 | return this.queries; 262 | } 263 | 264 | /** 265 | * The jmx url to connect to. If null, it builds this from host/port with a 266 | * standard configuration. Other JVM's may want to set this value. 267 | */ 268 | public String getUrl() { 269 | if (this.url == null) { 270 | if ((this.host == null) || (this.port == null)) { 271 | throw new RuntimeException("url is null and host or port is null. cannot construct url dynamically."); 272 | } 273 | return FRONT + this.host + ":" + this.port + BACK; 274 | } 275 | return this.url; 276 | } 277 | 278 | @JsonIgnore 279 | public JMXServiceURL getJmxServiceURL() throws MalformedURLException { 280 | return new JMXServiceURL(getUrl()); 281 | } 282 | 283 | @JsonIgnore 284 | public boolean isQueriesMultiThreaded() { 285 | return (this.numQueryThreads != null) && (this.numQueryThreads > 0); 286 | } 287 | 288 | /** 289 | * The number of query threads for this server. 290 | */ 291 | public Integer getNumQueryThreads() { 292 | return this.numQueryThreads; 293 | } 294 | 295 | /** 296 | * Each server can set a cronExpression for the scheduler. If the 297 | * cronExpression is null, then the job is run immediately and once. 298 | * Otherwise, it is added to the scheduler for immediate execution and run 299 | * according to the cronExpression. 300 | */ 301 | public String getCronExpression() { 302 | return this.cronExpression; 303 | } 304 | 305 | @Override 306 | public String toString() { 307 | return "Server [host=" + this.host + ", port=" + this.port + ", url=" + this.url + ", cronExpression=" + this.cronExpression 308 | + ", numQueryThreads=" + this.numQueryThreads + "]"; 309 | } 310 | 311 | 312 | /** 313 | * This is some obtuse shit for enabling weblogic support. 314 | *

315 | * http://download.oracle.com/docs/cd/E13222_01/wls/docs90/jmx/accessWLS. 316 | * html 317 | *

318 | * You'd set this to: weblogic.management.remote 319 | */ 320 | public String getProtocolProviderPackages() { 321 | return protocolProviderPackages; 322 | } 323 | 324 | public static Builder builder() { 325 | return new Builder(); 326 | } 327 | 328 | public static Builder builder(Server server) { 329 | return new Builder(server); 330 | } 331 | 332 | public static final class Builder { 333 | private String alias; 334 | private String host; 335 | private String port; 336 | private String username; 337 | private String password; 338 | private String protocolProviderPackages; 339 | private String url; 340 | private String cronExpression; 341 | private Integer numQueryThreads; 342 | private boolean local; 343 | private String trustStorePath; 344 | 345 | public Builder setTrustStorePath(String trustStorePath) { 346 | this.trustStorePath = trustStorePath; 347 | return this; 348 | } 349 | 350 | public Builder setTrustStorePass(String trustStorePass) { 351 | this.trustStorePass = trustStorePass; 352 | return this; 353 | } 354 | 355 | private String trustStorePass; 356 | private final List queries = new ArrayList(); 357 | 358 | private Builder() { 359 | } 360 | 361 | private Builder(Server server) { 362 | this.alias = server.alias; 363 | this.host = server.host; 364 | this.port = server.port; 365 | this.username = server.username; 366 | this.password = server.password; 367 | this.protocolProviderPackages = server.protocolProviderPackages; 368 | this.url = server.url; 369 | this.cronExpression = server.cronExpression; 370 | this.numQueryThreads = server.numQueryThreads; 371 | this.local = server.local; 372 | this.trustStorePath = server.trustStorePath; 373 | this.trustStorePass = server.trustStorePass; 374 | this.queries.addAll(server.queries); 375 | } 376 | 377 | public Builder setAlias(String alias) { 378 | this.alias = alias; 379 | return this; 380 | } 381 | 382 | public Builder setHost(String host) { 383 | this.host = host; 384 | return this; 385 | } 386 | 387 | public Builder setPort(String port) { 388 | this.port = port; 389 | return this; 390 | } 391 | 392 | public Builder setUsername(String username) { 393 | this.username = username; 394 | return this; 395 | } 396 | 397 | public Builder setPassword(String password) { 398 | this.password = password; 399 | return this; 400 | } 401 | 402 | public Builder setProtocolProviderPackages(String protocolProviderPackages) { 403 | this.protocolProviderPackages = protocolProviderPackages; 404 | return this; 405 | } 406 | 407 | public Builder setUrl(String url) { 408 | this.url = url; 409 | return this; 410 | } 411 | 412 | public Builder setCronExpression(String cronExpression) { 413 | this.cronExpression = cronExpression; 414 | return this; 415 | } 416 | 417 | public Builder setNumQueryThreads(Integer numQueryThreads) { 418 | this.numQueryThreads = numQueryThreads; 419 | return this; 420 | } 421 | 422 | public Builder setLocal(boolean local) { 423 | this.local = local; 424 | return this; 425 | } 426 | 427 | public Builder addQuery(Query query) { 428 | this.queries.add(query); 429 | return this; 430 | } 431 | 432 | public Builder addQueries(Query... queries) { 433 | this.queries.addAll(asList(queries)); 434 | return this; 435 | } 436 | 437 | public Builder addQueries(Set queries) { 438 | this.queries.addAll(queries); 439 | return this; 440 | } 441 | 442 | public Server build() { 443 | return new Server( 444 | alias, 445 | host, 446 | port, 447 | username, 448 | password, 449 | protocolProviderPackages, 450 | url, 451 | cronExpression, 452 | numQueryThreads, 453 | local, 454 | queries, 455 | trustStorePath, 456 | trustStorePass); 457 | } 458 | } 459 | 460 | 461 | public static void main(String args[]) throws Exception { 462 | ObjectMapper objectMapper = new ObjectMapper(); 463 | // Query[] queries = objectMapper.readValue(new File("src/monitors.json"), Query[].class); 464 | Server.Builder serverBuilder = new Server.Builder(); 465 | serverBuilder.setHost("localhost").setPort("12345"); 466 | //serverBuilder.setTrustStorePath("/home/user/Documents/keys/truststore.ts") 467 | //.setTrustStorePass("password").setPassword("derby"); 468 | Query query = Query.builder().setObj("java.lang:type=Memory") 469 | .addAttr("HeapMemoryUsage") 470 | .addAttr("NonHeapMemoryUsage") 471 | .build(); 472 | Server server = serverBuilder.build(); 473 | MBeanServerConnection connection = server.getServerConnection().getMBeanServerConnection(); 474 | JmxQueryProcessor queryProcessor = new JmxQueryProcessor(); 475 | /* for (Query query : queries) {*/ 476 | System.out.println(queryProcessor.processQuery(connection, query)); 477 | //} 478 | } 479 | 480 | class JMXX509TrustManager implements X509TrustManager { 481 | 482 | /* 483 | * The default PKIX X509TrustManager9. We'll delegate 484 | * decisions to it, and fall back to the logic in this class if the 485 | * default X509TrustManager doesn't trust it. 486 | */ 487 | X509TrustManager pkixTrustManager; 488 | 489 | JMXX509TrustManager(String tsFile, char[] pass) throws GeneralSecurityException,IOException { 490 | // create a "default" JSSE X509TrustManager. 491 | 492 | KeyStore ks = KeyStore.getInstance("JKS"); 493 | ks.load(new FileInputStream(tsFile), 494 | pass); 495 | 496 | TrustManagerFactory tmf = 497 | TrustManagerFactory.getInstance("PKIX"); 498 | tmf.init(ks); 499 | 500 | TrustManager tms[] = tmf.getTrustManagers(); 501 | 502 | /* 503 | * Iterate over the returned trustmanagers, look 504 | * for an instance of X509TrustManager. If found, 505 | * use that as our "default" trust manager. 506 | */ 507 | for (int i = 0; i < tms.length; i++) { 508 | if (tms[i] instanceof X509TrustManager) { 509 | pkixTrustManager = (X509TrustManager) tms[i]; 510 | return; 511 | } 512 | } 513 | } 514 | 515 | /* 516 | * Delegate to the default trust manager. 517 | */ 518 | public void checkClientTrusted(X509Certificate[] chain, String authType) 519 | throws CertificateException { 520 | try { 521 | pkixTrustManager.checkClientTrusted(chain, authType); 522 | } catch (CertificateException e) { 523 | LOGGER.error("Exception for checking client certs", e); 524 | } 525 | } 526 | 527 | /* 528 | * Delegate to the default trust manager. 529 | */ 530 | public void checkServerTrusted(X509Certificate[] chain, String authType) 531 | throws CertificateException { 532 | try { 533 | pkixTrustManager.checkServerTrusted(chain, authType); 534 | } catch (CertificateException e) { 535 | LOGGER.error("Exception for checking server certs",e); 536 | } 537 | } 538 | 539 | /* 540 | * Merely pass this through. 541 | */ 542 | public X509Certificate[] getAcceptedIssuers() { 543 | return pkixTrustManager.getAcceptedIssuers(); 544 | } 545 | } 546 | 547 | } 548 | -------------------------------------------------------------------------------- /src/main/java/org/graylog/inputs/jmx/JMXInput.java: -------------------------------------------------------------------------------- 1 | package org.graylog.inputs.jmx; 2 | 3 | import com.codahale.metrics.MetricRegistry; 4 | import com.google.inject.assistedinject.Assisted; 5 | import com.google.inject.assistedinject.AssistedInject; 6 | import org.graylog2.inputs.codecs.GelfCodec; 7 | import org.graylog2.plugin.LocalMetricRegistry; 8 | import org.graylog2.plugin.ServerStatus; 9 | import org.graylog2.plugin.configuration.Configuration; 10 | import org.graylog2.plugin.inputs.MessageInput; 11 | 12 | import javax.inject.Inject; 13 | 14 | /** 15 | * This is the plugin. Your class should implement one of the existing plugin 16 | * interfaces. (i.e. AlarmCallback, MessageInput, MessageOutput) 17 | */ 18 | public class JMXInput extends MessageInput { 19 | 20 | private static final String NAME = "JMX"; 21 | 22 | @AssistedInject 23 | public JMXInput(MetricRegistry metricRegistry, @Assisted Configuration configuration, 24 | JMXTransport.Factory transportFactory, LocalMetricRegistry localRegistry, 25 | GelfCodec.Factory codecFactory, Config config, 26 | Descriptor descriptor, ServerStatus serverStatus) { 27 | super(metricRegistry, configuration, transportFactory.create(configuration), localRegistry, 28 | codecFactory.create(configuration), config, descriptor, serverStatus); 29 | } 30 | 31 | public interface Factory extends MessageInput.Factory { 32 | @Override 33 | JMXInput create(Configuration configuration); 34 | 35 | @Override 36 | Config getConfig(); 37 | 38 | @Override 39 | Descriptor getDescriptor(); 40 | } 41 | 42 | public static class Descriptor extends MessageInput.Descriptor { 43 | @Inject 44 | public Descriptor() { 45 | super(NAME, false, ""); 46 | } 47 | } 48 | 49 | public static class Config extends MessageInput.Config { 50 | @Inject 51 | public Config(JMXTransport.Factory transport, GelfCodec.Factory codec) { 52 | super(transport.getConfig(), codec.getConfig()); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/org/graylog/inputs/jmx/JMXInputMetaData.java: -------------------------------------------------------------------------------- 1 | package org.graylog.inputs.jmx; 2 | 3 | import org.graylog2.plugin.PluginMetaData; 4 | import org.graylog2.plugin.ServerStatus; 5 | import org.graylog2.plugin.Version; 6 | 7 | import java.net.URI; 8 | import java.util.Collections; 9 | import java.util.Set; 10 | 11 | /** 12 | * Implement the PluginMetaData interface here. 13 | */ 14 | public class JMXInputMetaData implements PluginMetaData { 15 | @Override 16 | public String getUniqueId() { 17 | return "org.graylog.inputs.jmx.JMXInputPlugin"; 18 | } 19 | 20 | @Override 21 | public String getName() { 22 | return "JMXInput"; 23 | } 24 | 25 | @Override 26 | public String getAuthor() { 27 | return "Sivasamy Kaliappan"; 28 | } 29 | 30 | @Override 31 | public URI getURL() { 32 | return URI.create("https://www.graylog.org/"); 33 | } 34 | 35 | @Override 36 | public Version getVersion() { 37 | return new Version(1, 0, 2); 38 | } 39 | 40 | @Override 41 | public String getDescription() { 42 | return "Input Plugin to monitor JMX end points"; 43 | } 44 | 45 | @Override 46 | public Version getRequiredVersion() { 47 | return new Version(1, 0, 0); 48 | } 49 | 50 | @Override 51 | public Set getRequiredCapabilities() { 52 | return Collections.emptySet(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/graylog/inputs/jmx/JMXInputPlugin.java: -------------------------------------------------------------------------------- 1 | package org.graylog.inputs.jmx; 2 | 3 | import org.graylog2.plugin.Plugin; 4 | import org.graylog2.plugin.PluginMetaData; 5 | import org.graylog2.plugin.PluginModule; 6 | 7 | import java.util.Arrays; 8 | import java.util.Collection; 9 | 10 | /** 11 | * Implement the Plugin interface here. 12 | */ 13 | public class JMXInputPlugin implements Plugin { 14 | @Override 15 | public PluginMetaData metadata() { 16 | return new JMXInputMetaData(); 17 | } 18 | 19 | @Override 20 | public Collection modules () { 21 | return Arrays.asList(new JMXInputPluginModule()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/graylog/inputs/jmx/JMXInputPluginModule.java: -------------------------------------------------------------------------------- 1 | package org.graylog.inputs.jmx; 2 | 3 | import org.graylog2.plugin.PluginConfigBean; 4 | import org.graylog2.plugin.PluginModule; 5 | 6 | import java.util.Collections; 7 | import java.util.Set; 8 | 9 | /** 10 | * Extend the PluginModule abstract class here to add you plugin to the system. 11 | */ 12 | public class JMXInputPluginModule extends PluginModule { 13 | /** 14 | * Returns all configuration beans required by this plugin. 15 | * 16 | * Implementing this method is optional. The default method returns an empty {@link Set}. 17 | */ 18 | @Override 19 | public Set getConfigBeans() { 20 | return Collections.emptySet(); 21 | } 22 | 23 | @Override 24 | protected void configure() { 25 | installTransport(transportMapBinder(),"jmx-input-transport",JMXTransport.class); 26 | installInput(inputsMapBinder(), JMXInput.class, JMXInput.Factory.class); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/graylog/inputs/jmx/JMXTransport.java: -------------------------------------------------------------------------------- 1 | package org.graylog.inputs.jmx; 2 | 3 | import com.codahale.metrics.MetricRegistry; 4 | import com.codahale.metrics.MetricSet; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import com.google.common.collect.HashMultimap; 7 | import com.google.common.collect.Maps; 8 | import com.google.inject.assistedinject.Assisted; 9 | import com.google.inject.assistedinject.AssistedInject; 10 | import com.googlecode.jmxtrans.jmx.JmxQueryProcessor; 11 | import com.googlecode.jmxtrans.model.Query; 12 | import com.googlecode.jmxtrans.model.Result; 13 | import com.googlecode.jmxtrans.model.Server; 14 | import org.graylog.inputs.jmx.model.GLAttribute; 15 | import org.graylog.inputs.jmx.model.GLQuery; 16 | import org.graylog.inputs.jmx.model.GLQueryConfig; 17 | import org.graylog2.plugin.ServerStatus; 18 | import org.graylog2.plugin.configuration.Configuration; 19 | import org.graylog2.plugin.configuration.ConfigurationRequest; 20 | import org.graylog2.plugin.configuration.fields.ConfigurationField; 21 | import org.graylog2.plugin.configuration.fields.DropdownField; 22 | import org.graylog2.plugin.configuration.fields.NumberField; 23 | import org.graylog2.plugin.configuration.fields.TextField; 24 | import org.graylog2.plugin.inputs.MessageInput; 25 | import org.graylog2.plugin.inputs.MisfireException; 26 | import org.graylog2.plugin.inputs.annotations.ConfigClass; 27 | import org.graylog2.plugin.inputs.annotations.FactoryClass; 28 | import org.graylog2.plugin.inputs.codecs.CodecAggregator; 29 | import org.graylog2.plugin.inputs.transports.Transport; 30 | import org.graylog2.plugin.journal.RawMessage; 31 | import org.slf4j.Logger; 32 | import org.slf4j.LoggerFactory; 33 | 34 | import javax.management.MBeanServerConnection; 35 | import javax.management.ObjectName; 36 | import java.io.ByteArrayOutputStream; 37 | import java.io.File; 38 | import java.io.IOException; 39 | import java.util.*; 40 | import java.util.concurrent.Executors; 41 | import java.util.concurrent.ScheduledExecutorService; 42 | import java.util.concurrent.ScheduledFuture; 43 | import java.util.concurrent.TimeUnit; 44 | 45 | /** 46 | * Created on 2/9/15. 47 | */ 48 | public class JMXTransport implements Transport { 49 | 50 | private static Logger LOGGER = LoggerFactory.getLogger(JMXTransport 51 | .class.getName()); 52 | private final Configuration configuration; 53 | private final MetricRegistry metricRegistry; 54 | private ServerStatus serverStatus; 55 | private List servers; 56 | private GLQueryConfig queryConfig; 57 | private ScheduledExecutorService executorService; 58 | private List futures; 59 | private int executionInterval; 60 | private TimeUnit getExecutionIntervalTimeUnit; 61 | private String label; 62 | private Hashtable connections; 63 | 64 | private static final String CK_CONFIG_HOSTS = "configHosts"; 65 | private static final String CK_CONFIG_LABEL = "configLabel"; 66 | private static final String CK_CONFIG_PORT = "configPort"; 67 | private static final String CK_CONFIG_USER_NAME = "configUsername"; 68 | private static final String CK_CONFIG_PASSWORD = "configPassword"; 69 | private static final String CK_CONFIG_INTERVAL = "configInterval"; 70 | private static final String CK_CONFIG_INTERVAL_UNIT = "configIntervalUnit"; 71 | private static final String CK_CONFIG_TYPE = "configType"; 72 | private static final String CK_CONFIG_CUSTOM_FILE_PATH = "configCustomFilePath"; 73 | private static final String CK_CONFIG_TRUSTSTORE_PATH = "configTruststorePath"; 74 | private static final String CK_CONFIG_TRUSTSTORE_PASS = "configTruststorePass"; 75 | 76 | 77 | @AssistedInject 78 | public JMXTransport(@Assisted Configuration configuration, 79 | MetricRegistry metricRegistry, 80 | ServerStatus serverStatus) { 81 | this.configuration = configuration; 82 | this.metricRegistry = metricRegistry; 83 | this.serverStatus = serverStatus; 84 | } 85 | 86 | @Override 87 | public void setMessageAggregator(CodecAggregator codecAggregator) { 88 | 89 | } 90 | 91 | @Override 92 | public void launch(MessageInput messageInput) throws MisfireException { 93 | 94 | this.executionInterval = configuration.getInt(CK_CONFIG_INTERVAL); 95 | this.getExecutionIntervalTimeUnit = TimeUnit.valueOf(configuration.getString(CK_CONFIG_INTERVAL_UNIT)); 96 | this.label = configuration.getString(CK_CONFIG_LABEL); 97 | String hosts[] = configuration.getString(CK_CONFIG_HOSTS).split(","); 98 | 99 | servers = new ArrayList<>(hosts.length); 100 | 101 | for (String host : hosts) { 102 | Server server = Server.builder().setHost(host.trim()) 103 | .setPort(String.valueOf(configuration.getInt(CK_CONFIG_PORT))) 104 | .setUsername(configuration.getString(CK_CONFIG_USER_NAME)) 105 | .setPassword(configuration.getString(CK_CONFIG_PASSWORD)) 106 | .setTrustStorePath(configuration.getString(CK_CONFIG_TRUSTSTORE_PATH)) 107 | .setTrustStorePass(configuration.getString(CK_CONFIG_TRUSTSTORE_PASS)) 108 | .build(); 109 | servers.add(server); 110 | } 111 | 112 | String jmxObjectType = configuration.getString(CK_CONFIG_TYPE); 113 | ObjectMapper configMapper = new ObjectMapper(); 114 | String jsonFilePath = jmxObjectType; 115 | 116 | try { 117 | if ("custom".equals(jmxObjectType)) { 118 | jsonFilePath = configuration.getString(CK_CONFIG_CUSTOM_FILE_PATH); 119 | if (jsonFilePath != null && jsonFilePath.length() > 0) { 120 | if (new File(jsonFilePath).exists()) { 121 | queryConfig = configMapper.readValue(new File(jsonFilePath), GLQueryConfig.class); 122 | } else { 123 | LOGGER.error("Custom config file not present." + jsonFilePath); 124 | throw new MisfireException("Cannot find custom config file " + jsonFilePath); 125 | } 126 | } else { 127 | LOGGER.error("Custom config file not entered."); 128 | throw new MisfireException("Custom config file not entered"); 129 | } 130 | } else { 131 | queryConfig = configMapper.readValue( 132 | JMXTransport.class.getClassLoader().getResourceAsStream(jsonFilePath), 133 | GLQueryConfig.class); 134 | } 135 | } catch (IOException e) { 136 | LOGGER.error("Exception while parsing config file", e); 137 | throw new MisfireException("Exception while parsing config file " + jsonFilePath, e); 138 | } 139 | startMonitoring(messageInput); 140 | } 141 | 142 | 143 | private void startMonitoring(MessageInput messageInput) { 144 | executorService = Executors.newScheduledThreadPool(servers.size()); 145 | futures = new ArrayList<>(servers.size()); 146 | long initalDelayMillis = TimeUnit.MILLISECONDS.convert(Math.round(Math.random() * 60), TimeUnit.SECONDS); 147 | long executionIntervalMillis = TimeUnit.MILLISECONDS.convert(executionInterval, getExecutionIntervalTimeUnit); 148 | for (Server server : servers) { 149 | ScheduledFuture future = executorService.scheduleAtFixedRate(new PollTask(messageInput, server, queryConfig, label), 150 | initalDelayMillis, executionIntervalMillis, TimeUnit.MILLISECONDS); 151 | futures.add(future); 152 | } 153 | 154 | LOGGER.info("JMX Input Plugin started ..."); 155 | } 156 | 157 | @Override 158 | public void stop() { 159 | if (futures != null) { 160 | for (ScheduledFuture future : futures) { 161 | future.cancel(true); 162 | } 163 | } 164 | 165 | if (executorService != null) { 166 | executorService.shutdownNow(); 167 | } 168 | } 169 | 170 | @Override 171 | public MetricSet getMetricSet() { 172 | return null; 173 | } 174 | 175 | private class PollTask implements Runnable { 176 | 177 | private MessageInput messageInput; 178 | private Server server; 179 | private GLQueryConfig queryConfig; 180 | private JmxQueryProcessor queryProcessor; 181 | private String label; 182 | private ObjectMapper mapper; 183 | private Map configuredAttributes; 184 | private List queries; 185 | 186 | 187 | public PollTask(MessageInput messageInput, Server server, GLQueryConfig queryConfig, String label) { 188 | this.messageInput = messageInput; 189 | this.server = server; 190 | this.queryConfig = queryConfig; 191 | this.label = label; 192 | queryProcessor = new JmxQueryProcessor(); 193 | mapper = new ObjectMapper(); 194 | connections = new Hashtable<>(); 195 | populateConfiguredAttributes(); 196 | 197 | } 198 | 199 | private String getName() { 200 | return "JMX-Input-" + server.getUrl(); 201 | } 202 | 203 | private void populateConfiguredAttributes() { 204 | configuredAttributes = new HashMap<>(); 205 | queries = new ArrayList<>(); 206 | for (GLQuery glQuery : queryConfig.getQueries()) { 207 | Query.Builder queryBuilder = Query.builder().setObj(glQuery.getObject()); 208 | for (GLAttribute attribute : glQuery.getAttributes()) { 209 | queryBuilder.addAttr(attribute.getName()); 210 | String mapKey = attribute.getName(); 211 | if (attribute.getKey() != null) { 212 | mapKey += attribute.getKey(); 213 | } 214 | configuredAttributes.put(mapKey, 215 | attribute); 216 | } 217 | queries.add(queryBuilder.build()); 218 | } 219 | } 220 | 221 | 222 | @Override 223 | public void run() { 224 | Thread.currentThread().setName(getName()); 225 | fetchData(); 226 | } 227 | 228 | private void fetchData() { 229 | try { 230 | MBeanServerConnection connection = getConnection(server); 231 | if (connection != null) { 232 | Map event = createEvent(); 233 | for (Query query : queries) { 234 | HashMultimap results = queryProcessor.processQuery(connection, query); 235 | for (Map.Entry entry : results.entries()) { 236 | processResult(event, entry); 237 | } 238 | } 239 | publishToGLServer(event); 240 | } else { 241 | LOGGER.debug("Cannot get connection for server " + server); 242 | } 243 | 244 | } catch (Exception e) { 245 | LOGGER.error("Exception while querying " + server.getHost(), e); 246 | } 247 | } 248 | 249 | private Map createEvent() { 250 | Map eventData = Maps.newHashMap(); 251 | eventData.put("version", "1.1"); 252 | eventData.put("_object", queryConfig.getType()); 253 | eventData.put("host", server.getHost()); 254 | eventData.put("_label", label); 255 | //graylog needs a short_message as part of every event 256 | eventData.put("short_message", "JMX"); 257 | return eventData; 258 | } 259 | 260 | private void publishToGLServer(Map eventData) throws IOException { 261 | synchronized (JMXTransport.this) { 262 | //publish to graylog server 263 | ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); 264 | mapper.writeValue(byteStream, eventData); 265 | messageInput.processRawMessage(new RawMessage(byteStream.toByteArray())); 266 | byteStream.close(); 267 | } 268 | } 269 | 270 | //Ignore all chars, except alpha numeric, @_. 271 | private String sanitize(String string) { 272 | StringBuilder bldr = new StringBuilder(); 273 | for (int i = 0; i < string.length(); i++) { 274 | if (Character.isAlphabetic(string.codePointAt(i)) || 275 | Character.isDigit(string.codePointAt(i)) || 276 | string.charAt(i) == '_' || 277 | string.charAt(i) == '-' ) { 278 | bldr.append(Character.toLowerCase(string.charAt(i))); 279 | } 280 | } 281 | return bldr.toString(); 282 | } 283 | 284 | 285 | //process JMXTrans result object as per configured json 286 | private void processResult(Map event, Map.Entry objectResult) { 287 | Result result = objectResult.getValue(); 288 | String attrName = result.getAttributeName(); 289 | Set resultKeys = result.getValues().keySet(); 290 | 291 | Map objectProperties = new HashMap<>(); 292 | 293 | for (Map.Entry objPropEntry : objectResult.getKey().getKeyPropertyList().entrySet()) { 294 | objectProperties.put(objPropEntry.getKey(), sanitize(objPropEntry.getValue())); 295 | } 296 | 297 | for (String key : resultKeys) { 298 | String label; 299 | String attrKey = attrName; 300 | if (attrName.equals(key)) { 301 | if (configuredAttributes.containsKey(attrKey)) { 302 | if (configuredAttributes.get(attrKey).getLabel() != null) { 303 | label = formatLabel(objectProperties, configuredAttributes.get(attrKey).getLabel()); 304 | event.put("_" + label, result.getValues().get(key)); 305 | } 306 | } 307 | } else { 308 | attrKey += key; 309 | if (configuredAttributes.containsKey(attrKey)) { 310 | if (configuredAttributes.get(attrKey).getLabel() != null) { 311 | label = formatLabel(objectProperties, configuredAttributes.get(attrKey).getLabel()); 312 | event.put("_" + label, result.getValues().get(key)); 313 | } 314 | } 315 | } 316 | } 317 | } 318 | 319 | private String formatLabel(Map objectProperties, String label) { 320 | if (label.contains("{")) { 321 | for (Map.Entry objectPropertyEntry : objectProperties.entrySet()) { 322 | label = label.replaceAll("\\{" + objectPropertyEntry.getKey() + "\\}", objectPropertyEntry.getValue()); 323 | } 324 | } 325 | return label; 326 | } 327 | } 328 | 329 | private MBeanServerConnection getConnection(Server server) { 330 | MBeanServerConnection connection = null; 331 | boolean create = false; 332 | if (connections.containsKey(server.getUrl())) { 333 | connection = connections.get(server.getUrl()); 334 | try { 335 | connection.getMBeanCount(); 336 | } catch (IOException e) { 337 | //Connection not proper. So get a new connection 338 | LOGGER.debug("Connection not alive for server " + server, e); 339 | create = true; 340 | } 341 | } else { 342 | create = true; 343 | } 344 | 345 | if (create) { 346 | try { 347 | connection = server.getServerConnection().getMBeanServerConnection(); 348 | connections.put(server.getUrl(), connection); 349 | } catch (Exception e) { 350 | //Cannot create new Connection 351 | LOGGER.error("Cannot create new connection for server" + server, e); 352 | } 353 | } 354 | return connection; 355 | } 356 | 357 | 358 | @FactoryClass 359 | public interface Factory extends Transport.Factory { 360 | @Override 361 | JMXTransport create(Configuration configuration); 362 | 363 | @Override 364 | Config getConfig(); 365 | } 366 | 367 | @ConfigClass 368 | public static class Config implements Transport.Config { 369 | @Override 370 | public ConfigurationRequest getRequestedConfiguration() { 371 | final ConfigurationRequest cr = new ConfigurationRequest(); 372 | cr.addField(new TextField(CK_CONFIG_HOSTS, 373 | "Servers to monitor", 374 | "", 375 | "Comma separated IP Address/Host names to monitor")); 376 | cr.addField(new NumberField(CK_CONFIG_PORT, 377 | "Port", 378 | 1099, 379 | "Server JMX port to query", 380 | ConfigurationField.Optional.NOT_OPTIONAL)); 381 | 382 | cr.addField(new TextField(CK_CONFIG_LABEL, 383 | "Label", 384 | "", 385 | "Label to identify this HTTP monitor")); 386 | 387 | Map monitorTypes = new HashMap<>(); 388 | monitorTypes.put("jvm.json", "JVM"); 389 | monitorTypes.put("tomcat.json", "Tomcat"); 390 | monitorTypes.put("custom", "Custom"); 391 | cr.addField(new DropdownField(CK_CONFIG_TYPE, 392 | "JMX Object type", 393 | "jvm.json", 394 | monitorTypes, 395 | "JMX Object type to monitor", 396 | ConfigurationField.Optional.NOT_OPTIONAL)); 397 | 398 | cr.addField(new TextField(CK_CONFIG_CUSTOM_FILE_PATH, 399 | "Config File Path", 400 | "", 401 | "Absolute path of JSON config file.Applicable for Custom Object type", 402 | ConfigurationField.Optional.OPTIONAL 403 | )); 404 | 405 | cr.addField(new TextField(CK_CONFIG_USER_NAME, 406 | "Username", 407 | "", 408 | "Username for JMX Connection", 409 | ConfigurationField.Optional.OPTIONAL)); 410 | cr.addField(new TextField(CK_CONFIG_PASSWORD, 411 | "Password", 412 | "", 413 | "Password for JMX Connection", 414 | ConfigurationField.Optional.OPTIONAL, 415 | TextField.Attribute.IS_PASSWORD)); 416 | 417 | cr.addField(new NumberField(CK_CONFIG_INTERVAL, 418 | "Polling Interval", 419 | 1, 420 | "Time between between requests", 421 | ConfigurationField.Optional.NOT_OPTIONAL)); 422 | 423 | Map timeUnits = DropdownField.ValueTemplates.timeUnits(); 424 | //Do not add nano seconds and micro seconds 425 | timeUnits.remove(TimeUnit.NANOSECONDS.toString()); 426 | timeUnits.remove(TimeUnit.MICROSECONDS.toString()); 427 | timeUnits.remove(TimeUnit.MILLISECONDS.toString()); 428 | cr.addField(new DropdownField( 429 | CK_CONFIG_INTERVAL_UNIT, 430 | "Polling Interval time unit", 431 | TimeUnit.MINUTES.toString(), 432 | timeUnits, 433 | ConfigurationField.Optional.NOT_OPTIONAL 434 | )); 435 | 436 | cr.addField(new TextField(CK_CONFIG_TRUSTSTORE_PATH, 437 | "SSL Truststore Path", 438 | "", 439 | "Absolute path of SSL Truststore file (used when SSL is enabled)", 440 | ConfigurationField.Optional.OPTIONAL 441 | )); 442 | cr.addField(new TextField(CK_CONFIG_TRUSTSTORE_PASS, 443 | "SSL Trustsotre Password", 444 | "", 445 | "Password for SSLTruststore", 446 | ConfigurationField.Optional.OPTIONAL, 447 | TextField.Attribute.IS_PASSWORD)); 448 | 449 | 450 | return cr; 451 | } 452 | } 453 | } 454 | -------------------------------------------------------------------------------- /src/main/java/org/graylog/inputs/jmx/model/GLAttribute.java: -------------------------------------------------------------------------------- 1 | package org.graylog.inputs.jmx.model; 2 | 3 | /** 4 | * Created on 6/9/15. 5 | */ 6 | public class GLAttribute { 7 | String name,key,label; 8 | 9 | public String getName() { 10 | return name; 11 | } 12 | 13 | public void setName(String name) { 14 | this.name = name; 15 | } 16 | 17 | public String getKey() { 18 | return key; 19 | } 20 | 21 | public void setKey(String key) { 22 | this.key = key; 23 | } 24 | 25 | public String getLabel() { 26 | return label; 27 | } 28 | 29 | public void setLabel(String label) { 30 | this.label = label; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/graylog/inputs/jmx/model/GLQuery.java: -------------------------------------------------------------------------------- 1 | package org.graylog.inputs.jmx.model; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Created on 6/9/15. 7 | */ 8 | public class GLQuery { 9 | String object; 10 | List attributes; 11 | 12 | public String getObject() { 13 | return object; 14 | } 15 | 16 | public void setObject(String object) { 17 | this.object = object; 18 | } 19 | 20 | public List getAttributes() { 21 | return attributes; 22 | } 23 | 24 | public void setAttributes(List attributes) { 25 | this.attributes = attributes; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/graylog/inputs/jmx/model/GLQueryConfig.java: -------------------------------------------------------------------------------- 1 | package org.graylog.inputs.jmx.model; 2 | 3 | import org.graylog.inputs.jmx.model.GLQuery; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Created on 6/9/15. 9 | */ 10 | public class GLQueryConfig { 11 | private String type; 12 | private List queries; 13 | 14 | public String getType() { 15 | return type; 16 | } 17 | 18 | public void setType(String type) { 19 | this.type = type; 20 | } 21 | 22 | public List getQueries() { 23 | return queries; 24 | } 25 | 26 | public void setQueries(List queries) { 27 | this.queries = queries; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/org.graylog2.plugin.Plugin: -------------------------------------------------------------------------------- 1 | org.graylog.inputs.jmx.JMXInputPlugin -------------------------------------------------------------------------------- /src/main/resources/jvm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "jvm", 3 | "queries": [ 4 | { 5 | "object": "java.lang:type=Memory", 6 | "attributes": [ 7 | { 8 | "name": "HeapMemoryUsage", 9 | "key": "used", 10 | "label": "jvm.mem.heap.used" 11 | }, 12 | { 13 | "name": "HeapMemoryUsage", 14 | "key": "committed", 15 | "label": "jvm.mem.heap.committed" 16 | }, 17 | { 18 | "name": "NonHeapMemoryUsage", 19 | "key": "used", 20 | "label": "jvm.mem.nonheap.used" 21 | } 22 | ] 23 | }, 24 | { 25 | "object": "java.lang:type=Threading", 26 | "attributes": [ 27 | { 28 | "name": "ThreadCount", 29 | "label": "jvm.threads.count" 30 | } 31 | ] 32 | }, 33 | { 34 | "object": "java.lang:type=ClassLoading", 35 | "attributes": [ 36 | { 37 | "name": "LoadedClassCount", 38 | "label": "jvm.classes.loadedCount" 39 | } 40 | ] 41 | }, 42 | { 43 | "object": "java.lang:type=GarbageCollector,name=*", 44 | "attributes": [ 45 | { 46 | "name": "CollectionCount", 47 | "label": "jvm.gc.{name}.count" 48 | } 49 | ] 50 | }, 51 | { 52 | "object": "java.lang:type=OperatingSystem", 53 | "attributes": [ 54 | { 55 | "name": "SystemLoadAverage", 56 | "label": "jvm.os.systemLoadAverage" 57 | }, 58 | { 59 | "name": "ProcessCpuLoad", 60 | "label": "jvm.os.processCpuLoad" 61 | } 62 | ] 63 | } 64 | ] 65 | } -------------------------------------------------------------------------------- /src/main/resources/kafka.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "kafka", 3 | "queries": [ 4 | { 5 | "object": "kafka.server:type=BrokerTopicMetrics", 6 | "attributes": [ 7 | { 8 | "name": "MessagesInPerSec", 9 | "label": "kafka.message.in.rate" 10 | }, 11 | { 12 | "name": "BytesInPerSec", 13 | "label": "kafka.byte.in.rate" 14 | }, 15 | { 16 | "name": "BytesOutPerSec", 17 | "label": "kafka.bytes.out.rate" 18 | } 19 | ] 20 | }, 21 | { 22 | "object": "kafka.server:type=ReplicaManager", 23 | "attributes": [ 24 | { 25 | "name": "IsrShrinksPerSec", 26 | "label": "kafka.replication.isr.shrink.rate" 27 | }, 28 | { 29 | "name": "IsrExpandsPerSec", 30 | "label": "kafka.replication.isr.expands.rate" 31 | } 32 | ] 33 | }, 34 | { 35 | "object": "kafka.controller:type=ControllerStats", 36 | "attributes": [ 37 | { 38 | "name": "LeaderElectionRateAndTimeMs", 39 | "label": "kafka.controller.leader_elections" 40 | }, 41 | { 42 | "name": "UncleanLeaderElectionsPerSec", 43 | "label": "kafka.controller.unclean_leader_elections" 44 | } 45 | ] 46 | }, 47 | { 48 | "object": "kafka.log:type=LogFlushStats", 49 | "attributes": [ 50 | { 51 | "name": "LogFlushRateAndTimeMs", 52 | "label": "kafka.log.flush.rate" 53 | } 54 | ] 55 | } 56 | ] 57 | } -------------------------------------------------------------------------------- /src/main/resources/tomcat.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "tomcat", 3 | "queries": [ 4 | { 5 | "object": "Catalina:type=ThreadPool,name=*", 6 | "attributes": [ 7 | { 8 | "name": "currentThreadCount", 9 | "label": "tomcat.threads.{name}.count" 10 | }, 11 | { 12 | "name": "currentThreadsBusy", 13 | "label": "tomcat.threads.{name}.busy" 14 | }, 15 | { 16 | "name": "maxThreads", 17 | "label": "tomcat.threads.{name}.max" 18 | } 19 | ] 20 | }, 21 | { 22 | "object": "Catalina:type=GlobalRequestProcessor,*", 23 | "attributes": [ 24 | { 25 | "name": "bytesSent", 26 | "label": "tomcat.{name}.bytesSent" 27 | }, 28 | { 29 | "name": "bytesReceived", 30 | "label": "tomcat.{name}.bytesReceived" 31 | }, 32 | { 33 | "name": "errorCount", 34 | "label": "tomcat.{name}.errorCount" 35 | }, 36 | { 37 | "name": "requestCount", 38 | "label": "tomcat.{name}.requestCount" 39 | }, 40 | { 41 | "name": "maxTime", 42 | "label": "tomcat.{name}.maxTime" 43 | }, 44 | { 45 | "name": "processingTime", 46 | "label": "tomcat.{name}.processingTime" 47 | } 48 | ] 49 | }, 50 | { 51 | "object": "Catalina:type=Cache,*", 52 | "attributes": [ 53 | { 54 | "name": "accessCount", 55 | "label": "tomcat.cache.{context}.accessCount" 56 | }, 57 | { 58 | "name": "hitsCount", 59 | "label": "tomcat.cache.{context}.hitsCount" 60 | } 61 | ] 62 | } 63 | ] 64 | } --------------------------------------------------------------------------------