diff --git a/README.md b/README.md
index fe9f184..3eaccb2 100644
--- a/README.md
+++ b/README.md
@@ -27,19 +27,17 @@ Under tab "Available Plugins", select "SSHMon Samples Collector", then click "Ap
### Via Package from [JMeter-Plugins.org](https://jmeter-plugins.org/)
-Extract the [zip package](https://jmeter-plugins.org/files/packages/tilln-sshmon-1.0.zip) into JMeter's lib directory, then restart JMeter.
+Extract the [zip package](https://jmeter-plugins.org/files/packages/tilln-sshmon-1.1.zip) into JMeter's lib directory, then restart JMeter.
### Via Manual Download
-1. Copy the [jmeter-sshmon jar file](https://github.com/tilln/jmeter-sshmon/releases/download/1.0/jmeter-sshmon-1.0.jar) into JMeter's lib/ext directory.
+1. Copy the [jmeter-sshmon jar file](https://github.com/tilln/jmeter-sshmon/releases/download/1.1/jmeter-sshmon-1.1.jar) into JMeter's lib/ext directory.
2. Copy the following dependencies into JMeter's lib directory:
- * [kg.apc / jmeter-plugins-cmn-jmeter](https://search.maven.org/remotecontent?filepath=kg/apc/jmeter-plugins-cmn-jmeter/0.5/jmeter-plugins-cmn-jmeter-0.5.jar)
- * [commons-io / commons-io](https://search.maven.org/remotecontent?filepath=commons-io/commons-io/2.5/commons-io-2.5.jar)
- * [org.apache.commons / commons-pool](https://search.maven.org/remotecontent?filepath=org/apache/commons/commons-pool2/2.4.2/commons-pool2-2.4.2.jar)
+ * [kg.apc / jmeter-plugins-cmn-jmeter](https://search.maven.org/remotecontent?filepath=kg/apc/jmeter-plugins-cmn-jmeter/0.6/jmeter-plugins-cmn-jmeter-0.6.jar)
* [com.jcraft.jsch / jsch](https://search.maven.org/remotecontent?filepath=com/jcraft/jsch/0.1.54/jsch-0.1.54.jar)
3. Restart JMeter.
-**Important: Make sure to remove any older jar file version than `jmeter-plugins-cmn-jmeter-0.5.jar` from JMeter's lib directory!**
+**Important: Make sure to remove any older jar file version than `jmeter-plugins-cmn-jmeter-0.6.jar` from JMeter's lib directory!**
Usage
-----
@@ -100,13 +98,21 @@ For details refer to the [JMeter-Plugins wiki](https://jmeter-plugins.org/wiki/S
### JMeter Properties
The following properties control the plugin behaviour:
- * `jmeter.sshmon.knownHosts`: Filename of a known_hosts file containing public keys of trusted remote servers (in OpenSSH format). If not set, no validation will be performed.
- * `jmeter.sshmon.interval`: Define the metrics collection interval in milliseconds (default=1 second).
- * `jmeter.sshmon.forceOutputFile` - (true/false) makes sure JMeter writes metrics to CSV file in the current directory if no filename is specified (default=false).
+ * `jmeter.sshmon.knownHosts`: Filename of a known_hosts file containing public keys of trusted remote servers (in OpenSSH format).
+ If defined, connections to unknown hosts will be rejected (via `StrictHostKeyChecking=yes`).
+ If undefined, connections to unknown hosts will be established (via `StrictHostKeyChecking=no`).
+ Default: undefined.
+ * `jmeter.sshmon.interval`: Metrics collection interval in milliseconds.
+ This is inclusive of the execution time of the remote commands.
+ Default: 1 second.
+ * `jmeter.sshmon.forceOutputFile` (true/false): Makes sure JMeter writes metrics to CSV file in the current directory if no filename is specified.
+ Default: false.
Limitations
-----------
-* Samples are collected by a single thread, so if the command takes more than an insignificant amount of time to run, the frequency of sample collection will be limited.
-A separate monitor may be used in this case.
-* The help link on the plugin GUI currently points to an incorrect location. This is to be resolved with the next release of `jmeter-plugins-cmn-jmeter` (see [this PR](https://github.com/undera/jmeter-plugins/pull/162)).
+* Samples are collected by a single thread, so if a command takes more than an insignificant amount of time to run, the frequency of sample collection will be limited.
+Even more so if more than one command is sampled. In this case, use a separate monitor for each sample command.
+* When a JMeter test ends, this plugin will not interrupt the collector thread but let the current sample finish before stopping.
+This may take longer than the JMeter engine [waits](https://jmeter.apache.org/usermanual/get-started.html#shutdown) in headless (non-GUI) mode.
+In this case, increase the JMeter property `jmeter.exit.check.pause`.
diff --git a/pom.xml b/pom.xml
index 2ea62ec..2e4b44b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,12 +4,13 @@
co.nz.breakpoint.jmeter
jmeter-sshmon
jar
- 1.0
+ 1.1
jmeter-sshmon
- http://maven.apache.org
UTF-8
+ 1.8
+ 1.8
@@ -80,7 +81,6 @@
start
- true
java
-cp
@@ -130,7 +130,7 @@
kg.apc
jmeter-plugins-cmn-jmeter
- 0.5
+ 0.6
com.jcraft
@@ -140,12 +140,8 @@
org.apache.commons
commons-pool2
- 2.4.2
-
-
- commons-io
- commons-io
- 2.5
+ 2.6.0
+ provided
kg.apc
diff --git a/src/main/java/nz/co/breakpoint/jmeter/vizualizers/sshmon/JSchLogger.java b/src/main/java/nz/co/breakpoint/jmeter/vizualizers/sshmon/JSchLogger.java
new file mode 100644
index 0000000..c7eed46
--- /dev/null
+++ b/src/main/java/nz/co/breakpoint/jmeter/vizualizers/sshmon/JSchLogger.java
@@ -0,0 +1,34 @@
+package nz.co.breakpoint.jmeter.vizualizers.sshmon;
+
+import java.util.HashMap;
+import java.util.Map;
+import com.jcraft.jsch.Logger;
+import org.apache.jorphan.logging.LoggingManager;
+import org.apache.log.Priority;
+
+class JSchLogger implements Logger {
+
+ private static final org.apache.log.Logger log = LoggingManager.getLoggerForClass();
+
+ private static final Map levels = new HashMap();
+ static {
+ levels.put(Logger.DEBUG, Priority.DEBUG);
+ levels.put(Logger.INFO, Priority.INFO);
+ levels.put(Logger.WARN, Priority.WARN);
+ levels.put(Logger.ERROR, Priority.ERROR);
+ levels.put(Logger.FATAL, Priority.FATAL_ERROR);
+ }
+
+ @Override
+ public boolean isEnabled(int level) {
+ return log.isPriorityEnabled(levels.get(level));
+ }
+
+ @Override
+ public void log(int level, String message) {
+ Priority p = levels.get(level);
+ if (p != null) {
+ log.log(p, message);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/nz/co/breakpoint/jmeter/vizualizers/sshmon/SSHMonCollector.java b/src/main/java/nz/co/breakpoint/jmeter/vizualizers/sshmon/SSHMonCollector.java
index 8c75073..5ebee4e 100644
--- a/src/main/java/nz/co/breakpoint/jmeter/vizualizers/sshmon/SSHMonCollector.java
+++ b/src/main/java/nz/co/breakpoint/jmeter/vizualizers/sshmon/SSHMonCollector.java
@@ -53,4 +53,10 @@ protected void initiateConnectors() {
samplers.add(new SSHMonSampler(label, connectionDetails, command, isDelta));
}
}
+
+ @Override
+ public void testEnded(String host) {
+ super.testEnded(host);
+ SSHMonSampler.clearConnectionPool();
+ }
}
diff --git a/src/main/java/nz/co/breakpoint/jmeter/vizualizers/sshmon/SSHMonSampler.java b/src/main/java/nz/co/breakpoint/jmeter/vizualizers/sshmon/SSHMonSampler.java
index 3f68d88..b2f9a29 100644
--- a/src/main/java/nz/co/breakpoint/jmeter/vizualizers/sshmon/SSHMonSampler.java
+++ b/src/main/java/nz/co/breakpoint/jmeter/vizualizers/sshmon/SSHMonSampler.java
@@ -1,11 +1,10 @@
package nz.co.breakpoint.jmeter.vizualizers.sshmon;
-import java.io.InterruptedIOException;
+import java.io.ByteArrayOutputStream;
import kg.apc.jmeter.vizualizers.MonitoringSampler;
import kg.apc.jmeter.vizualizers.MonitoringSampleGenerator;
-import org.apache.commons.io.IOUtils;
import org.apache.commons.pool2.KeyedObjectPool;
import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
@@ -35,12 +34,23 @@ public class SSHMonSampler
*/
private static KeyedObjectPool pool;
static {
- GenericKeyedObjectPoolConfig config = new GenericKeyedObjectPoolConfig();
+ GenericKeyedObjectPoolConfig config = new GenericKeyedObjectPoolConfig<>();
config.setMinIdlePerKey(1);
+ config.setTestOnBorrow(true);
log.debug("Creating GenericKeyedObjectPool");
pool = new GenericKeyedObjectPool(new SSHSessionFactory(), config);
}
+ public static void clearConnectionPool() {
+ log.debug("Clearing connection pool");
+ try {
+ pool.clear();
+ }
+ catch (Exception e) {
+ log.error("Failed to clear connection pool: ", e);
+ }
+ }
+
public SSHMonSampler(String name, ConnectionDetails connectionDetails, String remoteCommand, boolean sampleDeltaValue) {
this.metricName = name;
this.connectionDetails = connectionDetails;
@@ -52,6 +62,7 @@ public SSHMonSampler(String name, ConnectionDetails connectionDetails, String re
public void generateSamples(MonitoringSampleGenerator collector) {
Session session = null;
ChannelExec channel = null;
+ ByteArrayOutputStream result = new ByteArrayOutputStream();
try {
log.debug("Borrowing session for "+connectionDetails);
@@ -60,10 +71,14 @@ public void generateSamples(MonitoringSampleGenerator collector) {
channel = (ChannelExec)session.openChannel("exec");
channel.setCommand(remoteCommand);
channel.setPty(true);
+ channel.setOutputStream(result);
channel.connect();
-
- final double val = Double.valueOf(IOUtils.toString(channel.getInputStream()).trim());
+ while (!channel.isClosed()) { // wait for command execution to finish
+ Thread.sleep(10);
+ }
+
+ final double val = Double.valueOf(result.toString());
if (sampleDeltaValue) {
if (!Double.isNaN(oldValue)) {
collector.generateSample(val - oldValue, metricName);
@@ -73,31 +88,30 @@ public void generateSamples(MonitoringSampleGenerator collector) {
collector.generateSample(val, metricName);
}
}
- catch (InterruptedIOException ex) { // stopping the test has caused a thread interrupt
- log.info(ex.toString());
+ catch (JSchException ex) {
+ log.error("Channel failure for "+connectionDetails, ex);
}
- catch (Exception ex) {
- log.error(ex.toString());
+ catch (Exception ex) {
+ log.error("Sample failure for "+connectionDetails, ex);
}
finally {
- if (channel != null && channel.isConnected()) {
+ if (channel != null) {
+ log.debug("Disconnecting channel for "+connectionDetails);
channel.disconnect();
}
- if (session != null) {
- try {
- if (session.isConnected()) {
- log.debug("Returning session for "+connectionDetails);
- pool.returnObject(connectionDetails, session);
- }
- else {
- log.debug("Invalidating session for "+connectionDetails);
- pool.invalidateObject(connectionDetails, session);
- }
+ try {
+ if (session != null && session.isConnected()) {
+ log.debug("Returning session for "+connectionDetails);
+ pool.returnObject(connectionDetails, session);
}
- catch (Exception ex) {
- log.warn(ex.getMessage());
+ else {
+ log.debug("Invalidating session for "+connectionDetails);
+ pool.invalidateObject(connectionDetails, session);
}
}
+ catch (Exception ex) {
+ log.warn("Failure returning session ", ex);
+ }
}
}
diff --git a/src/main/java/nz/co/breakpoint/jmeter/vizualizers/sshmon/SSHSessionFactory.java b/src/main/java/nz/co/breakpoint/jmeter/vizualizers/sshmon/SSHSessionFactory.java
index c5675e0..624ef4f 100644
--- a/src/main/java/nz/co/breakpoint/jmeter/vizualizers/sshmon/SSHSessionFactory.java
+++ b/src/main/java/nz/co/breakpoint/jmeter/vizualizers/sshmon/SSHSessionFactory.java
@@ -17,16 +17,21 @@ public class SSHSessionFactory extends BaseKeyedPooledObjectFactory wrap(Session session) {
- return new DefaultPooledObject(session);
+ return new DefaultPooledObject(session);
}
@Override