diff --git a/driver-jmx/pom.xml b/driver-jmx/pom.xml
new file mode 100644
index 000000000..368a4994f
--- /dev/null
+++ b/driver-jmx/pom.xml
@@ -0,0 +1,44 @@
+
+ 4.0.0
+
+
+
+ mvn-defaults
+ io.nosqlbench
+ 3.12.129-SNAPSHOT
+ ../mvn-defaults
+
+
+ driver-jmx
+ jar
+ ${project.artifactId}
+
+ A JMX nosqlbench ActivityType (AT) driver module;
+ This provides the ability to query system via JMX
+
+
+
+
+
+ io.nosqlbench
+ engine-api
+ 3.12.129-SNAPSHOT
+
+
+
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+
+ junit
+ junit
+ test
+
+
+
+
+
diff --git a/driver-jmx/src/main/java/io/nosqlbench/driver/jmx/JMXAction.java b/driver-jmx/src/main/java/io/nosqlbench/driver/jmx/JMXAction.java
new file mode 100644
index 000000000..fdd6d0010
--- /dev/null
+++ b/driver-jmx/src/main/java/io/nosqlbench/driver/jmx/JMXAction.java
@@ -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 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;
+ }
+}
diff --git a/driver-jmx/src/main/java/io/nosqlbench/driver/jmx/JMXActionDispenser.java b/driver-jmx/src/main/java/io/nosqlbench/driver/jmx/JMXActionDispenser.java
new file mode 100644
index 000000000..d0a2c6ac5
--- /dev/null
+++ b/driver-jmx/src/main/java/io/nosqlbench/driver/jmx/JMXActionDispenser.java
@@ -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);
+ }
+}
diff --git a/driver-jmx/src/main/java/io/nosqlbench/driver/jmx/JMXActivity.java b/driver-jmx/src/main/java/io/nosqlbench/driver/jmx/JMXActivity.java
new file mode 100644
index 000000000..7a039ece4
--- /dev/null
+++ b/driver-jmx/src/main/java/io/nosqlbench/driver/jmx/JMXActivity.java
@@ -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 sequence;
+
+ public JMXActivity(ActivityDef activityDef) {
+ super(activityDef);
+ }
+
+ @Override
+ public void initActivity() {
+ super.initActivity();
+ this.sequence = createOpSequenceFromCommands(ReadyJmxOp::new);
+ }
+
+ public OpSequence getSequencer() {
+ return sequence;
+ }
+}
diff --git a/driver-jmx/src/main/java/io/nosqlbench/driver/jmx/JMXActivityType.java b/driver-jmx/src/main/java/io/nosqlbench/driver/jmx/JMXActivityType.java
new file mode 100644
index 000000000..5f29622f3
--- /dev/null
+++ b/driver-jmx/src/main/java/io/nosqlbench/driver/jmx/JMXActivityType.java
@@ -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 {
+ @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);
+ }
+}
diff --git a/driver-jmx/src/main/java/io/nosqlbench/driver/jmx/ReadyJmxOp.java b/driver-jmx/src/main/java/io/nosqlbench/driver/jmx/ReadyJmxOp.java
new file mode 100644
index 000000000..6edde0e8b
--- /dev/null
+++ b/driver-jmx/src/main/java/io/nosqlbench/driver/jmx/ReadyJmxOp.java
@@ -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 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 cmdmap) {
+
+ JMXConnector connector = null;
+ try {
+ JMXServiceURL url = bindJMXServiceURL(cmdmap);
+ connector = JMXConnectorFactory.connect(url);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return connector;
+ }
+
+ private JMXServiceURL bindJMXServiceURL(Map 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;
+ }
+}
diff --git a/driver-jmx/src/main/java/io/nosqlbench/driver/jmx/ValueConverter.java b/driver-jmx/src/main/java/io/nosqlbench/driver/jmx/ValueConverter.java
new file mode 100644
index 000000000..ee841c594
--- /dev/null
+++ b/driver-jmx/src/main/java/io/nosqlbench/driver/jmx/ValueConverter.java
@@ -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);
+ }
+ }
+
+}
diff --git a/driver-jmx/src/main/java/io/nosqlbench/driver/jmx/formats/MBeanInfoConsoleFormat.java b/driver-jmx/src/main/java/io/nosqlbench/driver/jmx/formats/MBeanInfoConsoleFormat.java
new file mode 100644
index 000000000..c38c92cc2
--- /dev/null
+++ b/driver-jmx/src/main/java/io/nosqlbench/driver/jmx/formats/MBeanInfoConsoleFormat.java
@@ -0,0 +1,119 @@
+package io.nosqlbench.driver.jmx.formats;
+
+import javax.management.*;
+import java.util.Map;
+
+public class MBeanInfoConsoleFormat {
+
+ private static Map 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();
+
+ }
+}
diff --git a/driver-jmx/src/main/java/io/nosqlbench/driver/jmx/ops/JMXExplainOperation.java b/driver-jmx/src/main/java/io/nosqlbench/driver/jmx/ops/JMXExplainOperation.java
new file mode 100644
index 000000000..8b2023fef
--- /dev/null
+++ b/driver-jmx/src/main/java/io/nosqlbench/driver/jmx/ops/JMXExplainOperation.java
@@ -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);
+ }
+ }
+}
diff --git a/driver-jmx/src/main/java/io/nosqlbench/driver/jmx/ops/JMXReadOperation.java b/driver-jmx/src/main/java/io/nosqlbench/driver/jmx/ops/JMXReadOperation.java
new file mode 100644
index 000000000..9fb6df634
--- /dev/null
+++ b/driver-jmx/src/main/java/io/nosqlbench/driver/jmx/ops/JMXReadOperation.java
@@ -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);
+ }
+
+ }
+
+}
diff --git a/driver-jmx/src/main/java/io/nosqlbench/driver/jmx/ops/JmxOp.java b/driver-jmx/src/main/java/io/nosqlbench/driver/jmx/ops/JmxOp.java
new file mode 100644
index 000000000..da87578a0
--- /dev/null
+++ b/driver-jmx/src/main/java/io/nosqlbench/driver/jmx/ops/JmxOp.java
@@ -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();
+}
diff --git a/driver-jmx/src/main/resources/jmx.md b/driver-jmx/src/main/resources/jmx.md
new file mode 100644
index 000000000..c5db06e0d
--- /dev/null
+++ b/driver-jmx/src/main/resources/jmx.md
@@ -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.
\ No newline at end of file
diff --git a/driver-jmx/src/test/java/io/nosqlbench/driver/jmx/ValueConverterTest.java b/driver-jmx/src/test/java/io/nosqlbench/driver/jmx/ValueConverterTest.java
new file mode 100644
index 000000000..ac47b205e
--- /dev/null
+++ b/driver-jmx/src/test/java/io/nosqlbench/driver/jmx/ValueConverterTest.java
@@ -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);
+ }
+
+}
\ No newline at end of file
diff --git a/driver-jmx/src/test/resources/activities/jmx-test-1.yaml b/driver-jmx/src/test/resources/activities/jmx-test-1.yaml
new file mode 100644
index 000000000..1fe50283c
--- /dev/null
+++ b/driver-jmx/src/test/resources/activities/jmx-test-1.yaml
@@ -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
+
+
diff --git a/driver-jmx/src/test/resources/activities/jmx-test-2.yaml b/driver-jmx/src/test/resources/activities/jmx-test-2.yaml
new file mode 100644
index 000000000..b46c5232f
--- /dev/null
+++ b/driver-jmx/src/test/resources/activities/jmx-test-2.yaml
@@ -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
+
+
diff --git a/driver-jmx/src/test/resources/activities/jmx-test-3.yaml b/driver-jmx/src/test/resources/activities/jmx-test-3.yaml
new file mode 100644
index 000000000..96860076f
--- /dev/null
+++ b/driver-jmx/src/test/resources/activities/jmx-test-3.yaml
@@ -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
diff --git a/nb/pom.xml b/nb/pom.xml
index 65ed021f6..14fb46c75 100644
--- a/nb/pom.xml
+++ b/nb/pom.xml
@@ -86,6 +86,12 @@
3.12.129-SNAPSHOT
+
+ io.nosqlbench
+ driver-jmx
+ 3.12.129-SNAPSHOT
+
+
io.nosqlbench
driver-cql-shaded
diff --git a/pom.xml b/pom.xml
index d0511bd04..a6e40789b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -45,6 +45,7 @@
driver-web
driver-kafka
driver-mongodb
+ driver-jmx