From c2e177a6b6dab2a0e7283f9e7f4df0334dbcb94c Mon Sep 17 00:00:00 2001 From: colinpaicemq <39941593+colinpaicemq@users.noreply.github.com> Date: Mon, 16 Sep 2019 15:44:50 +0100 Subject: [PATCH 1/8] Support -loop n -every x, and fix bug in "missing dat" --- HELP | 37 +++++ JMXConnector.java | 278 ++++++++++++++++++++++++++++++++++++ JMXMetric.java | 349 ++++++++++++++++++++++++++++++++++++++++++++++ JMXQuery.java | 328 +++++++++++++++++++++++++++++++++++++++++++ README.md | 127 ++++++++++++----- 5 files changed, 1084 insertions(+), 35 deletions(-) create mode 100644 HELP create mode 100644 JMXConnector.java create mode 100644 JMXMetric.java create mode 100644 JMXQuery.java diff --git a/HELP b/HELP new file mode 100644 index 0000000..a40111e --- /dev/null +++ b/HELP @@ -0,0 +1,37 @@ +Usage: jmxquery [-url] [-username,u] [-password,p] [-query,q] [-incjvm] [-json] [-count] [-every] [-help] + +options are: + +-help, h + Prints this page + +-url + JMX URL, for example: "service:jmx:rmi:///jndi/rmi://localhost:1616/jmxrmi" + +-username, u + jmx username + +-password, p + jmx password + +-query, q + List of metrics to fetch in following format: {mBeanName}/{attribute}/{attributeKey}; + For example: "java.lang:type=Memory/HeapMemoryUsage/used" + {attributeKey} is optional and only used for Composite metric types. + Use semi-colon to separate metrics. + +-incjvm + Will add all standard JVM metrics to the -metrics query if used under java.lang domain + Useful utility function to add JVM metrics quickly and also for testing connections if + used by itself + +-json + Will output everything in JSON format, otherwise will be human readable text. Useful + for passing output to scripts. + +-count n + Loop this many times doing the query. It adds the following fields to the data + Date, time, time since start, loop counter + +-every n + Wait for this interval between loops. The default is 10 seconds \ No newline at end of file diff --git a/JMXConnector.java b/JMXConnector.java new file mode 100644 index 0000000..27a2bb9 --- /dev/null +++ b/JMXConnector.java @@ -0,0 +1,278 @@ +package com.outlyer.jmx.jmxquery; + +import com.outlyer.jmx.jmxquery.tools.JMXTools; +import java.io.IOException; +import java.util.ArrayList; +// import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import javax.management.InstanceNotFoundException; +import javax.management.IntrospectionException; +import javax.management.MBeanAttributeInfo; +import javax.management.MBeanInfo; +import javax.management.MBeanServerConnection; +import javax.management.MalformedObjectNameException; +import javax.management.ObjectInstance; +import javax.management.ObjectName; +import javax.management.ReflectionException; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.InvalidKeyException; +import javax.management.openmbean.TabularDataSupport; +import javax.management.remote.JMXConnectorFactory; +import javax.management.remote.JMXServiceURL; + +/** + * Connection class with utility functions for querying the JVM + * JMX interface for values + * + * @author David Gildeh (www.outlyer.com) + * Additions by Colin Paice + */ +public class JMXConnector { + + private javax.management.remote.JMXConnector connector; + private MBeanServerConnection connection; + + /** + * Connects to local process + * + * @param processName The name of the process to attach too, i.e. org.netbean.Main + * @throws IOException + */ + public JMXConnector(String processName) throws IOException { + connectLocalProcess(processName); + } + + /** + * Connect to the Java VM JMX + * + * @param url JMX Connection URL + * @param username JMX Connection username, null if none + * @param password JMX Connection password, null if none + * + * @throws IOException + */ + public JMXConnector(String url, String username, String password) throws IOException { + connect(url, username, password); + } + + /** + * Connect to a local JVM process via its displayName + * + * @param processName The displayName of the process, i.e. org.netbeans.Main + * @throws IOException + */ + private void connectLocalProcess(String processName) throws IOException { + String url = JMXTools.getLocalJMXConnection(processName); + connect(url, null, null); + } + + /** + * Connect to the Java VM JMX + * + * @param url JMX Connection URL + * @param username JMX Connection username, null if none + * @param password JMX Connection password, null if none + * + * @throws IOException + */ + private void connect(String url, String username, String password) throws IOException { + + if (url == null) { + throw new IOException("Cannot connect to null URL. If connecting via -proc option, check the JVM process name is correct or running."); + } + + JMXServiceURL jmxUrl = new JMXServiceURL(url); + + if (username != null) { + Map m = new HashMap(); + m.put(javax.management.remote.JMXConnector.CREDENTIALS, new String[]{username, password}); + connector = JMXConnectorFactory.connect(jmxUrl, m); + } else { + connector = JMXConnectorFactory.connect(jmxUrl); + } + + connection = connector.getMBeanServerConnection(); + } + + /** + * Disconnect JMX Connection + * + * @throws IOException + */ + public void disconnect() throws IOException { + if (connector != null) { + connector.close(); + connector = null; + } + } + + /** + * Fetches a list of metrics and their values in one go + * + * @param metricsList List of JMXMetrics to fetch + * @return A list of all the MBean metrics found from the query + * @throws java.io.IOException + * @throws javax.management.MalformedObjectNameException + * @throws javax.management.InstanceNotFoundException + * @throws javax.management.IntrospectionException + * @throws javax.management.ReflectionException + */ + public ArrayList getMetrics(ArrayList metricsList) throws IOException, + MalformedObjectNameException, InstanceNotFoundException, IntrospectionException, ReflectionException { + + ArrayList newMetricList = new ArrayList(); + for (JMXMetric metric : metricsList) { + ArrayList fetchedMetrics = getMetrics(metric); + newMetricList.addAll(fetchedMetrics); + } + return newMetricList; + } + + /** + * Main function to query and get metrics from JMX + * + * @param metricQuery Metric query to filter on, use *:* to list everything + * @return A list of all the MBean metrics found from the query + * @throws java.io.IOException + * @throws javax.management.MalformedObjectNameException + * @throws javax.management.InstanceNotFoundException + * @throws javax.management.IntrospectionException + * @throws javax.management.ReflectionException + */ + private ArrayList getMetrics(JMXMetric metricQuery) throws IOException, + MalformedObjectNameException, InstanceNotFoundException, IntrospectionException, ReflectionException { + + ArrayList metrics = new ArrayList(); + + MBeanInfo info = null; + JMXMetric attributeMetric = null; + + // Get list of MBeans from MBean Query + Set instances = connection.queryMBeans(new ObjectName(metricQuery.getmBeanName()), null); + Iterator iterator = instances.iterator(); + + // Iterate through results + while (iterator.hasNext()) { + + ObjectInstance instance = iterator.next(); + + try { + + // Get list of attributes for MBean + info = connection.getMBeanInfo(new ObjectName(instance.getObjectName().toString())); + MBeanAttributeInfo[] attributes = info.getAttributes(); + for (MBeanAttributeInfo attribute : attributes) { + + attributeMetric= new JMXMetric(instance.getObjectName().toString(), + attribute.getName(), + null); + attributeMetric.setmetricName(metricQuery.getmetricName()); + attributeMetric.setmetricLabels(metricQuery.getmetricLabels()); + + // If attribute given in query, only return those attributes + if ((metricQuery.getAttribute() != null) && + (! metricQuery.getAttribute().equals("*"))) { + + if (attribute.getName().equals(metricQuery.getAttribute())) { + // Set attribute type and get the metric(s) + attributeMetric.setAttributeType(attribute.getType()); + attributeMetric.setAttribute(attribute.getName()); + metrics.addAll(getAttributes(attributeMetric)); + } + } else { + + // Get all attributes for MBean Query + attributeMetric.setAttributeType(attribute.getType()); + attributeMetric.setAttribute(attribute.getName()); + metrics.addAll(getAttributes(attributeMetric)); + } + } + } catch (NullPointerException e) { + attributeMetric.setAttributeType(null); + attributeMetric.setValue(null); + metrics.add(attributeMetric); + } + } + + return metrics; + } + + /** + * Expand an attribute to get all keys and values for it + * + * @param attribute The attribute to expand + * @return A list of all the attribute keys/values + */ + private ArrayList getAttributes(JMXMetric attribute) { + return getAttributes(attribute, null); + } + + /** + * Recursive function to expand Attributes and get any values for them + * + * @param attribute The top attribute to expand values for + * @param value Null if calling, used to recursively get values + * @return A list of all the attributes and values for the attribute + */ + private ArrayList getAttributes(JMXMetric attribute, Object value) { + + ArrayList attributes = new ArrayList(); + + if (value == null) { + // First time running so get value from JMX connection + try { + value = connection.getAttribute(new ObjectName(attribute.getmBeanName()), attribute.getAttribute()); + } catch(Exception e) { + // Do nothing - these are thrown if value is UnAvailable + } + } + + if (value instanceof CompositeData) { + CompositeData cData = (CompositeData) value; + // If attribute has key specified, only get that otherwise get all keys + if (attribute.getAttributeKey() != null) { + try { + JMXMetric foundKey = new JMXMetric(attribute.getmBeanName(), + attribute.getAttribute(), + attribute.getAttributeKey()); + foundKey.setAttributeType(cData.get(attribute.getAttributeKey())); + foundKey.setmetricName(attribute.getmetricName()); + foundKey.setmetricLabels(attribute.getmetricLabels()); + // Fix recursive, endless loop if attribute is missing + // Specify" Missing instead of calling itself again + Object oValue = cData.get(attribute.getAttributeKey()); + if (oValue == null) oValue = "Missing"; + attributes.addAll(getAttributes(foundKey, oValue)); + // previous was + // attributes.addAll(getAttributes(foundKey, cData.get(attribute.getAttributeKey()))); + } catch (InvalidKeyException e) { + // Key doesn't exist so don't add to list + } + } else { + // List all the attribute keys + Set keys = cData.getCompositeType().keySet(); + for (String key : keys) { + JMXMetric foundKey = new JMXMetric(attribute.getmBeanName(), + attribute.getAttribute(), key); + foundKey.setAttributeType(cData.get(key)); + foundKey.setmetricName(attribute.getmetricName()); + foundKey.setmetricLabels(attribute.getmetricLabels()); + attributes.addAll(getAttributes(foundKey, cData.get(key))); + } + } + } else if (value instanceof TabularDataSupport) { + // Ignore getting values for these types + attribute.setAttributeType(value); + attributes.add(attribute); + } else { + attribute.setAttributeType(value); + attribute.setValue(value); + attributes.add(attribute); + } + + return attributes; + } +} \ No newline at end of file diff --git a/JMXMetric.java b/JMXMetric.java new file mode 100644 index 0000000..516b204 --- /dev/null +++ b/JMXMetric.java @@ -0,0 +1,349 @@ +package com.outlyer.jmx.jmxquery; + +import java.util.HashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.management.ObjectName; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.TabularDataSupport; + +/** + * Stores parameters for a single metric query passed into command line in format: + * + * {metricName}<{metricLabels}>=={mBeanName}/{attribute}/{attributeKey} + * + * E.g. jvm.memory.heap.used<>=java.lang:type=Memory/HeapMemoryUsage/used + * + * @author David Gildeh (www.outlyer.com) + * Updated Colin Paice to prefix JSON with da + */ +public class JMXMetric { + + private String metricName = null; + private HashMap metricLabels = new HashMap(); + private String mBeanName; + private String attribute; + private String attributeKey = null; + private String attributeType = null; + private Object value = null; + + public JMXMetric(String mBeanName, String attribute, String attributeKey) { + this.mBeanName = mBeanName; + this.attribute = attribute; + this.attributeKey = attributeKey; + } + + public JMXMetric(String metricQuery) throws ParseError { + this.parseMetricQuery(metricQuery); + } + + public String getmetricName() { + return metricName; + } + + public void setmetricName(String metricName) { + this.metricName = metricName; + } + + public HashMap getmetricLabels() { + return this.metricLabels; + } + + public void setmetricLabels(HashMap metricLabels) { + this.metricLabels.clear(); + this.metricLabels.putAll(metricLabels); + } + + public String getmBeanName() { + return mBeanName; + } + + public void setmBeanName(String mBeanName) { + this.mBeanName = mBeanName; + } + + public String getAttribute() { + return attribute; + } + + public void setAttribute(String attribute) { + this.attribute = attribute; + } + + public String getAttributeKey() { + return attributeKey; + } + + public void setAttributeKey(String attributeKey) { + this.attributeKey = attributeKey; + } + + public Object getValue() { + return value; + } + + public void setValue(Object value) { + this.value = value; + } + + public String getAttributeType() { + return attributeType; + } + + /** + * Will set type based on class instance of value + * + * @param value The value to get the type for + */ + public void setAttributeType(Object value) { + + if (value instanceof String) { + this.attributeType = "String"; + } else if (value == null) { + this.attributeType = "Null"; + } else if (value instanceof CompositeData) { + this.attributeType = "CompositeData"; + } else if (value instanceof TabularDataSupport) { + this.attributeType = "TabularDataSupport"; + } else if (value instanceof ObjectName) { + this.attributeType = "ObjectName"; + } else if (value instanceof Integer) { + this.attributeType = "Integer"; + } else if (value instanceof Long) { + this.attributeType = "Long"; + } else if (value instanceof Double) { + this.attributeType = "Double"; + } else if (value instanceof Boolean) { + this.attributeType = "Boolean"; + } else { + this.attributeType = value.getClass().getSimpleName(); + } + } + + /** + * Forces the object to replace any tokens in metricName or metricLabels from + * the mBean object properties, attribute or attributeKey. The following + * tokens are replaced: + * + * {attribute} - Will replace with this.attribute + * {attributeKey} - Will replace with this.attributeKey + * {XXX} - Will replace with any mBean object property with same name as XXX + * + */ + public void replaceTokens() { + // Only run if metricName isn't null + if (this.metricName != null) { + + HashMap replacements = new HashMap(); + if (this.attribute != null) { + replacements.put("attribute", this.attribute); + } + if (this.attributeKey != null) { + replacements.put("attributeKey", this.attributeKey); + } + // Get properties from mBeanName + int firstColon = this.mBeanName.indexOf(':'); + String[] props = this.mBeanName.substring(firstColon + 1) + .split("(?!\\B\"[^\"]*),(?![^\"]*\"\\B)"); + for (int i = 0; i < props.length; i++) { + String[] parts = props[i].split("="); + replacements.put(parts[0], parts[1]); + } + + // First replace tokens in metricName + this.metricName = this.replaceTokens(this.metricName, replacements); + // Then labels + for (String key : this.metricLabels.keySet()) { + String value = this.metricLabels.get(key); + if (value.indexOf("}") > 0) { + value = this.replaceTokens(value, replacements); + this.metricLabels.put(key, value); + } + } + } + } + + /** + * Replaces the text tokens in {} with values if found in the replacements + * HashMap, otherwise just put the token name there instead + * + * @param text The text to replace + * @param replacements A HashMap of replacement tokens + * @return The final string with tokens replaced + */ + private String replaceTokens(String text, HashMap replacements) { + // Looking for tokens in {}, i.e. {name} + Pattern pattern = Pattern.compile("\\{(.+?)\\}"); + Matcher matcher = pattern.matcher(text); + StringBuilder builder = new StringBuilder(); + int i = 0; + while (matcher.find()) { + String replacement = replacements.get(matcher.group(1)); + builder.append(text.substring(i, matcher.start())); + if (replacement == null) + builder.append(matcher.group(0)); + else + builder.append(replacement); + i = matcher.end(); + } + builder.append(text.substring(i, text.length())); + // Remove all quotations and spaces from any replacements + return builder.toString().replaceAll("\"", "").replaceAll(" ", "_"); + } + + /** + * Helper function to parse query string in following format and initialise + * Metric class: + * + * {metricName}<{metricLabels}>=={mBeanName}/{attribute}/{attributeKey}; + * + * where {metricName}<{metricLabels}> is optional and can include tokens + * + * E.g. java_lang_{attribute}_{key}==java.lang:type=Memory/HeapMemoryUsage/used; + * + * @param metricQuery + */ + private void parseMetricQuery(String metricQuery) throws ParseError { + + try { + String query = metricQuery; + + // metricName is optional + if (metricQuery.indexOf("==") > 0) { + int seperator = metricQuery.indexOf("=="); + String metricNamePart = query.substring(0, seperator); + query = query.substring(seperator + 2); + + // Parse metric name and labels + if (metricNamePart.indexOf("<") > 0) { + int labelSeperator = metricNamePart.indexOf("<"); + this.metricName = metricNamePart.substring(0, labelSeperator); + String labelsPart = metricNamePart.substring(labelSeperator + 1).replace(">", ""); + // This finds all commas which are not inside double quotes. + String[] labels = labelsPart.split("(?!\\B\"[^\"]*),(?![^\"]*\"\\B)"); + for (int i=0; i < labels.length; i++) { + String[] parts = labels[i].split("="); + if (parts.length < 2) { + throw new ParseError("Label format " + labelsPart + " is invalid."); + } + this.metricLabels.put(parts[0], parts[1]); + } + } else { + this.metricName = metricNamePart; + } + } + + // Parse Query + int firstColon = query.indexOf(':'); + String beanName = query.substring(0, firstColon + 1); + query = query.substring(firstColon + 1); + + // This finds all commas which are not inside double quotes. + String[] paths = query.split("(?!\\B\"[^\"]*),(?![^\"]*\"\\B)"); + for (int i=0; i < paths.length - 1; i++) { + beanName += paths[i] + ","; + } + + query = paths[paths.length - 1]; + String[] parts = query.split("(?!\\B\"[^\"]*)/(?![^\"]*\"\\B)"); + + beanName += parts[0]; + this.mBeanName = beanName; + + if (parts.length > 1) { + this.attribute = parts[1]; + } + if (parts.length > 2) { + this.attributeKey = parts[2]; + } + + } catch (Exception e) { + throw new ParseError("Error Parsing Metic Query: " + metricQuery , e); + } + } + + @Override + public String toString() { + String s = ""; + + if (this.metricName != null) { + s += this.metricName + "<"; + int keyCount = 0; + for (String key : this.metricLabels.keySet()) { + s += key + "=" + this.metricLabels.get(key); + if (++keyCount < this.metricLabels.size()) { + s += ","; + } + } + s += ">"; + + } else { + s += this.mBeanName; + if (this.attribute != null) { + s += "/" + this.attribute; + } + if (this.attributeKey != null) { + s += "/" + this.attributeKey; + } + } + if (attributeType != null) { + s += " (" + attributeType + ")"; + } + if (value != null) { + s += " = " + value.toString(); + } + + return s; + } + + /** + * Returns JSON representation of metric, appended with any additional values passed in + * + * @return JSON String + */ + public String toJSON(String[] passedStrings) { + + String beanName = this.mBeanName.replace("\"", "\\\""); + + String json = "{"; + if (this.metricName != null) { + json += "\"metricName\" : \"" + this.metricName + "\","; + json += "\"metricLabels\" : {"; + int keyCount = 0; + for (String key : this.metricLabels.keySet()) { + json += "\"" + key + "\" : \"" + this.metricLabels.get(key) + "\""; + if (++keyCount < this.metricLabels.size()) { + json += ","; + } + } + json += "},"; + } + json += "\"mBeanName\" : \"" + beanName + "\""; + json += ", \"attribute\" : \"" + this.attribute + "\""; + if (this.attributeKey != null) { + json += ", \"attributeKey\" : \"" + this.attributeKey + "\""; + } + if (this.attributeType != null) { + json += ", \"attributeType\" : \"" + this.attributeType + "\""; + } + if (this.value != null) { + if ((this.value instanceof Integer) || + (this.value instanceof Long) || + (this.value instanceof Double) || + (this.value instanceof Boolean)) { + json += ", \"value\" : " + this.value.toString(); + } else { + json += ", \"value\" : \"" + this.value.toString() + "\""; + } + } + // add any passed in values + for (int iStringArray =0; iStringArray < passedStrings.length; iStringArray++) + { + json += ","+passedStrings[iStringArray]; + } + + json += "}"; + + return json; + } +} \ No newline at end of file diff --git a/JMXQuery.java b/JMXQuery.java new file mode 100644 index 0000000..3498d43 --- /dev/null +++ b/JMXQuery.java @@ -0,0 +1,328 @@ +package com.outlyer.jmx.jmxquery; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintStream; + +import java.util.ArrayList; +import java.util.Arrays; +import javax.management.MalformedObjectNameException; +// add the support for the time of day formatting etc +import java.sql.Timestamp; +import java.text.SimpleDateFormat; +import java.lang.System; +import java.lang.Runtime; + + + +/** + * + * JMXQuery is used for local or remote request of JMX attributes + * + * @author David Gildeh (www.outlyer.com) + * updates by Colin Paice to support loop, and date time support + * + */ +public class JMXQuery { + + private JMXConnector connector; + private final ArrayList metrics = new ArrayList(); + + // Command Line Parameters + String url = null; + String username = null; + String password = null; + boolean outputJSON = false; + long loopCount = 0; + long every = 0; + + /** + * @param args + */ + public static void main(String[] args) throws Exception { + SimpleDateFormat sdfdate = new SimpleDateFormat("yyyy/MM/dd"); + SimpleDateFormat sdftime = new SimpleDateFormat("HH:mm:ss.SSS"); + Timestamp timestampStart = new Timestamp(System.currentTimeMillis()); + Timestamp timestampCollectTime = timestampStart; + Timestamp timestampEndPreviousCollectTime = timestampCollectTime; + String dateNow ; + String timeNow ; + long secondsFromStart = 0; + + String arrayPrefix = ""; + String passedStrings[]; + String header = ""; + + // Initialise + JMXQuery query = new JMXQuery(); + query.parse(args); + + // if we have a loop, and we are doing JSON output trap CTRL-C to write a closing ] + if (query.outputJSON) + Runtime.getRuntime().addShutdownHook( + new Thread() { + public void run() { System.out.println("]");} + } + ); + + // Initialise JMX Connection + try { + query.connector = new JMXConnector(query.url, query.username, query.password); + } catch (IOException ioe) { + if (query.outputJSON) { + System.out.println("{ \"error\": \"connection-error\", \"message\":\"" + ioe.getMessage() + "\"}"); + System.exit(2); + } else { + System.out.println("Error connecting to JMX endpoint: " + ioe.getMessage()); + System.exit(2); + } + } + + + + + if (query.outputJSON) System.out.println("["); + // check we have sensible values for loop time and loop count + if (query.every == 0 ) query.every = 10000; // millisecond value + if (query.loopCount == 0 ) query.loopCount = 1; + for (long iLoop = 0;; iLoop ++) + { + // check to see if we have been asked to loop + if (iLoop >= query.loopCount) + break; + if (iLoop > 0) + { + // wait for the user specified period + // try to keep sleep time as close to specified time by excluding the time + // getting the data + // if it takes 2 second to collect the data, and the interval between collect data is 10 seconds + // then we should wait for 10 - 2 seconds + // if the time to collect is longer than specified interval just use the sleep time as specified + // by the end user + + long sleepTime = query.every - (timestampEndPreviousCollectTime.getTime()-timestampCollectTime.getTime()); + if ( sleepTime < 1) sleepTime = query.every; + Thread.sleep(sleepTime); + } + timestampCollectTime = new Timestamp(System.currentTimeMillis()); + dateNow = sdfdate.format(timestampCollectTime); + timeNow = sdftime.format(timestampCollectTime); + // convert time delta from milliseconds to seconds + secondsFromStart = (timestampCollectTime.getTime() - timestampStart.getTime())/1000; + + if (query.loopCount > 0) + { + passedStrings = new String[]{ " \"Date\" : \"" + dateNow + " \"", + " \"Time\" : \"" + timeNow + " \"", + " \"secondsFromStart\" : " + secondsFromStart, + " \"loop\" : " + iLoop + }; + header = "Date:"+dateNow+ " Time:"+ timeNow+" Seconds from start:"+secondsFromStart +" loop:" +iLoop; + } + else + { + passedStrings = new String[] {}; // empty, no additional data appended + } + // Process Query + try { + ArrayList outputMetrics = query.connector.getMetrics(query.metrics); + + if (query.outputJSON) { + System.out.println(arrayPrefix + "["); // either '' or , + arrayPrefix = ","; // separate each array with a , + int count = 0; + + for (JMXMetric metric : outputMetrics) { + metric.replaceTokens(); + if (count > 0) { + System.out.print(", \n" + metric.toJSON(passedStrings)); + } else { + count++; + System.out.print(metric.toJSON(passedStrings)); + } + } + System.out.println("]"); + System.out.flush(); // so it gets passed on to the next stage + } else { + if (query.loopCount > 0) + System.out.println(header); + for (JMXMetric metric : outputMetrics) { + metric.replaceTokens(); + System.out.println(metric.toString()); + } + System.out.println("====================="); + System.out.println("Total Metrics Found: " + String.valueOf(outputMetrics.size())); + } + } catch (IOException ioe) { + if (query.outputJSON) { + System.out.println("{ \"error\": \"query-connection-error\", \"message\":\"" + ioe.getMessage() + "\"}"); + System.exit(2); + } else { + System.out.println("There was an IO Error running the query '" + query.metrics.toString() + "': " + ioe.getMessage()); + System.exit(2); + } + } catch (MalformedObjectNameException me) { + if (query.outputJSON) { + System.out.println("{ \"error\": \"bad-query\", \"message\":\"" + me.getMessage() + "\"}"); + System.exit(2); + } else { + System.out.println("The query '" + query.metrics.toString() + "' is invalid: " + me.getMessage()); + System.exit(2); + } + } catch (Exception e) { + if (query.outputJSON) { + System.out.println("{ \"error\": \"general-exception\", \"message\":\"" + e.getMessage() + "\"}"); + System.exit(2); + } else { + System.out.println("An exception was thrown while running the query '" + query.metrics.toString() + "': " + e.getMessage()); + System.out.println(Arrays.toString(e.getStackTrace())); + System.exit(2); + } + } // end of try + timestampEndPreviousCollectTime = new Timestamp(System.currentTimeMillis()); + + } // end of for iloop + // Do not put out trailing ] as the shutdown exit does it + // if (query.outputJSON) + // System.out.println("]"); + + // Disconnect from JMX Cleanly + query.connector.disconnect(); + } + + /** + * Get key JVM stats. Utility method for quickly grabbing key java metrics + * and also for testing + */ + private void includeJVMStats() { + + // Class Loading + metrics.add(new JMXMetric("java.lang:type=ClassLoading", "LoadedClassCount", null)); + metrics.add(new JMXMetric("java.lang:type=ClassLoading", "UnloadedClassCount", null)); + metrics.add(new JMXMetric("java.lang:type=ClassLoading", "TotalLoadedClassCount", null)); + + // Garbage Collection + metrics.add(new JMXMetric("java.lang:type=GarbageCollector,*", "CollectionTime", null)); + metrics.add(new JMXMetric("java.lang:type=GarbageCollector,*", "CollectionCount", null)); + + // Memory + metrics.add(new JMXMetric("java.lang:type=Memory", "HeapMemoryUsage", "committed")); + metrics.add(new JMXMetric("java.lang:type=Memory", "HeapMemoryUsage", "init")); + metrics.add(new JMXMetric("java.lang:type=Memory", "HeapMemoryUsage", "max")); + metrics.add(new JMXMetric("java.lang:type=Memory", "HeapMemoryUsage", "used")); + metrics.add(new JMXMetric("java.lang:type=Memory", "NonHeapMemoryUsage", "committed")); + metrics.add(new JMXMetric("java.lang:type=Memory", "NonHeapMemoryUsage", "init")); + metrics.add(new JMXMetric("java.lang:type=Memory", "NonHeapMemoryUsage", "max")); + metrics.add(new JMXMetric("java.lang:type=Memory", "NonHeapMemoryUsage", "used")); + + // Operating System + metrics.add(new JMXMetric("java.lang:type=OperatingSystem", "OpenFileDescriptorCount", null)); + metrics.add(new JMXMetric("java.lang:type=OperatingSystem", "MaxFileDescriptorCount", null)); + metrics.add(new JMXMetric("java.lang:type=OperatingSystem", "CommittedVirtualMemorySize", null)); + metrics.add(new JMXMetric("java.lang:type=OperatingSystem", "TotalSwapSpaceSize", null)); + metrics.add(new JMXMetric("java.lang:type=OperatingSystem", "FreeSwapSpaceSize", null)); + metrics.add(new JMXMetric("java.lang:type=OperatingSystem", "ProcessCpuTime", null)); + metrics.add(new JMXMetric("java.lang:type=OperatingSystem", "FreePhysicalMemorySize", null)); + metrics.add(new JMXMetric("java.lang:type=OperatingSystem", "TotalPhysicalMemorySize", null)); + metrics.add(new JMXMetric("java.lang:type=OperatingSystem", "SystemCpuLoad", null)); + metrics.add(new JMXMetric("java.lang:type=OperatingSystem", "ProcessCpuLoad", null)); + metrics.add(new JMXMetric("java.lang:type=OperatingSystem", "SystemLoadAverage", null)); + + // Runtime + metrics.add(new JMXMetric("java.lang:type=Runtime", "Uptime", null)); + + // Threading + metrics.add(new JMXMetric("java.lang:type=Threading", "ThreadCount", null)); + metrics.add(new JMXMetric("java.lang:type=Threading", "PeakThreadCount", null)); + metrics.add(new JMXMetric("java.lang:type=Threading", "DaemonThreadCount", null)); + metrics.add(new JMXMetric("java.lang:type=Threading", "TotalStartedThreadCount", null)); + + // Memory Pools + metrics.add(new JMXMetric("java.lang:type=MemoryPool,*", "Usage", "committed")); + metrics.add(new JMXMetric("java.lang:type=MemoryPool,*", "Usage", "init")); + metrics.add(new JMXMetric("java.lang:type=MemoryPool,*", "Usage", "max")); + metrics.add(new JMXMetric("java.lang:type=MemoryPool,*", "Usage", "used")); + } + + /** + * Parse runtime argument commands + * + * @param args Command line arguments + * @throws ParseError + */ + private void parse(String[] args) throws ParseError { + + try { + for (int i = 0; i < args.length; i++) { + String option = args[i]; + if (option.equals("-help") || option.equals("-h")) { + + printHelp(System.out); + System.exit(0); + + } else if (option.equals("-url")) { + url = args[++i]; + } else if (option.equals("-username") || option.equals("-u")) { + username = args[++i]; + } else if (option.equals("-password") || option.equals("-p")) { + password = args[++i]; + // additional variables for looping and duration of loop + } else if (option.equals("-count") || option.equals("-c")) { + loopCount = Long.parseLong(args[++i]); + } else if (option.equals("-every") || option.equals("-e")) { + every = 1000 *Long.parseLong(args[++i]); // in millseconds + + } else if (option.equals("-query") || option.equals("-q")) { + + // Parse query string to break up string in format: + // {mbean}/{attribute}/{key}; + String[] query = args[++i].split(";"); + for (String metricQuery : query) { + metrics.add(new JMXMetric(metricQuery)); + } + + } else if (option.equals("-json")) { + outputJSON = true; + } else if (option.equals("-incjvm")) { + includeJVMStats(); + } + } + + // Check that required parameters are given + if (url == null && (metrics.size() > 1)) { + System.out.println("Required options not specified."); + printHelp(System.out); + System.exit(0); + } + } catch (Exception e) { + throw new ParseError(e); + } + } + + /* + * Prints Help Text + */ + private void printHelp(PrintStream out) { + InputStream is = JMXQuery.class.getClassLoader().getResourceAsStream("com/outlyer/jmx/jmxquery/HELP"); + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + try { + while (true) { + String s = reader.readLine(); + if (s == null) { + break; + } + out.println(s); + } + } catch (IOException e) { + out.println(e); + } finally { + try { + reader.close(); + } catch (IOException e) { + out.println(e); + } + } + } +} diff --git a/README.md b/README.md index 4b20b98..af193ac 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,92 @@ -# JMXQuery - -This project provides a command line tool written in Java and packaged as a Jar to allow you to connect to and query a JMX endpoint on a Java Virtual Machine. - -The command line tool can be used standalone, or with the Python module also included in this project, if you want to provide a way to query JMX from Python. - -This project was originally written in December 2014 and has been used in the Outlyer monitoring agent since to provide monitoring for JVM applications via Nagios plugins. - -Outlyer plugins use the Jar via the Python module to query JVM metrics via JMX and provide those for dashboards and alerts on Outlyer. - -However, this module can also be used standalone with any other monitoring tool that can run shell commands or include the Python module. - -There are two folders under this repo: - -## Java -This contains all the source to build and compile the Jar command line tool that connects to the JVM JMX endpoint. - -## Python -This contains all the source to connect to the Jar with a simple to use Python class that handles all the communication via the command line. - -## Usage -Full instructions on how to run the JAR directly on the command line are provided under the Java folder. To use the Python module just use the pip installer: - -``` -pip install jmxquery -``` - -__Note: The Python module only supports Python 3 at this time__ - -## Contributing - -This project is released under the MIT License so you are free to use it in your own projects. However contributions are welcome and can be made via a fork/PR for review. - -Issues can also be raised in this repo if you find any, so please report them here for our attention. - +JMX Query +========= + +A simple jar to query JMX data from a JVM and return in a format that can easily be used in Nagios check scripts. + +Requires Java 1.5 or above. + + +Usage +------ + +``` +jmxquery [-url] [-username,u] [-password,p] [-query,q] [-incjvm] [-json] [-help] +``` + +options are: + +-help, h + Prints help page + +-url + JMX URL, for example: "service:jmx:rmi:///jndi/rmi://localhost:1616/jmxrmi" + +-username, u + jmx username if required + +-password, p + jmx password if required + +-query, q + List of metrics to fetch in following format: {mBeanName}/{attribute}/{attributeKey}; + For example: "java.lang:type=Memory/HeapMemoryUsage/used" + {attributeKey} is optional and only used for Composite metric types. + Use semi-colon to separate metrics. + +-incjvm + Will add all standard JVM metrics to the -metrics query if used under java.lang domain + Useful utility function to add JVM metrics quickly and also for testing connections if + used by itself + +-json + Will output everything in JSON format, otherwise will be human readable text. Useful + for passing output to scripts. + +Example Usage +------------- + +### Listing available metrics + +List all metrics: + +``` +java -jar jmxquery.jar -url service:jmx:rmi:///jndi/rmi://localhost:1616/jmxrmi -q "*:*" +``` + +To filter on a particular domain so you only see the JMX metrics available under that (i.e. java.lang) you can use the following command: + +``` +java -jar jmxquery.jar -url service:jmx:rmi:///jndi/rmi://localhost:1616/jmxrmi -q "java.lang:*" +``` + +If you want to filter on attribute name you could use the following query: + +``` +java -jar jmxquery.jar -url service:jmx:rmi:///jndi/rmi://localhost:1616/jmxrmi -q "*:*/HeapMemoryUsage" +``` + +This will list any MBean attributes that have that attribue name in the JVM. + +### Get a metric value + +``` +java -jar JMXQuery.jar -url service:jmx:rmi:///jndi/rmi://localhost:1616/jmxrmi -q "java.lang:type=ClassLoading/LoadedClassCount" +``` + +You can get multiple values by joining the mbeans together with semi colons. + +``` +java -jar JMXQuery.jar -url service:jmx:rmi:///jndi/rmi://localhost:1616/jmxrmi -q "java.lang:type=ClassLoading/LoadedClassCount;java.lang:type=ClassLoading/UnloadedClassCount" +``` + +Building the Jar +---------------- + +Simply run the ./build.sh, modifying the build parameters for your environment in the script. This will compile the code for Java 1.5 and build the Jar ready to run. + +License & Credits +----------------- + +This tool was inspired by https://code.google.com/p/jmxquery/ but has been completely rewritten by David Gildeh from Outlyer (www.outlyer.com). + +It is licensed under the MIT License (https://opensource.org/licenses/MIT) From dc0352c85bfd27c4e4b9777487379dd5d96e6aec Mon Sep 17 00:00:00 2001 From: colinpaicemq <39941593+colinpaicemq@users.noreply.github.com> Date: Mon, 16 Sep 2019 15:52:20 +0100 Subject: [PATCH 2/8] wrong diretory --- JMXConnector.java | 278 ---------------------------------------------- 1 file changed, 278 deletions(-) delete mode 100644 JMXConnector.java diff --git a/JMXConnector.java b/JMXConnector.java deleted file mode 100644 index 27a2bb9..0000000 --- a/JMXConnector.java +++ /dev/null @@ -1,278 +0,0 @@ -package com.outlyer.jmx.jmxquery; - -import com.outlyer.jmx.jmxquery.tools.JMXTools; -import java.io.IOException; -import java.util.ArrayList; -// import java.util.Arrays; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; -import javax.management.InstanceNotFoundException; -import javax.management.IntrospectionException; -import javax.management.MBeanAttributeInfo; -import javax.management.MBeanInfo; -import javax.management.MBeanServerConnection; -import javax.management.MalformedObjectNameException; -import javax.management.ObjectInstance; -import javax.management.ObjectName; -import javax.management.ReflectionException; -import javax.management.openmbean.CompositeData; -import javax.management.openmbean.InvalidKeyException; -import javax.management.openmbean.TabularDataSupport; -import javax.management.remote.JMXConnectorFactory; -import javax.management.remote.JMXServiceURL; - -/** - * Connection class with utility functions for querying the JVM - * JMX interface for values - * - * @author David Gildeh (www.outlyer.com) - * Additions by Colin Paice - */ -public class JMXConnector { - - private javax.management.remote.JMXConnector connector; - private MBeanServerConnection connection; - - /** - * Connects to local process - * - * @param processName The name of the process to attach too, i.e. org.netbean.Main - * @throws IOException - */ - public JMXConnector(String processName) throws IOException { - connectLocalProcess(processName); - } - - /** - * Connect to the Java VM JMX - * - * @param url JMX Connection URL - * @param username JMX Connection username, null if none - * @param password JMX Connection password, null if none - * - * @throws IOException - */ - public JMXConnector(String url, String username, String password) throws IOException { - connect(url, username, password); - } - - /** - * Connect to a local JVM process via its displayName - * - * @param processName The displayName of the process, i.e. org.netbeans.Main - * @throws IOException - */ - private void connectLocalProcess(String processName) throws IOException { - String url = JMXTools.getLocalJMXConnection(processName); - connect(url, null, null); - } - - /** - * Connect to the Java VM JMX - * - * @param url JMX Connection URL - * @param username JMX Connection username, null if none - * @param password JMX Connection password, null if none - * - * @throws IOException - */ - private void connect(String url, String username, String password) throws IOException { - - if (url == null) { - throw new IOException("Cannot connect to null URL. If connecting via -proc option, check the JVM process name is correct or running."); - } - - JMXServiceURL jmxUrl = new JMXServiceURL(url); - - if (username != null) { - Map m = new HashMap(); - m.put(javax.management.remote.JMXConnector.CREDENTIALS, new String[]{username, password}); - connector = JMXConnectorFactory.connect(jmxUrl, m); - } else { - connector = JMXConnectorFactory.connect(jmxUrl); - } - - connection = connector.getMBeanServerConnection(); - } - - /** - * Disconnect JMX Connection - * - * @throws IOException - */ - public void disconnect() throws IOException { - if (connector != null) { - connector.close(); - connector = null; - } - } - - /** - * Fetches a list of metrics and their values in one go - * - * @param metricsList List of JMXMetrics to fetch - * @return A list of all the MBean metrics found from the query - * @throws java.io.IOException - * @throws javax.management.MalformedObjectNameException - * @throws javax.management.InstanceNotFoundException - * @throws javax.management.IntrospectionException - * @throws javax.management.ReflectionException - */ - public ArrayList getMetrics(ArrayList metricsList) throws IOException, - MalformedObjectNameException, InstanceNotFoundException, IntrospectionException, ReflectionException { - - ArrayList newMetricList = new ArrayList(); - for (JMXMetric metric : metricsList) { - ArrayList fetchedMetrics = getMetrics(metric); - newMetricList.addAll(fetchedMetrics); - } - return newMetricList; - } - - /** - * Main function to query and get metrics from JMX - * - * @param metricQuery Metric query to filter on, use *:* to list everything - * @return A list of all the MBean metrics found from the query - * @throws java.io.IOException - * @throws javax.management.MalformedObjectNameException - * @throws javax.management.InstanceNotFoundException - * @throws javax.management.IntrospectionException - * @throws javax.management.ReflectionException - */ - private ArrayList getMetrics(JMXMetric metricQuery) throws IOException, - MalformedObjectNameException, InstanceNotFoundException, IntrospectionException, ReflectionException { - - ArrayList metrics = new ArrayList(); - - MBeanInfo info = null; - JMXMetric attributeMetric = null; - - // Get list of MBeans from MBean Query - Set instances = connection.queryMBeans(new ObjectName(metricQuery.getmBeanName()), null); - Iterator iterator = instances.iterator(); - - // Iterate through results - while (iterator.hasNext()) { - - ObjectInstance instance = iterator.next(); - - try { - - // Get list of attributes for MBean - info = connection.getMBeanInfo(new ObjectName(instance.getObjectName().toString())); - MBeanAttributeInfo[] attributes = info.getAttributes(); - for (MBeanAttributeInfo attribute : attributes) { - - attributeMetric= new JMXMetric(instance.getObjectName().toString(), - attribute.getName(), - null); - attributeMetric.setmetricName(metricQuery.getmetricName()); - attributeMetric.setmetricLabels(metricQuery.getmetricLabels()); - - // If attribute given in query, only return those attributes - if ((metricQuery.getAttribute() != null) && - (! metricQuery.getAttribute().equals("*"))) { - - if (attribute.getName().equals(metricQuery.getAttribute())) { - // Set attribute type and get the metric(s) - attributeMetric.setAttributeType(attribute.getType()); - attributeMetric.setAttribute(attribute.getName()); - metrics.addAll(getAttributes(attributeMetric)); - } - } else { - - // Get all attributes for MBean Query - attributeMetric.setAttributeType(attribute.getType()); - attributeMetric.setAttribute(attribute.getName()); - metrics.addAll(getAttributes(attributeMetric)); - } - } - } catch (NullPointerException e) { - attributeMetric.setAttributeType(null); - attributeMetric.setValue(null); - metrics.add(attributeMetric); - } - } - - return metrics; - } - - /** - * Expand an attribute to get all keys and values for it - * - * @param attribute The attribute to expand - * @return A list of all the attribute keys/values - */ - private ArrayList getAttributes(JMXMetric attribute) { - return getAttributes(attribute, null); - } - - /** - * Recursive function to expand Attributes and get any values for them - * - * @param attribute The top attribute to expand values for - * @param value Null if calling, used to recursively get values - * @return A list of all the attributes and values for the attribute - */ - private ArrayList getAttributes(JMXMetric attribute, Object value) { - - ArrayList attributes = new ArrayList(); - - if (value == null) { - // First time running so get value from JMX connection - try { - value = connection.getAttribute(new ObjectName(attribute.getmBeanName()), attribute.getAttribute()); - } catch(Exception e) { - // Do nothing - these are thrown if value is UnAvailable - } - } - - if (value instanceof CompositeData) { - CompositeData cData = (CompositeData) value; - // If attribute has key specified, only get that otherwise get all keys - if (attribute.getAttributeKey() != null) { - try { - JMXMetric foundKey = new JMXMetric(attribute.getmBeanName(), - attribute.getAttribute(), - attribute.getAttributeKey()); - foundKey.setAttributeType(cData.get(attribute.getAttributeKey())); - foundKey.setmetricName(attribute.getmetricName()); - foundKey.setmetricLabels(attribute.getmetricLabels()); - // Fix recursive, endless loop if attribute is missing - // Specify" Missing instead of calling itself again - Object oValue = cData.get(attribute.getAttributeKey()); - if (oValue == null) oValue = "Missing"; - attributes.addAll(getAttributes(foundKey, oValue)); - // previous was - // attributes.addAll(getAttributes(foundKey, cData.get(attribute.getAttributeKey()))); - } catch (InvalidKeyException e) { - // Key doesn't exist so don't add to list - } - } else { - // List all the attribute keys - Set keys = cData.getCompositeType().keySet(); - for (String key : keys) { - JMXMetric foundKey = new JMXMetric(attribute.getmBeanName(), - attribute.getAttribute(), key); - foundKey.setAttributeType(cData.get(key)); - foundKey.setmetricName(attribute.getmetricName()); - foundKey.setmetricLabels(attribute.getmetricLabels()); - attributes.addAll(getAttributes(foundKey, cData.get(key))); - } - } - } else if (value instanceof TabularDataSupport) { - // Ignore getting values for these types - attribute.setAttributeType(value); - attributes.add(attribute); - } else { - attribute.setAttributeType(value); - attribute.setValue(value); - attributes.add(attribute); - } - - return attributes; - } -} \ No newline at end of file From 3fbb4f0d29baa126ae58eed0e07491a46f217ce1 Mon Sep 17 00:00:00 2001 From: colinpaicemq <39941593+colinpaicemq@users.noreply.github.com> Date: Mon, 16 Sep 2019 15:52:41 +0100 Subject: [PATCH 3/8] wrong directory --- HELP | 37 ------------------------------------- 1 file changed, 37 deletions(-) delete mode 100644 HELP diff --git a/HELP b/HELP deleted file mode 100644 index a40111e..0000000 --- a/HELP +++ /dev/null @@ -1,37 +0,0 @@ -Usage: jmxquery [-url] [-username,u] [-password,p] [-query,q] [-incjvm] [-json] [-count] [-every] [-help] - -options are: - --help, h - Prints this page - --url - JMX URL, for example: "service:jmx:rmi:///jndi/rmi://localhost:1616/jmxrmi" - --username, u - jmx username - --password, p - jmx password - --query, q - List of metrics to fetch in following format: {mBeanName}/{attribute}/{attributeKey}; - For example: "java.lang:type=Memory/HeapMemoryUsage/used" - {attributeKey} is optional and only used for Composite metric types. - Use semi-colon to separate metrics. - --incjvm - Will add all standard JVM metrics to the -metrics query if used under java.lang domain - Useful utility function to add JVM metrics quickly and also for testing connections if - used by itself - --json - Will output everything in JSON format, otherwise will be human readable text. Useful - for passing output to scripts. - --count n - Loop this many times doing the query. It adds the following fields to the data - Date, time, time since start, loop counter - --every n - Wait for this interval between loops. The default is 10 seconds \ No newline at end of file From c4f10f0a39700952e29e334eebecc3bc30344b18 Mon Sep 17 00:00:00 2001 From: colinpaicemq <39941593+colinpaicemq@users.noreply.github.com> Date: Mon, 16 Sep 2019 15:53:02 +0100 Subject: [PATCH 4/8] wrong directory --- JMXQuery.java | 328 -------------------------------------------------- 1 file changed, 328 deletions(-) delete mode 100644 JMXQuery.java diff --git a/JMXQuery.java b/JMXQuery.java deleted file mode 100644 index 3498d43..0000000 --- a/JMXQuery.java +++ /dev/null @@ -1,328 +0,0 @@ -package com.outlyer.jmx.jmxquery; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.PrintStream; - -import java.util.ArrayList; -import java.util.Arrays; -import javax.management.MalformedObjectNameException; -// add the support for the time of day formatting etc -import java.sql.Timestamp; -import java.text.SimpleDateFormat; -import java.lang.System; -import java.lang.Runtime; - - - -/** - * - * JMXQuery is used for local or remote request of JMX attributes - * - * @author David Gildeh (www.outlyer.com) - * updates by Colin Paice to support loop, and date time support - * - */ -public class JMXQuery { - - private JMXConnector connector; - private final ArrayList metrics = new ArrayList(); - - // Command Line Parameters - String url = null; - String username = null; - String password = null; - boolean outputJSON = false; - long loopCount = 0; - long every = 0; - - /** - * @param args - */ - public static void main(String[] args) throws Exception { - SimpleDateFormat sdfdate = new SimpleDateFormat("yyyy/MM/dd"); - SimpleDateFormat sdftime = new SimpleDateFormat("HH:mm:ss.SSS"); - Timestamp timestampStart = new Timestamp(System.currentTimeMillis()); - Timestamp timestampCollectTime = timestampStart; - Timestamp timestampEndPreviousCollectTime = timestampCollectTime; - String dateNow ; - String timeNow ; - long secondsFromStart = 0; - - String arrayPrefix = ""; - String passedStrings[]; - String header = ""; - - // Initialise - JMXQuery query = new JMXQuery(); - query.parse(args); - - // if we have a loop, and we are doing JSON output trap CTRL-C to write a closing ] - if (query.outputJSON) - Runtime.getRuntime().addShutdownHook( - new Thread() { - public void run() { System.out.println("]");} - } - ); - - // Initialise JMX Connection - try { - query.connector = new JMXConnector(query.url, query.username, query.password); - } catch (IOException ioe) { - if (query.outputJSON) { - System.out.println("{ \"error\": \"connection-error\", \"message\":\"" + ioe.getMessage() + "\"}"); - System.exit(2); - } else { - System.out.println("Error connecting to JMX endpoint: " + ioe.getMessage()); - System.exit(2); - } - } - - - - - if (query.outputJSON) System.out.println("["); - // check we have sensible values for loop time and loop count - if (query.every == 0 ) query.every = 10000; // millisecond value - if (query.loopCount == 0 ) query.loopCount = 1; - for (long iLoop = 0;; iLoop ++) - { - // check to see if we have been asked to loop - if (iLoop >= query.loopCount) - break; - if (iLoop > 0) - { - // wait for the user specified period - // try to keep sleep time as close to specified time by excluding the time - // getting the data - // if it takes 2 second to collect the data, and the interval between collect data is 10 seconds - // then we should wait for 10 - 2 seconds - // if the time to collect is longer than specified interval just use the sleep time as specified - // by the end user - - long sleepTime = query.every - (timestampEndPreviousCollectTime.getTime()-timestampCollectTime.getTime()); - if ( sleepTime < 1) sleepTime = query.every; - Thread.sleep(sleepTime); - } - timestampCollectTime = new Timestamp(System.currentTimeMillis()); - dateNow = sdfdate.format(timestampCollectTime); - timeNow = sdftime.format(timestampCollectTime); - // convert time delta from milliseconds to seconds - secondsFromStart = (timestampCollectTime.getTime() - timestampStart.getTime())/1000; - - if (query.loopCount > 0) - { - passedStrings = new String[]{ " \"Date\" : \"" + dateNow + " \"", - " \"Time\" : \"" + timeNow + " \"", - " \"secondsFromStart\" : " + secondsFromStart, - " \"loop\" : " + iLoop - }; - header = "Date:"+dateNow+ " Time:"+ timeNow+" Seconds from start:"+secondsFromStart +" loop:" +iLoop; - } - else - { - passedStrings = new String[] {}; // empty, no additional data appended - } - // Process Query - try { - ArrayList outputMetrics = query.connector.getMetrics(query.metrics); - - if (query.outputJSON) { - System.out.println(arrayPrefix + "["); // either '' or , - arrayPrefix = ","; // separate each array with a , - int count = 0; - - for (JMXMetric metric : outputMetrics) { - metric.replaceTokens(); - if (count > 0) { - System.out.print(", \n" + metric.toJSON(passedStrings)); - } else { - count++; - System.out.print(metric.toJSON(passedStrings)); - } - } - System.out.println("]"); - System.out.flush(); // so it gets passed on to the next stage - } else { - if (query.loopCount > 0) - System.out.println(header); - for (JMXMetric metric : outputMetrics) { - metric.replaceTokens(); - System.out.println(metric.toString()); - } - System.out.println("====================="); - System.out.println("Total Metrics Found: " + String.valueOf(outputMetrics.size())); - } - } catch (IOException ioe) { - if (query.outputJSON) { - System.out.println("{ \"error\": \"query-connection-error\", \"message\":\"" + ioe.getMessage() + "\"}"); - System.exit(2); - } else { - System.out.println("There was an IO Error running the query '" + query.metrics.toString() + "': " + ioe.getMessage()); - System.exit(2); - } - } catch (MalformedObjectNameException me) { - if (query.outputJSON) { - System.out.println("{ \"error\": \"bad-query\", \"message\":\"" + me.getMessage() + "\"}"); - System.exit(2); - } else { - System.out.println("The query '" + query.metrics.toString() + "' is invalid: " + me.getMessage()); - System.exit(2); - } - } catch (Exception e) { - if (query.outputJSON) { - System.out.println("{ \"error\": \"general-exception\", \"message\":\"" + e.getMessage() + "\"}"); - System.exit(2); - } else { - System.out.println("An exception was thrown while running the query '" + query.metrics.toString() + "': " + e.getMessage()); - System.out.println(Arrays.toString(e.getStackTrace())); - System.exit(2); - } - } // end of try - timestampEndPreviousCollectTime = new Timestamp(System.currentTimeMillis()); - - } // end of for iloop - // Do not put out trailing ] as the shutdown exit does it - // if (query.outputJSON) - // System.out.println("]"); - - // Disconnect from JMX Cleanly - query.connector.disconnect(); - } - - /** - * Get key JVM stats. Utility method for quickly grabbing key java metrics - * and also for testing - */ - private void includeJVMStats() { - - // Class Loading - metrics.add(new JMXMetric("java.lang:type=ClassLoading", "LoadedClassCount", null)); - metrics.add(new JMXMetric("java.lang:type=ClassLoading", "UnloadedClassCount", null)); - metrics.add(new JMXMetric("java.lang:type=ClassLoading", "TotalLoadedClassCount", null)); - - // Garbage Collection - metrics.add(new JMXMetric("java.lang:type=GarbageCollector,*", "CollectionTime", null)); - metrics.add(new JMXMetric("java.lang:type=GarbageCollector,*", "CollectionCount", null)); - - // Memory - metrics.add(new JMXMetric("java.lang:type=Memory", "HeapMemoryUsage", "committed")); - metrics.add(new JMXMetric("java.lang:type=Memory", "HeapMemoryUsage", "init")); - metrics.add(new JMXMetric("java.lang:type=Memory", "HeapMemoryUsage", "max")); - metrics.add(new JMXMetric("java.lang:type=Memory", "HeapMemoryUsage", "used")); - metrics.add(new JMXMetric("java.lang:type=Memory", "NonHeapMemoryUsage", "committed")); - metrics.add(new JMXMetric("java.lang:type=Memory", "NonHeapMemoryUsage", "init")); - metrics.add(new JMXMetric("java.lang:type=Memory", "NonHeapMemoryUsage", "max")); - metrics.add(new JMXMetric("java.lang:type=Memory", "NonHeapMemoryUsage", "used")); - - // Operating System - metrics.add(new JMXMetric("java.lang:type=OperatingSystem", "OpenFileDescriptorCount", null)); - metrics.add(new JMXMetric("java.lang:type=OperatingSystem", "MaxFileDescriptorCount", null)); - metrics.add(new JMXMetric("java.lang:type=OperatingSystem", "CommittedVirtualMemorySize", null)); - metrics.add(new JMXMetric("java.lang:type=OperatingSystem", "TotalSwapSpaceSize", null)); - metrics.add(new JMXMetric("java.lang:type=OperatingSystem", "FreeSwapSpaceSize", null)); - metrics.add(new JMXMetric("java.lang:type=OperatingSystem", "ProcessCpuTime", null)); - metrics.add(new JMXMetric("java.lang:type=OperatingSystem", "FreePhysicalMemorySize", null)); - metrics.add(new JMXMetric("java.lang:type=OperatingSystem", "TotalPhysicalMemorySize", null)); - metrics.add(new JMXMetric("java.lang:type=OperatingSystem", "SystemCpuLoad", null)); - metrics.add(new JMXMetric("java.lang:type=OperatingSystem", "ProcessCpuLoad", null)); - metrics.add(new JMXMetric("java.lang:type=OperatingSystem", "SystemLoadAverage", null)); - - // Runtime - metrics.add(new JMXMetric("java.lang:type=Runtime", "Uptime", null)); - - // Threading - metrics.add(new JMXMetric("java.lang:type=Threading", "ThreadCount", null)); - metrics.add(new JMXMetric("java.lang:type=Threading", "PeakThreadCount", null)); - metrics.add(new JMXMetric("java.lang:type=Threading", "DaemonThreadCount", null)); - metrics.add(new JMXMetric("java.lang:type=Threading", "TotalStartedThreadCount", null)); - - // Memory Pools - metrics.add(new JMXMetric("java.lang:type=MemoryPool,*", "Usage", "committed")); - metrics.add(new JMXMetric("java.lang:type=MemoryPool,*", "Usage", "init")); - metrics.add(new JMXMetric("java.lang:type=MemoryPool,*", "Usage", "max")); - metrics.add(new JMXMetric("java.lang:type=MemoryPool,*", "Usage", "used")); - } - - /** - * Parse runtime argument commands - * - * @param args Command line arguments - * @throws ParseError - */ - private void parse(String[] args) throws ParseError { - - try { - for (int i = 0; i < args.length; i++) { - String option = args[i]; - if (option.equals("-help") || option.equals("-h")) { - - printHelp(System.out); - System.exit(0); - - } else if (option.equals("-url")) { - url = args[++i]; - } else if (option.equals("-username") || option.equals("-u")) { - username = args[++i]; - } else if (option.equals("-password") || option.equals("-p")) { - password = args[++i]; - // additional variables for looping and duration of loop - } else if (option.equals("-count") || option.equals("-c")) { - loopCount = Long.parseLong(args[++i]); - } else if (option.equals("-every") || option.equals("-e")) { - every = 1000 *Long.parseLong(args[++i]); // in millseconds - - } else if (option.equals("-query") || option.equals("-q")) { - - // Parse query string to break up string in format: - // {mbean}/{attribute}/{key}; - String[] query = args[++i].split(";"); - for (String metricQuery : query) { - metrics.add(new JMXMetric(metricQuery)); - } - - } else if (option.equals("-json")) { - outputJSON = true; - } else if (option.equals("-incjvm")) { - includeJVMStats(); - } - } - - // Check that required parameters are given - if (url == null && (metrics.size() > 1)) { - System.out.println("Required options not specified."); - printHelp(System.out); - System.exit(0); - } - } catch (Exception e) { - throw new ParseError(e); - } - } - - /* - * Prints Help Text - */ - private void printHelp(PrintStream out) { - InputStream is = JMXQuery.class.getClassLoader().getResourceAsStream("com/outlyer/jmx/jmxquery/HELP"); - BufferedReader reader = new BufferedReader(new InputStreamReader(is)); - try { - while (true) { - String s = reader.readLine(); - if (s == null) { - break; - } - out.println(s); - } - } catch (IOException e) { - out.println(e); - } finally { - try { - reader.close(); - } catch (IOException e) { - out.println(e); - } - } - } -} From 457b0d4bd3988797c80b5dee5d4c68707fb6459f Mon Sep 17 00:00:00 2001 From: colinpaicemq <39941593+colinpaicemq@users.noreply.github.com> Date: Mon, 16 Sep 2019 15:53:17 +0100 Subject: [PATCH 5/8] wrong directory --- JMXMetric.java | 349 ------------------------------------------------- 1 file changed, 349 deletions(-) delete mode 100644 JMXMetric.java diff --git a/JMXMetric.java b/JMXMetric.java deleted file mode 100644 index 516b204..0000000 --- a/JMXMetric.java +++ /dev/null @@ -1,349 +0,0 @@ -package com.outlyer.jmx.jmxquery; - -import java.util.HashMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.management.ObjectName; -import javax.management.openmbean.CompositeData; -import javax.management.openmbean.TabularDataSupport; - -/** - * Stores parameters for a single metric query passed into command line in format: - * - * {metricName}<{metricLabels}>=={mBeanName}/{attribute}/{attributeKey} - * - * E.g. jvm.memory.heap.used<>=java.lang:type=Memory/HeapMemoryUsage/used - * - * @author David Gildeh (www.outlyer.com) - * Updated Colin Paice to prefix JSON with da - */ -public class JMXMetric { - - private String metricName = null; - private HashMap metricLabels = new HashMap(); - private String mBeanName; - private String attribute; - private String attributeKey = null; - private String attributeType = null; - private Object value = null; - - public JMXMetric(String mBeanName, String attribute, String attributeKey) { - this.mBeanName = mBeanName; - this.attribute = attribute; - this.attributeKey = attributeKey; - } - - public JMXMetric(String metricQuery) throws ParseError { - this.parseMetricQuery(metricQuery); - } - - public String getmetricName() { - return metricName; - } - - public void setmetricName(String metricName) { - this.metricName = metricName; - } - - public HashMap getmetricLabels() { - return this.metricLabels; - } - - public void setmetricLabels(HashMap metricLabels) { - this.metricLabels.clear(); - this.metricLabels.putAll(metricLabels); - } - - public String getmBeanName() { - return mBeanName; - } - - public void setmBeanName(String mBeanName) { - this.mBeanName = mBeanName; - } - - public String getAttribute() { - return attribute; - } - - public void setAttribute(String attribute) { - this.attribute = attribute; - } - - public String getAttributeKey() { - return attributeKey; - } - - public void setAttributeKey(String attributeKey) { - this.attributeKey = attributeKey; - } - - public Object getValue() { - return value; - } - - public void setValue(Object value) { - this.value = value; - } - - public String getAttributeType() { - return attributeType; - } - - /** - * Will set type based on class instance of value - * - * @param value The value to get the type for - */ - public void setAttributeType(Object value) { - - if (value instanceof String) { - this.attributeType = "String"; - } else if (value == null) { - this.attributeType = "Null"; - } else if (value instanceof CompositeData) { - this.attributeType = "CompositeData"; - } else if (value instanceof TabularDataSupport) { - this.attributeType = "TabularDataSupport"; - } else if (value instanceof ObjectName) { - this.attributeType = "ObjectName"; - } else if (value instanceof Integer) { - this.attributeType = "Integer"; - } else if (value instanceof Long) { - this.attributeType = "Long"; - } else if (value instanceof Double) { - this.attributeType = "Double"; - } else if (value instanceof Boolean) { - this.attributeType = "Boolean"; - } else { - this.attributeType = value.getClass().getSimpleName(); - } - } - - /** - * Forces the object to replace any tokens in metricName or metricLabels from - * the mBean object properties, attribute or attributeKey. The following - * tokens are replaced: - * - * {attribute} - Will replace with this.attribute - * {attributeKey} - Will replace with this.attributeKey - * {XXX} - Will replace with any mBean object property with same name as XXX - * - */ - public void replaceTokens() { - // Only run if metricName isn't null - if (this.metricName != null) { - - HashMap replacements = new HashMap(); - if (this.attribute != null) { - replacements.put("attribute", this.attribute); - } - if (this.attributeKey != null) { - replacements.put("attributeKey", this.attributeKey); - } - // Get properties from mBeanName - int firstColon = this.mBeanName.indexOf(':'); - String[] props = this.mBeanName.substring(firstColon + 1) - .split("(?!\\B\"[^\"]*),(?![^\"]*\"\\B)"); - for (int i = 0; i < props.length; i++) { - String[] parts = props[i].split("="); - replacements.put(parts[0], parts[1]); - } - - // First replace tokens in metricName - this.metricName = this.replaceTokens(this.metricName, replacements); - // Then labels - for (String key : this.metricLabels.keySet()) { - String value = this.metricLabels.get(key); - if (value.indexOf("}") > 0) { - value = this.replaceTokens(value, replacements); - this.metricLabels.put(key, value); - } - } - } - } - - /** - * Replaces the text tokens in {} with values if found in the replacements - * HashMap, otherwise just put the token name there instead - * - * @param text The text to replace - * @param replacements A HashMap of replacement tokens - * @return The final string with tokens replaced - */ - private String replaceTokens(String text, HashMap replacements) { - // Looking for tokens in {}, i.e. {name} - Pattern pattern = Pattern.compile("\\{(.+?)\\}"); - Matcher matcher = pattern.matcher(text); - StringBuilder builder = new StringBuilder(); - int i = 0; - while (matcher.find()) { - String replacement = replacements.get(matcher.group(1)); - builder.append(text.substring(i, matcher.start())); - if (replacement == null) - builder.append(matcher.group(0)); - else - builder.append(replacement); - i = matcher.end(); - } - builder.append(text.substring(i, text.length())); - // Remove all quotations and spaces from any replacements - return builder.toString().replaceAll("\"", "").replaceAll(" ", "_"); - } - - /** - * Helper function to parse query string in following format and initialise - * Metric class: - * - * {metricName}<{metricLabels}>=={mBeanName}/{attribute}/{attributeKey}; - * - * where {metricName}<{metricLabels}> is optional and can include tokens - * - * E.g. java_lang_{attribute}_{key}==java.lang:type=Memory/HeapMemoryUsage/used; - * - * @param metricQuery - */ - private void parseMetricQuery(String metricQuery) throws ParseError { - - try { - String query = metricQuery; - - // metricName is optional - if (metricQuery.indexOf("==") > 0) { - int seperator = metricQuery.indexOf("=="); - String metricNamePart = query.substring(0, seperator); - query = query.substring(seperator + 2); - - // Parse metric name and labels - if (metricNamePart.indexOf("<") > 0) { - int labelSeperator = metricNamePart.indexOf("<"); - this.metricName = metricNamePart.substring(0, labelSeperator); - String labelsPart = metricNamePart.substring(labelSeperator + 1).replace(">", ""); - // This finds all commas which are not inside double quotes. - String[] labels = labelsPart.split("(?!\\B\"[^\"]*),(?![^\"]*\"\\B)"); - for (int i=0; i < labels.length; i++) { - String[] parts = labels[i].split("="); - if (parts.length < 2) { - throw new ParseError("Label format " + labelsPart + " is invalid."); - } - this.metricLabels.put(parts[0], parts[1]); - } - } else { - this.metricName = metricNamePart; - } - } - - // Parse Query - int firstColon = query.indexOf(':'); - String beanName = query.substring(0, firstColon + 1); - query = query.substring(firstColon + 1); - - // This finds all commas which are not inside double quotes. - String[] paths = query.split("(?!\\B\"[^\"]*),(?![^\"]*\"\\B)"); - for (int i=0; i < paths.length - 1; i++) { - beanName += paths[i] + ","; - } - - query = paths[paths.length - 1]; - String[] parts = query.split("(?!\\B\"[^\"]*)/(?![^\"]*\"\\B)"); - - beanName += parts[0]; - this.mBeanName = beanName; - - if (parts.length > 1) { - this.attribute = parts[1]; - } - if (parts.length > 2) { - this.attributeKey = parts[2]; - } - - } catch (Exception e) { - throw new ParseError("Error Parsing Metic Query: " + metricQuery , e); - } - } - - @Override - public String toString() { - String s = ""; - - if (this.metricName != null) { - s += this.metricName + "<"; - int keyCount = 0; - for (String key : this.metricLabels.keySet()) { - s += key + "=" + this.metricLabels.get(key); - if (++keyCount < this.metricLabels.size()) { - s += ","; - } - } - s += ">"; - - } else { - s += this.mBeanName; - if (this.attribute != null) { - s += "/" + this.attribute; - } - if (this.attributeKey != null) { - s += "/" + this.attributeKey; - } - } - if (attributeType != null) { - s += " (" + attributeType + ")"; - } - if (value != null) { - s += " = " + value.toString(); - } - - return s; - } - - /** - * Returns JSON representation of metric, appended with any additional values passed in - * - * @return JSON String - */ - public String toJSON(String[] passedStrings) { - - String beanName = this.mBeanName.replace("\"", "\\\""); - - String json = "{"; - if (this.metricName != null) { - json += "\"metricName\" : \"" + this.metricName + "\","; - json += "\"metricLabels\" : {"; - int keyCount = 0; - for (String key : this.metricLabels.keySet()) { - json += "\"" + key + "\" : \"" + this.metricLabels.get(key) + "\""; - if (++keyCount < this.metricLabels.size()) { - json += ","; - } - } - json += "},"; - } - json += "\"mBeanName\" : \"" + beanName + "\""; - json += ", \"attribute\" : \"" + this.attribute + "\""; - if (this.attributeKey != null) { - json += ", \"attributeKey\" : \"" + this.attributeKey + "\""; - } - if (this.attributeType != null) { - json += ", \"attributeType\" : \"" + this.attributeType + "\""; - } - if (this.value != null) { - if ((this.value instanceof Integer) || - (this.value instanceof Long) || - (this.value instanceof Double) || - (this.value instanceof Boolean)) { - json += ", \"value\" : " + this.value.toString(); - } else { - json += ", \"value\" : \"" + this.value.toString() + "\""; - } - } - // add any passed in values - for (int iStringArray =0; iStringArray < passedStrings.length; iStringArray++) - { - json += ","+passedStrings[iStringArray]; - } - - json += "}"; - - return json; - } -} \ No newline at end of file From 15a57f74ea91b3bf5c37e9ab7ba3b0941d4cf25b Mon Sep 17 00:00:00 2001 From: colinpaicemq <39941593+colinpaicemq@users.noreply.github.com> Date: Mon, 16 Sep 2019 15:55:01 +0100 Subject: [PATCH 6/8] Support -loop, -every and bug with "missing" --- .../outlyer/jmx/jmxquery/JMXConnector.java | 11 +- .../com/outlyer/jmx/jmxquery/JMXMetric.java | 11 +- .../com/outlyer/jmx/jmxquery/JMXQuery.java | 115 ++++++++++++++++-- 3 files changed, 122 insertions(+), 15 deletions(-) diff --git a/java/src/main/java/com/outlyer/jmx/jmxquery/JMXConnector.java b/java/src/main/java/com/outlyer/jmx/jmxquery/JMXConnector.java index 23842a5..27a2bb9 100644 --- a/java/src/main/java/com/outlyer/jmx/jmxquery/JMXConnector.java +++ b/java/src/main/java/com/outlyer/jmx/jmxquery/JMXConnector.java @@ -3,7 +3,7 @@ import com.outlyer.jmx.jmxquery.tools.JMXTools; import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; +// import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.Map; @@ -28,6 +28,7 @@ * JMX interface for values * * @author David Gildeh (www.outlyer.com) + * Additions by Colin Paice */ public class JMXConnector { @@ -240,7 +241,13 @@ private ArrayList getAttributes(JMXMetric attribute, Object value) { foundKey.setAttributeType(cData.get(attribute.getAttributeKey())); foundKey.setmetricName(attribute.getmetricName()); foundKey.setmetricLabels(attribute.getmetricLabels()); - attributes.addAll(getAttributes(foundKey, cData.get(attribute.getAttributeKey()))); + // Fix recursive, endless loop if attribute is missing + // Specify" Missing instead of calling itself again + Object oValue = cData.get(attribute.getAttributeKey()); + if (oValue == null) oValue = "Missing"; + attributes.addAll(getAttributes(foundKey, oValue)); + // previous was + // attributes.addAll(getAttributes(foundKey, cData.get(attribute.getAttributeKey()))); } catch (InvalidKeyException e) { // Key doesn't exist so don't add to list } diff --git a/java/src/main/java/com/outlyer/jmx/jmxquery/JMXMetric.java b/java/src/main/java/com/outlyer/jmx/jmxquery/JMXMetric.java index c6859c2..516b204 100644 --- a/java/src/main/java/com/outlyer/jmx/jmxquery/JMXMetric.java +++ b/java/src/main/java/com/outlyer/jmx/jmxquery/JMXMetric.java @@ -15,6 +15,7 @@ * E.g. jvm.memory.heap.used<>=java.lang:type=Memory/HeapMemoryUsage/used * * @author David Gildeh (www.outlyer.com) + * Updated Colin Paice to prefix JSON with da */ public class JMXMetric { @@ -296,11 +297,11 @@ public String toString() { } /** - * Returns JSON representation of metric + * Returns JSON representation of metric, appended with any additional values passed in * * @return JSON String */ - public String toJSON() { + public String toJSON(String[] passedStrings) { String beanName = this.mBeanName.replace("\"", "\\\""); @@ -334,7 +335,13 @@ public String toJSON() { } else { json += ", \"value\" : \"" + this.value.toString() + "\""; } + } + // add any passed in values + for (int iStringArray =0; iStringArray < passedStrings.length; iStringArray++) + { + json += ","+passedStrings[iStringArray]; } + json += "}"; return json; diff --git a/java/src/main/java/com/outlyer/jmx/jmxquery/JMXQuery.java b/java/src/main/java/com/outlyer/jmx/jmxquery/JMXQuery.java index 351f652..3498d43 100644 --- a/java/src/main/java/com/outlyer/jmx/jmxquery/JMXQuery.java +++ b/java/src/main/java/com/outlyer/jmx/jmxquery/JMXQuery.java @@ -9,12 +9,20 @@ import java.util.ArrayList; import java.util.Arrays; import javax.management.MalformedObjectNameException; +// add the support for the time of day formatting etc +import java.sql.Timestamp; +import java.text.SimpleDateFormat; +import java.lang.System; +import java.lang.Runtime; + + /** * * JMXQuery is used for local or remote request of JMX attributes * * @author David Gildeh (www.outlyer.com) + * updates by Colin Paice to support loop, and date time support * */ public class JMXQuery { @@ -27,16 +35,38 @@ public class JMXQuery { String username = null; String password = null; boolean outputJSON = false; + long loopCount = 0; + long every = 0; /** * @param args */ public static void main(String[] args) throws Exception { - + SimpleDateFormat sdfdate = new SimpleDateFormat("yyyy/MM/dd"); + SimpleDateFormat sdftime = new SimpleDateFormat("HH:mm:ss.SSS"); + Timestamp timestampStart = new Timestamp(System.currentTimeMillis()); + Timestamp timestampCollectTime = timestampStart; + Timestamp timestampEndPreviousCollectTime = timestampCollectTime; + String dateNow ; + String timeNow ; + long secondsFromStart = 0; + + String arrayPrefix = ""; + String passedStrings[]; + String header = ""; + // Initialise JMXQuery query = new JMXQuery(); query.parse(args); - + + // if we have a loop, and we are doing JSON output trap CTRL-C to write a closing ] + if (query.outputJSON) + Runtime.getRuntime().addShutdownHook( + new Thread() { + public void run() { System.out.println("]");} + } + ); + // Initialise JMX Connection try { query.connector = new JMXConnector(query.url, query.username, query.password); @@ -50,23 +80,74 @@ public static void main(String[] args) throws Exception { } } - // Process Query - try { + + + + if (query.outputJSON) System.out.println("["); + // check we have sensible values for loop time and loop count + if (query.every == 0 ) query.every = 10000; // millisecond value + if (query.loopCount == 0 ) query.loopCount = 1; + for (long iLoop = 0;; iLoop ++) + { + // check to see if we have been asked to loop + if (iLoop >= query.loopCount) + break; + if (iLoop > 0) + { + // wait for the user specified period + // try to keep sleep time as close to specified time by excluding the time + // getting the data + // if it takes 2 second to collect the data, and the interval between collect data is 10 seconds + // then we should wait for 10 - 2 seconds + // if the time to collect is longer than specified interval just use the sleep time as specified + // by the end user + + long sleepTime = query.every - (timestampEndPreviousCollectTime.getTime()-timestampCollectTime.getTime()); + if ( sleepTime < 1) sleepTime = query.every; + Thread.sleep(sleepTime); + } + timestampCollectTime = new Timestamp(System.currentTimeMillis()); + dateNow = sdfdate.format(timestampCollectTime); + timeNow = sdftime.format(timestampCollectTime); + // convert time delta from milliseconds to seconds + secondsFromStart = (timestampCollectTime.getTime() - timestampStart.getTime())/1000; + + if (query.loopCount > 0) + { + passedStrings = new String[]{ " \"Date\" : \"" + dateNow + " \"", + " \"Time\" : \"" + timeNow + " \"", + " \"secondsFromStart\" : " + secondsFromStart, + " \"loop\" : " + iLoop + }; + header = "Date:"+dateNow+ " Time:"+ timeNow+" Seconds from start:"+secondsFromStart +" loop:" +iLoop; + } + else + { + passedStrings = new String[] {}; // empty, no additional data appended + } + // Process Query + try { ArrayList outputMetrics = query.connector.getMetrics(query.metrics); + if (query.outputJSON) { - System.out.println("["); + System.out.println(arrayPrefix + "["); // either '' or , + arrayPrefix = ","; // separate each array with a , int count = 0; + for (JMXMetric metric : outputMetrics) { metric.replaceTokens(); if (count > 0) { - System.out.print(", \n" + metric.toJSON()); + System.out.print(", \n" + metric.toJSON(passedStrings)); } else { count++; - System.out.print(metric.toJSON()); + System.out.print(metric.toJSON(passedStrings)); } } System.out.println("]"); + System.out.flush(); // so it gets passed on to the next stage } else { + if (query.loopCount > 0) + System.out.println(header); for (JMXMetric metric : outputMetrics) { metric.replaceTokens(); System.out.println(metric.toString()); @@ -99,9 +180,15 @@ public static void main(String[] args) throws Exception { System.out.println(Arrays.toString(e.getStackTrace())); System.exit(2); } - } - - // Disconnect from JMX Cleanly + } // end of try + timestampEndPreviousCollectTime = new Timestamp(System.currentTimeMillis()); + + } // end of for iloop + // Do not put out trailing ] as the shutdown exit does it + // if (query.outputJSON) + // System.out.println("]"); + + // Disconnect from JMX Cleanly query.connector.disconnect(); } @@ -181,6 +268,12 @@ private void parse(String[] args) throws ParseError { username = args[++i]; } else if (option.equals("-password") || option.equals("-p")) { password = args[++i]; + // additional variables for looping and duration of loop + } else if (option.equals("-count") || option.equals("-c")) { + loopCount = Long.parseLong(args[++i]); + } else if (option.equals("-every") || option.equals("-e")) { + every = 1000 *Long.parseLong(args[++i]); // in millseconds + } else if (option.equals("-query") || option.equals("-q")) { // Parse query string to break up string in format: @@ -232,4 +325,4 @@ private void printHelp(PrintStream out) { } } } -} \ No newline at end of file +} From 88ebe9d1943bf52ea6763a9210448340e432aeb4 Mon Sep 17 00:00:00 2001 From: colinpaicemq <39941593+colinpaicemq@users.noreply.github.com> Date: Mon, 16 Sep 2019 15:56:38 +0100 Subject: [PATCH 7/8] Support of -loop and -every --- java/src/main/resources/com/outlyer/jmx/jmxquery/HELP | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/java/src/main/resources/com/outlyer/jmx/jmxquery/HELP b/java/src/main/resources/com/outlyer/jmx/jmxquery/HELP index 3ed082d..a40111e 100644 --- a/java/src/main/resources/com/outlyer/jmx/jmxquery/HELP +++ b/java/src/main/resources/com/outlyer/jmx/jmxquery/HELP @@ -1,4 +1,4 @@ -Usage: jmxquery [-url] [-username,u] [-password,p] [-query,q] [-incjvm] [-json] [-help] +Usage: jmxquery [-url] [-username,u] [-password,p] [-query,q] [-incjvm] [-json] [-count] [-every] [-help] options are: @@ -27,4 +27,11 @@ options are: -json Will output everything in JSON format, otherwise will be human readable text. Useful - for passing output to scripts. \ No newline at end of file + for passing output to scripts. + +-count n + Loop this many times doing the query. It adds the following fields to the data + Date, time, time since start, loop counter + +-every n + Wait for this interval between loops. The default is 10 seconds \ No newline at end of file From 3116106eed93237d91172f9110e11e4b180289cd Mon Sep 17 00:00:00 2001 From: colinpaicemq <39941593+colinpaicemq@users.noreply.github.com> Date: Mon, 16 Sep 2019 15:59:18 +0100 Subject: [PATCH 8/8] Support -loop and -every options --- README.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index af193ac..d4b9eb8 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ JMX Query ========= A simple jar to query JMX data from a JVM and return in a format that can easily be used in Nagios check scripts. +It has been extended to loop for a specified number of times, every n seconds. Requires Java 1.5 or above. @@ -10,7 +11,7 @@ Usage ------ ``` -jmxquery [-url] [-username,u] [-password,p] [-query,q] [-incjvm] [-json] [-help] +jmxquery [-url] [-username,u] [-password,p] [-query,q] [-incjvm] [-json] [-count -every] [-help] ``` options are: @@ -42,6 +43,16 @@ options are: Will output everything in JSON format, otherwise will be human readable text. Useful for passing output to scripts. +-count n will loop n times. + +-every n seconds. This is used with -count to run the query periodically, and so collect + the data over a period. If using -json the data can easily be post processed into charts. + If using -count ... extra information is provided + date 2019/08/25 + time 12:11:38.764 + seconds since start, so 0,10,20, second etc - to make it easier to plot + loop 0,1,2,3 etc + Example Usage -------------