├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── RELEASE-NOTES ├── build.sbt ├── project ├── build.properties └── plugins.sbt ├── publish.sbt ├── scripts ├── gcforce ├── jmxgrep ├── lsnum ├── lsthreads └── shortest ├── src ├── main │ └── scala │ │ ├── fr │ │ └── janalyse │ │ │ └── jmx │ │ │ ├── ASSignature.scala │ │ │ ├── AttributeMetaData.scala │ │ │ ├── Credentials.scala │ │ │ ├── DataWrappers.scala │ │ │ ├── JMX.scala │ │ │ ├── JMXJsr160.scala │ │ │ ├── JMXOptions.scala │ │ │ ├── JMXclassicalImpl.scala │ │ │ ├── JMXrmiImpl.scala │ │ │ ├── LazyLogging.scala │ │ │ ├── Lock.scala │ │ │ ├── RichAttribute.scala │ │ │ ├── RichMBean.scala │ │ │ ├── ServiceThreads.scala │ │ │ ├── StackEntry.scala │ │ │ ├── ThreadInfo.scala │ │ │ └── package.scala │ │ └── jajmx │ │ └── package.scala └── test │ └── scala │ └── fr │ └── janalyse │ └── jmx │ ├── JMXAPITest.scala │ └── JMXJolokiaTest.scala └── version.sbt /.gitignore: -------------------------------------------------------------------------------- 1 | *.jar 2 | *.class 3 | dist/* 4 | target/ 5 | *.pscala 6 | *~ 7 | .classpath 8 | .project 9 | .cache 10 | .settings 11 | images/ 12 | *.png 13 | nohup.out 14 | .DS_Store 15 | /bin/ 16 | .idea 17 | *.iml 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | 3 | scala: 4 | - 2.12.11 5 | - 2.13.1 6 | 7 | branches: 8 | only: 9 | - master 10 | 11 | jdk: 12 | - openjdk8 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JAJMX - High level scala JMX API [![Build Status][travisImg]][travisLink] [![License][licenseImg]][licenseLink] [![Maven][mavenImg]][mavenLink] [![Scaladex][scaladexImg]][scaladexLink] [![Codacy][codacyImg]][codacyLink] [![codecov][codecovImg]][codecovLink] 2 | 3 | The goal is to simplify jmx operations on remote (or local) JVM. Work on this library is still in progress... but implemented features work well. One of the main usages of this jmx abstraction layer is to simplify extraction of jmx metrics such as getting jdbc connections or busy threads usage trends. This library only requires one IP and one PORT in order to connect to a remote JMX plateform, no service url is required ! 4 | 5 | [*Scala docs*](http://www.janalyse.fr/scaladocs/janalyse-jmx) 6 | 7 | In your build.sbt, add this (available in maven central) : 8 | ``` 9 | libraryDependencies += "fr.janalyse" %% "janalyse-jmx" % version 10 | ``` 11 | 12 | ## Console mode usage example : 13 | 14 | ``` 15 | $ java -jar jajmx.jar 16 | Welcome to Scala version 2.10.1 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_45). 17 | Type in expressions to have them evaluated. 18 | Type :help for more information. 19 | 20 | scala> import jajmx._ 21 | import jajmx._ 22 | 23 | scala> val jmx = JMX() 24 | 25 | scala> import jmx._ 26 | import jmx._ 27 | 28 | scala> verbosegc() 29 | 30 | scala> gcforce() 31 | [GC 62584K->30221K(310080K), 0.0015270 secs] 32 | [Full GC 30221K->30014K(310080K), 0.1879440 secs] 33 | 34 | scala> osVersion 35 | res3: Option[String] = Some(3.7.10-gentoo) 36 | 37 | scala> osName 38 | res4: Option[String] = Some(Linux) 39 | 40 | scala> mbeans.take(10).map(_.name).foreach{println _} 41 | java.lang:type=Memory 42 | java.lang:type=MemoryPool,name=PS Eden Space 43 | java.lang:type=MemoryPool,name=PS Survivor Space 44 | java.lang:type=MemoryPool,name=Code Cache 45 | java.lang:type=GarbageCollector,name=PS MarkSweep 46 | java.lang:type=Runtime 47 | java.lang:type=ClassLoading 48 | java.lang:type=Threading 49 | java.util.logging:type=Logging 50 | java.lang:type=Compilation 51 | 52 | scala> val mythreading=get("java.lang:type=Runtime") 53 | mythreading: Option[fr.janalyse.jmx.RichMBean] = Some(RichMBean(java.lang:type=Runtime,,,,)) 54 | 55 | scala> mythreading.map{_.attributes.foreach{attr => println(attr.name)}} 56 | SynchronizerUsageSupported 57 | ThreadCount 58 | TotalStartedThreadCount 59 | ThreadAllocatedMemorySupported 60 | ThreadContentionMonitoringSupported 61 | ThreadAllocatedMemoryEnabled 62 | AllThreadIds 63 | DaemonThreadCount 64 | ThreadContentionMonitoringEnabled 65 | CurrentThreadUserTime 66 | PeakThreadCount 67 | ObjectMonitorUsageSupported 68 | ThreadCpuTimeSupported 69 | CurrentThreadCpuTime 70 | ThreadCpuTimeEnabled 71 | CurrentThreadCpuTimeSupported 72 | 73 | scala> close 74 | 75 | scala> :q 76 | 77 | ``` 78 | 79 | ## gcforce script 80 | 81 | Only provide to the script, host and port of a remote JVM with JMX enabled, and this script will force the JVM to Garbage Collect major operation. 82 | 83 | ```scala 84 | #!/bin/sh 85 | exec java -verbosegc -jar jajmx.jar "$0" "$@" 86 | !# 87 | 88 | if (args.size < 2) { 89 | println("Usage : jmxgrep host port") 90 | println(" args are not compliant so now let's connecting to myself, and force a gc...") 91 | } 92 | 93 | import fr.janalyse.jmx._ 94 | 95 | val options = args.toList match { 96 | case host::port::_ => Some(JMXOptions(host,port.toInt)) 97 | case _ => None 98 | } 99 | JMX.once(options) { _.gcforce } 100 | ``` 101 | 102 | 103 | 104 | A short gc force script (a self test) : 105 | 106 | ```scala 107 | #!/bin/sh 108 | exec java -jar jajmx.jar "$0" "$@" 109 | !# 110 | 111 | jajmx.JMX.once() { jmx => 112 | jmx.verbosegc() 113 | jmx.gcforce() 114 | } 115 | 116 | ``` 117 | 118 | 119 | ## lsnum script 120 | 121 | This scala script search for jmx numerical values. This jmxgrep script can be tested against itself : "lsnum" 122 | 123 | ```scala 124 | #!/bin/sh 125 | exec java -jar jajmx.jar "$0" "$@" 126 | !# 127 | 128 | import fr.janalyse.jmx._ 129 | 130 | val options = args.toList match { 131 | case host::port::_ => Some(JMXOptions(host,port.toInt)) 132 | case _ => None 133 | } 134 | 135 | JMX.once(options) { jmx => 136 | for { 137 | mbean <- jmx.mbeans 138 | attr <- mbean.attributes.collect{case n:RichNumberAttribute => n} 139 | value <- mbean.getLong(attr) 140 | } { 141 | println(s"${mbean.name} - ${attr.name} = ${value}") 142 | } 143 | } 144 | 145 | ``` 146 | 147 | ## jmxgrep script 148 | 149 | This scala script search matching mbean name, attribute name, or value satisfying the given set of regular exception. This jmxgrep script can be tested against itself : "jmxgrep - vendor version" 150 | 151 | ```scala 152 | #!/bin/sh 153 | exec java -jar jajmx.jar "$0" "$@" 154 | !# 155 | 156 | import jajmx._ 157 | 158 | if (args.size == 0) { 159 | println("Usage : jmxgrep host port - searchMask1 ... searchMaskN") 160 | println(" if no args given so now let's connecting to myself, and list my mbeans...") 161 | } 162 | 163 | val (options, masks) = args.toList match { 164 | case host::port::"-"::masks => 165 | (Some(JMXOptions(host,port.toInt)), masks.map{s=>("(?i)"+s).r}) 166 | case "-"::masks => (None, masks.map{s=>("(?i)"+s).r}) 167 | case _ => (None,List.empty[util.matching.Regex]) 168 | } 169 | 170 | def truncate(str:String, n:Int=60) = { 171 | val nonl=str.replaceAll("\n", " ").replaceAll("\r", "") 172 | if (nonl.size>n) nonl.take(n)+"..." else nonl 173 | } 174 | 175 | JMX.once(options) { jmx => 176 | for { 177 | mbean <- jmx.mbeans 178 | attr <- mbean.attributes 179 | value <- mbean.getString(attr) } { 180 | 181 | val found = List(mbean.name, attr.name, value).exists{item => 182 | masks.exists{_.findFirstIn(item).isDefined } 183 | } 184 | if (masks.isEmpty || found) 185 | println(s"${mbean.name} - ${attr.name} = ${truncate(value)}") 186 | } 187 | } 188 | ``` 189 | 190 | ## lsthreads script 191 | 192 | Connect to a remote JVM using just the host adress and the used JMX port, and then list all active threads and their current states. 193 | 194 | ```scala 195 | #!/bin/sh 196 | exec java -jar jajmx.jar "$0" "$@" 197 | !# 198 | import jajmx._ 199 | 200 | val options = args.toList match { 201 | case host::port::_ => Some(JMXOptions(host,port.toInt)) 202 | case _ => None 203 | } 204 | 205 | JMX.once(options) { jmx => 206 | for (dump <- jmx.threadsDump(0)) { 207 | val threads = dump.threads 208 | val countByState = 209 | threads 210 | .groupBy(_.status) 211 | .map{ case (state,sublist) => state -> sublist.size} 212 | .map{ case (state,count) => state+":"+count} 213 | .mkString(" ") 214 | 215 | println("Total %d threads, %s".format(threads.size, countByState)) 216 | for ( ti <- threads sortBy {_.id } ) { 217 | println("%d - %s - %s".format(ti.id, ti.status, ti.name) ) 218 | } 219 | } 220 | } 221 | ``` 222 | 223 | 224 | # JMX Configuration Notes 225 | 226 | ## Default JSR160 Configuration 227 | 228 | ```sh 229 | JAVA_OPTS=$JAVA_OPTS" -Dcom.sun.management.jmxremote" 230 | JAVA_OPTS=$JAVA_OPTS" -Dcom.sun.management.jmxremote.port=2500" 231 | JAVA_OPTS=$JAVA_OPTS" -Dcom.sun.management.jmxremote.authenticate=false" 232 | JAVA_OPTS=$JAVA_OPTS" -Dcom.sun.management.jmxremote.ssl=false" 233 | JAVA_OPTS=$JAVA_OPTS" -Djava.rmi.server.hostname=192.168.0.184" 234 | ``` 235 | With authentication : 236 | ```sh 237 | JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote.authenticate=true" 238 | 239 | $JAVA_HOME/jre/lib/management/jmxremote.password 240 | $JAVA_HOME/jre/lib/management/jmxremote.access 241 | ``` 242 | 243 | ## FOR JBOSS 5.1 244 | 245 | STANDARD JMX can be used to get JBOSS metrics, but unfortunately JVM metrics can't be gotten 246 | (Interesting information : http://labs.consol.de/blog/jmx4perl/jboss-remote-jmx/) 247 | 248 | - ENABLE standard JMX (-Dcom.sun.management.jmxremote*) AND USE A DIFFENT PORT than the one use below : 2090 249 | - JVM 6 required 250 | - The following module must be deployed : jmx-remoting.sar/ 251 | (if not available, get one in official JBOSS packaging jboss-5.1.0.GA) 252 | - modify the file ./server/default/deploy/jmx-remoting.sar/META-INF/jboss-service.xml to set the port 253 | ```xml 254 | 255 | 256 | 259 | 10.104.65.101 260 | 1090 261 | 262 | 263 | ``` 264 | - Use the network interface using the option : -Djava.rmi.server.hostname=... 265 | 266 | => We will monitor JBOSS using JMX with 2 ports ! 1090 & 2090, 1090 for jvm mbeans, and 2090 for jboss mbeans 267 | 268 | ## For JBOSS `<` 7.x & `>=` 6 269 | 270 | ```sh 271 | JAVA_OPTS=$JAVA_OPTS" -Dcom.sun.management.jmxremote" 272 | JAVA_OPTS=$JAVA_OPTS" -Djboss.platform.mbeanserver" 273 | JAVA_OPTS=$JAVA_OPTS" -Djavax.management.builder.initial=org.jboss.system.server.jmx.MBeanServerBuilderImpl" 274 | JAVA_OPTS=$JAVA_OPTS" -Dcom.sun.management.jmxremote.port=2500" 275 | JAVA_OPTS=$JAVA_OPTS" -Dcom.sun.management.jmxremote.authenticate=false" 276 | JAVA_OPTS=$JAVA_OPTS" -Dcom.sun.management.jmxremote.ssl=false" 277 | ``` 278 | 279 | TAKE CARE, to avoid error messages (ignored by processing), add jboss client jars to classpath 280 | 281 | 282 | ## For Jetty 283 | Use standard JSR 160 jmxrmi connection, but jetty mbeans must be enabled as by 284 | adding such block in jetty.xml configuration file, and setting all "StatsOn" to true : 285 | 286 | ```xml 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | ``` 300 | 301 | works at least for jetty 6.1.x. jetty-management.jar must be added to the classpath if not already present. 302 | 303 | 304 | ## For JBOSS 7.x 305 | 306 | jboss-client.jar must be added to the classpath 307 | 308 | To specify the right management listening network interface 309 | ```sh 310 | JAVA_OPTS="$JAVA_OPTS -Djboss.bind.address.management=10.134.115.167" 311 | ``` 312 | 313 | ``` 314 | test@testhost ~/servers/jboss/bin $ ./add-user.sh 315 | 316 | What type of user do you wish to add? 317 | a) Management User (mgmt-users.properties) 318 | b) Application User (application-users.properties) 319 | (a): a 320 | 321 | Enter the details of the new user to add. 322 | Realm (ManagementRealm) : 323 | Username : admin 324 | Password : 325 | Re-enter Password : 326 | The username 'admin' is easy to guess 327 | Are you sure you want to add user 'admin' yes/no? yes 328 | About to add user 'admin' for realm 'ManagementRealm' 329 | Is this correct yes/no? yes 330 | Added user 'admin' to file '/opt/servers/jboss-as-7.1.1.Final/standalone/configuration/mgmt-users.properties' 331 | Added user 'admin' to file '/opt/servers/jboss-as-7.1.1.Final/domain/configuration/mgmt-users.properties' 332 | ``` 333 | 334 | ### Small checks with JBOSS 7 335 | 336 | #### using jconsole : 337 | ``` 338 | ./jboss-as-7.1.1.Final/bin/jconsole.sh 339 | service:jmx:remoting-jmx://10.134.115.167:9999 username = admin password = "" 340 | 341 | if local jboss : 342 | jconsole -J-Djava.class.path=/home/dcr/.gentoo/java-config-2/current-user-vm/lib/jconsole.jar:/home/dcr/.gentoo/java-config-2/current-user-vm/lib/tools.jar:/home/dcr/servers/jboss-as-7.1.1.Final/lib/jboss-client.jar 343 | ``` 344 | 345 | #### using JAJMX : 346 | ``` 347 | $ java -classpath ./bin/client/jboss-client.jar:/opt/analysis/analysis.jar com.orange.analysis.Main 348 | Welcome to Scala version 2.10.0 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_09). 349 | Type in expressions to have them evaluated. 350 | Type :help for more information. 351 | 352 | scala> val jmx = jajmx.JMX("10.134.115.167", 9999, Some("admin")) 353 | jmx: fr.janalyse.jmx.JMX = fr.janalyse.jmx.JMXclassicalImpl@7ccea5c6 354 | 355 | scala> jmx.whoami 356 | res0: Option[fr.janalyse.jmx.ASSignature] = Some(JBossSignature(7.1.1.Final,/home/dcr/servers/jboss-as-7.1.1.Final/standalone,jboss.as)) 357 | ``` 358 | 359 | 360 | [mavenImg]: https://img.shields.io/maven-central/v/fr.janalyse/janalyse-jmx_2.13.svg 361 | [mavenLink]: https://search.maven.org/#search%7Cga%7C1%7Cfr.janalyse.janalyse-jmx 362 | 363 | [scaladexImg]: https://index.scala-lang.org/dacr/jajmx/janalyse-jmx/latest.svg 364 | [scaladexLink]: https://index.scala-lang.org/dacr/jajmx 365 | 366 | [licenseImg]: https://img.shields.io/github/license/dacr/jajmx.svg 367 | [licenseLink]: LICENSE 368 | 369 | [codacyImg]: https://img.shields.io/codacy/ebf47b1789cd4ab4abea8a2df09f5d11.svg 370 | [codacyLink]: https://www.codacy.com/app/dacr/jajmx/dashboard 371 | 372 | [codecovImg]: https://img.shields.io/codecov/c/github/dacr/jajmx/master.svg 373 | [codecovLink]: http://codecov.io/github/dacr/jajmx?branch=master 374 | 375 | [travisImg]: https://img.shields.io/travis/dacr/jajmx.svg 376 | [travisLink]:https://travis-ci.org/dacr/jajmx 377 | -------------------------------------------------------------------------------- /RELEASE-NOTES: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------ 2 | JAJMX (JANALYSE-JMX) - JMX SCALA API 3 | Crosson David - crosson.david@gmail.com 4 | ------------------------------------------------------------------ 5 | 6 | ------------------------------------------------------------------ 7 | 0.8.3 2019-06-20 8 | 9 | - modernize for all scala releases and use git branches to manage each set of scala release, 10 | master will be kept for the latest release of the language 11 | 12 | ------------------------------------------------------------------ 13 | 0.7.4-SNAPSHOT (2016-04-08) 14 | 15 | - support for jboss/wildfly http-remoting-jmx, remote+http protocols 16 | 17 | ------------------------------------------------------------------ 18 | 0.7.2 (2015-03-19) 19 | 20 | - scala releases updates 21 | - no more support for scala 2.9.3 22 | - travis CI support 23 | 24 | ------------------------------------------------------------------ 25 | 0.7.1 (2014-09-10) 26 | 27 | - JMX attributes name&desc strings (cf RichAttribute) are now pooled (intern() is used) 28 | => decrease heap memory usage 29 | But it may required to increase string cache : 30 | -XX:StringTableSize=1001113 (default value is : 1009 , 60013 since java 6u40) 31 | http://java-performance.info/string-intern-in-java-6-7-8/ 32 | 33 | - better exception handling and arbitaring between errors and no value available 34 | 35 | - TMP REMOVED : support for jolokia jmx API (HTTP / JSON) 36 | - TMP REMOVED : json4s-native 3.2.8 dependency added for JSON parsing purposes 37 | - TMP REMOVED : httpclient 4.3.3 dependency added for HTTP client requests 38 | - TMP REMOVED : jolokia-client-java 1.2.0 dependency added 39 | 40 | - TMP DESACTIVATED : scala test because of API changes and no support for 2.9, 2.10 and 2.11 within the same release 41 | 42 | - scala 2.9.3 support 43 | - scala 2.10.4 support 44 | - scala 2.11.0 support 45 | - slf4j 1.7.7 46 | - json4s 3.2.9 47 | - scalatest 1.9.2 48 | - and various minor impact changes 49 | 50 | ------------------------------------------------------------------ 51 | 0.6.4 (2014-03-27) 52 | - added "joramActivationSpec" to the exclusion list when looking 53 | for a jmx implementation... jonas bugs, if we lookup to those kind 54 | of instances it generates a global jmx failure... 55 | - scala 2.10.3 56 | 57 | ------------------------------------------------------------------ 58 | 0.6.3 (2013-05-13) 59 | - cpuTimes in threads dumps available in option 60 | - threads & cpuTimes test case added ("Thread dumps & CPU times test") 61 | - JMX : new properties shortcuts 62 | + osName, osVersion 63 | + javaVersion, javaRuntimeVersion 64 | - JMX : new methods 65 | + threadCpuTime(id: Long) 66 | + uptime 67 | + verbosegc(state: Boolean) 68 | - ServiceThreads.threads is now a list instead of an Iterable 69 | (because groupBy, sortBy, ... will be typical operations for it...) 70 | - fix Local JMX without any options that don't exit 71 | (additionnalCleanup parameter added to JMXclassicalImpl) 72 | 73 | ------------------------------------------------------------------ 74 | 0.6.2 (2013-05-04) 75 | - NOK : Matthew fix finally not taken into account 76 | https://code.google.com/p/janalyse-jmx/issues/detail?id=2 77 | REVERTED BACK TO INITIAL IMPLEMENTATION, this fix generates errors such as 78 | "(Signature mismatch for operation dumpAllThreads: (Z, Z) should be (boolean, boolean)))" 79 | So initial implementation is the right one, will study this problem more in depth, after some fixes... 80 | - buildOperationSignature fixes with correct and tested signatures : 81 | String => "java.lang.String" 82 | Array[String] => "[Ljava.lang.String;" 83 | - add tests for all type conversions supported by buildOperationSignature 84 | - JMX.unregister method added 85 | - scala 2.10.1 86 | - scala-logging 1.0.1 87 | - sbt-eclipse 2.1.2 88 | - code source reorganized 89 | 90 | ------------------------------------------------------------------ 91 | 0.6.1 (2013-01-16) 92 | - now using sbt 0.12.1 93 | - now using sbteclipse 2.1.0 94 | - now using scalatest 1.9.1 95 | - add support for scala 2.10.0 96 | - fix a warning with a type erasure problem 97 | - trait ASSignature is now sealed 98 | - various enhancements 99 | - adding scalalogging support, thanks to Dragos Manolescu 100 | - Fix : some attributes are not discovered : 101 | error message Couln't build attributes map for MBean com.mchange.v2.c3p0:type=PooledDataSource[2vs2698saverehuwbtrg|47c39412] (null) 102 | => JMX AttributeInfo getDescription may return null value, using the Option Monad to manage that possible null value 103 | 104 | ------------------------------------------------------------------ 105 | 0.5.0 (2012-07-31) 106 | - cleanup, refactoring & general enhancements 107 | - Support for both generic host/port and jmxservice url approaches (although hidden by default) 108 | - automatic jmxurl lookups using known formats (external jars may be required, as for example jboss-client.jar) 109 | - application server type detection (whoami method) : jboss, tomcat, jonas, webmethod, jetty taken into account 110 | + more tests required against various releases... 111 | - straightforward threads dumps method added (with automatic support of dumpAllThreads starting from Java 6) 112 | - JMXOptions can now be built from a JMXServiceURL + credentials 113 | - Performance enhancements 114 | + mbeaninfos cached 115 | - README file added 116 | - JMX.apply now with optional username & password as arguments 117 | - new methods.fields to RichMBean : 118 | + attributeNames():List[String] 119 | + domain 120 | 121 | ------------------------------------------------------------------ 122 | 0.4.0 123 | - ConnectException taken into account in RichMBean genericGetter in order to propagates connection failures and allow api users to restarts... 124 | - CompositeData & TabularData support enhancements 125 | - API CHANGES !! getOption disappear, get method now returns an Option. previous get method replaced by apply. 126 | - more tests cases 127 | - jmx mbeans queries was broken, now the query is build with an object name instead of a string 128 | - findMBeanServers made public and moved to JMX object 129 | - checkServiceURL added to JMXObject : checks if a jmx service url works. 130 | - getInt added to mbean 131 | - now using sbt 0.11.3 132 | - now using sbt-assembly 0.8.1 133 | - now using sbteclipse 2.1.0-RC1 134 | - now using scalatest 1.8 135 | - trap exceptions while building attributes map in RichMBean 136 | - various examples scripts fixes 137 | 138 | 139 | ------------------------------------------------------------------ 140 | 0.3.1 141 | - Added an additionnal filter on rmi registry result list to avoid some border side effect when trying to lookup for some entries 142 | Looks like Jonas doesn't like when we lookup entries such as TMFactory or RMI_SERVER_RPC, it broke all next lookup attempts !!! 143 | (findMBeanServers) 144 | - updated for scala 2.9.2 support 145 | - scalatest 1.7.2 146 | 147 | 148 | ------------------------------------------------------------------ 149 | 0.3.0 150 | - JMXOptions now contains an extra field "name" which allow user to friendly identify a remote jmx system 151 | - added a package object jajmx to define shortcuts to fr.janalyse.jmx.JMX class and object 152 | - JMX.connect renamed to JMX.once 153 | - scaladoc generation now works fine, follow hack described here : https://github.com/harrah/xsbt/issues/85 154 | 155 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | name := "janalyse-jmx" 2 | organization :="fr.janalyse" 3 | homepage := Some(new URL("https://github.com/dacr/jajmx")) 4 | licenses += "Apache 2" -> url(s"http://www.apache.org/licenses/LICENSE-2.0.txt") 5 | scmInfo := Some(ScmInfo(url(s"https://github.com/dacr/jajmx"), s"git@github.com:dacr/jajmx.git")) 6 | 7 | scalaVersion := "2.13.1" 8 | scalacOptions ++= Seq( "-deprecation", "-unchecked", "-feature", "-language:implicitConversions", "-language:reflectiveCalls") 9 | 10 | crossScalaVersions := Seq("2.12.11", "2.13.1") 11 | // 2.10.7 : generates java 6 bytecodes 12 | // 2.11.12 : generates java 6 bytecodes 13 | // 2.12.8 : generates java 8 bytecodes && JVM8 required for compilation 14 | // 2.13.0 : generates java 8 bytecodes && JVM8 required for compilation 15 | 16 | libraryDependencies ++= Seq( 17 | "org.slf4j" % "slf4j-api" % "1.7.30", 18 | "org.scalatest" %% "scalatest" % "3.1.1" % "test" 19 | ) 20 | 21 | testOptions in Test += { 22 | val rel = scalaVersion.value.split("[.]").take(2).mkString(".") 23 | Tests.Argument( 24 | "-oDF", // -oW to remove colors 25 | "-u", s"target/junitresults/scala-$rel/" 26 | ) 27 | } 28 | 29 | initialCommands in console := 30 | """ 31 | |import fr.janalyse.jmx._ 32 | |import javax.management.ObjectName 33 | |""".stripMargin 34 | 35 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.3.8 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.13") 2 | addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.0") 3 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.2") 4 | 5 | addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.6.1") 6 | addSbtPlugin("com.codacy" % "sbt-codacy-coverage" % "3.0.3") 7 | -------------------------------------------------------------------------------- /publish.sbt: -------------------------------------------------------------------------------- 1 | pomIncludeRepository := { _ => false } 2 | 3 | releaseCrossBuild := true 4 | releasePublishArtifactsAction := PgpKeys.publishSigned.value 5 | publishMavenStyle := true 6 | publishArtifact in Test := false 7 | publishTo := Some(if (isSnapshot.value) Opts.resolver.sonatypeSnapshots else Opts.resolver.sonatypeStaging) 8 | 9 | PgpKeys.useGpg in Global := true // workaround with pgp and sbt 1.2.x 10 | pgpSecretRing := pgpPublicRing.value // workaround with pgp and sbt 1.2.x 11 | 12 | pomExtra in Global := { 13 | 14 | 15 | dacr 16 | David Crosson 17 | https://github.com/dacr 18 | 19 | 20 | } 21 | 22 | 23 | import ReleaseTransformations._ 24 | releaseProcess := Seq[ReleaseStep]( 25 | checkSnapshotDependencies, 26 | inquireVersions, 27 | //runClean, 28 | runTest, 29 | setReleaseVersion, 30 | commitReleaseVersion, 31 | tagRelease, 32 | publishArtifacts, 33 | setNextVersion, 34 | commitNextVersion, 35 | releaseStepCommand("sonatypeReleaseAll"), 36 | pushChanges 37 | ) 38 | -------------------------------------------------------------------------------- /scripts/gcforce: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | exec java -verbosegc -jar jajmx.jar "$0" "$@" 3 | !# 4 | 5 | if (args.size < 2) { 6 | println("Usage : jmxgrep host port") 7 | println(" args are not compliant so now let's connecting to myself, and force a gc...") 8 | } 9 | 10 | import fr.janalyse.jmx._ 11 | 12 | val options = args.toList match { 13 | case host::port::_ => Some(JMXOptions(host,port.toInt)) 14 | case _ => None 15 | } 16 | JMX.once(options) { _.gcforce } 17 | 18 | -------------------------------------------------------------------------------- /scripts/jmxgrep: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | exec java -jar jajmx.jar "$0" "$@" 3 | !# 4 | 5 | import jajmx._ 6 | 7 | if (args.size == 0) { 8 | println("Usage : jmxgrep host port - searchMask1 ... searchMaskN") 9 | println(" if no args given so now let's connecting to myself, and list my mbeans...") 10 | } 11 | 12 | val (options, masks) = args.toList match { 13 | case host::port::"-"::masks => 14 | (Some(JMXOptions(host,port.toInt)), masks.map{s=>("(?i)"+s).r}) 15 | case "-"::masks => (None, masks.map{s=>("(?i)"+s).r}) 16 | case _ => (None,List.empty[util.matching.Regex]) 17 | } 18 | 19 | def truncate(str:String, n:Int=60) = { 20 | val nonl=str.replaceAll("\n", " ").replaceAll("\r", "") 21 | if (nonl.size>n) nonl.take(n)+"..." else nonl 22 | } 23 | 24 | JMX.once(options) { jmx => 25 | for { 26 | mbean <- jmx.mbeans 27 | attr <- mbean.attributes 28 | value <- mbean.getString(attr) } { 29 | 30 | val found = List(mbean.name, attr.name, value).exists{item => 31 | masks.exists{_.findFirstIn(item).isDefined } 32 | } 33 | if (masks.isEmpty || found) 34 | println(s"${mbean.name} - ${attr.name} = ${truncate(value)}") 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /scripts/lsnum: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | exec java -jar jajmx.jar "$0" "$@" 3 | !# 4 | 5 | import fr.janalyse.jmx._ 6 | 7 | val options = args.toList match { 8 | case host::port::_ => Some(JMXOptions(host,port.toInt)) 9 | case _ => None 10 | } 11 | 12 | JMX.once(options) { jmx => 13 | for { 14 | mbean <- jmx.mbeans 15 | attr <- mbean.attributes.collect{case n:RichNumberAttribute => n} 16 | value <- mbean.getLong(attr) 17 | } { 18 | println(s"${mbean.name} - ${attr.name} = ${value}") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /scripts/lsthreads: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | exec java -jar jajmx.jar "$0" "$@" 3 | !# 4 | import jajmx._ 5 | 6 | val options = args.toList match { 7 | case host::port::_ => Some(JMXOptions(host,port.toInt)) 8 | case _ => None 9 | } 10 | 11 | JMX.once(options) { jmx => 12 | for (dump <- jmx.threadsDump(0)) { 13 | val threads = dump.threads 14 | val countByState = 15 | threads 16 | .groupBy(_.status) 17 | .map{ case (state,sublist) => state -> sublist.size} 18 | .map{ case (state,count) => state+":"+count} 19 | .mkString(" ") 20 | 21 | println("Total %d threads, %s".format(threads.size, countByState)) 22 | for ( ti <- threads sortBy {_.id } ) { 23 | println("%d - %s - %s".format(ti.id, ti.status, ti.name) ) 24 | } 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /scripts/shortest: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | exec java -jar jajmx.jar "$0" "$@" 3 | !# 4 | 5 | jajmx.JMX.once() { jmx => 6 | jmx.verbosegc() 7 | jmx.gcforce() 8 | } 9 | -------------------------------------------------------------------------------- /src/main/scala/fr/janalyse/jmx/ASSignature.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 David Crosson 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 | package fr.janalyse.jmx 17 | 18 | sealed trait ASSignature { 19 | val release: String 20 | val home: String 21 | val domain: String // jmx domain where to find all mbeans or "". 22 | val family: String // AS family name : jboss, tomcat, jetty, jonas, ... 23 | } 24 | 25 | case class TomcatSignature( 26 | release: String, // tomcat release 27 | home: String, // tomcat home directory (CATALINA_HOME) 28 | domain: String = "Catalina" // tomcat JMX Domain 29 | ) extends ASSignature { 30 | val family = "tomcat" 31 | } 32 | 33 | case class JettySignature( 34 | release: String, 35 | home: String, 36 | domain: String = "") extends ASSignature { 37 | val family = "jetty" 38 | } 39 | 40 | case class WebMethodsSignature( 41 | release: String, // Webmethod release 42 | home: String, // Webmethod home directory (WM_HOME) 43 | domain: String = "" // Not yet used 44 | // com.webmethods.sc.config.configRoot 45 | ) extends ASSignature { 46 | val family = "webmethods" 47 | } 48 | 49 | case class JonasSignature( 50 | release: String, // jonas release 51 | domain: String, // jonas JMX domain name 52 | name: String, // jonas Instance name 53 | home: String, // jonasBase jmx value or jonas.base property value 54 | root: String // jonasRoot jmx value or jonas.root property value 55 | ) extends ASSignature { 56 | val family = "jonas" 57 | } 58 | 59 | case class JBossSignature( 60 | release: String, // jboss release 61 | home: String, // jboss home directory (WM_HOME) 62 | domain: String = "jboss.as" // jboss.as 63 | ) extends ASSignature { 64 | val family = "jboss" 65 | } 66 | 67 | object ASSignature { 68 | private def getJonasDomain(jmx: JMX) = jmx.domains find { domain => 69 | !jmx.mbeans("%s:j2eeType=J2EEServer,*".format(domain)).isEmpty 70 | } 71 | private def getJonasName(jmx: JMX, domain: String): Option[String] = 72 | jmx.mbeans("%s:j2eeType=J2EEServer,*".format(domain)).flatMap(_.keys.get("name")).headOption 73 | 74 | private def lookup4jonasSignature(jmx: JMX): Option[ASSignature] = { 75 | for ( 76 | domain <- getJonasDomain(jmx); 77 | name <- getJonasName(jmx, domain); 78 | j2eeserver <- jmx.get("%s:j2eeType=J2EEServer,name=%s".format(domain, name)); 79 | asname <- j2eeserver.getString("serverName"); 80 | rel <- j2eeserver.getString("serverVersion") 81 | ) yield { 82 | lazy val basedirFromEngine = jmx.get("%s:type=Engine".format(domain)) flatMap { _.getString("baseDir") } 83 | lazy val basedirFromProps = jmx.systemProperties.get("jonas.base") 84 | val jonasBase = j2eeserver.getString("jonasBase") getOrElse (basedirFromEngine getOrElse basedirFromProps.get) // TODO not 100% safe 85 | lazy val rootdirFromProps = jmx.systemProperties.get("jonas.root") orElse jmx.systemProperties.get("install.root") 86 | val jonasRoot = j2eeserver.getString("jonasRoot") getOrElse rootdirFromProps.get // TODO not 100% safe 87 | 88 | JonasSignature(rel, domain, name, jonasBase, jonasRoot) 89 | } 90 | } 91 | 92 | private def lookup4jettySignature(jmx: JMX): Option[ASSignature] = { 93 | val jettydomains = jmx.domains.filter(_.contains("jetty")) 94 | if (jettydomains.isEmpty) None else { 95 | val mbs = jettydomains.flatMap(jd => jmx.mbeans(jd + ":*")) 96 | for ( 97 | mb <- mbs.find(_.attributesNames.contains("version")); 98 | version <- mb.getString("version"); 99 | home <- jmx.systemProperties.get("server.home"); 100 | name <- jmx.systemProperties.get("server.name") 101 | ) yield { 102 | JettySignature(release = version, home = home, domain = mb.domain) 103 | } 104 | } 105 | } 106 | 107 | private def lookup4webmethodsSignature(jmx: JMX) = { 108 | val wmIsBaseDirOption = (jmx.systemProperties.get("WM_HOME") map { _ + "/IntegrationServer" }) orElse { 109 | jmx.get("WmTomcat:type=Engine") map { engine => 110 | engine[String]("baseDir").split("/").init.mkString("/") 111 | } 112 | } 113 | for ( 114 | wmhome <- wmIsBaseDirOption 115 | ) yield { 116 | val isjars = jmx.systemProperties.get("wm.server.class.path") map { _.split(":").toList filter { _.contains("IS") } } 117 | val relsfromjar = isjars map { _.map(_.split("/").last.replace("IS_", "").replace(".jar", "").replace("-", ".")) } 118 | val rel = relsfromjar map { _.head } 119 | 120 | WebMethodsSignature(rel getOrElse "unknown", wmhome) 121 | } 122 | } 123 | 124 | private def lookup4jbossSignature(jmx: JMX) = { 125 | // At least ok with 7.1.x; TODO check with older releases 126 | for ( 127 | jbossas <- jmx.get("jboss.as:management-root=server"); 128 | release <- jbossas.getString("releaseVersion"); 129 | core <- jmx.get("jboss.as:core-service=server-environment"); 130 | baseDir <- core.getString("baseDir") 131 | ) yield { 132 | JBossSignature( 133 | release = release, 134 | home = baseDir 135 | ) 136 | } 137 | } 138 | 139 | private def lookup4tomcatSignature(jmx: JMX) = { 140 | for ( 141 | catalina <- jmx.get("Catalina:type=Server"); 142 | engine <- jmx.get("Catalina:type=Engine"); 143 | serverInfo <- catalina.getString("serverInfo"); 144 | Array(id, rel) <- Option(serverInfo.split("/", 2)) 145 | ) yield { 146 | lazy val basedirFromProps = jmx.systemProperties.get("catalina.home") 147 | val basedir = engine.getString("baseDir") getOrElse basedirFromProps.get 148 | TomcatSignature(rel, basedir) 149 | } 150 | } 151 | 152 | def whoami(jmx: JMX): Option[ASSignature] = { 153 | // The order is important, most specific to the most general. 154 | // (for example tomcat is sometimes embedded into other application servers) 155 | val searchOrder: Stream[Option[ASSignature]] = Stream( 156 | lookup4jbossSignature(jmx), 157 | lookup4jettySignature(jmx), 158 | lookup4jonasSignature(jmx), 159 | lookup4webmethodsSignature(jmx), 160 | lookup4tomcatSignature(jmx) 161 | ) 162 | searchOrder.find(_.isDefined).map(_.get) 163 | } 164 | } 165 | 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /src/main/scala/fr/janalyse/jmx/AttributeMetaData.scala: -------------------------------------------------------------------------------- 1 | package fr.janalyse.jmx 2 | 3 | case class AttributeMetaData( 4 | name:String, 5 | adesc:String, 6 | atype:String, 7 | rw:Boolean) 8 | -------------------------------------------------------------------------------- /src/main/scala/fr/janalyse/jmx/Credentials.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 David Crosson 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 | package fr.janalyse.jmx 17 | 18 | case class Credentials(username: String = "", password: String = "") 19 | -------------------------------------------------------------------------------- /src/main/scala/fr/janalyse/jmx/DataWrappers.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 David Crosson 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 | package fr.janalyse.jmx 17 | 18 | import scala.collection.JavaConverters._ 19 | 20 | import javax.management.openmbean.TabularDataSupport 21 | import javax.management.openmbean.TabularData 22 | import javax.management.openmbean.CompositeDataSupport 23 | import javax.management.openmbean.CompositeData 24 | 25 | case class CompositeDataWrapper[CDT <: CompositeData](cd: CDT) { 26 | lazy val content: Map[String, Object] = { 27 | val keys = cd.getCompositeType().keySet().asScala 28 | val tuples = keys.toList map { k => (k -> cd.get(k)) } 29 | tuples.toMap 30 | } 31 | def toWrapper() = this 32 | //def get(key:String):Option[Object] = content.get(key) 33 | def getString(key: String): Option[String] = content.get(key) map { _.toString } 34 | def get[A](key: String): Option[A] = content.get(key) map { _.asInstanceOf[A] } 35 | 36 | // TODO Improve number support and add more typed getter for basic types 37 | def getNumber[N >: Number](key: String): Option[N] = { 38 | for (raw <- content.get(key)) yield { 39 | raw match { 40 | case e: java.lang.Byte => e.toDouble 41 | case e: java.lang.Short => e.toDouble 42 | case e: java.lang.Integer => e.toDouble 43 | case e: java.lang.Long => e.toDouble 44 | case e: java.lang.Float => e.toDouble 45 | case e: java.lang.Double => e 46 | case e: java.lang.String => e.toDouble 47 | } 48 | } 49 | } 50 | } 51 | 52 | case class TabularDataWrapper[TD <: TabularData](tabularData: TD) { 53 | lazy val content: Map[String, Map[String, Object]] = { 54 | val indexNamesInCell = tabularData.getTabularType().getIndexNames() 55 | val tuples = tabularData.values.asScala collect { 56 | case v: CompositeDataSupport => 57 | val cellContent = v.content 58 | val key = indexNamesInCell.asScala map { cellContent.get(_).get } mkString ("-") 59 | val submap = cellContent filterNot { case (k, _) => indexNamesInCell contains k } 60 | 61 | key -> submap 62 | 63 | case (k /*: java.util.List[String]*/ , v: CompositeData) => 64 | val cellContent = v.content 65 | val key = indexNamesInCell.asScala map { cellContent.get(_).get } mkString ("-") 66 | val submap = cellContent filterNot { case (k, _) => indexNamesInCell contains k } 67 | 68 | key -> submap 69 | } 70 | tuples.toMap 71 | } 72 | def toWrapper() = this 73 | def get(key: String): Option[Map[String, Object]] = content.get(key) 74 | def get(key: String, subkey: String): Option[Object] = content.get(key) flatMap { _.get(subkey) } 75 | def getString(key: String, subkey: String): Option[String] = get(key, subkey) map { _.toString } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/scala/fr/janalyse/jmx/JMX.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 David Crosson 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 | package fr.janalyse.jmx 17 | 18 | import java.lang.management.ManagementFactory 19 | import javax.management.ObjectName 20 | import javax.management.remote.rmi.RMIConnection 21 | import javax.management.remote.rmi.RMIServer 22 | import javax.management.remote.rmi.RMIServerImpl_Stub 23 | import javax.management.openmbean.{ CompositeData, CompositeDataSupport } 24 | import javax.management.openmbean.{ TabularData, TabularDataSupport } 25 | import java.rmi.MarshalledObject 26 | import java.rmi.UnmarshalException 27 | import javax.management.openmbean.CompositeData 28 | import scala.collection.JavaConverters._ 29 | import javax.management.remote.JMXConnector 30 | import javax.management.remote.JMXServiceURL 31 | import javax.management.remote.{ JMXConnectorServerFactory, JMXConnectorFactory } 32 | import java.rmi.server.RMIClientSocketFactory 33 | import javax.management.remote.rmi.RMIJRMPServerImpl 34 | import java.rmi.server.RMIServerSocketFactory 35 | import javax.net.ServerSocketFactory 36 | import java.net.ServerSocket 37 | import java.rmi.server.RMISocketFactory 38 | import javax.management.RuntimeMBeanException 39 | import scala.math.ScalaNumber 40 | import javax.management.MBeanInfo 41 | import javax.management.MBeanServerConnection 42 | import javax.management.remote.JMXConnectorServer 43 | import java.rmi.registry.LocateRegistry 44 | 45 | trait JMX { 46 | val options: Option[JMXOptions] 47 | // Service name introduced in order to mixup threads dumps, and allow data processing (eg groupBy) 48 | val serviceName = options.flatMap(_.name) getOrElse "default" 49 | 50 | def close() 51 | def exists(name: String): Boolean 52 | def domains: List[String] 53 | def names(query:String):List[String] 54 | def mbeans(query: String): List[RichMBean] 55 | def names():List[String]=names("*:*") 56 | def mbeans(): List[RichMBean]=mbeans("*:*") 57 | def apply(name: String): RichMBean 58 | def get(name: String): Option[RichMBean] = if (exists(name)) Some(apply(name)) else None 59 | 60 | def whoami(): Option[ASSignature] = ASSignature.whoami(this) 61 | 62 | def os() = get("java.lang:type=OperatingSystem") 63 | def runtime() = get("java.lang:type=Runtime") 64 | def threading() = get("java.lang:type=Threading") 65 | def memory() = get("java.lang:type=Memory") 66 | def classLoading() = get("java.lang:type=ClassLoading") 67 | def compilation() = get("java.lang:type=Compilation") 68 | 69 | def gcforce() { memory.map(_.call("gc")) } 70 | 71 | def verbosegc(state:Boolean=true) { memory.map(_.set("Verbose", state)) } 72 | 73 | def threadCpuTime(id:Long) = { 74 | for { 75 | th <- threading 76 | cpuTime <- getThreadCpuTime(id, th) 77 | } yield cpuTime 78 | } 79 | 80 | def uptimeInMs = for { 81 | rt <- runtime 82 | uptime <- rt.get[Long]("Uptime") 83 | } yield uptime 84 | 85 | 86 | 87 | def threadsDump(stackMaxDepth: Int = 9999, withCpuTime:Boolean=false): Option[ServiceThreads] = { 88 | javaSpecificationVersion match { 89 | case Some("1.5") => incrementalThreadDump(stackMaxDepth, withCpuTime) 90 | case _ => simultaneousThreadDump(stackMaxDepth, withCpuTime) 91 | } 92 | } 93 | 94 | private def getThreadCpuTime(id:Long, threading:RichMBean) = 95 | threading.call[Long]("getThreadCpuTime", id).filter(_>=0) 96 | 97 | private def incrementalThreadDump(stackMaxDepth: Int, withCpuTime:Boolean): Option[ServiceThreads] = { // Java 5 98 | for { 99 | th <- threading 100 | ids <- th.get[Array[Long]]("AllThreadIds") 101 | infos <- th.call[Array[CompositeData]]("getThreadInfo", ids, stackMaxDepth) 102 | } yield { 103 | val threads = 104 | infos 105 | .toList 106 | .filterNot(_ == null) 107 | .filter(_.get("threadId") != null) 108 | .map(cd => ThreadInfo(serviceName, cd)) 109 | 110 | 111 | val cpuTimes:Map[Long,Option[Long]] = 112 | if (withCpuTime) threads.map(_.id).map(id => id->getThreadCpuTime(id,th)).toMap 113 | else Map.empty 114 | ServiceThreads(serviceName, threads, cpuTimes) 115 | } 116 | } 117 | 118 | private def simultaneousThreadDump(stackMaxDepth: Int, withCpuTime:Boolean): Option[ServiceThreads] = { // Starting from Java 6 119 | for { 120 | th <- threading 121 | infos <- th.call[Array[CompositeData]]("dumpAllThreads", true, true) 122 | } yield { 123 | val threads = infos.map(ThreadInfo(serviceName, _)).toList 124 | /* TODO : Operation not always available, understand why... 125 | val cpuTimes:Map[Long,Option[Long]] = if (withCpuTime) { 126 | val ids = threads.map(_.id).toArray 127 | th.call[Array[Long]]("getThreadCpuTime", ids) 128 | .map{ ct => ids.zip(ct).map{case (id,thcpu)=> id->Option(thcpu)}.toMap} 129 | .getOrElse(Map.empty) 130 | } else Map.empty 131 | */ 132 | val cpuTimes:Map[Long,Option[Long]] = 133 | if (withCpuTime) threads.map(_.id).map(id => id->getThreadCpuTime(id,th)).toMap 134 | else Map.empty 135 | ServiceThreads(serviceName, threads, cpuTimes) 136 | } 137 | } 138 | 139 | lazy val javaHome: Option[String] = systemProperties.get("java.home") 140 | lazy val userHome: Option[String] = systemProperties.get("user.home") 141 | lazy val userName: Option[String] = systemProperties.get("user.name") 142 | lazy val osName: Option[String] = systemProperties.get("os.name") 143 | lazy val osVersion: Option[String] = systemProperties.get("os.version") 144 | lazy val javaVersion : Option[String] = systemProperties.get("java.version") 145 | lazy val javaRuntimeVersion : Option[String] = systemProperties.get("java.runtime.version") 146 | lazy val javaSpecificationVersion: Option[String] = systemProperties.get("java.specification.version") // 1.5, 1.6, ... 147 | 148 | lazy val systemProperties = { 149 | var sysprops = Map.empty[String, String] 150 | for { 151 | rt <- runtime; 152 | props <- rt.get[TabularDataSupport]("SystemProperties"); 153 | (key, values) <- props.content; 154 | value <- values.get("value") 155 | } { sysprops += key -> value.toString } 156 | sysprops 157 | } 158 | protected def buildOperationSignature(args: Array[Any]) = { 159 | /* */ 160 | // http://home.pacifier.com/~mmead/jni/cs510ajp/index.html / http://en.wikipedia.org/wiki/Java_Native_Interface#Mapping_types 161 | args map { 162 | case _: Int => "int" 163 | case _: Short => "short" 164 | case _: Long => "long" 165 | case _: Float => "float" 166 | case _: Double => "double" 167 | case _: Boolean => "boolean" 168 | case _: Byte => "byte" 169 | case _: Char => "char" 170 | case _: String => "java.lang.String" 171 | case _: Array[Int] => "[I" 172 | case _: Array[Short] => "[S" 173 | case _: Array[Long] => "[J" 174 | case _: Array[Float] => "[F" 175 | case _: Array[Double] => "[D" 176 | case _: Array[Boolean] => "[Z" 177 | case _: Array[Byte] => "[B" 178 | case _: Array[Char] => "[C" 179 | case _: Array[String] => "[Ljava.lang.String;" 180 | } 181 | } 182 | 183 | def getAttributesMetaData(objectName: ObjectName):List[AttributeMetaData] 184 | 185 | def getAttributesMap(objectName: ObjectName): Map[String, RichAttribute] = { 186 | val result = for { 187 | meta <- getAttributesMetaData(objectName) 188 | } yield { 189 | val n = meta.name.intern() 190 | val d = Option(meta.adesc).map(_.trim).filterNot(_.size == 0).filterNot(_ == n).map(_.intern()) 191 | val a: RichAttribute = meta.atype match { 192 | case "java.lang.Boolean" | "boolean" | "java.lang.boolean" => RichBooleanAttribute(n, d) 193 | case "java.lang.Byte" | "byte" => RichByteAttribute(n, d) 194 | case "java.lang.Short" | "short" => RichShortAttribute(n, d) 195 | case "java.lang.Integer" | "int" => RichIntAttribute(n, d) 196 | case "java.lang.Long" | "long" => RichLongAttribute(n, d) 197 | case "java.lang.Float" | "float" => RichFloatAttribute(n, d) 198 | case "java.lang.Double" | "double" => RichDoubleAttribute(n, d) 199 | case "java.lang.String" | "String" => RichStringAttribute(n, d) 200 | //case "java.util.List" => 201 | //case "java.util.Properties" => 202 | case "[Ljava.lang.String;" => RichStringArrayAttribute(n, d) 203 | //case "javax.management.openmbean.TabularData" => 204 | case "javax.management.openmbean.CompositeData" 205 | |"javax.management.openmbean.CompositeDataSupport" => RichCompositeDataAttribute(n, d) 206 | //case "[Ljavax.management.openmbean.CompositeData;" => 207 | //case "[Ljavax.management.ObjectName;" => 208 | case x => 209 | //println("Warning: Not supported jmx attribute value type %s for %s mbean %s".format(x, n, name)) 210 | //println(x) 211 | RichGenericAttribute(n, d) 212 | } 213 | n -> a 214 | } 215 | result.toMap 216 | } 217 | 218 | } 219 | 220 | object JMX extends LazyLogging { 221 | def register(ob: Object, obname: ObjectName) = ManagementFactory.getPlatformMBeanServer.registerMBean(ob, obname) 222 | def unregister(obname: ObjectName) = ManagementFactory.getPlatformMBeanServer.unregisterMBean(obname) 223 | 224 | def usingJMX[T <: { def close() }, R](resource: T)(block: T => R) = { 225 | try block(resource) 226 | finally resource.close 227 | } 228 | 229 | def once[R]( 230 | host: String, 231 | port: Int, 232 | username: Option[String] = None, 233 | password: Option[String] = None)(block: JMX => R): R = { 234 | once[R](JMXOptions(host = host, port = port, username = username, password = password))(block) 235 | } 236 | 237 | def once[R]()(block: JMX => R): R = once[R](someOptions=None)(block) 238 | def once[R](options: JMXOptions)(block: JMX => R): R = once[R](Some(options))(block) 239 | def once[R](someOptions: Option[JMXOptions])(block: JMX => R): R = usingJMX(getImpl(someOptions)) { block(_) } 240 | def once[R](url: String)(block: JMX => R): R = usingJMX(getImpl(url)) { block(_) } 241 | 242 | def apply(options: JMXOptions) = getImpl(Some(options)) 243 | def apply(options: Option[JMXOptions]) = getImpl(options) 244 | //def apply(host: String, port: Int) = getImpl(Some(JMXOptions(host = host, port = port))) 245 | def apply(host: String, port: Int, username: Option[String] = None, password: Option[String] = None) = getImpl(Some(JMXOptions(host = host, port = port, username = username, password = password))) 246 | def apply() = getImpl(options=None) 247 | def apply(serviceurl:String) = getImpl(serviceurl) 248 | 249 | // For jboss "jboss-client.jar" is mandatory 250 | def jboss1ServiceURL(host: String, port:Int = 9990) = "service:jmx:remote+http://%s:%d".format(host,port) 251 | def jboss2ServiceURL(host: String, port:Int = 9990) = "service:jmx:http-remoting-jmx://%s:%d".format(host,port) 252 | def jboss3ServiceURL(host: String, port: Int = 9999) = "service:jmx:remoting-jmx://%s:%d".format(host, port) 253 | def jonasServiceURL(host: String, port: Int = 1099, name: String = "jonas") = "service:jmx:rmi:///jndi/rmi://%s:%d/jrmpconnector_%s".format(host, port, name) 254 | def jsr160ServiceURL(host: String, port: Int = 2500) = "service:jmx:rmi:///jndi/rmi://%s:%d/jmxrmi".format(host, port) 255 | 256 | private def getImpl(options: Option[JMXOptions] = None): JMX = { 257 | options match { 258 | case None => getLocalImpl() // For quick test or evaluation purposes 259 | case Some(cfg) => 260 | jolokiaLookup(cfg) orElse jmxLookup(cfg) getOrElse { 261 | throw new RuntimeException("Couldn't find any jmx connector for %s".format(cfg.toString)) 262 | } 263 | } 264 | } 265 | 266 | private def getImpl(url:String) : JMX = { 267 | val serviceURL = new JMXServiceURL(url) 268 | val connector = JMXConnectorFactory.connect(serviceURL) 269 | new JMXclassicalImpl(connector) 270 | } 271 | 272 | private def jolokiaLookup(opt: JMXOptions) : Option[JMX] = { 273 | /* 274 | import org.apache.http.impl.client._ 275 | import org.apache.http.client.methods.HttpGet 276 | import org.apache.http.auth._ 277 | import org.apache.http.util.EntityUtils 278 | 279 | import opt.{host,port} 280 | 281 | def getHttpClient() = { 282 | val credsProvider = new BasicCredentialsProvider() 283 | for { username <- opt.username ; password <- opt.password } { 284 | credsProvider.setCredentials( 285 | new AuthScope(host, port), 286 | new UsernamePasswordCredentials(username, password)) 287 | } 288 | HttpClients.custom() 289 | .setDefaultCredentialsProvider(credsProvider) 290 | .build() 291 | } 292 | 293 | val httpclient = getHttpClient() 294 | try { 295 | val context = opt.contextbase.getOrElse("/jolokia") 296 | val baseUrl = s"http://$host:$port" + context 297 | val testUrl = baseUrl + "/version" 298 | val httpget = new HttpGet(testUrl) 299 | val response = httpclient.execute(httpget) 300 | try { 301 | val rc = response.getStatusLine().getStatusCode() 302 | val entity = response.getEntity 303 | val content = io.Source.fromInputStream(entity.getContent).getLines().mkString("\n") 304 | EntityUtils.consume(entity) 305 | if (content.contains("jolokia")) 306 | Some(new JMXjolokiaImpl(getHttpClient, baseUrl, Some(opt.copy(contextbase=Some(context))))) 307 | else None 308 | } finally { 309 | response.close() 310 | } 311 | } catch { 312 | case e:java.net.SocketException => None // Nothing behind the specified port 313 | case e:org.apache.http.NoHttpResponseException => None // Not Http response ! 314 | } finally { 315 | httpclient.close() 316 | } 317 | * 318 | */ 319 | None 320 | } 321 | 322 | private def jmxLookup(opt: JMXOptions): Option[JMX] = { 323 | getMBeanServerFromKnownJMXServiceUrl(opt) orElse { 324 | JMX.findMBeanServers(opt.host, opt.port, opt.credentials) 325 | .headOption 326 | .map(new JMXrmiImpl(_, Some(opt))) 327 | } 328 | } 329 | 330 | private def getMBeanServerFromKnownJMXServiceUrl(opt: JMXOptions): Option[JMX] = { 331 | opt.url map { u => new JMXclassicalImpl(jmxurl2connector(u, opt.credentials), Some(opt)) } orElse { 332 | val urls = Stream( 333 | jboss1ServiceURL(opt.host, opt.port), 334 | jboss2ServiceURL(opt.host, opt.port), 335 | jboss3ServiceURL(opt.host, opt.port), 336 | jonasServiceURL(opt.host, opt.port, opt.name.getOrElse("jonas")), 337 | jsr160ServiceURL(opt.host, opt.port) 338 | ).map(new JMXServiceURL(_)) 339 | urls.find(jmxurlAlive(_, opt.credentials)) 340 | .map(jmxurl2connector(_, opt.credentials)) 341 | .map(new JMXclassicalImpl(_, Some(opt))) 342 | } 343 | } 344 | 345 | def jmxurl2connector(url: JMXServiceURL, credentialsopt: Option[Credentials] = None): JMXConnector = { 346 | //println(credentialsopt) 347 | credentialsopt match { 348 | case None => JMXConnectorFactory.connect(url) 349 | case Some(credentials) => 350 | val env = new java.util.HashMap[String, Any]() 351 | env.put(JMXConnector.CREDENTIALS, Array[String](credentials.username, credentials.password)) 352 | JMXConnectorFactory.connect(url, env) 353 | } 354 | } 355 | 356 | def jmxurlAlive(url: JMXServiceURL, credentialsopt: Option[Credentials]): Boolean = { 357 | val result = try { 358 | val connector = jmxurl2connector(url, credentialsopt) 359 | var jmx: Option[JMX] = None 360 | try { 361 | jmx = Some(new JMXclassicalImpl(connector)) 362 | jmx.map(_.domains.size >= 0) getOrElse false 363 | } catch { 364 | case _: Exception => false 365 | } finally { 366 | jmx.foreach(_.close) 367 | } 368 | } catch { 369 | case _: Exception => false 370 | } 371 | //println(url.toString + " ==> " + result) 372 | 373 | result 374 | } 375 | 376 | private def getLocalImpl(): JMX = { 377 | val (jmxurl, chosenPort, additionnalCleaning) = JMX.jmxLocalInit() 378 | logger.warn("WARNING : No JMX options given, automatic self connect done (%s)".format(jmxurl.toString)) 379 | new JMXclassicalImpl(jmxurl2connector(jmxurl), additionalCleaning = Some(additionnalCleaning)) 380 | } 381 | 382 | def checkServiceURL(urlstr: String): String = { 383 | val url = new JMXServiceURL(urlstr) 384 | var jmxc: Option[JMXConnector] = None 385 | try { 386 | jmxc = Some(JMXConnectorFactory.connect(url)) 387 | val mbsc = jmxc.get.getMBeanServerConnection() 388 | val domains = mbsc.getDomains() 389 | } finally { jmxc.foreach(_.close) } 390 | urlstr 391 | } 392 | 393 | // Self init procedure, of course more for tests or eval purposes 394 | private def jmxLocalInit(): Tuple3[JMXServiceURL, Int, ()=>Any ] = { 395 | // TODO : Not safe... 396 | val csf = RMISocketFactory.getDefaultSocketFactory() 397 | var chosenPort: Option[Int] = None 398 | val ssf = new RMIServerSocketFactory { 399 | def createServerSocket(port: Int): ServerSocket = { 400 | val ssock = ServerSocketFactory.getDefault().createServerSocket(port) 401 | chosenPort = Some(ssock.getLocalPort()) 402 | ssock 403 | } 404 | } 405 | LocateRegistry.createRegistry(0, csf, ssf) 406 | val url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:%d/jmxapitestrmi".format(chosenPort.get)) 407 | val mbs = ManagementFactory.getPlatformMBeanServer() 408 | val cs = JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbs) 409 | cs.start() 410 | //Thread.sleep(500) // TODO : To remove, how to know when everything is ready ? 411 | while(!cs.isActive()) Thread.sleep(100) 412 | (url, chosenPort.get, cs.stop ) 413 | } 414 | 415 | def findMBeanServers(host: String, port: Int, credentials: Option[Credentials]): List[RMIConnection] = { 416 | def searchMissingClass(e: Throwable): String = { 417 | e match { 418 | case null => "?" 419 | case ex: ClassNotFoundException => ex.getMessage().trim().split("\\s+")(0) 420 | case _ => searchMissingClass(e.getCause) 421 | } 422 | } 423 | 424 | val lr = java.rmi.registry.LocateRegistry.getRegistry(host, port) 425 | //filterNot => some jonas MBeans causes registry data corruption if lookuped then all others call are in exception 426 | 427 | val entries = lr.list.toList 428 | .filterNot(_ contains "TMFactory") 429 | .filterNot(_ contains "RMI_SERVER_RPC") 430 | .filterNot(_ contains "joramActivationSpec") 431 | 432 | val results = entries map { entry => 433 | try { 434 | val credentialsArray: Array[String] = credentials match { 435 | case None => Array() 436 | case Some(Credentials(username, password)) => Array(username, password) 437 | } 438 | lr.lookup(entry) match { 439 | case stub: RMIServerImpl_Stub => Some(stub.newClient(credentialsArray)) 440 | case rsi: RMIServer => Some(rsi.newClient(credentialsArray)) 441 | case r @ _ => logger.error("Do not know how to manage %s class %s".format(entry, r.getClass)); None 442 | } 443 | } catch { 444 | case e: UnmarshalException => 445 | //println("%s - Missing = %s".format(entry, searchMissingClass(e))) 446 | //if (entry contains "jrmp") e.printStackTrace() 447 | None 448 | } 449 | } 450 | results.flatten 451 | 452 | } 453 | } 454 | -------------------------------------------------------------------------------- /src/main/scala/fr/janalyse/jmx/JMXJsr160.scala: -------------------------------------------------------------------------------- 1 | package fr.janalyse.jmx 2 | 3 | import javax.management.ObjectName 4 | import javax.management.MBeanInfo 5 | import javax.management.MBeanAttributeInfo 6 | 7 | 8 | trait JMXJsr160 extends JMX with LazyLogging { 9 | 10 | def convert(in: Any):Object = { 11 | in match { 12 | case e:Int => BigInt(e) 13 | case e:Integer => BigInt(e) 14 | case e:Short => BigInt(e) 15 | case e:Long=> BigInt(e) 16 | case e:Double => new java.lang.Double(e) 17 | case e:Float => new java.lang.Double(e) 18 | case e:Boolean => new java.lang.Boolean(e) 19 | case e:Array[Object] => e.toList.map(convert) 20 | 21 | case e:javax.management.openmbean.CompositeDataSupport => 22 | val res = for { (key,value) <- e.content} yield key->convert(value) 23 | res 24 | 25 | //case e:javax.management.openmbean.TabularDataSupport => 26 | // for { (key,data) <- e.content } 27 | 28 | case e => e.asInstanceOf[Object] 29 | } 30 | } 31 | 32 | def getMBeanInfo(objectName: ObjectName):MBeanInfo 33 | 34 | def getAttributesMetaData(objectName: ObjectName):List[AttributeMetaData] = { 35 | try { 36 | val attrsInfo: List[MBeanAttributeInfo] = getMBeanInfo(objectName).getAttributes().toList 37 | val attrsMetaData = attrsInfo map { ai => 38 | val name = ai.getName() 39 | val adesc = ai.getDescription() 40 | val atype = ai.getType() 41 | val rw = ai.isWritable() 42 | AttributeMetaData(name, adesc, atype, rw) 43 | } 44 | attrsMetaData 45 | } catch { 46 | case e: Exception => 47 | logger.warn("Couln't build attributes map for MBean %s (%s)".format(objectName.toString, e.getMessage), e) 48 | List.empty 49 | } 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/scala/fr/janalyse/jmx/JMXOptions.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 David Crosson 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 | package fr.janalyse.jmx 17 | 18 | import javax.management.remote.JMXServiceURL 19 | 20 | case class JMXOptions( 21 | host: String = "localhost", 22 | port: Int = 1099, 23 | url: Option[JMXServiceURL] = None, 24 | contextbase:Option[String] = None, 25 | name: Option[String] = None, 26 | username: Option[String] = None, 27 | password: Option[String] = None, 28 | connectTimeout: Long = 30000, 29 | retryCount: Int = 5, 30 | retryDelay: Long = 2000) { 31 | val credentials: Option[Credentials] = (username, password) match { 32 | case (None, None) => None 33 | case (None, _) => None 34 | case (Some(u), Some(p)) => Some(Credentials(u, p)) 35 | case (Some(u), None) => Some(Credentials(u, "")) 36 | } 37 | } 38 | 39 | object JMXOptions { 40 | def apply(url: JMXServiceURL): JMXOptions = 41 | new JMXOptions(url.getHost, url.getPort, url = Some(url)) 42 | 43 | def apply(url: JMXServiceURL, username: String, password: String): JMXOptions = 44 | new JMXOptions(url.getHost, url.getPort, url = Some(url), username = Some(username), password = Some(password)) 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/scala/fr/janalyse/jmx/JMXclassicalImpl.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 David Crosson 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 | package fr.janalyse.jmx 17 | 18 | import javax.management.remote.JMXConnector 19 | import javax.management.ObjectName 20 | import javax.management.MBeanServerConnection 21 | import scala.collection.JavaConverters._ 22 | import javax.management.MBeanInfo 23 | import javax.management.RuntimeMBeanException 24 | import java.rmi.UnmarshalException 25 | 26 | private class JMXclassicalImpl( 27 | conn: JMXConnector, 28 | val options: Option[JMXOptions] = None, 29 | additionalCleaning: Option[() => Any] = None) extends JMXJsr160 { 30 | private lazy val mbsc: MBeanServerConnection = conn.getMBeanServerConnection 31 | 32 | def close() = { 33 | try { 34 | conn.close() 35 | } catch { 36 | case e: Exception => logger.warn("Exception while closing rmi connection, let's ignore and continue..."+e.getMessage) 37 | } 38 | try { 39 | additionalCleaning.foreach{_()} 40 | } catch { 41 | case e: Exception => logger.warn("Exception in additionnal cleaning procesure, let's ignore and continue..."+e.getMessage) 42 | } 43 | } 44 | 45 | def getMBeanInfo(objectName: ObjectName):MBeanInfo = mbsc.getMBeanInfo(objectName) 46 | 47 | def getAttribute(objectName: ObjectName, attrname: String):Option[Object] = { 48 | try { 49 | val res = mbsc.getAttribute(objectName, attrname) 50 | //Option(convert(res)) 51 | Option(res) 52 | } catch { 53 | case e: RuntimeMBeanException /* if e.getCause().isInstanceOf[UnsupportedOperationException] */=> None 54 | case e: javax.management.MBeanException => None 55 | case e: javax.management.RuntimeOperationsException => None 56 | case e: javax.management.ReflectionException => None 57 | case e: javax.management.AttributeNotFoundException => None 58 | case e: UnmarshalException => None 59 | case e: java.lang.IllegalArgumentException => None 60 | case e: java.rmi.ConnectException => throw e 61 | case e: java.net.ConnectException => throw e 62 | case e: java.net.SocketException => throw e 63 | case e: javax.management.InstanceNotFoundException => throw e 64 | case e: java.io.IOException => throw e //None 65 | case x: Exception => 66 | logger.warn("Warning: Error while getting value for attribute "+attrname+" mbean "+objectName.getCanonicalName(), x) 67 | None 68 | } 69 | } 70 | 71 | def setAttribute(objectName: ObjectName, attrname: String, attrvalue: Any) { 72 | val attribute = new javax.management.Attribute(attrname, attrvalue) 73 | mbsc.setAttribute(objectName, attribute) 74 | } 75 | def invoke(objectName: ObjectName, operationName: String, args: Array[Any]): Option[Any] = { 76 | Option(mbsc.invoke(objectName, operationName, args.map(_.asInstanceOf[Object]), buildOperationSignature(args))) 77 | } 78 | private def newMBean(objectName: ObjectName) = 79 | RichMBean( 80 | objectName, 81 | () => getAttributesMap(objectName), 82 | (attrname) => getAttribute(objectName, attrname), 83 | (attrname, attrval) => setAttribute(objectName, attrname, attrval), 84 | (operationName, args) => invoke(objectName, operationName, args) 85 | ) 86 | def domains: List[String] = mbsc.getDomains().toList 87 | def names(query:String):List[String]=mbsc.queryNames(null, string2objectName(query)).asScala.toList.map(_.getCanonicalName()) 88 | override def names():List[String]=mbsc.queryNames(null, null).asScala.toList.map(_.getCanonicalName()) 89 | def exists(name: String): Boolean = mbsc.queryNames(name, null).size > 0 90 | def apply(name: String): RichMBean = newMBean(name) 91 | def mbeans(query: String): List[RichMBean] = mbsc.queryNames(null, string2objectName(query)).asScala.toList map { newMBean(_) } 92 | override def mbeans(): List[RichMBean] = mbsc.queryNames(null, null).asScala.toList map { newMBean(_) } 93 | } 94 | 95 | -------------------------------------------------------------------------------- /src/main/scala/fr/janalyse/jmx/JMXrmiImpl.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 David Crosson 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 | package fr.janalyse.jmx 17 | 18 | import javax.management.remote.rmi.RMIConnection 19 | import javax.management.ObjectName 20 | 21 | import scala.collection.JavaConverters._ 22 | 23 | 24 | private class JMXrmiImpl(rmiConnection: RMIConnection, val options: Option[JMXOptions] = None) extends JMXJsr160 { 25 | 26 | def close() = try { 27 | rmiConnection.close 28 | } catch { 29 | case e:Exception => logger.error("Exception while closing rmi connection, let's ignore and continue...") 30 | } 31 | 32 | def getMBeanInfo(objectName: ObjectName) = rmiConnection.getMBeanInfo(objectName, null) 33 | 34 | def getAttribute(objectName: ObjectName, attrname: String) = { 35 | val res = rmiConnection.getAttribute(objectName, attrname, null) 36 | //Option(convert(res)) 37 | Option(res) 38 | } 39 | 40 | def setAttribute(objectName: ObjectName, attrname: String, attrvalue: Any) { 41 | val attribute = new javax.management.Attribute(attrname, attrvalue) 42 | rmiConnection.setAttribute(objectName, attribute, null) 43 | } 44 | def invoke(objectName: ObjectName, operationName: String, args: Array[Any]): Option[Any] = { 45 | Option(rmiConnection.invoke(objectName, operationName, args, buildOperationSignature(args), null)) 46 | } 47 | private def newMBean(objectName: ObjectName) = 48 | RichMBean( 49 | objectName, 50 | () => getAttributesMap(objectName), 51 | (attrname) => getAttribute(objectName, attrname), 52 | (attrname, attrval) => setAttribute(objectName, attrname, attrval), 53 | (operationName, args) => invoke(objectName, operationName, args) 54 | ) 55 | 56 | def domains: List[String] = rmiConnection.getDomains(null).toList 57 | def names(query:String):List[String]=rmiConnection.queryNames(null, string2objectName(query),null).asScala.toList.map(_.getCanonicalName()) 58 | override def names():List[String]=rmiConnection.queryNames(null, null,null).asScala.toList.map(_.getCanonicalName()) 59 | def exists(name: String): Boolean = rmiConnection.queryNames(name, null, null).size > 0 60 | def apply(name: String): RichMBean = newMBean(name) 61 | def mbeans(query: String): List[RichMBean] = rmiConnection.queryNames(null, string2objectName(query), null).asScala.toList map { newMBean(_) } 62 | override def mbeans(): List[RichMBean] = rmiConnection.queryNames(null, null, null).asScala.toList map { newMBean(_) } 63 | } 64 | 65 | -------------------------------------------------------------------------------- /src/main/scala/fr/janalyse/jmx/LazyLogging.scala: -------------------------------------------------------------------------------- 1 | package fr.janalyse.jmx 2 | 3 | import org.slf4j._ 4 | 5 | trait LazyLogging { 6 | val logger = LoggerFactory.getLogger(getClass) 7 | } 8 | -------------------------------------------------------------------------------- /src/main/scala/fr/janalyse/jmx/Lock.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 David Crosson 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 | package fr.janalyse.jmx 17 | 18 | case class Lock( 19 | name: String, 20 | ownerId: Option[Long], 21 | ownerName: Option[String], 22 | serviceName: String) 23 | -------------------------------------------------------------------------------- /src/main/scala/fr/janalyse/jmx/RichAttribute.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 David Crosson 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 | package fr.janalyse.jmx 17 | 18 | import javax.management.openmbean.{ CompositeData, CompositeDataSupport } 19 | //import org.json4s._ 20 | 21 | 22 | trait RichAttribute { 23 | val name: String 24 | val desc: Option[String] 25 | def asString(ob: Object): String = ob match { 26 | // case JString(str) => str 27 | case x => x.toString() // TODO BAD 28 | } 29 | } 30 | 31 | trait RichArrayAttribute extends RichAttribute { 32 | } 33 | 34 | trait RichNumberAttribute extends RichAttribute { 35 | def asDouble(ob: Object): Double = ob match { // TODO BAD 36 | case e: java.lang.Byte => e.toDouble 37 | case e: java.lang.Short => e.toDouble 38 | case e: java.lang.Integer => e.toDouble 39 | case e: java.lang.Long => e.toDouble 40 | case e: java.lang.Float => e.toDouble 41 | case e: java.lang.Double => e 42 | case e: java.lang.String => e.toDouble 43 | case e: BigInt => e.toDouble 44 | case e: BigDecimal => e.toDouble 45 | } 46 | def asLong(ob: Object): Long = ob match { // TODO BAD 47 | case e: java.lang.Byte => e.toLong 48 | case e: java.lang.Short => e.toLong 49 | case e: java.lang.Integer => e.toLong 50 | case e: java.lang.Long => e 51 | case e: java.lang.Float => e.toLong 52 | case e: java.lang.Double => e.toLong 53 | case e: java.lang.String => e.toLong 54 | case e: BigInt => e.toLong 55 | case e: BigDecimal => e.toLong 56 | } 57 | def asInt(ob: Object): Int = ob match { // TODO BAD 58 | case e: java.lang.Byte => e.toInt 59 | case e: java.lang.Short => e.toInt 60 | case e: java.lang.Integer => e 61 | case e: java.lang.Long => e.toInt 62 | case e: java.lang.Float => e.toInt 63 | case e: java.lang.Double => e.toInt 64 | case e: java.lang.String => e.toInt 65 | case e: BigInt => e.toInt 66 | case e: BigDecimal => e.toInt 67 | } 68 | 69 | } 70 | 71 | case class RichCompositeDataAttribute(name: String, desc: Option[String] = None) extends RichAttribute { 72 | override def asString(ob: Object): String = { 73 | val cd = ob.asInstanceOf[CompositeData] // TODO BAD 74 | cd.toString() 75 | } 76 | def asMap(ob: Object) = ob.asInstanceOf[CompositeData].content // TODO BAD 77 | def asNumberMap[N >: Number](ob: Object): Map[String, N] = { 78 | val cd = ob.asInstanceOf[CompositeData] // TODO BAD 79 | cd.content flatMap { 80 | case (name, value) => 81 | value match { 82 | case x: java.lang.Long => Some((name, x))//Some(name -> new runtime.RichDouble(x.toDouble)) 83 | case x: java.lang.Float => Some((name, x))//Some(name -> new runtime.RichDouble(x.toDouble)) 84 | case x: java.lang.Double => Some((name, x))//Some(name -> new runtime.RichDouble(x.toDouble)) 85 | case x: java.lang.Integer => Some((name, x))//Some(name -> new runtime.RichDouble(x.toDouble)) 86 | case _ => None 87 | } 88 | } 89 | } 90 | } 91 | 92 | case class RichStringArrayAttribute(name: String, desc: Option[String] = None) extends RichArrayAttribute { 93 | override def asString(ob: Object): String = { // TODO BAD 94 | ob.asInstanceOf[Array[String]].toList.mkString(", ") 95 | } 96 | } 97 | 98 | case class RichBooleanAttribute(name: String, desc: Option[String] = None) extends RichAttribute 99 | 100 | case class RichStringAttribute(name: String, desc: Option[String] = None) extends RichAttribute 101 | 102 | case class RichGenericAttribute(name: String, desc: Option[String] = None) extends RichAttribute 103 | 104 | case class RichByteAttribute(name: String, desc: Option[String] = None) extends RichNumberAttribute 105 | case class RichShortAttribute(name: String, desc: Option[String] = None) extends RichNumberAttribute 106 | case class RichIntAttribute(name: String, desc: Option[String] = None) extends RichNumberAttribute 107 | case class RichLongAttribute(name: String, desc: Option[String] = None) extends RichNumberAttribute 108 | case class RichDoubleAttribute(name: String, desc: Option[String] = None) extends RichNumberAttribute 109 | case class RichFloatAttribute(name: String, desc: Option[String] = None) extends RichNumberAttribute 110 | -------------------------------------------------------------------------------- /src/main/scala/fr/janalyse/jmx/RichMBean.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 David Crosson 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 | package fr.janalyse.jmx 17 | 18 | import javax.management.ObjectName 19 | import javax.management.MBeanInfo 20 | import javax.management.MBeanAttributeInfo 21 | import javax.management.RuntimeMBeanException 22 | import java.rmi.UnmarshalException 23 | 24 | import scala.collection.JavaConverters._ 25 | 26 | 27 | case class RichMBean( 28 | objectName: ObjectName, 29 | attributesMapGetter: () => Map[String, RichAttribute], 30 | attributeGetter: (String) => Option[Object], 31 | attributeSetter: (String, Any) => Unit, 32 | operationCaller: (String, Array[Any]) => Option[Any]) extends LazyLogging { 33 | 34 | override def toString() = name 35 | 36 | val name = objectName.toString() 37 | val domain = objectName.getDomain() 38 | val keys = mapAsScalaMap(objectName.getKeyPropertyList()) 39 | 40 | lazy val attributesMap: Map[String, RichAttribute] = attributesMapGetter() 41 | 42 | def get[A](name: String): Option[A] = attributeGetter(name).map(_.asInstanceOf[A]) 43 | 44 | def apply[A](attrname: String) = attributeGetter(attrname).map(_.asInstanceOf[A]).get 45 | def set(attrname: String, value: Any) { attributeSetter(attrname, value) } 46 | def apply[A](attrnames: String*): List[A] = attrnames.toList map { apply[A](_) } 47 | def call[A](operation: String, args: Any*): Option[A] = operationCaller(operation, args.toArray[Any]).map(_.asInstanceOf[A]) 48 | 49 | private def genericGetter[T, R <: RichAttribute](attr: R, getter: (Object) => T): Option[T] = { 50 | try { 51 | attributeGetter(attr.name).map(getter(_)) 52 | } catch { 53 | case e: RuntimeMBeanException /*if e.getCause().isInstanceOf[UnsupportedOperationException]*/ => None 54 | case e: javax.management.MBeanException => None 55 | case e: javax.management.RuntimeOperationsException => None 56 | case e: javax.management.ReflectionException => None 57 | case e: javax.management.AttributeNotFoundException => None 58 | case e: UnmarshalException => None 59 | case e: java.lang.IllegalArgumentException => None 60 | case e: java.rmi.ConnectException => throw e 61 | case e: java.net.ConnectException => throw e 62 | case e: java.net.SocketException => throw e 63 | case e: javax.management.InstanceNotFoundException => throw e 64 | case e: java.io.IOException => throw e //None 65 | case x: Exception => 66 | logger.warn("Warning: Error while getting value for attribute "+attr.name+" mbean "+name, x) 67 | None 68 | } 69 | } 70 | 71 | def getString(attr: RichAttribute): Option[String] = genericGetter(attr, attr.asString) 72 | 73 | def getDouble(attr: RichNumberAttribute): Option[Double] = genericGetter(attr, attr.asDouble) 74 | 75 | def getLong(attr: RichNumberAttribute): Option[Long] = genericGetter(attr, attr.asLong) 76 | 77 | def getInt(attr: RichNumberAttribute): Option[Int] = genericGetter(attr, attr.asInt) 78 | 79 | def getComposite(attr: RichCompositeDataAttribute): Option[Map[String, Object]] = genericGetter(attr, attr.asMap) 80 | 81 | def getNumberComposite[N >: Number](attr: RichCompositeDataAttribute): Option[Map[String, N]] = genericGetter(attr, attr.asNumberMap[N]) 82 | 83 | def getString(attrname: String): Option[String] = attributesMap.get(attrname).flatMap(getString(_)) 84 | 85 | def getDouble(attrname: String): Option[Double] = attributesMap.get(attrname).collect({ case x: RichNumberAttribute => x }).flatMap(getDouble(_)) 86 | 87 | def getLong(attrname: String): Option[Long] = attributesMap.get(attrname).collect({ case x: RichNumberAttribute => x }).flatMap(getLong(_)) 88 | 89 | def getInt(attrname: String): Option[Int] = attributesMap.get(attrname).collect({ case x: RichNumberAttribute => x }).flatMap(getInt(_)) 90 | 91 | def getComposite(attrname: String): Option[Map[String, Object]] = attributesMap.get(attrname).collect({ case x: RichCompositeDataAttribute => x }).flatMap(getComposite(_)) 92 | 93 | def getNumberComposite[N >: Number](attrname: String): Option[Map[String, N]] = attributesMap.get(attrname).collect({ case x: RichCompositeDataAttribute => x }).flatMap(getNumberComposite(_)) 94 | 95 | def attributes(): List[RichAttribute] = attributesMap.values.toList 96 | 97 | def attributesNames() : List[String] = attributesMap.values.toList.map(_.name) 98 | } 99 | -------------------------------------------------------------------------------- /src/main/scala/fr/janalyse/jmx/ServiceThreads.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 David Crosson 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 | package fr.janalyse.jmx 17 | 18 | /** 19 | * cpuTimes units is nanoseconds, divide by 10^6 to get milliseconds 20 | */ 21 | case class ServiceThreads( 22 | serviceName: String, 23 | threads: List[ThreadInfo], 24 | cpuTimes: Map[Long, Option[Long]]) { 25 | val locks = threads.flatMap(_.lock) 26 | val activeLocks = locks filter { _.ownerName.isDefined } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /src/main/scala/fr/janalyse/jmx/StackEntry.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 David Crosson 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 | package fr.janalyse.jmx 17 | 18 | case class StackEntry( 19 | className: String, 20 | methodName: String, 21 | nativeMethod: Boolean, 22 | fileName: String, 23 | lineNumber: Int) { 24 | val id = "%s.%s".format(className, methodName) 25 | val pkg = className.split("[.]").init.mkString(".") 26 | def detailed = "%s (%s:%s)".format(id, if (fileName != null) fileName else "?", if (lineNumber > 0) lineNumber.toString else "?") 27 | override def toString = id 28 | } 29 | -------------------------------------------------------------------------------- /src/main/scala/fr/janalyse/jmx/ThreadInfo.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 David Crosson 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 | package fr.janalyse.jmx 17 | 18 | import javax.management.openmbean.CompositeData 19 | 20 | case class ThreadInfo( 21 | name: String, 22 | id: Long, 23 | status: String, 24 | stack: List[StackEntry], 25 | lock: Option[Lock], 26 | blockedCount: Long, 27 | waitedCount: Long, 28 | serviceName: String) 29 | 30 | object ThreadInfo { 31 | def apply(serviceName: String, ti: CompositeData) = { 32 | val id = ti.get("threadId").toString.toLong 33 | val name = ti.get("threadName").toString 34 | val state = ti.get("threadState").toString 35 | val blockedCount = ti.get("blockedCount").toString.toLong 36 | val waitedCount = ti.get("waitedCount").toString.toLong 37 | val stack = ti.get("stackTrace").asInstanceOf[Array[CompositeData]] map { stackitem => 38 | val className = stackitem.get("className").asInstanceOf[String] 39 | val fileName = stackitem.get("fileName").asInstanceOf[String] 40 | val lineNumber = stackitem.get("lineNumber").asInstanceOf[Int] 41 | val methodName = stackitem.get("methodName").asInstanceOf[String] 42 | val nativeMethod = stackitem.get("nativeMethod").asInstanceOf[Boolean] 43 | StackEntry( 44 | className = className, 45 | fileName = fileName, 46 | lineNumber = lineNumber, 47 | methodName = methodName, 48 | nativeMethod = nativeMethod) 49 | } 50 | val lock = Option(ti.get("lockName")) map { _.toString } filter { _.size > 0 } map { lockName => 51 | val lockOwnerId = Option(ti.get("lockOwnerId")) map { _.asInstanceOf[Long] } filterNot { _ == -1 } 52 | val lockOwnerName = Option(ti.get("lockOwnerName")) map { _.toString.trim } 53 | Lock(lockName, lockOwnerId, lockOwnerName, serviceName) 54 | } 55 | new ThreadInfo( 56 | name, 57 | id, 58 | state, 59 | stack.toList, 60 | lock, 61 | blockedCount, 62 | waitedCount, 63 | serviceName) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/scala/fr/janalyse/jmx/package.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 David Crosson 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 | package fr.janalyse 17 | 18 | import scala.collection.JavaConverters._ 19 | 20 | import javax.management.ObjectName 21 | import javax.management.ObjectInstance 22 | import java.rmi.MarshalledObject 23 | import javax.management.openmbean.CompositeData 24 | import javax.management.openmbean.CompositeDataSupport 25 | import javax.management.openmbean.TabularData 26 | import javax.management.openmbean.TabularDataSupport 27 | 28 | package object jmx { 29 | 30 | 31 | implicit def string2objectName(name: String): ObjectName = new ObjectName(name) 32 | implicit def objectInstance2ObjectName(that: ObjectInstance): ObjectName = that.getObjectName 33 | //implicit def objectName2RichMBean(that:ObjectName)(implicit ajmx:JMX) = RichMBean(that) 34 | //implicit def string2RichMBean(that:String)(implicit ajmx:JMX):RichMBean = RichMBean(that) 35 | implicit def toMarshalledObject[A](that: A): MarshalledObject[A] = new MarshalledObject[A](that) 36 | 37 | implicit def compositeDataAsScalaWrapper[CD <: CompositeData](cd: CD): CompositeDataWrapper[CD] = CompositeDataWrapper(cd) 38 | //implicit def compositeDataSupportAsScalaWrapper(cd : CompositeDataSupport): CompositeDataSupportWrapper = CompositeDataSupportWrapper(cd) 39 | implicit def tabularDataAsScalaWrapper[TD <: TabularData](td: TD): TabularDataWrapper[TD] = TabularDataWrapper(td) 40 | //implicit def tubularDataSupportAsScalaWrapper(td:TabularDataSupport):TabularDataSupportWrapper = TabularDataSupportWrapper(td) 41 | 42 | } -------------------------------------------------------------------------------- /src/main/scala/jajmx/package.scala: -------------------------------------------------------------------------------- 1 | package object jajmx { 2 | type JMX = fr.janalyse.jmx.JMX 3 | val JMX = fr.janalyse.jmx.JMX 4 | type JMXOptions = fr.janalyse.jmx.JMXOptions 5 | val JMXOptions = fr.janalyse.jmx.JMXOptions 6 | } 7 | -------------------------------------------------------------------------------- /src/test/scala/fr/janalyse/jmx/JMXAPITest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 David Crosson 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 | 17 | package fr.janalyse.jmx 18 | 19 | import org.scalatest.funsuite._ 20 | import org.scalatest.matchers._ 21 | import java.rmi.registry.LocateRegistry 22 | import java.lang.management.ManagementFactory 23 | import javax.management.remote.JMXConnectorServerFactory 24 | import javax.management.remote.JMXConnectorFactory 25 | import javax.management.remote.JMXServiceURL 26 | import scala.beans.BeanProperty 27 | import javax.management.openmbean.CompositeDataSupport 28 | import javax.management.openmbean.TabularDataSupport 29 | import javax.management.openmbean.CompositeData 30 | import javax.management.RuntimeMBeanException 31 | 32 | /* 33 | trait JMXSelftInit { 34 | val jmxself = synchronized { try { 35 | JMX.connect("127.0.0.1",4500) { implicit jmx => 36 | } 37 | } catch { 38 | case x => 39 | LocateRegistry.createRegistry(4500) 40 | val url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:4500/jmxapitestrmi") 41 | val mbs = ManagementFactory.getPlatformMBeanServer() 42 | val cs = JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbs) 43 | cs.start 44 | Thread.sleep(500) // because some computers are really too fast 45 | } 46 | } 47 | } 48 | */ 49 | 50 | 51 | class JMXAPITest extends AnyFunSuite with should.Matchers { 52 | 53 | def howLongFor[T](what: () => T) = { 54 | val begin = System.currentTimeMillis() 55 | val result = what() 56 | val end = System.currentTimeMillis() 57 | (end - begin, result) 58 | } 59 | 60 | // ================================================================================================ 61 | 62 | // -- Define Some MBean Interface 63 | trait SomeoneMBean { 64 | def getAge(): Int 65 | def lowercase(input: String): String 66 | def arraylowercase(input: Array[String]): Array[String] 67 | def addInt(toadd: Int, addTo: Array[Int]): Array[Int] 68 | //def addShort(toadd: Short, addTo: Array[Short]): Array[Short] 69 | def addLong(toadd: Long, addTo: Array[Long]): Array[Long] 70 | def addFloat(toadd: Float, addTo: Array[Float]): Array[Float] 71 | def addDouble(toadd: Double, addTo: Array[Double]): Array[Double] 72 | def fillBoolean(fillWith: Boolean, that: Array[Boolean]): Array[Boolean] 73 | def fillByte(fillWith: Byte, that: Array[Byte]): Array[Byte] 74 | def fillChar(fillWith: Char, that: Array[Char]): Array[Char] 75 | } 76 | // -- Our JMX Managed class 77 | case class Someone(name: String, @BeanProperty age: Int) extends SomeoneMBean { 78 | def lowercase(input: String) = input.toLowerCase() 79 | def arraylowercase(input: Array[String]) = input.map(_.toLowerCase()) 80 | def addInt(toadd: Int, addTo: Array[Int]) = addTo.map(_ + toadd) 81 | //def addShort(toadd: Short, addTo: Array[Short]) = addTo.map(_ + toadd) 82 | def addLong(toadd: Long, addTo: Array[Long]) = addTo.map(_ + toadd) 83 | def addFloat(toadd: Float, addTo: Array[Float]) = addTo.map(_ + toadd) 84 | def addDouble(toadd: Double, addTo: Array[Double]) = addTo.map(_ + toadd) 85 | def fillBoolean(fillWith: Boolean, that: Array[Boolean]) = that.map(x => fillWith) 86 | def fillByte(fillWith: Byte, that: Array[Byte]) = that.map(x => fillWith) 87 | def fillChar(fillWith: Char, that: Array[Char]) = that.map(x => fillWith) 88 | } 89 | 90 | // -- The test case : Creates a JMX managed instance, and queries it 91 | test("MBean create & use") { 92 | val marvin = Someone("Marvin", 30) 93 | try { 94 | JMX.register(marvin, s"people:name=${marvin.name}") 95 | 96 | val marvinAgeFromJMX = JMX.once() { jmx => 97 | jmx("people:name=Marvin").get[Int]("Age") 98 | } 99 | marvinAgeFromJMX should equal(Some(30)) 100 | info("Marvin age is %s".format(marvinAgeFromJMX map { _.toString } getOrElse "Unknown")) 101 | } finally { 102 | JMX.unregister(s"people:name=${marvin.name}") 103 | } 104 | } 105 | 106 | test("MBean call tests") { 107 | val marvin = Someone("Marvin", 30) 108 | try { 109 | JMX.register(marvin, s"people:name=${marvin.name}") 110 | 111 | JMX.once() { jmx => 112 | val jmxmarvin = jmx(s"people:name=${marvin.name}") 113 | jmxmarvin.call[String]("lowercase", "TOTO") should equal(Some("toto")) 114 | jmxmarvin.call[Array[String]]("arraylowercase", Array("TOTO", "TATA")).map(_.toList) should equal(Some(List("toto", "tata"))) 115 | jmxmarvin.call[Array[Int]]("addInt", 1, Array(1, 2)).map(_.toList) should equal(Some(List(2, 3))) 116 | //jmxmarvin.call[Array[Short]]("addShort", 1.toShort, Array(1.toShort, 2.toShort)).map(_.toList) should equal(Some(List(2.toShort, 3.toShort))) 117 | jmxmarvin.call[Array[Long]]("addLong", 1L, Array(1L, 2L)).map(_.toList) should equal(Some(List(2L, 3L))) 118 | jmxmarvin.call[Array[Float]]("addFloat", 1f, Array(1f, 2f)).map(_.toList) should equal(Some(List(2f, 3f))) 119 | jmxmarvin.call[Array[Double]]("addDouble", 1d, Array(1d, 2d)).map(_.toList) should equal(Some(List(2d, 3d))) 120 | jmxmarvin.call[Array[Boolean]]("fillBoolean", true, Array(false)).map(_.toList) should equal(Some(List(true))) 121 | jmxmarvin.call[Array[Byte]]("fillByte", 255.toByte, Array(0.toByte)).map(_.toList) should equal(Some(List(255.toByte))) 122 | jmxmarvin.call[Array[Char]]("fillChar", 'X', Array('_')).map(_.toList) should equal(Some(List('X'))) 123 | } 124 | } finally { 125 | JMX.unregister(s"people:name=${marvin.name}") 126 | } 127 | } 128 | 129 | // ================================================================================================ 130 | 131 | test("Simple JMX test") { 132 | JMX.once() { jmx => 133 | val os = jmx("java.lang:type=OperatingSystem") 134 | val List(name, version) = os[String]("Name", "Version") 135 | 136 | name.size should be > (0) 137 | version.size should be > (0) 138 | info("OS Name & Version : %s %s".format(name, version)) 139 | 140 | val runtime = jmx("java.lang:type=Runtime") 141 | val vmname: String = runtime("VmName") 142 | vmname should ( include("Java") or include("OpenJDK")) 143 | info("JVM NAME : %s".format(vmname)) 144 | 145 | val threading = jmx("java.lang:type=Threading") 146 | threading.set("ThreadCpuTimeEnabled", true) 147 | val cpuTimeEnabled = threading.get[Boolean]("ThreadCpuTimeEnabled") 148 | info("isThreadCpuTimeEnabled=".format(cpuTimeEnabled)) 149 | 150 | val mem = jmx("java.lang:type=Memory") 151 | mem.call("gc") 152 | info("Explicit GC invoked") 153 | } 154 | } 155 | 156 | // ================================================================================================ 157 | test("short and safe usage") { 158 | JMX.once() { jmx => 159 | val osname1 = jmx.get("java.lang:type=OperatingSystem") flatMap { _.getString("Name") } 160 | //or 161 | val osname2 = for (os <- jmx.os; name <- os.getString("Name")) yield name 162 | } 163 | } 164 | // ================================================================================================ 165 | test("one line jmx browsing") { 166 | val results = 167 | for (mb <- JMX().mbeans; attr <- mb.attributes; value <- mb.getString(attr)) yield attr.name -> value 168 | 169 | results.size should be > (0) 170 | } 171 | // ================================================================================================ 172 | test("jmx mbeans query") { 173 | JMX.once() { jmx => 174 | jmx.mbeans("java.lang:type=GarbageCollector,*").size should be > (0) 175 | jmx.mbeans("Catalina:type=ThreadPool,*").size should equal(0) 176 | } 177 | } 178 | // ================================================================================================ 179 | test("get numeric attributes") { 180 | JMX.once() { jmx => 181 | for (os <- jmx.os) { 182 | val numAttrs = os.attributes collect { case x: RichNumberAttribute => x } 183 | val numValues = numAttrs map { os.getDouble(_) } 184 | } 185 | } 186 | } 187 | // ================================================================================================ 188 | test("jmx shorcuts") { 189 | JMX.once() { jmx => 190 | jmx.os should not equal (None) 191 | jmx.runtime should not equal (None) 192 | jmx.memory should not equal (None) 193 | jmx.threading should not equal (None) 194 | } 195 | } 196 | // ================================================================================================ 197 | test("jmx open data conversions") { 198 | JMX.once() { jmx => 199 | 200 | // ----- CompositeData support 201 | for (mem <- jmx.memory) { 202 | val heapusage = mem[CompositeDataSupport]("HeapMemoryUsage").toWrapper 203 | val used = heapusage.getNumber("used") 204 | info("current heap usage : %d Mo".format(used.get.longValue / 1024 / 1024)) 205 | used should not equal (None) 206 | } 207 | 208 | // ----- CompositeData support 209 | for (mem <- jmx.memory) { 210 | val heapusage = mem.getNumberComposite("HeapMemoryUsage").get 211 | val used = heapusage.get("used") 212 | info("current heap usage : %d Mo".format(used.get.longValue / 1024 / 1024)) 213 | used should not equal (None) 214 | } 215 | 216 | // ----- Browsing system properties 217 | for ( 218 | rt <- jmx.runtime; 219 | props <- rt.get[TabularDataSupport]("SystemProperties"); 220 | (key, values) <- props.content; 221 | value <- values.get("value") 222 | ) { 223 | info("%s = %s".format(key, value.toString)) 224 | } 225 | 226 | // ----- System Properties and TabularData support 227 | for (rt <- jmx.runtime) { 228 | val sysprops = rt[TabularDataSupport]("SystemProperties").toWrapper 229 | val jv = sysprops.get("java.version") 230 | jv should not equal (None) 231 | jv.get("value") should equal(util.Properties.javaVersion) 232 | sysprops.get("java.version", "value") should equal(Some(util.Properties.javaVersion)) 233 | sysprops.getString("java.version", "value") should equal(Some(util.Properties.javaVersion)) 234 | } 235 | 236 | // ----- CompositeData array support 237 | val hotspotDiag = jmx.get("com.sun.management:type=HotSpotDiagnostic") 238 | val diagOpts = hotspotDiag.get[Array[CompositeData]]("DiagnosticOptions") 239 | val diagProps = (diagOpts map { cd => cd.get("name") -> cd.get("value") }).toMap 240 | 241 | diagProps should contain key ("PrintGC") 242 | } 243 | } 244 | // ================================================================================================ 245 | test("Simple composite data retrieve") { 246 | JMX.once() { jmx => 247 | for ( 248 | mem <- jmx.memory; 249 | data <- mem.getNumberComposite("HeapMemoryUsage"); 250 | (name, value) <- data 251 | ) { 252 | info("%s = %d".format(name, value.longValue)) 253 | } 254 | } 255 | } 256 | // ================================================================================================ 257 | 258 | test("More complex nested composite data values") { 259 | import java.lang.management.MemoryUsage 260 | JMX.once() { jmx => 261 | for ( 262 | scavenge <- jmx.get("java.lang:type=GarbageCollector,name=PS MarkSweep"); 263 | lastgc <- scavenge.get[CompositeData]("LastGcInfo").map(_.toWrapper) 264 | ) { 265 | val id = lastgc.get[Int]("id") 266 | val startTime = lastgc.get[Long]("startTime") 267 | val endTime = lastgc.get[Long]("endTime") 268 | val duration = lastgc.get[Long]("duration") 269 | val aftergc = lastgc.get[Map[String, MemoryUsage]]("memoryUsageAfterGc") 270 | val beforegc = lastgc.get[Map[String, MemoryUsage]]("memoryUsageBeforeGc") 271 | 272 | aftergc.isDefined should equal(true) 273 | beforegc.isDefined should equal(true) 274 | 275 | } 276 | } 277 | } 278 | 279 | // ================================================================================================ 280 | 281 | test("Thread dumps & CPU times test") { 282 | JMX.once() { jmx => 283 | val th2testName = "testME" 284 | val during = 5 * 1000L 285 | // ----------- 286 | val th2test = new Thread(th2testName) { 287 | def computeToto(x:Long) = x+1 288 | def computeLoop() { 289 | val time = System.currentTimeMillis() 290 | var count: Long = 0 291 | while (System.currentTimeMillis() - time <= during) { 292 | count = computeToto(count + 123) % 100000L 293 | } 294 | Thread.sleep(1*1000L) 295 | } 296 | override def run() { 297 | computeLoop() 298 | } 299 | } 300 | th2test.start 301 | // ----------- 302 | val dump1opt = jmx.threadsDump(5, true) 303 | Thread.sleep(during) 304 | val dump2opt = jmx.threadsDump(5, true) 305 | 306 | dump1opt should be ('defined) 307 | dump2opt should be ('defined) 308 | 309 | val th1opt = dump1opt.flatMap(_.threads.find(_.name == th2testName)) 310 | th1opt should be ('defined) 311 | 312 | val th2opt = dump2opt.flatMap(_.threads.find(_.name == th2testName)) 313 | th2opt should be ('defined) 314 | 315 | th1opt.get.stack.map(_.methodName) should contain("computeLoop") 316 | th2opt.get.stack.map(_.methodName) should contain("computeLoop") 317 | 318 | val cputimeInMSopt = for { 319 | dump1 <- dump1opt 320 | dump2 <- dump2opt 321 | th1 <- th1opt 322 | th2 <- th2opt 323 | cputimes1opt <- dump1.cpuTimes.get(th1.id) 324 | cputimes2opt <- dump2.cpuTimes.get(th2.id) 325 | cputimes1 <- cputimes1opt 326 | cputimes2 <- cputimes2opt 327 | } yield { 328 | (cputimes2 - cputimes1)/1000/1000 // Now in milliseconds instead of nanoseconds 329 | } 330 | 331 | cputimeInMSopt should be ('defined) 332 | 333 | val cpuPercent = cputimeInMSopt.get*100/during 334 | info(s"Thread CPU usage = ${cpuPercent}") 335 | 336 | // testME thread will of course use 1 cpu, so percent should be >90% 337 | cpuPercent should be >(50L) 338 | } 339 | } 340 | 341 | 342 | ignore("Complex types") { 343 | JMX.once() { jmx => 344 | val rt = jmx("java.lang:type=Runtime") 345 | val th = jmx("java.lang:type=Threading") 346 | val mem = jmx("java.lang:type=Memory") 347 | 348 | // Array[String] 349 | val args = rt.get[List[String]]("InputArguments") 350 | args.get.head 351 | 352 | // Array[Long] 353 | val ids = th.get[List[BigInt]]("AllThreadIds") 354 | ids.get.head 355 | 356 | // TabularDataSupport 357 | val props = rt.get[Map[String,String]]("SystemProperties") 358 | props.get.get("user.dir") 359 | 360 | // CompositeDataSupport 361 | val heap = mem.get[Map[String,BigInt]]("HeapMemoryUsage") 362 | heap.get.get("max") 363 | 364 | } 365 | } 366 | 367 | } 368 | 369 | -------------------------------------------------------------------------------- /src/test/scala/fr/janalyse/jmx/JMXJolokiaTest.scala: -------------------------------------------------------------------------------- 1 | package fr.janalyse.jmx 2 | 3 | /* 4 | import org.scalatest.FunSuite 5 | import org.scalatest.Matchers._ 6 | import org.scalatest.junit.JUnitRunner 7 | import javax.management.ObjectName 8 | 9 | 10 | @RunWith(classOf[JUnitRunner]) 11 | class JMXJolokiaTest extends FunSuite with ShouldMatchers { 12 | 13 | /* 14 | * Those tests requires a local tomcat on 8080, running jolokia war 15 | * and a standard JSR 160 port configured on port 4500 16 | * (to compare results between jolokia jmx and JSR160 jmx) 17 | */ 18 | 19 | val jolokiaOpts = JMXOptions(host = "localhost", port = 8080) 20 | val jmxOpts = JMXOptions(host = "localhost", port = 4500) 21 | 22 | test("Simple JMX test") { 23 | JMX.once(jolokiaOpts) { jmx => 24 | val os = jmx("java.lang:type=OperatingSystem") 25 | val List(name, version) = os[String]("Name", "Version") 26 | name.size should be > (0) 27 | version.size should be > (0) 28 | info("OS Name & Version : %s %s".format(name, version)) 29 | } 30 | } 31 | 32 | test("Browsing & compare") { 33 | def attrsSet(opts: JMXOptions) = { 34 | JMX.once(opts) { jmx => 35 | for { mbean <- jmx.mbeans; attr <- mbean.attributes } 36 | yield new ObjectName(mbean.name) -> attr.name 37 | }.toSet 38 | } 39 | val attrsUsingJsr160 = attrsSet(jmxOpts) 40 | val attrsUsingJolokia = attrsSet(jolokiaOpts) 41 | 42 | val missing = attrsUsingJsr160 -- attrsUsingJolokia 43 | for { miss <- missing} { 44 | info(s"Missing : $miss") 45 | } 46 | info(s"Found ${attrsUsingJolokia.size} attributes using jolokia") 47 | info(s"Found ${attrsUsingJsr160.size} attributes using jsr160") 48 | attrsUsingJolokia.size should equal(attrsUsingJsr160.size) 49 | } 50 | 51 | 52 | 53 | test("Numeric attributes") { 54 | JMX.once(jolokiaOpts) { jmx => 55 | for (os <- jmx.os) { 56 | val numAttrs = os.attributes collect { case x: RichNumberAttribute => x } 57 | val numValues = numAttrs map { a => a.name -> os.getDouble(a).get } 58 | for{(k,v) <- numValues} info(s"$k=$v") 59 | numValues.size should be >(0) 60 | } 61 | } 62 | } 63 | 64 | test("Complex types") { 65 | JMX.once(jolokiaOpts) { jmx => 66 | val rt = jmx("java.lang:type=Runtime") 67 | val th = jmx("java.lang:type=Threading") 68 | val mem = jmx("java.lang:type=Memory") 69 | 70 | // Array[String] 71 | val args = rt.get[List[String]]("InputArguments") 72 | args.get.head 73 | 74 | // Array[Long] 75 | val ids = th.get[List[BigInt]]("AllThreadIds") 76 | ids.get.head 77 | 78 | // TabularDataSupport 79 | val props = rt.get[Map[String,String]]("SystemProperties") 80 | props.get.get("user.dir") 81 | 82 | // CompositeDataSupport 83 | val heap = mem.get[Map[String,BigInt]]("HeapMemoryUsage") 84 | heap.get.get("max") 85 | } 86 | } 87 | 88 | 89 | } 90 | */ 91 | -------------------------------------------------------------------------------- /version.sbt: -------------------------------------------------------------------------------- 1 | version in ThisBuild := "0.8.5-SNAPSHOT" 2 | --------------------------------------------------------------------------------