add initial JMX driver implementation

This commit is contained in:
Jonathan Shook 2020-07-15 08:42:47 -05:00
parent fff55212da
commit 0b3d30caf0
18 changed files with 648 additions and 0 deletions

44
driver-jmx/pom.xml Normal file
View File

@ -0,0 +1,44 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>mvn-defaults</artifactId>
<groupId>io.nosqlbench</groupId>
<version>3.12.129-SNAPSHOT</version>
<relativePath>../mvn-defaults</relativePath>
</parent>
<artifactId>driver-jmx</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>
A JMX nosqlbench ActivityType (AT) driver module;
This provides the ability to query system via JMX
</description>
<dependencies>
<dependency>
<groupId>io.nosqlbench</groupId>
<artifactId>engine-api</artifactId>
<version>3.12.129-SNAPSHOT</version>
</dependency>
<!-- test scope only -->
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,37 @@
package io.nosqlbench.driver.jmx;
import io.nosqlbench.driver.jmx.ops.JmxOp;
import io.nosqlbench.engine.api.activityapi.core.SyncAction;
import io.nosqlbench.engine.api.activityapi.planning.OpSequence;
import io.nosqlbench.engine.api.activityimpl.ActivityDef;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class JMXAction implements SyncAction {
private final static Logger logger = LoggerFactory.getLogger(JMXAction.class);
private final ActivityDef activityDef;
private final int slot;
private final JMXActivity activity;
private OpSequence<ReadyJmxOp> sequencer;
public JMXAction(ActivityDef activityDef, int slot, JMXActivity activity) {
this.activityDef = activityDef;
this.slot = slot;
this.activity = activity;
}
@Override
public void init() {
this.sequencer = activity.getSequencer();
}
@Override
public int runCycle(long value) {
ReadyJmxOp readyJmxOp = sequencer.get(value);
JmxOp jmxOp = readyJmxOp.bind(value);
jmxOp.execute();
return 0;
}
}

View File

@ -0,0 +1,17 @@
package io.nosqlbench.driver.jmx;
import io.nosqlbench.engine.api.activityapi.core.Action;
import io.nosqlbench.engine.api.activityapi.core.ActionDispenser;
public class JMXActionDispenser implements ActionDispenser {
private JMXActivity activity;
public JMXActionDispenser(JMXActivity activity) {
this.activity = activity;
}
@Override
public Action getAction(int slot) {
return new JMXAction(activity.getActivityDef(),slot,activity);
}
}

View File

@ -0,0 +1,25 @@
package io.nosqlbench.driver.jmx;
import io.nosqlbench.engine.api.activityapi.core.Activity;
import io.nosqlbench.engine.api.activityapi.planning.OpSequence;
import io.nosqlbench.engine.api.activityimpl.ActivityDef;
import io.nosqlbench.engine.api.activityimpl.SimpleActivity;
public class JMXActivity extends SimpleActivity implements Activity {
private OpSequence<ReadyJmxOp> sequence;
public JMXActivity(ActivityDef activityDef) {
super(activityDef);
}
@Override
public void initActivity() {
super.initActivity();
this.sequence = createOpSequenceFromCommands(ReadyJmxOp::new);
}
public OpSequence<ReadyJmxOp> getSequencer() {
return sequence;
}
}

View File

@ -0,0 +1,24 @@
package io.nosqlbench.driver.jmx;
import io.nosqlbench.engine.api.activityapi.core.ActionDispenser;
import io.nosqlbench.engine.api.activityapi.core.ActivityType;
import io.nosqlbench.engine.api.activityimpl.ActivityDef;
import io.nosqlbench.nb.annotations.Service;
@Service(ActivityType.class)
public class JMXActivityType implements ActivityType<JMXActivity> {
@Override
public String getName() {
return "jmx";
}
@Override
public JMXActivity getActivity(ActivityDef activityDef) {
return new JMXActivity(activityDef);
}
@Override
public ActionDispenser getActionDispenser(JMXActivity activity) {
return new JMXActionDispenser(activity);
}
}

View File

