├── .gitignore ├── CHANGES.txt ├── LICENSE ├── Makefile ├── README.md ├── TODO.txt ├── pom.xml ├── scripts └── jmxstat └── src └── main └── java └── net └── jlee └── jmxstat └── Main.java /.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .project 3 | .settings 4 | target 5 | dist 6 | *~ 7 | .#* 8 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | # -*- mode: org -*- 2 | #+TITLE: jmxstat changelog 3 | #+AUTHOR: Benoit Delbosc 4 | 5 | * Version 0.4.0 2012-09-03 6 | ** Enable operation invocation with params only boolean and String so far 7 | ** Adding a quiet mode to be silent if attribute does not exists 8 | * Version 0.3.0 2012-05-29 9 | ** Enable operation invocation 10 | * Version 0.2.1 2011-05-19 11 | ** jmxstat script uses exec, it is easier to get the pid of the jvm. 12 | * Version 0.2.0 2011-04-10 13 | ** Packaged version with a jmxstat sh script 14 | ** Column output like sar/vmstat 15 | ** Add count param like sar 16 | ** Contention lock monitoring --contention option 17 | ** Perform GC with the --performGC option 18 | * Version 0.1-SNAPHOT initial version by jonjlee 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Jonathan Lee 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # FunkLoad Makefile 2 | # $Id: $ 3 | # 4 | .PHONY: clean pkg install uninstall 5 | 6 | VERSION=0.4.0 7 | JMXSTAT_HOME=$(PREFIX)/jmxstat 8 | TARGET=gateway:/opt/public-dev/public/ben/jmxstat 9 | 10 | clean: 11 | -find . "(" -name "*~" -or -name ".#*" -or -name "#*" ")" -print0 | xargs -0 rm -f 12 | -rm -rf ./dist/ 13 | 14 | build: 15 | mvn clean package 16 | 17 | distrib: 18 | -scp ./dist/jmxstat-*.tgz $(TARGET)/ 19 | 20 | pkg: build 21 | -rm -rf ./dist/ 22 | mkdir -p ./dist/jmxstat-$(VERSION) 23 | cp ./Makefile ./dist/jmxstat-$(VERSION)/ 24 | cp ./scripts/jmxstat ./dist/jmxstat-$(VERSION)/ 25 | sed -i "s/VERSION$$/$(VERSION)/g" ./dist/jmxstat-$(VERSION)/jmxstat 26 | cp ./target/*.jar ./dist/jmxstat-$(VERSION)/ 27 | (cd ./dist; tar czvf jmxstat-$(VERSION).tgz jmxstat-$(VERSION)/) 28 | 29 | install: 30 | cp -r ../jmxstat-$(VERSION) /usr/local/ 31 | -rm -f /usr/local/bin/jmxstat 32 | cp jmxstat /usr/local/bin/jmxstat 33 | chmod +x /usr/local/bin/jmxstat 34 | 35 | 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | NAME 2 | 3 | jmxstat - Poll JMX attributes and more 4 | 5 | SYNOPSIS 6 | 7 | jmxstat [--peformGC|--contention|--disable-contention] [mbean.name[attribute.field], ...] [interval [count]] 8 | or 9 | java -jar jmxstat-VERSION.jar [--peformGC|--contention|--disable-contention] [mbean.name[attribute.field], ...] [interval [count]] 10 | 11 | DESCRIPTION 12 | 13 | jmxstat connects to a remote JMX server via RMI and reads MBean attributes 14 | at a regular interval. Authentication is not supported. 15 | 16 | jmxstat supports these arguments: 17 | 18 | host:port Host and port of the JMX enabled process to connect to 19 | 20 | mbean-attributes Space separated list of mbean names and attributes to 21 | query in the following format: 22 | 23 | mbeanDomain:mbeanKey=mbeanValues,...[mbeanAttribute1[.field],...] 24 | 25 | If the attribute starts with '!' invoke the method. 26 | 27 | --contention Render blockedCount and blockedTime for all threads. 28 | blockedCount is the total number of times threads 29 | blocked to enter or reenter a monitor. 30 | blockedTime is the time elapsed in milliseconds for 31 | all threads blocked to enter or reenter a monitor. 32 | 33 | --disable-contention Disable the blocked time monitoring activated by the 34 | --contention options. 35 | 36 | --performGC Perform a full GC. 37 | 38 | interval Pause _interval_ seconds between each query. 39 | 40 | count Select count records at interval second intervals. 41 | 42 | EXAMPLES 43 | 44 | **Precondition**. These examples assume a JMX enabled process is listening 45 | on localhost:9999. To use jmxstat itself as the monitored process: 46 | 47 | java -Dcom.sun.management.jmxremote.port=9999 -Dcom.sun.management.jmxremote.authentice=false -Dcom.sun.management.jmxremote.ssl=false -jar jmxstat.jar localhost:9999 48 | 49 | Display the number of loaded classes (every 5 seconds, by default): 50 | 51 | jmxstat localhost:9999 java.lang:type=ClassLoading[LoadedClassCount] 52 | 53 | Display heap usage and thread count every 2 seconds 3 times: 54 | 55 | jmxstat localhost:9999 java.lang:type=Memory[HeapMemoryUsage.max,HeapMemoryUsage.committed,HeapMemoryUsage.used] java.lang:type=Threading[ThreadCount] 2 3 56 | 57 | Display thread count and contention information: 58 | 59 | jmxstat localhost:9999 --contention java.lang:type=Threading[ThreadCount] 2 3 60 | 61 | Perform a Full garbage collector: 62 | 63 | jmxstat localhost:9999 --performGC 64 | 65 | Same but using a method invocation: 66 | 67 | jmxstat localhost:1089 java.lang:type=Memory[!gc] 1 1 68 | 69 | Invoking method with parameters: 70 | 71 | jmxstat localhost:1089 localhost:1089 org.nuxeo:name=EventMonitoring,type=service[!setListenerEnabledFlag/auditLoggerListener/false] 1 1 72 | 73 | 74 | INSTALL/DOWNLOAD 75 | 76 | Get the latest package from: 77 | 78 | 79 | Then install: 80 | tar xzvf jmxstat-VERSION.tgz 81 | cd jmxstat-VERSION 82 | sudo make install 83 | -------------------------------------------------------------------------------- /TODO.txt: -------------------------------------------------------------------------------- 1 | # -*- mode: org -*- 2 | #+TITLE: jmxstat to do list 3 | #+AUTHOR: Benoit Delbosc 4 | 5 | * Features 6 | ** executable jar 7 | try http://skife.org/java/unix/2011/06/20/really_executable_jars.html 8 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | net.jlee 6 | jmxstat 7 | jar 8 | 0.4.0 9 | JMX poller 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | maven-compiler-plugin 18 | 19 | 1.6 20 | 1.6 21 | 22 | 23 | 24 | org.apache.maven.plugins 25 | maven-jar-plugin 26 | 27 | 28 | 29 | true 30 | net.jlee.jmxstat.Main 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /scripts/jmxstat: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | VERSION=VERSION 3 | exec java -jar /usr/local/jmxstat-$VERSION/jmxstat-$VERSION.jar $@ 4 | -------------------------------------------------------------------------------- /src/main/java/net/jlee/jmxstat/Main.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Jonathan Lee 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * Contributors: 17 | * Jonathan Lee 18 | * Benoit Delbosc 19 | * 20 | */ 21 | package net.jlee.jmxstat; 22 | 23 | import java.io.IOException; 24 | import java.text.SimpleDateFormat; 25 | import java.util.ArrayList; 26 | import java.util.Calendar; 27 | import java.util.List; 28 | 29 | import javax.management.Attribute; 30 | import javax.management.AttributeNotFoundException; 31 | import javax.management.InstanceNotFoundException; 32 | import javax.management.InvalidAttributeValueException; 33 | import javax.management.MBeanException; 34 | import javax.management.MBeanServerConnection; 35 | import javax.management.MalformedObjectNameException; 36 | import javax.management.ObjectName; 37 | import javax.management.ReflectionException; 38 | import javax.management.openmbean.CompositeData; 39 | import javax.management.remote.JMXConnector; 40 | import javax.management.remote.JMXConnectorFactory; 41 | import javax.management.remote.JMXServiceURL; 42 | 43 | public class Main { 44 | 45 | private static final int DEFAULT_INTERVAL = 5; 46 | 47 | private static final int MAX_RETRY = 5; 48 | 49 | private static final String DATE_FORMAT_NOW = "HH:mm:ss"; 50 | 51 | private static final String THREADING_OBJ_NAME = "java.lang:type=Threading"; 52 | 53 | private static final String MEMORY_OBJ_NAME = "java.lang:type=Memory"; 54 | 55 | private static final String GC = "gc"; 56 | 57 | private static final String CONTENTION_ATTR = "ThreadContentionMonitoringEnabled"; 58 | 59 | private static final String DUMP_ALL_THREADS = "dumpAllThreads"; 60 | 61 | private static final String BLOCKED_TIME = "blockedTime"; 62 | 63 | private static final String BLOCKED_COUNT = "blockedCount"; 64 | 65 | private static class Options { 66 | 67 | private static final String CONTENTION_ARG = "--contention"; 68 | 69 | private static final String DISABLE_CONTENTION_ARG = "--disable-contention"; 70 | 71 | private static final Object PERFORM_GC_ARG = "--performGC"; 72 | 73 | private static final Object QUIET_ARG = "--quiet"; 74 | 75 | String url; 76 | 77 | int interval; 78 | 79 | int count = 0; 80 | 81 | boolean contention = false; 82 | 83 | boolean disableContention = false; 84 | 85 | boolean performGC = false; 86 | 87 | boolean quiet = false; 88 | 89 | List attrs = null; 90 | 91 | public Options(String[] args) { 92 | if (args.length < 2) { 93 | usage(); 94 | } 95 | url = args[0]; 96 | // Check for the last 2 args if they are interval/count 97 | int i = 0; 98 | int c = 0; 99 | try { 100 | c = Integer.parseInt(args[args.length - 1]); 101 | i = Integer.parseInt(args[args.length - 2]); 102 | } catch (Exception e) { 103 | } 104 | if (i > 0) { 105 | count = c; 106 | interval = i; 107 | } else if (c > 0) { 108 | interval = c; 109 | } else { 110 | interval = DEFAULT_INTERVAL; 111 | } 112 | 113 | attrs = new ArrayList(); 114 | for (i = 1; i < args.length; i++) { 115 | String def = args[i]; 116 | if (CONTENTION_ARG.equals(def)) { 117 | contention = true; 118 | disableContention = false; 119 | continue; 120 | } 121 | if (DISABLE_CONTENTION_ARG.equals(def)) { 122 | disableContention = true; 123 | contention = false; 124 | continue; 125 | } else if (PERFORM_GC_ARG.equals(def)) { 126 | performGC = true; 127 | continue; 128 | } else if (QUIET_ARG.equals(def)) { 129 | quiet = true; 130 | } 131 | int startIdx = def.indexOf("["); 132 | int endIdx = def.lastIndexOf("]"); 133 | if (startIdx >= 0 && endIdx > startIdx) { 134 | ObjectName objectName; 135 | try { 136 | objectName = new ObjectName(def.substring(0, startIdx)); 137 | } catch (Exception e) { 138 | error("Invalid mbean attribute list - " + e.toString(), 139 | 1); 140 | return; 141 | } 142 | String[] items = def.substring(startIdx + 1, endIdx).split( 143 | ","); 144 | for (String attr : items) { 145 | int subIdx = attr.indexOf("."); 146 | if (subIdx >= 0) { 147 | attrs.add(new MbeanAttr(objectName, attr.substring( 148 | 0, subIdx), attr.substring(subIdx + 1))); 149 | } else { 150 | attrs.add(new MbeanAttr(objectName, attr)); 151 | } 152 | } 153 | } 154 | } 155 | } 156 | } 157 | 158 | private static class MbeanAttr { 159 | ObjectName name; 160 | 161 | String attr; 162 | 163 | String subAttr; 164 | 165 | public MbeanAttr(ObjectName name, String attr) { 166 | this(name, attr, null); 167 | } 168 | 169 | public MbeanAttr(ObjectName name, String attr, String subAttr) { 170 | this.name = name; 171 | this.attr = attr; 172 | this.subAttr = subAttr; 173 | } 174 | } 175 | 176 | private static void error(String msg, int err) { 177 | System.err.println(msg); 178 | System.exit(err); 179 | } 180 | 181 | private static void usage() { 182 | error("Usage:\n" // 183 | + "jmxstat " 184 | + "[--performGC] [--contention] [--quiet] [mbean.name[attribute.field], ...] [interval [count]]", 185 | 1); 186 | } 187 | 188 | private static String now() { 189 | Calendar cal = Calendar.getInstance(); 190 | SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT_NOW); 191 | return sdf.format(cal.getTime()); 192 | } 193 | 194 | private static MBeanServerConnection connect(String url) throws IOException { 195 | JMXServiceURL jmxUrl = new JMXServiceURL( 196 | "service:jmx:rmi:///jndi/rmi://" + url + "/jmxrmi"); 197 | JMXConnector jmxc = JMXConnectorFactory.connect(jmxUrl, null); 198 | return jmxc.getMBeanServerConnection(); 199 | 200 | } 201 | 202 | /** 203 | * Enable or disable the thread contention monitoring. 204 | * 205 | */ 206 | private static void setContentionMonitoring(MBeanServerConnection mbsc, 207 | Boolean value) throws MalformedObjectNameException, 208 | NullPointerException, AttributeNotFoundException, 209 | InstanceNotFoundException, MBeanException, ReflectionException, 210 | IOException, InvalidAttributeValueException { 211 | ObjectName objectName = new ObjectName(THREADING_OBJ_NAME); 212 | Object val = mbsc.getAttribute(objectName, CONTENTION_ATTR); 213 | if (value.toString().equals(val.toString())) { 214 | // already set 215 | return; 216 | } 217 | Attribute attribute = new Attribute(CONTENTION_ATTR, value); 218 | mbsc.setAttribute(objectName, attribute); 219 | // System.out.println(CONTENTION_ATTR + ":" + val.toString() + " to " 220 | // + value.toString()); 221 | } 222 | 223 | /** 224 | * Write sum of blockedCount and blocketTime for all threads. 225 | * 226 | * blockedCount: Returns the total number of times that the thread 227 | * associated with this ThreadInfo blocked to enter or reenter a monitor. 228 | * 229 | * blockedTime: Returns the approximate accumulated elapsed time (in 230 | * milliseconds) that the thread associated with this ThreadInfo has blocked 231 | * to enter or reenter a monitor since thread contention monitoring is 232 | * enabled. 233 | * 234 | * */ 235 | private static void writeContentionInfo(MBeanServerConnection mbsc, 236 | StringBuilder out) throws MalformedObjectNameException, 237 | NullPointerException, InstanceNotFoundException, MBeanException, 238 | ReflectionException, IOException { 239 | ObjectName objectName = new ObjectName(THREADING_OBJ_NAME); 240 | Object[] params = { new Boolean(true), new Boolean(true) }; 241 | String[] signature = { "boolean", "boolean" }; 242 | Object[] threads = (Object[]) mbsc.invoke(objectName, DUMP_ALL_THREADS, 243 | params, signature); 244 | Long blockedTime = 0L; 245 | Long blockedCount = 0L; 246 | for (Object thread : threads) { 247 | Long l = (Long) ((CompositeData) thread).get(BLOCKED_TIME); 248 | blockedTime += l; 249 | l = (Long) ((CompositeData) thread).get(BLOCKED_COUNT); 250 | blockedCount += l; 251 | } 252 | out.append(blockedCount.toString()); 253 | out.append("\t"); 254 | out.append(blockedTime.toString()); 255 | out.append("\t"); 256 | } 257 | 258 | private static void performGC(MBeanServerConnection mbsc) 259 | throws MalformedObjectNameException, NullPointerException, 260 | InstanceNotFoundException, MBeanException, ReflectionException, 261 | IOException { 262 | ObjectName objectName = new ObjectName(MEMORY_OBJ_NAME); 263 | Object[] threads = (Object[]) mbsc.invoke(objectName, GC, null, null); 264 | } 265 | 266 | public static void main(String[] args) throws Exception { 267 | boolean retryEnabled = false; 268 | int retry = 0; 269 | 270 | Options options = new Options(args); 271 | StringBuilder out = new StringBuilder(); 272 | // header 273 | out.append("date\t"); 274 | for (MbeanAttr attr : options.attrs) { 275 | out.append(attr.attr); 276 | if (attr.subAttr != null) { 277 | out.append(".").append(attr.subAttr); 278 | } 279 | out.append("\t"); 280 | } 281 | if (options.contention) { 282 | out.append("blockedCount\tblockedTimeMs\t"); 283 | } 284 | String header = out.toString(); 285 | System.out.println(header); 286 | int i = 0; 287 | for (;;) { 288 | try { 289 | // Connect to JMX server 290 | MBeanServerConnection mbsc = connect(options.url); 291 | retryEnabled = true; // only allow retries after first 292 | if (options.contention) { 293 | setContentionMonitoring(mbsc, true); 294 | } 295 | if (options.disableContention) { 296 | setContentionMonitoring(mbsc, false); 297 | } 298 | if (options.performGC) { 299 | performGC(mbsc); 300 | System.out.println(now()); 301 | } 302 | // successful connection 303 | if (!options.contention && options.attrs.size() == 0) { 304 | return; 305 | } 306 | for (;;) { 307 | out = new StringBuilder(); 308 | out.append(now()); 309 | out.append("\t"); 310 | // Read each mbean attribute specified on the command line 311 | for (MbeanAttr attr : options.attrs) { 312 | Object val; 313 | if (!attr.attr.startsWith("!")) { 314 | try { 315 | val = mbsc.getAttribute(attr.name, attr.attr); 316 | } catch (AttributeNotFoundException e) { 317 | if (!options.quiet) { 318 | throw (e); 319 | } else { 320 | continue; 321 | } 322 | } 323 | } else { 324 | // execute operation, params are separated with / 325 | String[] items = attr.attr.substring(1).split("/"); 326 | String operationName = items[0]; 327 | Object params[] = null; 328 | String signatures[] = null; 329 | int nbParams = items.length - 1; 330 | if (nbParams > 0) { 331 | params = new Object[items.length - 1]; 332 | signatures = new String[items.length - 1]; 333 | for (int j = 0; j < nbParams; j++) { 334 | String param = items[j + 1]; 335 | if ("true".equals(param) 336 | || "false".equals(param)) { 337 | params[j] = Boolean.valueOf(param).booleanValue(); 338 | signatures[j] = "boolean"; 339 | } else { 340 | params[j] = param; 341 | signatures[j] = String.class.getName(); 342 | } 343 | } 344 | } 345 | try { 346 | val = mbsc.invoke(attr.name, operationName, 347 | params, signatures); 348 | } catch (InstanceNotFoundException e) { 349 | if (!options.quiet) { 350 | throw (e); 351 | } else { 352 | continue; 353 | } 354 | } 355 | } 356 | // Read fields from CompositeData attributes if user 357 | // specified a sub-attribute 358 | // via dot notation (e.g. 359 | // java.lang:type=Memory[HeapMemoryUsage.max]) 360 | if (attr.subAttr != null 361 | && val instanceof CompositeData) { 362 | val = ((CompositeData) val).get(attr.subAttr); 363 | } 364 | out.append(val); 365 | out.append("\t"); 366 | } 367 | if (options.contention) { 368 | writeContentionInfo(mbsc, out); 369 | } 370 | System.out.println(out.toString()); 371 | i++; 372 | if (options.count > 0 && i >= options.count) { 373 | return; 374 | } 375 | Thread.sleep(options.interval * 1000); 376 | retry = 0; // reset exponential retry back-off after a 377 | // successful read 378 | } 379 | } catch (IOException e) { 380 | if (retryEnabled && retry < MAX_RETRY) { 381 | // Exponential retry back-off 382 | int sleep = Math.min(30, (int) Math.pow(2, ++retry)); 383 | System.err.println("Communcation error. Retry [" + retry 384 | + "/" + MAX_RETRY + "] in " + sleep + "s.\n" + e); 385 | Thread.sleep(sleep * 1000); 386 | } else { 387 | error("Error reading from " + options.url + "\n" + e, 2); 388 | } 389 | } 390 | } 391 | } 392 | } 393 | --------------------------------------------------------------------------------