diff --git a/java/README.md b/java/README.md index af193ac..b26edeb 100644 --- a/java/README.md +++ b/java/README.md @@ -10,7 +10,7 @@ Usage ------ ``` -jmxquery [-url] [-username,u] [-password,p] [-query,q] [-incjvm] [-json] [-help] +jmxquery [-url] [-username,u] [-password,p] [-query,q] [-call,c] [-incjvm] [-json] [-help] ``` options are: @@ -33,6 +33,12 @@ options are: {attributeKey} is optional and only used for Composite metric types. Use semi-colon to separate metrics. +-call, c + Call a method using a base64 encoded JSON as parameter + JSON format: + {"objectName":"{mBeanName}","name":"{methodName}","params":[{"value":"{value}","type":"{type}"},...]} + Supported types: char, short, int, long, boolean, double, float, String, Float, Double, Short, Int, Long + -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 @@ -79,6 +85,12 @@ 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" ``` +``` +Invoke the add method: {"objectName":"com.outlyer.jmx.jmxquery.app.tests:type=Test","name":"add","params":[{"value":"Test ","type":"String"},{"value":"test","type":"String"}]} + +java -jar JMXQuery.jar -url service:jmx:rmi:///jndi/rmi://localhost:1616/jmxrmi -c eyJvYmplY3ROYW1lIjoiY29tLm91dGx5ZXIuam14LmpteHF1ZXJ5LmFwcC50ZXN0czp0eXBlPVRlc3QiLCJuYW1lIjoiYWRkIiwicGFyYW1zIjpbeyJ2YWx1ZSI6IlRlc3QgIiwidHlwZSI6IlN0cmluZyJ9LHsidmFsdWUiOiJ0ZXN0IiwidHlwZSI6IlN0cmluZyJ9XX0= + +``` Building the Jar ---------------- diff --git a/java/pom.xml b/java/pom.xml index 3674428..d64000f 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -31,6 +31,11 @@ system ${toolsjar} + + com.fasterxml.jackson.core + jackson-databind + 2.9.8 + 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..8691aa8 100644 --- a/java/src/main/java/com/outlyer/jmx/jmxquery/JMXConnector.java +++ b/java/src/main/java/com/outlyer/jmx/jmxquery/JMXConnector.java @@ -3,14 +3,17 @@ 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.Attribute; +import javax.management.AttributeNotFoundException; import javax.management.InstanceNotFoundException; import javax.management.IntrospectionException; +import javax.management.InvalidAttributeValueException; import javax.management.MBeanAttributeInfo; +import javax.management.MBeanException; import javax.management.MBeanInfo; import javax.management.MBeanServerConnection; import javax.management.MalformedObjectNameException; @@ -108,6 +111,74 @@ public void disconnect() throws IOException { } } + /** + * invokes a method on the server using JMX + * + * @param name + * - The object name of the MBean on which the method is to be + * invoked. + * @param method + * - The name of the method to be invoked. + * @param args + * - An array containing the arguments of the method to be set + * when the operation is invoked + * @param signatures + * - An array containing the signatures of the arguments, an + * array of class names in the format returned by + * Class.getName(). + * @return result of invoking the operation on the MBean specified + * @throws java.io.IOException + * @throws javax.management.MalformedObjectNameException + * @throws javax.management.InstanceNotFoundException + * @throws javax.management.IntrospectionException + * @throws javax.management.ReflectionException + * @throws MBeanException + */ + public Object invoke(String objectName, String method, Object[] args, String[] signatures) + throws IOException, MalformedObjectNameException, InstanceNotFoundException, IntrospectionException, + ReflectionException, MBeanException { + return connection.invoke(new ObjectName(objectName), method, args, signatures); + } + + /** + * set an attribute on the server using JMX + * + * @param name + * - The object name of the MBean on which the method is to be + * invoked. + * @param field + * - The name of the field to be invoked. + * @param value + * - A value of type parameter + * @return void + * @throws java.io.IOException + * @throws javax.management.MalformedObjectNameException + * @throws javax.management.InstanceNotFoundException + * @throws javax.management.IntrospectionException + * @throws javax.management.ReflectionException + * @throws MBeanException + * @throws InvalidAttributeValueException + * @throws AttributeNotFoundException + */ + public void set(String objectName, String field, Object value) + throws IOException, MalformedObjectNameException, InstanceNotFoundException, IntrospectionException, + ReflectionException, MBeanException, AttributeNotFoundException, InvalidAttributeValueException { + connection.setAttribute(new ObjectName(objectName), new Attribute(field,value)); + } + + /** + * query the actual name of the MBean + * + * @param name + * - The object search string. + * @return actual name of the MBean + * @throws java.io.IOException + * @throws javax.management.MalformedObjectNameException + */ + public ObjectName queryName(String name) throws MalformedObjectNameException, IOException { + return connection.queryNames(new ObjectName(name), null).iterator().next(); + } + /** * Fetches a list of metrics and their values in one go * 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..aac0ab3 100644 --- a/java/src/main/java/com/outlyer/jmx/jmxquery/JMXMetric.java +++ b/java/src/main/java/com/outlyer/jmx/jmxquery/JMXMetric.java @@ -289,11 +289,42 @@ public String toString() { s += " (" + attributeType + ")"; } if (value != null) { - s += " = " + value.toString(); + if (value instanceof String) { + s += " = " + value.toString(); + } else { + s += " = " + valueToString(value); + } } return s; } + + private static String valueToString(Object value) { + if (value == null) { + return "null"; + } + if (value.getClass().isArray()) { + return arrayToString((Object[]) value); + } + if ((value instanceof Integer) || (value instanceof Long) || (value instanceof Double) + || (value instanceof Boolean)) { + return value.toString(); + } + return new StringBuilder().append("\"").append(value).append("\"").toString(); + } + + private static String arrayToString(Object[] array) { + String s = "["; + boolean separatorRequired = false; + for (Object v : array) { + if (separatorRequired) + s += ","; + separatorRequired = true; + s += valueToString(v); + } + s += "]"; + return s; + } /** * Returns JSON representation of metric @@ -326,14 +357,7 @@ public String toJSON() { 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() + "\""; - } + json += ", \"value\" : " + valueToString(value); } 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..92a9169 100644 --- a/java/src/main/java/com/outlyer/jmx/jmxquery/JMXQuery.java +++ b/java/src/main/java/com/outlyer/jmx/jmxquery/JMXQuery.java @@ -5,11 +5,16 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintStream; - import java.util.ArrayList; import java.util.Arrays; +import java.util.Base64; + import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.outlyer.jmx.jmxquery.object.JMXAttribute; +import com.outlyer.jmx.jmxquery.object.JMXMethod; /** * * JMXQuery is used for local or remote request of JMX attributes @@ -28,6 +33,8 @@ public class JMXQuery { String password = null; boolean outputJSON = false; + JMXMethod methodInvoke = null; + JMXAttribute setterInvoke = null; /** * @param args */ @@ -52,6 +59,33 @@ public static void main(String[] args) throws Exception { // Process Query try { + // Set attribute + if (query.setterInvoke != null) { + ObjectName objName = query.connector.queryName(query.setterInvoke.getObjectName()); + + query.connector.set(objName.getCanonicalName(), query.setterInvoke.getName(), + query.setterInvoke.getTypedValue()); + query.connector.disconnect(); + return; + } + // Method invoke + if (query.methodInvoke != null) { + ObjectName objName = query.connector.queryName(query.methodInvoke.getObjectName()); + + Object ret = query.connector.invoke(objName.getCanonicalName(), query.methodInvoke.getName(), + query.methodInvoke.getValues(), query.methodInvoke.getTypes()); + if (ret != null) { + if (query.outputJSON) { + System.out.println("{ \"result\": \"" + toString(ret) + "\"}"); + } else { + System.out.println(toString(ret)); + } + } + query.connector.disconnect(); + return; + } + + // Read attributes ArrayList outputMetrics = query.connector.getMetrics(query.metrics); if (query.outputJSON) { System.out.println("["); @@ -105,6 +139,18 @@ public static void main(String[] args) throws Exception { query.connector.disconnect(); } + private static final String toString(Object obj) { + if (obj instanceof Object[]) { + StringBuilder strBuild = new StringBuilder(); + for (Object el : (Object[]) obj) { + strBuild.append(el.toString() + "\n"); + } + return strBuild.toString(); + + } + return obj.toString(); + } + /** * Get key JVM stats. Utility method for quickly grabbing key java metrics * and also for testing @@ -181,15 +227,23 @@ private void parse(String[] args) throws ParseError { username = args[++i]; } else if (option.equals("-password") || option.equals("-p")) { password = args[++i]; - } else if (option.equals("-query") || option.equals("-q")) { - + } 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("-call") || option.equals("-c")) { + ObjectMapper objectMapper = new ObjectMapper(); + byte[] decodedBytes = Base64.getDecoder().decode(args[++i]); + String decodedString = new String(decodedBytes); + methodInvoke = objectMapper.readValue(decodedString, JMXMethod.class); + } else if (option.equals("-set") || option.equals("-s")) { + ObjectMapper objectMapper = new ObjectMapper(); + byte[] decodedBytes = Base64.getDecoder().decode(args[++i]); + String decodedString = new String(decodedBytes); + setterInvoke = objectMapper.readValue(decodedString, JMXAttribute.class); } else if (option.equals("-json")) { outputJSON = true; } else if (option.equals("-incjvm")) { diff --git a/java/src/main/java/com/outlyer/jmx/jmxquery/object/JMXAttribute.java b/java/src/main/java/com/outlyer/jmx/jmxquery/object/JMXAttribute.java new file mode 100644 index 0000000..336b2d2 --- /dev/null +++ b/java/src/main/java/com/outlyer/jmx/jmxquery/object/JMXAttribute.java @@ -0,0 +1,44 @@ +package com.outlyer.jmx.jmxquery.object; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +/** + * + * JMXAttribute is used for set the JMX attributes + * + * @author Tibor Kiss + * + */ +public class JMXAttribute extends JMXObject { + + private JMXParam value = null; + + /** + * get the JMX MBean value + */ + public JMXParam getValue() { + return value; + } + + /** + * return the actual value + */ + @JsonIgnore + public Object getTypedValue() { + if(value == null) + return null; + return JMXObjectTypeConverter.toObject(value.getType(), (String)value.getValue()); + } + + /** + * set the JMX MBean value + * + * @param value + * JMX MBean new value + */ + public void setValue(JMXParam value) { + this.value = value; + } + + +} diff --git a/java/src/main/java/com/outlyer/jmx/jmxquery/object/JMXMethod.java b/java/src/main/java/com/outlyer/jmx/jmxquery/object/JMXMethod.java new file mode 100644 index 0000000..3ce3df6 --- /dev/null +++ b/java/src/main/java/com/outlyer/jmx/jmxquery/object/JMXMethod.java @@ -0,0 +1,67 @@ +package com.outlyer.jmx.jmxquery.object; + +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +/** + * + * JMXMethod is used for local or remote invoke of JMX methods + * + * @author Tibor Kiss + * + */ +public class JMXMethod extends JMXObject { + + private final List params = new ArrayList(); + + + /** + * return the list of parameters + * + * @return list of parameters + */ + public List getParams() { + return params; + } + + /** + * This method can be used to add a parameter to a method + * + * @param value + * @param type + */ + public void addParam(Object value, String type) { + params.add(new JMXParam(value, type)); + } + + /** + * This method returns the values as an array + * + * @return array of value + */ + @JsonIgnore + public Object[] getValues() { + List result = new ArrayList(); + for (JMXParam par : params) { + result.add(JMXObjectTypeConverter.toObject(JMXObjectTypeConverter.typeMap.get(par.getType()), par.getValue().toString())); + } + return result.toArray(new Object[result.size()]); + } + + /** + * This method returns the values types as an array + * + * @return array of the type of values + */ + @JsonIgnore + public String[] getTypes() { + List result = new ArrayList(); + for (JMXParam par : params) { + result.add(JMXObjectTypeConverter.typeMap.get(par.getType())); + } + return result.toArray(new String[result.size()]); + } + +} diff --git a/java/src/main/java/com/outlyer/jmx/jmxquery/object/JMXObject.java b/java/src/main/java/com/outlyer/jmx/jmxquery/object/JMXObject.java new file mode 100644 index 0000000..b4a7cfd --- /dev/null +++ b/java/src/main/java/com/outlyer/jmx/jmxquery/object/JMXObject.java @@ -0,0 +1,49 @@ +package com.outlyer.jmx.jmxquery.object; + +/** + * This class is used to set a JMX object value + * @author Kiss Tibor + * + */ +public class JMXObject { + private String objectName; + private String name; + + /** + * return the JMX MBean name + * + * @return JMX MBean name + */ + public String getObjectName() { + return objectName; + } + + /** + * Set the JMX MBean name + * + * @param objectName + * JMX MBean name + */ + public void setObjectName(String objectName) { + this.objectName = objectName; + } + + /** + * return the name of the JMX method + * + * @return name of the JMX method + */ + public String getName() { + return name; + } + + /** + * set the name of the JMX method + * + * @param name + * name of the JMX method + */ + public void setName(String name) { + this.name = name; + } +} diff --git a/java/src/main/java/com/outlyer/jmx/jmxquery/object/JMXObjectTypeConverter.java b/java/src/main/java/com/outlyer/jmx/jmxquery/object/JMXObjectTypeConverter.java new file mode 100644 index 0000000..50d66d0 --- /dev/null +++ b/java/src/main/java/com/outlyer/jmx/jmxquery/object/JMXObjectTypeConverter.java @@ -0,0 +1,59 @@ +package com.outlyer.jmx.jmxquery.object; + +import java.util.HashMap; +import java.util.Map; + +/** + * This class is used to have general logic for type serialization and conversion + * @author Kiss Tibor + * + */ +public class JMXObjectTypeConverter { + + static final Map typeMap = new HashMap(); + static { + typeMap.put("String", String.class.getName()); + typeMap.put("Double", Double.class.getName()); + typeMap.put("Float", Float.class.getName()); + typeMap.put("Short", Short.class.getName()); + typeMap.put("Integer", Integer.class.getName()); + typeMap.put("Long", Long.class.getName()); + typeMap.put("Boolean", Boolean.class.getName()); + + typeMap.put("char", char.class.getName()); + typeMap.put("short", short.class.getName()); + typeMap.put("int", int.class.getName()); + typeMap.put("long", long.class.getName()); + typeMap.put("double", double.class.getName()); + typeMap.put("float", float.class.getName()); + typeMap.put("boolean", boolean.class.getName()); + typeMap.put("double", double.class.getName()); + + } + + /*** + * This methods convert a string to the specified type + * @param typeName the expected type name + * @param value the value as a text + * @return the object + */ + public final static Object toObject(final String typeName, final String value) { + if (Boolean.class.getName().endsWith(typeName)) + return Boolean.parseBoolean(value); + if (Byte.class.getName().endsWith(typeName)) + return Byte.parseByte(value); + if (Short.class.getName().endsWith(typeName)) + return Short.parseShort(value); + if (Integer.class.getName().endsWith(typeName)) + return Integer.parseInt(value); + if (Long.class.getName().endsWith(typeName)) + return Long.parseLong(value); + if (Float.class.getName().endsWith(typeName)) + return Float.parseFloat(value); + if (Double.class.getName().endsWith(typeName)) + return Double.parseDouble(value); + + return value; + } + +} diff --git a/java/src/main/java/com/outlyer/jmx/jmxquery/object/JMXParam.java b/java/src/main/java/com/outlyer/jmx/jmxquery/object/JMXParam.java new file mode 100644 index 0000000..a768e42 --- /dev/null +++ b/java/src/main/java/com/outlyer/jmx/jmxquery/object/JMXParam.java @@ -0,0 +1,62 @@ +package com.outlyer.jmx.jmxquery.object; + +/** +* +* JMXParam is used for pass parameters to a JMXMethod +* +* @author Tibor Kiss +* +*/ +public class JMXParam { + private Object value; + private String type; + + /** + * Default constructor + */ + public JMXParam() { + this(null, null); + } + + /** + * This constructor is used to create a parameter + * + * @param value + * is the actual value + * @param type + * is a basic type from: + * (String,Double,Float,Short,Integer,Long,Boolean,char,double,float,short,integer,long, + * boolean) + */ + public JMXParam(Object value, String type) { + setValue(value); + setType(type); + } + + /** + * returns the parameter value + * + * @return parameter value + */ + public Object getValue() { + return value; + } + + /** + * set the parameters value + * + * @param value + * parameters value + */ + public void setValue(Object value) { + this.value = value; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } +} diff --git a/java/src/main/resources/com/outlyer/jmx/jmxquery/HELP b/java/src/main/resources/com/outlyer/jmx/jmxquery/HELP index 3ed082d..008a741 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] [-call,c] [-incjvm] [-json] [-help] options are: @@ -20,6 +20,12 @@ options are: {attributeKey} is optional and only used for Composite metric types. Use semi-colon to separate metrics. +-call, c + Call a method using a base64 encoded JSON as parameter + JSON format: + {"objectName":"{mBeanName}","name":"{methodName}","params":[{"value":"{value}","type":"{type}"},...]} + Supported types: char, short, int, long, boolean, double, float, String, Float, Double, Short, Int, Long + -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 diff --git a/java/src/test/java/com/outlyer/jmx/jmxquery/app/tests/JMXTestApp.java b/java/src/test/java/com/outlyer/jmx/jmxquery/app/tests/JMXTestApp.java new file mode 100644 index 0000000..3ef3421 --- /dev/null +++ b/java/src/test/java/com/outlyer/jmx/jmxquery/app/tests/JMXTestApp.java @@ -0,0 +1,38 @@ +package com.outlyer.jmx.jmxquery.app.tests; + +import java.lang.management.ManagementFactory; + +import javax.management.InstanceAlreadyExistsException; +import javax.management.MBeanRegistrationException; +import javax.management.MBeanServer; +import javax.management.MalformedObjectNameException; +import javax.management.NotCompliantMBeanException; +import javax.management.ObjectName; + +public class JMXTestApp { + + public static void main(String[] args) { + + MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); + ObjectName name; + try { + name = new ObjectName("com.outlyer.jmx.jmxquery.app.tests:type=Test"); + Test mbean = new Test(); + mbs.registerMBean(mbean, name); + + System.out.println("Waiting forever..."); + Thread.sleep(Long.MAX_VALUE); + } catch (MalformedObjectNameException e) { + e.printStackTrace(); + } catch (InstanceAlreadyExistsException e) { + e.printStackTrace(); + } catch (MBeanRegistrationException e) { + e.printStackTrace(); + } catch (NotCompliantMBeanException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + +} diff --git a/java/src/test/java/com/outlyer/jmx/jmxquery/app/tests/Test.java b/java/src/test/java/com/outlyer/jmx/jmxquery/app/tests/Test.java new file mode 100644 index 0000000..630c302 --- /dev/null +++ b/java/src/test/java/com/outlyer/jmx/jmxquery/app/tests/Test.java @@ -0,0 +1,56 @@ +package com.outlyer.jmx.jmxquery.app.tests; + +public class Test implements TestMBean { + + private String names[] = new String[3]; + private Integer val; + + public void setNames(String[] names) { + this.names = names; + } + + public String[] getNames() { + names[0] = "Test1"; + names[2] = "Test3"; + return names; + } + + public void sayHello() { + System.out.println("Hello"); + + } + + public int add(int x, int y) { + return x + y; + } + + public String value() { + System.out.println("Hello"); + return "Hello"; + } + + public int valueInt() { + return 5; + } + + public String valueString(String str) { + return str; + } + + public void setVal(final int val) { + this.val = val; + } + + public String add(String x, String y) { + + return x + y; + } + + public Double add(Double x, int y) { + return x + y; + } + + public int getVal() { + return this.val; + } +} diff --git a/java/src/test/java/com/outlyer/jmx/jmxquery/app/tests/TestMBean.java b/java/src/test/java/com/outlyer/jmx/jmxquery/app/tests/TestMBean.java new file mode 100644 index 0000000..8b91fdb --- /dev/null +++ b/java/src/test/java/com/outlyer/jmx/jmxquery/app/tests/TestMBean.java @@ -0,0 +1,19 @@ +package com.outlyer.jmx.jmxquery.app.tests; + +public interface TestMBean { + + public void sayHello(); + public int add(int x, int y); + public String add(String x, String y); + public Double add(Double x, int y); + + public String value(); + + public int valueInt(); + + public void setVal(final int val); + public int getVal(); + + public String valueString(String str); + String[] getNames(); +} \ No newline at end of file diff --git a/java/src/test/java/com/outlyer/jmx/jmxquery/tests/JMXQueryTest.java b/java/src/test/java/com/outlyer/jmx/jmxquery/tests/JMXQueryTest.java index f08313b..ba6e3be 100644 --- a/java/src/test/java/com/outlyer/jmx/jmxquery/tests/JMXQueryTest.java +++ b/java/src/test/java/com/outlyer/jmx/jmxquery/tests/JMXQueryTest.java @@ -1,14 +1,20 @@ package com.outlyer.jmx.jmxquery.tests; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import com.outlyer.jmx.jmxquery.JMXQuery; +import com.outlyer.jmx.jmxquery.object.JMXAttribute; +import com.outlyer.jmx.jmxquery.object.JMXMethod; +import com.outlyer.jmx.jmxquery.object.JMXObject; +import com.outlyer.jmx.jmxquery.object.JMXParam; import com.outlyer.jmx.jmxquery.tools.JMXTools; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; -import static org.junit.Assert.*; +import java.util.Base64; /** * Runs set of tests for JMXQuery command line tool * @@ -35,6 +41,20 @@ public void setUp() { public void tearDown() { } + @Test + public void testArrayAttribute() throws Exception { + JMXQuery.main(new String[] { "-url", "service:jmx:rmi://localhost:12345/jndi/rmi://localhost:12345/jmxrmi", + "-q", "com.outlyer.jmx.jmxquery.app.tests:type=Test/Names" }); + + } + + @Test + public void testArrayAttributeJSON() throws Exception { + JMXQuery.main(new String[] { "-url", "service:jmx:rmi://localhost:12345/jndi/rmi://localhost:12345/jmxrmi", + "-q", "-json", "com.outlyer.jmx.jmxquery.app.tests:type=Test/Names" }); + + } + @Test public void testHelpPage() throws Exception { //JMXQuery.main(new String[]{"-help"}); @@ -79,4 +99,102 @@ public void testListMBeans() throws Exception { JMXQuery.main(new String[]{"-url", url, "-list", "mbeans", "*:*"}); } + + @Test + public void testInvokeSayHello() throws Exception { + JMXMethod method = new JMXMethod(); + method.setName("sayHello"); + method.setObjectName("com.outlyer.jmx.jmxquery.app.tests:type=Test"); + + invokeMethod(method); + } + + @Test + public void testInvokeValue() throws Exception { + JMXMethod method = new JMXMethod(); + method.setName("value"); + method.setObjectName("com.outlyer.jmx.jmxquery.app.tests:type=Test"); + + invokeMethod(method); + } + + @Test + public void testInvokeGetString() throws Exception { + JMXMethod method = new JMXMethod(); + method.setName("valueString"); + method.setObjectName("com.outlyer.jmx.jmxquery.app.tests:type=Test"); + method.addParam("String\r\nok; hello test", "String"); + + invokeMethod(method); + } + + @Test + public void testInvokeAddIntInt() throws Exception { + JMXMethod method = new JMXMethod(); + method.setName("add"); + method.setObjectName("com.outlyer.jmx.jmxquery.app.tests:type=Test"); + method.addParam(23, "int"); + method.addParam(25, "int"); + + invokeMethod(method); + } + + @Test + public void testInvokeAddDoubleInt() throws Exception { + JMXMethod method = new JMXMethod(); + method.setName("add"); + method.setObjectName("com.outlyer.jmx.jmxquery.app.tests:type=Test"); + method.addParam(23.3, "Double"); + method.addParam(25, "int"); + + invokeMethod(method); + } + + @Test + public void testSetIntegerValue() throws Exception { + JMXAttribute attribute = new JMXAttribute(); + attribute.setName("Val"); + attribute.setObjectName("com.outlyer.jmx.jmxquery.app.tests:type=Test"); + attribute.setValue(new JMXParam(23, "Integer")); + invokeSetter(attribute); + JMXQuery.main(new String[] { "-url", "service:jmx:rmi://localhost:12345/jndi/rmi://localhost:12345/jmxrmi", + "-q", "com.outlyer.jmx.jmxquery.app.tests:type=Test/Val" }); + } + + @Test + public void testInvokeAddStringString() throws Exception { + JMXMethod method = new JMXMethod(); + method.setName("add"); + method.setObjectName("com.outlyer.jmx.jmxquery.app.tests:type=Test"); + method.addParam("Test ", "String"); + method.addParam("test", "String"); + + invokeMethod(method); + } + + private static final String serialze(JMXObject object) { + ObjectMapper objectMapper = new ObjectMapper(); + String originalInput = null; + try { + originalInput = objectMapper.writeValueAsString(object); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + System.out.println(originalInput); + String encodedString = Base64.getEncoder().encodeToString(originalInput.getBytes()); + System.out.println(encodedString); + return encodedString; + } + + private static void invokeMethod(JMXMethod method) throws Exception { + String encodedString = serialze(method); + JMXQuery.main(new String[] { "-url", "service:jmx:rmi://localhost:12345/jndi/rmi://localhost:12345/jmxrmi", + "-c", encodedString }); + } + + private static void invokeSetter(JMXAttribute attribute) throws Exception { + String encodedString = serialze(attribute); + JMXQuery.main(new String[] { "-url", "service:jmx:rmi://localhost:12345/jndi/rmi://localhost:12345/jmxrmi", + "-s", encodedString }); + } } \ No newline at end of file diff --git a/python/jmxquery/JMXQuery-0.1.8.jar b/python/jmxquery/JMXQuery-0.1.8.jar index af2bf0c..ce8628c 100644 Binary files a/python/jmxquery/JMXQuery-0.1.8.jar and b/python/jmxquery/JMXQuery-0.1.8.jar differ diff --git a/python/jmxquery/__init__.py b/python/jmxquery/__init__.py index 410b961..459705a 100644 --- a/python/jmxquery/__init__.py +++ b/python/jmxquery/__init__.py @@ -11,6 +11,7 @@ from typing import List from enum import Enum import logging +import base64 # Full Path to Jar JAR_PATH = os.path.dirname(os.path.realpath(__file__)) + '/JMXQuery-0.1.8.jar' @@ -110,6 +111,59 @@ def to_string(self): return string +class JMXParam: + """ + A JMX Parameter for JMXMethod. + """ + + def __init__(self, + value: str="", + value_type: str = "String"): + """ + This method creates a JMXMethod param + :param value: + :param value_type: + """ + self.value = value + self.value_type = value_type + +class JMXMethod: + """ + A JMX Method which is used to invoke a specific MBean method from the JVM. + """ + + def __init__(self, + m_bean_name: str, + method_name: str = None, + params: List[JMXParam] = []): + """ + Creates instance of JMXMethod + + :param m_bean_name: The JMX MBean name. E.g. + :param method_name: JMX method name + :param params: a list of JMXParam parameters + """ + self.mBeanName = m_bean_name + self.methodName = method_name + self.params = params + + def json(self): + """ + this method return the json serialization of this object + :return: json string + """ + json_param = ",".join(["{\"value\":\""+param.value+"\", type:\""+param.value_type+"\"}" for param in self.params]) + return "{\"objectName\":\""+self.mBeanName+"\",\"name\":\""+self.methodName+"\",\"params\":["+json_param+"]}" + + def base64json(self): + """ + return the base64 encoded string of the json serialization of this object + :return: base64 json serialization of JMXMethod + """ + json_result = self.json() + json_result = json_result.encode('ascii') + return base64.b64encode(json_result).decode("utf-8") + class JMXConnection(object): """ The main class that connects to the JMX endpoint via a local JAR to run queries @@ -132,7 +186,7 @@ def __init__(self, connection_uri: str, jmx_username: str = None, jmx_password: if java_path != None: self.java_path = java_path - def __run_jar(self, queries: List[JMXQuery], timeout) -> List[JMXQuery]: + def __run_query(self, queries: List[JMXQuery], timeout) -> List[JMXQuery]: """ Run the JAR and return the results @@ -170,6 +224,39 @@ def __run_jar(self, queries: List[JMXQuery], timeout) -> List[JMXQuery]: metrics = self.__load_from_json(jsonOutput) return metrics + def __run_method(self, method: List[JMXMethod], timeout) -> str: + """ + Run the JAR and return the results + + :param query: The query + :return: The full command array to run via subprocess + """ + + command = [self.java_path, '-jar', JAR_PATH, '-url', self.connection_uri] + if self.jmx_username: + command.extend(["-u", self.jmx_username, "-p", self.jmx_password]) + + command.extend(["-c", method.base64json()]) + logger.debug("Running command: " + str(command)) + + output = "" + try: + output = subprocess.run(command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + timeout=timeout, + check=True) + + output = output.stdout.decode('utf-8') + except subprocess.TimeoutExpired as err: + logger.error("Error calling JMX, Timeout of " + str(err.timeout) + " Expired: " + err.output.decode('utf-8')) + except subprocess.CalledProcessError as err: + logger.error("Error calling JMX: " + err.output.decode('utf-8')) + raise err + + logger.debug("Output received: " + output) + return output + def __load_from_json(self, jsonOutput: str) -> List[JMXQuery]: """ Loads the list of returned metrics from JSON response @@ -206,4 +293,13 @@ def query(self, queries: List[JMXQuery], timeout=DEFAULT_JAR_TIMEOUT) -> List[JM :param queries: A list of JMXQuerys to query the JVM for :return: A list of JMXQuerys found in the JVM with their current values """ - return self.__run_jar(queries, timeout) + return self.__run_query(queries, timeout) + + def call(self, method: JMXMethod, timeout=DEFAULT_JAR_TIMEOUT) -> str: + """ + Run a list of JMX Queries against the JVM and get the results + + :param method: A JMXMethod to be executed on the JVM + :return: A string represents the result of the method + """ + return self.__run_method(method, timeout) \ No newline at end of file