@ -0,0 +1,87 @@
package io.nosqlbench.driver.jmx;
import io.nosqlbench.driver.jmx.ops.JMXExplainOperation;
import io.nosqlbench.driver.jmx.ops.JMXReadOperation;
import io.nosqlbench.driver.jmx.ops.JmxOp;
import io.nosqlbench.engine.api.templating.CommandTemplate;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.Map;
import java.util.Optional;
public class ReadyJmxOp {
private final CommandTemplate command;
public ReadyJmxOp(CommandTemplate command) {
this.command = command;
}
public JmxOp bind(long value) {
Map<String, String> cmdmap = command.getCommand(value);
JMXConnector connector = bindConnector(cmdmap);
if (!cmdmap.containsKey("object")) {
throw new RuntimeException("You must specify an object in a jmx operation as in object=...");
}
ObjectName objectName = null;
try {
String object = cmdmap.get("object");
if (object==null) {
throw new RuntimeException("You must specify an object name for any JMX operation.");
}
objectName = new ObjectName(object);
} catch (MalformedObjectNameException e) {
e.printStackTrace();
}
if (cmdmap.containsKey("readvar")) {
return new JMXReadOperation(connector, objectName, cmdmap.get("readvar"), cmdmap.get("as_type"),cmdmap.get("as_name"));
} else if (cmdmap.containsKey("explain")) {
return new JMXExplainOperation(connector,objectName);
}
throw new RuntimeException("No valid form of JMX operation was determined from the provided command details:" + cmdmap.toString());
}
private JMXConnector bindConnector(Map<String, String> cmdmap) {
JMXConnector connector = null;
try {
JMXServiceURL url = bindJMXServiceURL(cmdmap);
connector = JMXConnectorFactory.connect(url);
} catch (IOException e) {
e.printStackTrace();
}
return connector;
}
private JMXServiceURL bindJMXServiceURL(Map<String, String> cmdmap) {
JMXServiceURL url = null;
try {
if (cmdmap.containsKey("url")) {
url = new JMXServiceURL(cmdmap.get("url"));
} else {
if (cmdmap.containsKey("host")) {
throw new RuntimeException("You must provide at least a host if you do not provide a url.");
}
String protocol = cmdmap.get("protocol");
String host = cmdmap.get("host");
int port = Optional.ofNullable(cmdmap.get("port")).map(Integer::parseInt).orElse(0);
String path = cmdmap.get("path");
url = new JMXServiceURL(protocol, host, port, path);
}
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
return url;
}
}

View File

@ -0,0 +1,77 @@
package io.nosqlbench.driver.jmx;
import java.util.function.Function;
public class ValueConverter {
public static Object convert(String typeName, Object o) {
if (!typeName.contains(".")) {
if (Number.class.isAssignableFrom(o.getClass())) {
switch (typeName) {
case "String":
return o.toString();
case "long":
case "Long":
return (((Number) o).longValue());
case "int":
case "Integer":
return (((Number) o).intValue());
case "double":
case "Double":
return (((Number) o).doubleValue());
case "float":
case "Float":
return (((Number) o).floatValue());
case "short":
case "Short":
return (((Number) o).shortValue());
case "byte":
case "Byte":
return (((Number) o).byteValue());
default:
throw new RuntimeException("For numeric values, you can only convert to long,int,double,float,byte,short or String");
}
} else {
String value;
if (CharSequence.class.isAssignableFrom(o.getClass())) {
value = (String) o;
} else {
value = o.toString();
}
switch (typeName) {
case "String":
return value;
case "long":
case "Long":
return Long.parseLong(value);
case "int":
case "Integer":
return Integer.parseInt(value);
case "double":
case "Double":
return Double.parseDouble(value);
case "float":
case "Float":
return Float.parseFloat(value);
case "short":
case "Short":
return Short.parseShort(value);
case "byte":
case "Byte":
return Byte.parseByte(value);
default:
throw new RuntimeException("For String values, you can only convert to long, int, double, float, short, byte, or String");
}
}
}
try {
Class<?> aClass = Class.forName(typeName);
return aClass.cast(o);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,119 @@
package io.nosqlbench.driver.jmx.formats;
import javax.management.*;
import java.util.Map;
public class MBeanInfoConsoleFormat {
private static Map<Integer,String> MbeanOpImpacts = Map.of(
MBeanOperationInfo.ACTION,"ACTION",
MBeanOperationInfo.ACTION_INFO,"ACTION_INFO",
MBeanOperationInfo.UNKNOWN,"UNKNOWN",
MBeanOperationInfo.INFO,"INFO");
// Not including Descriptors here
public static String formatAsText(MBeanInfo info, ObjectName objectName) {
StringBuilder sb = new StringBuilder();
sb.append("### MBeanInfo for '").append(objectName).append("'\n");
String className = info.getClassName();
sb.append("# classname: ").append(className).append("\n");
String description = info.getDescription();
sb.append("# ").append(description).append("\n");
MBeanConstructorInfo[] constructors = info.getConstructors();
if (constructors.length > 0) {
sb.append("## constructors:\n");
for (MBeanConstructorInfo constructor : constructors) {
String ctorDesc = constructor.getDescription();
sb.append("# ").append(ctorDesc).append("\n");
String name = constructor.getName();
sb.append("# ").append(name).append("(");
sb.append(pramDetail(constructor.getSignature(), " "));
sb.append(" )\n");
// sb.append(" [").append(descriptorDetail(constructor.getDescriptor())).append("]\n");
}
}
MBeanAttributeInfo[] attributes = info.getAttributes();
if (attributes.length > 0) {
sb.append("## attributes:\n");
for (MBeanAttributeInfo attribute : attributes) {
String attrDesc = attribute.getDescription();
String attrName = attribute.getName();
String attrType = attribute.getType();
sb.append("# ").append(attrDesc).append("\n");
sb.append("- '").append(attrName).append("' type=").append(attrType);
sb.append("readable=").append(attribute.isReadable()).append(" writable=").append(attribute.isWritable()).append(" is_is=").append(attribute.isIs());
sb.append("\n");
// sb.append(" [").append(descriptorDetail(attribute.getDescriptor())).append("]\n");
}
}
MBeanNotificationInfo[] notifications = info.getNotifications();
if (notifications.length > 0) {
sb.append("## notifications:\n");
for (MBeanNotificationInfo notification : notifications) {
String notifName = notification.getName();
String notifDesc = notification.getDescription();
String[] notifTypes = notification.getNotifTypes();
Class<? extends MBeanNotificationInfo> notifClass = notification.getClass();
sb.append("# ").append(notifDesc).append("\n");
sb.append("- ").append(notifName).append(" [").append(descriptorDetail(notification.getDescriptor())).append("]\n");
if (notifTypes.length > 0) {
for (String notifType : notifTypes) {
sb.append(" - ").append(notifType).append("\n");
}
}
}
}
MBeanOperationInfo[] operations = info.getOperations();
if (operations.length > 0) {
sb.append("## operations:\n");
for (MBeanOperationInfo operation : operations) {
String opDesc = operation.getDescription();
String opName = operation.getName();
MBeanParameterInfo[] opSig = operation.getSignature();
Class<? extends MBeanOperationInfo> opClass = operation.getClass();
sb.append("# ").append(opDesc).append("\n");
sb.append("- ").append(opName).append("(");
sb.append(pramDetail(operation.getSignature(), " "));
sb.append(") -> ").append(operation.getReturnType());
sb.append(" impact=").append(MbeanOpImpacts.get(operation.getImpact())).append("\n");
}
}
return sb.toString();
}
private static String descriptorDetail(Descriptor descriptor) {
StringBuilder sb = new StringBuilder();
sb.append("valid=").append(descriptor.isValid());
String[] fieldNames = descriptor.getFieldNames();
for (String field : fieldNames) {
sb.append(" ").append(field).append("=").append(descriptor.getFieldValue(field));
}
return sb.toString();
}
private static String pramDetail(MBeanParameterInfo[] signature, String prefix) {
StringBuilder sb = new StringBuilder();
for (MBeanParameterInfo paramInfo : signature) {
String desc = paramInfo.getDescription();
if (desc != null) {
sb.append(prefix).append(" # ").append(desc).append("\n");
}
sb.append(prefix).append(" - ").append(paramInfo.getName()).append("\n");
}
return sb.toString();
}
}

View File

@ -0,0 +1,25 @@
package io.nosqlbench.driver.jmx.ops;
import io.nosqlbench.driver.jmx.formats.MBeanInfoConsoleFormat;
import javax.management.*;
import javax.management.remote.JMXConnector;
import java.io.IOException;
public class JMXExplainOperation extends JmxOp {
public JMXExplainOperation(JMXConnector connector, ObjectName objectName) {
super(connector,objectName);
}
@Override
public void execute() {
MBeanServerConnection bean = getMBeanConnection();
try {
MBeanInfo info = bean.getMBeanInfo(objectName);
String mbeanInfoText = MBeanInfoConsoleFormat.formatAsText(info, objectName);
System.out.println(mbeanInfoText);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,42 @@
package io.nosqlbench.driver.jmx.ops;
import io.nosqlbench.driver.jmx.ValueConverter;
import io.nosqlbench.virtdata.library.basics.core.threadstate.SharedState;
import org.apache.commons.math4.analysis.function.Exp;
import javax.management.*;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXServiceURL;
import java.io.IOException;
public class JMXReadOperation extends JmxOp {
private final String attribute;
private final String asType;
private final String asName;
public JMXReadOperation(JMXConnector connector, ObjectName objectName, String attribute, String asType, String asName) {
super(connector, objectName);
this.attribute = attribute;
this.asType = asType;
this.asName = asName;
}
@Override
public void execute() {
try {
Object value = getMBeanConnection().getAttribute(objectName, this.attribute);
logger.trace("read attribute '" + value +"': " + value);
if (asType!=null) {
value = ValueConverter.convert(asType,value);
}
String storedName = (asName==null) ? attribute : asName;
SharedState.tl_ObjectMap.get().put(storedName,value);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,36 @@
package io.nosqlbench.driver.jmx.ops;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
/**
* All JMX Operations should built on this base type.
*/
public abstract class JmxOp {
protected final static Logger logger = LoggerFactory.getLogger(JmxOp.class);
protected JMXConnector connector;
protected ObjectName objectName;
public JmxOp(JMXConnector connector, ObjectName objectName) {
this.connector = connector;
this.objectName = objectName;
}
public MBeanServerConnection getMBeanConnection() {
MBeanServerConnection connection = null;
try {
connection = connector.getMBeanServerConnection();
return connection;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public abstract void execute();
}

View File

@ -0,0 +1,71 @@
# JMX Driver
The JMX Driver allows you to do various JMX operations against a JMX service URL
and object name.
You must specify the service URL and object name in the op template. Alternately, you can specify
the protocol, host, port and path. Each cycle, the full JMX operation is derived from the
op template, and executed.
In the first version of this driver, only reads are supported.
# Connection Options
JMX transports can be configured in a myriad of ways. The options below allow you to add
connection options such as SSL and authentication.
- **ssl** - Use SSL settings provided. Thes SSL settings are from the NoSQLBench standard
SSL support
# Example Operations
## readvar
The readvar operation is used to read a named attribute of the named object and store it in the
thread local variable map.
```
statements:
- read1:
url: service:jmx:rmi:///jndi/rmi://dsehost:7199/jmxrmi
object: org.apache.cassandra.metrics:type=Compaction,name=PendingTasks
readvar: Value
as_type: int
as_name: pending_tasks
```
The `as_type` and `as_name` are optional, and if provided will set the name and data type used in
the thread local variable map.
- *as_type* can be any of long, int, double, float, byte, short, or String. If the original type
is convertable to a number, then it will be converted to a number and then to the desired type. If it
is not, it will be converted to String form first and then to the desired type. If the type name
contains dots, as in a fully-qualified class name, then direct class casting will be used if the
types are compatible.
A combined format is available if you don't want to put every command property on a separate line.
In this format, the first entry in the command map is taken as the command name and a set of key=value
command arguments. It is semantically equivalent to the above example, only more compact.
```
statements:
- read1: readvar=Value as_type=int as_name=pending_tasks
url: service:jmx:rmi:///jndi/rmi://dsehost:7199/jmxrmi
object: org.apache.cassandra.metrics:type=Compaction,name=PendingTasks
```
## explain
If you want to see the details about a managed object, you can use the explain command:
```
statements:
- explain1:
url: service:jmx:rmi:///jndi/rmi://dsehost:7199/jmxrmi
object: org.apache.cassandra.metrics:type=Compaction,name=PendingTasks
explain: object
```
This will use the MBeanInfo to interrogate the named management bean and provide a summary
of it's available attriburtes, operations, notifications, and constructors to stdout.
This is not meant for bulk testing, but more for explaining and documenting JMX beans.

View File

@ -0,0 +1,15 @@
package io.nosqlbench.driver.jmx;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class ValueConverterTest {
@Test
public void testConvertStringDouble() {
String s = "3";
double d = (double) ValueConverter.convert("double", s);
}
}

View File

@ -0,0 +1,10 @@
# (src/test/resources/activities/) jmx-test-1.yaml
statements:
- read1:
url: service:jmx:rmi:///jndi/rmi://dsehost:7199/jmxrmi
object: org.apache.cassandra.metrics:type=Compaction,name=PendingTasks
readvar: Value
as_type: int
as_name: pending_tasks

View File

@ -0,0 +1,8 @@
# (src/test/resources/activities/) jmx-test-2.yaml
statements:
- explain1:
url: service:jmx:rmi:///jndi/rmi://dsehost:7199/jmxrmi
object: org.apache.cassandra.metrics:type=Compaction,name=PendingTasks
explain: object

View File

@ -0,0 +1,4 @@
statements:
- read1: readvar=Value as_type=int as_name=pending_tasks
url: service:jmx:rmi:///jndi/rmi://dsehost:7199/jmxrmi
object: org.apache.cassandra.metrics:type=Compaction,name=PendingTasks

View File

@ -86,6 +86,12 @@
<version>3.12.129-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.nosqlbench</groupId>
<artifactId>driver-jmx</artifactId>
<version>3.12.129-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.nosqlbench</groupId>
<artifactId>driver-cql-shaded</artifactId>

View File

@ -45,6 +45,7 @@
<module>driver-web</module>
<module>driver-kafka</module>
<module>driver-mongodb</module>
<module>driver-jmx</module>
<!-- VIRTDATA MODULES -->