├── .gitignore ├── LICENSE ├── README.md ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── stephan │ │ └── tof │ │ └── jmxmon │ │ ├── Config.java │ │ ├── Constants.java │ │ ├── HttpClientUtils.java │ │ ├── JMXCall.java │ │ ├── JMXMonitor.java │ │ ├── JVMDataExtractor.java │ │ ├── JVMGCGenInfoExtractor.java │ │ ├── JVMGCThroughputExtractor.java │ │ ├── JVMMemoryUsedExtractor.java │ │ ├── JVMThreadExtractor.java │ │ ├── Utils.java │ │ ├── bean │ │ ├── CustomDoubleSerialize.java │ │ ├── FalconItem.java │ │ ├── GCData.java │ │ ├── JVMContext.java │ │ ├── JVMData.java │ │ └── JacksonUtil.java │ │ └── jmxutil │ │ ├── JConsoleContext.java │ │ ├── LocalVirtualMachine.java │ │ ├── MemoryPoolProxy.java │ │ ├── MemoryPoolStat.java │ │ ├── NamedThreadFactory.java │ │ └── ProxyClient.java └── resources │ ├── conf.properties │ └── log4j.properties └── test └── java └── com └── stephan └── tof └── jmxmon └── bean ├── FalconPostDataTest.java └── JVMContextTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /.settings 3 | /.project 4 | /.classpath 5 | /coverage-report 6 | /test.jvmcontext.json 7 | /jmxmon.jvm.context.json 8 | /test.falconPostData.json 9 | -------------------------------------------------------------------------------- /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 | # jmxmon 简介 2 | jmxmon是一个基于open-falcon的jmx监控插件,通过这个插件,结合open-falcon agent,可以采集任何开启了JMX服务端口的java进程的服务状态,并将采集信息自动上报给open-falcon服务端 3 | 4 | ## 主要功能 5 | 6 | 通过jmx采集java进程的jvm信息,包括gc耗时、gc次数、gc吞吐、老年代使用率、新生代晋升大小、活跃线程数等信息。 7 | 8 | 对应用程序代码无侵入,几乎不占用系统资源。 9 | 10 | ## 环境需求 11 | 12 | Linux 13 | 14 | JDK>=1.6 15 | 16 | Open-Falcon>=0.0.5 17 | 18 | 目标java进程开启jmx端口 19 | 20 | ## jmxmon部署 21 | 22 | 1. 安装并启动open-falcon agent 23 | 2. 下载并解压编译好的 [release包](https://github.com/toomanyopenfiles/jmxmon/releases/latest) 到目标安装目录下 24 | 3. cp conf.example.properties conf.properties 25 | 4. 修改conf.properties配置文件,一般情况下只需要将jmx.ports的端口号配置上就可以了 26 | 5. sh control start 27 | 6. sh control tail查看日志,或者cat var/app.log以确认程序是否正常启动 28 | 29 | ## 配置说明 30 | 配置文件默认文件名为conf.properties,内容说明如下: 31 | 32 | # 工作目录用来存放jmxmon的临时缓存文件,注意不要修改此目录下的文件 33 | workDir=./ 34 | 35 | # 需要监听的本地jmx端口,支持监听多个端口,多端口用逗号分隔 36 | jmx.ports=10000,10001,10002,10003 37 | 38 | # 本地agent的上报url,如果使用open-falcon的默认配置,则这里不需要改变 39 | agent.posturl=http://localhost:1988/v1/push 40 | 41 | # 可选项:上报给open-falcon的endpoint,默认值为本机hostname。不建议修改 42 | #hostname= 43 | 44 | # 可选项:上报给open-falcon的上报间隔,默认值60,单位秒。不建议修改 45 | #step= 46 | 47 | ## 采集指标 48 | | Counters | Type | Notes| 49 | |-----|------|------| 50 | | parnew.gc.avg.time | GAUGE | 一分钟内,每次YoungGC(parnew)的平均耗时 | 51 | | concurrentmarksweep.gc.avg.time | GAUGE | 一分钟内,每次CMSGC的平均耗时 | 52 | | parnew.gc.count | GAUGE | 一分钟内,YoungGC(parnew)的总次数 | 53 | | concurrentmarksweep.gc.count | GAUGE | 一分钟内,CMSGC的总次数 | 54 | | gc.throughput | GAUGE | GC的总吞吐率(应用运行时间/进程总运行时间) | 55 | | new.gen.promotion | GAUGE | 一分钟内,新生代的内存晋升总大小 | 56 | | new.gen.avg.promotion | GAUGE | 一分钟内,平均每次YoungGC的新生代内存晋升大小 | 57 | | old.gen.mem.used | GAUGE | 老年代的内存使用量 | 58 | | old.gen.mem.ratio | GAUGE | 老年代的内存使用率 | 59 | | thread.active.count | GAUGE | 当前活跃线程数 | 60 | | thread.peak.count | GAUGE | 峰值线程数 | 61 | 62 | ## 建议设置监控告警项 63 | 64 | 不同应用根据其特点,可以灵活调整触发条件及触发阈值 65 | 66 | | 告警项 | 触发条件 | 备注| 67 | |-----|------|------| 68 | | gc.throughput | all(#3)<98 | gc吞吐率低于98%,影响性能 | 69 | | old.gen.mem.ratio | all(#3)>90 | 老年代内存使用率高于90%,需要调优 | 70 | | thread.active.count | all(#3)>500 | 线程数过多,影响性能 | 71 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | com.stephan.tof 5 | jmxmon 6 | 0.0.2 7 | jar 8 | jmxmon 9 | 10 | 11 | 12 | Stephan Gao 13 | blueswind830630@gmail.com 14 | 15 | 16 | 17 | UTF-8 18 | 2.5.2 19 | 20 | 21 | 22 | 23 | 24 | src/main/java 25 | 26 | **/* 27 | 28 | 29 | **/*.java 30 | 31 | false 32 | 33 | 34 | src/main/resources 35 | 36 | **/* 37 | 38 | false 39 | 40 | 41 | 42 | 43 | org.apache.maven.plugins 44 | maven-assembly-plugin 45 | 2.5.5 46 | 47 | 48 | jar-with-dependencies 49 | 50 | 51 | 52 | 53 | make-assembly 54 | 55 | package 56 | 57 | 58 | single 59 | 60 | 61 | 62 | 63 | 64 | org.apache.maven.plugins 65 | maven-compiler-plugin 66 | 2.5.1 67 | 68 | 1.7 69 | 1.7 70 | UTF-8 71 | 72 | 73 | 74 | org.apache.maven.plugins 75 | maven-surefire-plugin 76 | 2.12.4 77 | 78 | -Dfile.encoding=UTF8 79 | 80 | 81 | 82 | org.apache.maven.plugins 83 | maven-javadoc-plugin 84 | 2.9 85 | 86 | 87 | package 88 | 89 | jar 90 | 91 | 92 | 93 | 94 | UTF-8 95 | UTF-8 96 | UTF-8 97 | 98 | 99 | 100 | org.apache.maven.plugins 101 | maven-source-plugin 102 | 103 | 104 | package 105 | 106 | jar 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | jdk.tools 117 | jdk.tools 118 | 1.7 119 | system 120 | ${JAVA_HOME}/lib/tools.jar 121 | 122 | 123 | 124 | commons-configuration 125 | commons-configuration 126 | 1.10 127 | 128 | 129 | commons-io 130 | commons-io 131 | 2.4 132 | 133 | 134 | 135 | log4j 136 | log4j 137 | 1.2.17 138 | jar 139 | 140 | 141 | org.slf4j 142 | slf4j-log4j12 143 | 1.7.12 144 | 145 | 146 | 147 | com.fasterxml.jackson.core 148 | jackson-databind 149 | ${jackson.version} 150 | 151 | 152 | com.fasterxml.jackson.core 153 | jackson-core 154 | ${jackson.version} 155 | 156 | 157 | com.fasterxml.jackson.core 158 | jackson-annotations 159 | ${jackson.version} 160 | 161 | 162 | 163 | com.google.guava 164 | guava 165 | 13.0.1 166 | 167 | 168 | 169 | org.jmockit 170 | jmockit 171 | 1.16 172 | test 173 | 174 | 175 | org.jmockit 176 | jmockit-coverage 177 | 1.16 178 | test 179 | 180 | 181 | junit 182 | junit 183 | 4.11 184 | test 185 | 186 | 187 | org.assertj 188 | assertj-core 189 | 2.0.0 190 | test 191 | 192 | 193 | -------------------------------------------------------------------------------- /src/main/java/com/stephan/tof/jmxmon/Config.java: -------------------------------------------------------------------------------- 1 | package com.stephan.tof.jmxmon; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | 6 | import org.apache.commons.configuration.ConfigurationException; 7 | import org.apache.commons.configuration.PropertiesConfiguration; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import com.stephan.tof.jmxmon.bean.JVMContext; 12 | import com.stephan.tof.jmxmon.bean.JacksonUtil; 13 | 14 | public class Config { 15 | 16 | public static final Config I = new Config(); 17 | 18 | private Logger logger = LoggerFactory.getLogger(getClass()); 19 | 20 | private String workDir; 21 | private File jvmContextFile; // 用来存放JVM数据文件(比如上一次的gc总耗时、gc总次数等),文件保存在workDir下 22 | private JVMContext jvmContext = new JVMContext(); // JVM数据文件对象 23 | 24 | private String hostname; 25 | private String agentPostUrl; 26 | private int step; 27 | 28 | private String jmxHost; 29 | private int[] jmxPorts; 30 | 31 | private Config(){} 32 | 33 | public void init(String configPath) throws ConfigurationException, IOException{ 34 | logger.info("init config"); 35 | 36 | PropertiesConfiguration config = new PropertiesConfiguration(configPath); 37 | config.setThrowExceptionOnMissing(true); 38 | 39 | this.workDir = config.getString("workDir"); 40 | if (new File(workDir).isDirectory() == false) { 41 | throw new IllegalArgumentException("workDir is not a directory"); 42 | } 43 | 44 | this.hostname = config.getString("hostname", Utils.getHostNameForLinux()); 45 | 46 | this.jvmContextFile = new File(workDir, "jmxmon.jvm.context.json"); 47 | 48 | if (jvmContextFile.exists() && jvmContextFile.isFile() && 49 | jvmContextFile.length() > 0) { 50 | logger.info(jvmContextFile.getAbsolutePath() + " is exist, start loading..."); 51 | this.jvmContext = JacksonUtil.readBeanFromFile(jvmContextFile, JVMContext.class); 52 | } else { 53 | logger.info(jvmContextFile.getAbsolutePath() + " is not exist"); 54 | } 55 | 56 | this.agentPostUrl = config.getString("agent.posturl"); 57 | this.step = config.getInt("step", Constants.defaultStep); 58 | 59 | // 默认的jmxHost为localhost,除非通过-D参数设置(线上不建议以远程方式采集,最好每台机器上部署agent,这样agent才能水平伸缩) 60 | this.jmxHost = System.getProperty("debug.jmx.host"); 61 | if (this.jmxHost == null) { 62 | this.jmxHost = "localhost"; 63 | } 64 | 65 | String[] jmxPortArray = config.getStringArray("jmx.ports"); 66 | jmxPorts = new int[jmxPortArray.length]; 67 | for (int i = 0; i < jmxPortArray.length; i++) { 68 | jmxPorts[i] = Integer.parseInt(jmxPortArray[i]); 69 | } 70 | 71 | logger.info("init config ok"); 72 | } 73 | 74 | /** 75 | * 保存数据文件 76 | * @throws IOException 77 | */ 78 | public void flush() throws IOException { 79 | JacksonUtil.writeBeanToFile(jvmContextFile, jvmContext, true); 80 | } 81 | 82 | /** 83 | * @return the workDir 84 | */ 85 | public String getWorkDir() { 86 | return workDir; 87 | } 88 | 89 | /** 90 | * @return the hostname 91 | */ 92 | public String getHostname() { 93 | return hostname; 94 | } 95 | 96 | /** 97 | * @return the agentPostUrl 98 | */ 99 | public String getAgentPostUrl() { 100 | return agentPostUrl; 101 | } 102 | 103 | /** 104 | * @return the step 105 | */ 106 | public int getStep() { 107 | return step; 108 | } 109 | 110 | /** 111 | * @return the jmxHost 112 | */ 113 | public String getJmxHost() { 114 | return jmxHost; 115 | } 116 | 117 | /** 118 | * @return the jmxPorts 119 | */ 120 | public int[] getJmxPorts() { 121 | return jmxPorts; 122 | } 123 | 124 | /** 125 | * @return the jvmContext 126 | */ 127 | public JVMContext getJvmContext() { 128 | return jvmContext; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/main/java/com/stephan/tof/jmxmon/Constants.java: -------------------------------------------------------------------------------- 1 | package com.stephan.tof.jmxmon; 2 | 3 | public class Constants { 4 | 5 | public static enum CounterType { COUNTER, GAUGE } 6 | 7 | public static final String gcAvgTime = "gc.avg.time"; 8 | public static final String gcCount = "gc.count"; 9 | public static final String gcThroughput = "gc.throughput"; 10 | public static final String newGenPromotion = "new.gen.promotion"; 11 | public static final String newGenAvgPromotion = "new.gen.avg.promotion"; 12 | public static final String oldGenMemUsed = "old.gen.mem.used"; 13 | public static final String oldGenMemRatio = "old.gen.mem.ratio"; 14 | public static final String threadActiveCount = "thread.active.count"; 15 | public static final String threadPeakCount = "thread.peak.count"; 16 | 17 | public static final String tagSeparator = ","; 18 | public static final String metricSeparator = "."; 19 | public static final int defaultStep = 60; // 单位秒 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/stephan/tof/jmxmon/HttpClientUtils.java: -------------------------------------------------------------------------------- 1 | package com.stephan.tof.jmxmon; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.DataOutputStream; 5 | import java.io.IOException; 6 | import java.io.InputStreamReader; 7 | import java.net.HttpURLConnection; 8 | import java.net.MalformedURLException; 9 | import java.net.URL; 10 | 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | /** 15 | * update httpclient to HttpUrlConnection
16 | * 17 | * @since 2015-08-17
18 | * @author stevenDing
19 | * 20 | */ 21 | public class HttpClientUtils { 22 | 23 | private static final int readTimeout = 20000; 24 | private static final int connTimeout = 5000; 25 | private Logger logger = LoggerFactory.getLogger(this.getClass()); 26 | private static final HttpClientUtils httpClient = new HttpClientUtils(); 27 | private static final int defaultRetryTimes = 3; 28 | public static final int errorStatusCode = 900; 29 | public static final int okStatusCode = 200; 30 | public static final String defaultContentType = "application/json; charset=utf-8"; 31 | public static final String urlencodedContentType = "application/x-www-form-urlencoded"; 32 | 33 | public static HttpClientUtils getInstance() { 34 | return httpClient; 35 | } 36 | 37 | public HttpResult post(String url, String content,String contentType){ 38 | 39 | StringBuffer buffer = new StringBuffer(); 40 | BufferedReader reader = null; 41 | HttpURLConnection conn = null; 42 | HttpResult result = new HttpResult(); 43 | int i=0; 44 | while(true){ 45 | if(i>=defaultRetryTimes || result.getStatusCode()==okStatusCode){ 46 | break; 47 | } 48 | i++; 49 | try{ 50 | URL postUrl = new URL(url); 51 | // 打开连接 52 | conn = (HttpURLConnection) postUrl.openConnection(); 53 | conn.setConnectTimeout(connTimeout); 54 | conn.setReadTimeout(readTimeout); 55 | // 设置是否向connection输出,因为这个是post请求,参数要放在http正文内,因此需要设为true 56 | conn.setDoOutput(true); 57 | conn.setDoInput(true); 58 | conn.setRequestMethod("POST"); 59 | // Post 请求不能使用缓存 60 | conn.setUseCaches(false); 61 | // URLConnection.setInstanceFollowRedirects是成员函数,仅作用于当前函数 62 | conn.setInstanceFollowRedirects(true); 63 | // 配置本次连接的Content-type,配置为application/x-www-form-urlencoded的 64 | // 意思是正文是urlencoded编码过的form参数,下面我们可以看到我们对正文内容使用URLEncoder.encode进行编码 65 | //conn.setRequestProperty("Content-Type","application/x-www-form-urlencoded"); 66 | //conn.setRequestProperty("Content-Type", "text/html; charset=utf-8"); 67 | //conn.setRequestProperty("Content-Type", "text/xml; charset=utf-8"); 68 | //conn.setRequestProperty("Content-Type", "text/json; charset=utf-8"); 69 | //conn.setRequestProperty("Content-Type", "application/json; charset=utf-8"); 70 | if(contentType!=null && contentType.length()>0){ 71 | conn.setRequestProperty("Content-Type", contentType); 72 | }else{ 73 | conn.setRequestProperty("Content-Type", defaultContentType); 74 | } 75 | // 连接,从postUrl.openConnection()至此的配置必须要在connect之前完成, 76 | // 要注意的是connection.getOutputStream会隐含的进行connect。 77 | conn.connect(); 78 | DataOutputStream out = new DataOutputStream(conn.getOutputStream()); 79 | out.write(content.getBytes("UTF-8")); 80 | 81 | out.flush(); 82 | out.close(); // flush and close 83 | reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); 84 | String line; 85 | while ((line = reader.readLine()) != null) { 86 | buffer.append(line); 87 | } 88 | result.setContent(buffer.toString()); 89 | result.setStatusCode(okStatusCode); 90 | }catch(MalformedURLException e) { 91 | logger.error(e.getMessage(),e); 92 | result.setStatusCode(errorStatusCode); 93 | result.setT(e); 94 | }catch (IOException e){ 95 | logger.error(e.getMessage(),e); 96 | result.setStatusCode(errorStatusCode); 97 | result.setT(e); 98 | }catch(Exception e){ 99 | logger.error(e.getMessage(),e); 100 | result.setStatusCode(errorStatusCode); 101 | result.setT(e); 102 | }finally{ 103 | if(reader!=null){ 104 | try{ 105 | reader.close(); 106 | }catch(IOException e){ 107 | logger.error(e.getMessage(),e); 108 | } 109 | } 110 | if(conn!=null){ 111 | conn.disconnect(); 112 | } 113 | } 114 | } 115 | return result; 116 | } 117 | 118 | public HttpResult post(String url, String content) { 119 | return post(url, content, defaultContentType); 120 | } 121 | 122 | public class HttpResult { 123 | private String content; 124 | private int statusCode = errorStatusCode; 125 | private Throwable t; 126 | 127 | public Throwable getT() { 128 | return t; 129 | } 130 | 131 | public void setT(Throwable t) { 132 | this.t = t; 133 | } 134 | 135 | public String getContent() { 136 | return content; 137 | } 138 | 139 | public void setContent(String content) { 140 | this.content = content; 141 | } 142 | 143 | public int getStatusCode() { 144 | return statusCode; 145 | } 146 | 147 | public void setStatusCode(int statusCode) { 148 | this.statusCode = statusCode; 149 | } 150 | 151 | @Override 152 | public String toString() { 153 | return "statusCode :"+statusCode+" content :"+content; 154 | } 155 | } 156 | 157 | } 158 | -------------------------------------------------------------------------------- /src/main/java/com/stephan/tof/jmxmon/JMXCall.java: -------------------------------------------------------------------------------- 1 | package com.stephan.tof.jmxmon; 2 | 3 | import java.util.List; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import com.stephan.tof.jmxmon.bean.FalconItem; 9 | import com.stephan.tof.jmxmon.jmxutil.ProxyClient; 10 | 11 | public abstract class JMXCall { 12 | 13 | protected final Logger logger = LoggerFactory.getLogger(getClass()); 14 | 15 | private final ProxyClient proxyClient; 16 | private final int jmxPort; 17 | 18 | public JMXCall(final ProxyClient proxyClient, int jmxPort) { 19 | this.proxyClient = proxyClient; 20 | 21 | if (jmxPort <= 0) { 22 | throw new IllegalStateException("jmxPort is 0, client=" + proxyClient.getUrl()); 23 | } 24 | this.jmxPort = jmxPort; 25 | } 26 | 27 | /** 28 | * 调用jmx接口获取数据 29 | * 30 | * @return 31 | * @throws Exception 32 | */ 33 | public abstract T call() throws Exception; 34 | 35 | /** 36 | * 将jmx获取到的数据组装成Openfalcon数据返回 37 | * 38 | * @param jmxResultData 39 | * @return 40 | * @throws Exception 41 | */ 42 | public abstract List build(T jmxResultData) throws Exception; 43 | 44 | /** 45 | * @return the proxyClient 46 | */ 47 | public ProxyClient getProxyClient() { 48 | return proxyClient; 49 | } 50 | 51 | /** 52 | * @return the jmxPort 53 | */ 54 | public int getJmxPort() { 55 | return jmxPort; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/stephan/tof/jmxmon/JMXMonitor.java: -------------------------------------------------------------------------------- 1 | package com.stephan.tof.jmxmon; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.concurrent.Executors; 7 | import java.util.concurrent.ScheduledExecutorService; 8 | import java.util.concurrent.TimeUnit; 9 | 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import com.stephan.tof.jmxmon.HttpClientUtils.HttpResult; 14 | import com.stephan.tof.jmxmon.JVMGCGenInfoExtractor.GCGenInfo; 15 | import com.stephan.tof.jmxmon.JVMMemoryUsedExtractor.MemoryUsedInfo; 16 | import com.stephan.tof.jmxmon.JVMThreadExtractor.ThreadInfo; 17 | import com.stephan.tof.jmxmon.bean.FalconItem; 18 | import com.stephan.tof.jmxmon.bean.JacksonUtil; 19 | import com.stephan.tof.jmxmon.jmxutil.ProxyClient; 20 | 21 | public class JMXMonitor { 22 | 23 | private static Logger logger = LoggerFactory.getLogger(JMXMonitor.class); 24 | 25 | public static void main(String[] args) { 26 | if (args.length != 1) { 27 | throw new IllegalArgumentException("Usage: configFile"); 28 | } 29 | 30 | try { 31 | Config.I.init(args[0]); 32 | } catch (Exception e) { 33 | logger.error(e.getMessage(), e); 34 | throw new IllegalStateException(e); // 抛出异常便于外部脚本感知 35 | } 36 | 37 | ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); 38 | executor.scheduleAtFixedRate(new Runnable() { 39 | @Override 40 | public void run() { 41 | runTask(); 42 | } 43 | }, 0, Config.I.getStep(), TimeUnit.SECONDS); 44 | 45 | } 46 | 47 | /** 48 | * 49 | */ 50 | private static void runTask() { 51 | try { 52 | List items = new ArrayList(); 53 | 54 | for (int jmxPort : Config.I.getJmxPorts()) { 55 | // 从JMX中获取JVM信息 56 | ProxyClient proxyClient = null; 57 | try { 58 | proxyClient = ProxyClient.getProxyClient(Config.I.getJmxHost(), jmxPort, null, null); 59 | proxyClient.connect(); 60 | 61 | JMXCall> gcGenInfoExtractor = new JVMGCGenInfoExtractor(proxyClient, jmxPort); 62 | Map genInfoMap = gcGenInfoExtractor.call(); 63 | items.addAll(gcGenInfoExtractor.build(genInfoMap)); 64 | 65 | JMXCall gcThroughputExtractor = new JVMGCThroughputExtractor(proxyClient, jmxPort); 66 | Double gcThroughput = gcThroughputExtractor.call(); 67 | items.addAll(gcThroughputExtractor.build(gcThroughput)); 68 | 69 | JMXCall memoryUsedExtractor = new JVMMemoryUsedExtractor(proxyClient, jmxPort); 70 | MemoryUsedInfo memoryUsedInfo = memoryUsedExtractor.call(); 71 | items.addAll(memoryUsedExtractor.build(memoryUsedInfo)); 72 | 73 | JMXCall threadExtractor = new JVMThreadExtractor(proxyClient, jmxPort); 74 | ThreadInfo threadInfo = threadExtractor.call(); 75 | items.addAll(threadExtractor.build(threadInfo)); 76 | } finally { 77 | if (proxyClient != null) { 78 | proxyClient.disconnect(); 79 | } 80 | } 81 | } 82 | 83 | // 发送items给Openfalcon agent 84 | String content = JacksonUtil.writeBeanToString(items, false); 85 | HttpResult postResult = HttpClientUtils.getInstance().post(Config.I.getAgentPostUrl(), content); 86 | logger.info("post status=" + postResult.getStatusCode() + 87 | ", post url=" + Config.I.getAgentPostUrl() + ", content=" + content); 88 | if (postResult.getStatusCode() != HttpClientUtils.okStatusCode || 89 | postResult.getT() != null) { 90 | throw postResult.getT(); 91 | } 92 | 93 | // 将context数据回写文件 94 | Config.I.flush(); 95 | } catch (Throwable e) { 96 | logger.error(e.getMessage(), e); 97 | } 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/com/stephan/tof/jmxmon/JVMDataExtractor.java: -------------------------------------------------------------------------------- 1 | package com.stephan.tof.jmxmon; 2 | 3 | import java.io.IOException; 4 | import java.lang.management.GarbageCollectorMXBean; 5 | import java.lang.management.RuntimeMXBean; 6 | import java.lang.management.ThreadMXBean; 7 | import java.util.Collection; 8 | 9 | import com.stephan.tof.jmxmon.jmxutil.MemoryPoolProxy; 10 | import com.stephan.tof.jmxmon.jmxutil.ProxyClient; 11 | 12 | public abstract class JVMDataExtractor extends JMXCall { 13 | 14 | private final Collection gcMXBeanList; 15 | 16 | private final RuntimeMXBean runtimeMXBean; 17 | 18 | private final Collection memoryPoolList; 19 | 20 | private final ThreadMXBean threadMXBean; 21 | 22 | public JVMDataExtractor(ProxyClient proxyClient, int jmxPort) throws IOException { 23 | super(proxyClient, jmxPort); 24 | gcMXBeanList = proxyClient.getGarbageCollectorMXBeans(); 25 | runtimeMXBean = proxyClient.getRuntimeMXBean(); 26 | memoryPoolList = proxyClient.getMemoryPoolProxies(); 27 | threadMXBean = proxyClient.getThreadMXBean(); 28 | } 29 | 30 | /** 31 | * @return the gcMXBeanList 32 | */ 33 | public Collection getGcMXBeanList() { 34 | return gcMXBeanList; 35 | } 36 | 37 | /** 38 | * @return the runtimeMXBean 39 | */ 40 | public RuntimeMXBean getRuntimeMXBean() { 41 | return runtimeMXBean; 42 | } 43 | 44 | /** 45 | * @return the memoryPool 46 | */ 47 | public Collection getMemoryPoolList() { 48 | return memoryPoolList; 49 | } 50 | 51 | /** 52 | * @return the threadMXBean 53 | */ 54 | public ThreadMXBean getThreadMXBean() { 55 | return threadMXBean; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/stephan/tof/jmxmon/JVMGCGenInfoExtractor.java: -------------------------------------------------------------------------------- 1 | package com.stephan.tof.jmxmon; 2 | 3 | import java.io.IOException; 4 | import java.lang.management.GarbageCollectorMXBean; 5 | import java.util.ArrayList; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | import org.apache.commons.lang.StringUtils; 11 | 12 | import com.stephan.tof.jmxmon.Constants.CounterType; 13 | import com.stephan.tof.jmxmon.JVMGCGenInfoExtractor.GCGenInfo; 14 | import com.stephan.tof.jmxmon.bean.FalconItem; 15 | import com.stephan.tof.jmxmon.bean.GCData; 16 | import com.stephan.tof.jmxmon.bean.JVMContext; 17 | import com.stephan.tof.jmxmon.jmxutil.ProxyClient; 18 | 19 | public class JVMGCGenInfoExtractor extends JVMDataExtractor> { 20 | 21 | public JVMGCGenInfoExtractor(ProxyClient proxyClient, int jmxPort) throws IOException { 22 | super(proxyClient, jmxPort); 23 | } 24 | 25 | /** 26 | * 获取时间窗口内,每代GC的平均耗时
27 | * 返回值:gcMXBean name -> avgGCTime 28 | */ 29 | @Override 30 | public Map call() throws Exception { 31 | Map result = new HashMap(); 32 | 33 | JVMContext c = Config.I.getJvmContext(); 34 | for (GarbageCollectorMXBean gcMXBean : getGcMXBeanList()) { 35 | long gcTotalTime = gcMXBean.getCollectionTime(); 36 | long gcTotalCount = gcMXBean.getCollectionCount(); 37 | 38 | GCData gcData = c.getJvmData(getJmxPort()).getGcData(gcMXBean.getName()); 39 | long lastGCTotalTime = gcData.getCollectionTime(); 40 | long lastGCTotalCount = gcData.getCollectionCount(); 41 | 42 | long tmpGCTime = gcTotalTime - lastGCTotalTime; 43 | long gcCount = gcTotalCount - lastGCTotalCount; 44 | if (lastGCTotalCount <= 0 || gcCount < 0) { 45 | gcCount = -1; 46 | } 47 | 48 | double avgGCTime = gcCount > 0 ? tmpGCTime / gcCount : 0; 49 | 50 | GCGenInfo gcGenInfo = new GCGenInfo(avgGCTime, gcCount); 51 | result.put(gcMXBean.getName(), gcGenInfo); 52 | 53 | logger.debug("mxbean=" + gcMXBean.getName() + 54 | ", gcTotalTime=" + gcTotalTime + ", gcTotalCount=" + gcTotalCount + 55 | ", lastGCTotalTime=" + lastGCTotalTime + ", lastGCTotalCount=" + lastGCTotalCount + 56 | ", avgGCTime=" + avgGCTime + ", gcCount=" + gcCount); 57 | 58 | // update last data 59 | gcData.setCollectionTime(gcTotalTime); 60 | gcData.setCollectionCount(gcTotalCount); 61 | gcData.setUnitTimeCollectionCount(gcCount); 62 | } 63 | 64 | return result; 65 | } 66 | 67 | @Override 68 | public List build(Map jmxResultData) 69 | throws Exception { 70 | List items = new ArrayList(); 71 | 72 | // 将jvm信息封装成openfalcon格式数据 73 | for (String gcMXBeanName : jmxResultData.keySet()) { 74 | FalconItem avgTimeItem = new FalconItem(); 75 | avgTimeItem.setCounterType(CounterType.GAUGE.toString()); 76 | avgTimeItem.setEndpoint(Config.I.getHostname()); 77 | avgTimeItem.setMetric(StringUtils.lowerCase(gcMXBeanName + Constants.metricSeparator + Constants.gcAvgTime)); 78 | avgTimeItem.setStep(Constants.defaultStep); 79 | avgTimeItem.setTags(StringUtils.lowerCase("jmxport=" + getJmxPort())); 80 | avgTimeItem.setTimestamp(System.currentTimeMillis() / 1000); 81 | avgTimeItem.setValue(jmxResultData.get(gcMXBeanName).getGcAvgTime()); 82 | items.add(avgTimeItem); 83 | 84 | FalconItem countItem = new FalconItem(); 85 | countItem.setCounterType(CounterType.GAUGE.toString()); 86 | countItem.setEndpoint(Config.I.getHostname()); 87 | countItem.setMetric(StringUtils.lowerCase(gcMXBeanName + Constants.metricSeparator + Constants.gcCount)); 88 | countItem.setStep(Constants.defaultStep); 89 | countItem.setTags(StringUtils.lowerCase("jmxport=" + getJmxPort())); 90 | countItem.setTimestamp(System.currentTimeMillis() / 1000); 91 | countItem.setValue(jmxResultData.get(gcMXBeanName).getGcCount()); 92 | items.add(countItem); 93 | } 94 | 95 | return items; 96 | } 97 | 98 | class GCGenInfo { 99 | private final double gcAvgTime; 100 | private final long gcCount; 101 | 102 | public GCGenInfo(double gcAvgTime, long gcCount) { 103 | this.gcAvgTime = gcAvgTime; 104 | this.gcCount = gcCount; 105 | } 106 | 107 | /** 108 | * @return the gcAvgTime 109 | */ 110 | public double getGcAvgTime() { 111 | return gcAvgTime; 112 | } 113 | 114 | /** 115 | * @return the gcCount 116 | */ 117 | public long getGcCount() { 118 | return gcCount; 119 | } 120 | } 121 | 122 | } 123 | -------------------------------------------------------------------------------- /src/main/java/com/stephan/tof/jmxmon/JVMGCThroughputExtractor.java: -------------------------------------------------------------------------------- 1 | package com.stephan.tof.jmxmon; 2 | 3 | import java.io.IOException; 4 | import java.lang.management.GarbageCollectorMXBean; 5 | import java.lang.management.RuntimeMXBean; 6 | import java.util.ArrayList; 7 | import java.util.Collection; 8 | import java.util.List; 9 | 10 | import org.apache.commons.lang.StringUtils; 11 | 12 | import com.stephan.tof.jmxmon.Constants.CounterType; 13 | import com.stephan.tof.jmxmon.bean.FalconItem; 14 | import com.stephan.tof.jmxmon.jmxutil.ProxyClient; 15 | 16 | public class JVMGCThroughputExtractor extends JVMDataExtractor { 17 | 18 | public JVMGCThroughputExtractor(ProxyClient proxyClient, int jmxPort) 19 | throws IOException { 20 | super(proxyClient, jmxPort); 21 | } 22 | 23 | @Override 24 | public Double call() throws Exception { 25 | RuntimeMXBean rbean = getRuntimeMXBean(); 26 | 27 | long upTime = rbean.getUptime(); 28 | long totalGCTime = 0; 29 | Collection list = getGcMXBeanList(); 30 | for (GarbageCollectorMXBean bean : list) { 31 | totalGCTime += bean.getCollectionTime(); 32 | } 33 | 34 | double gcThroughput = (double) (upTime - totalGCTime) * 100 / (double) upTime; 35 | return gcThroughput; 36 | } 37 | 38 | @Override 39 | public List build(Double jmxResultData) throws Exception { 40 | List items = new ArrayList(); 41 | 42 | // 将jvm信息封装成openfalcon格式数据 43 | FalconItem item = new FalconItem(); 44 | item.setCounterType(CounterType.GAUGE.toString()); 45 | item.setEndpoint(Config.I.getHostname()); 46 | item.setMetric(StringUtils.lowerCase(Constants.gcThroughput)); 47 | item.setStep(Constants.defaultStep); 48 | item.setTags(StringUtils.lowerCase("jmxport=" + getJmxPort())); 49 | item.setTimestamp(System.currentTimeMillis() / 1000); 50 | item.setValue(jmxResultData); 51 | 52 | items.add(item); 53 | 54 | return items; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/stephan/tof/jmxmon/JVMMemoryUsedExtractor.java: -------------------------------------------------------------------------------- 1 | package com.stephan.tof.jmxmon; 2 | 3 | import java.io.IOException; 4 | import java.lang.management.GarbageCollectorMXBean; 5 | import java.util.ArrayList; 6 | import java.util.Collection; 7 | import java.util.List; 8 | 9 | import org.apache.commons.lang.StringUtils; 10 | 11 | import com.stephan.tof.jmxmon.Constants.CounterType; 12 | import com.stephan.tof.jmxmon.JVMMemoryUsedExtractor.MemoryUsedInfo; 13 | import com.stephan.tof.jmxmon.bean.FalconItem; 14 | import com.stephan.tof.jmxmon.bean.GCData; 15 | import com.stephan.tof.jmxmon.bean.JVMContext; 16 | import com.stephan.tof.jmxmon.jmxutil.MemoryPoolProxy; 17 | import com.stephan.tof.jmxmon.jmxutil.ProxyClient; 18 | 19 | public class JVMMemoryUsedExtractor extends JVMDataExtractor { 20 | 21 | public JVMMemoryUsedExtractor(ProxyClient proxyClient, int jmxPort) 22 | throws IOException { 23 | super(proxyClient, jmxPort); 24 | } 25 | 26 | @Override 27 | public MemoryUsedInfo call() throws Exception { 28 | long oldGenUsed = 0; 29 | long maxOldGenMemory = 0; 30 | Collection memoryPoolList = getMemoryPoolList(); 31 | for (MemoryPoolProxy memoryPool : memoryPoolList) { 32 | String poolName = memoryPool.getStat().getPoolName(); 33 | // see: http://stackoverflow.com/questions/16082004/how-to-identify-tenured-space/16083569#16083569 34 | if (poolName.contains("Old Gen") || poolName.contains("Tenured Gen")) { 35 | oldGenUsed = memoryPool.getStat().getUsage().getUsed(); 36 | maxOldGenMemory = memoryPool.getStat().getUsage().getMax(); 37 | break; 38 | } 39 | } 40 | double oldGenUsedRatio = maxOldGenMemory > 0 ? oldGenUsed * 100d / maxOldGenMemory : 0; 41 | 42 | // see: http://stackoverflow.com/questions/32002001/how-to-get-minor-and-major-garbage-collection-count-in-jdk-7-and-jdk-8 43 | JVMContext c = Config.I.getJvmContext(); 44 | GarbageCollectorMXBean[] gcMXBeanArray = getGcMXBeanList().toArray(new GarbageCollectorMXBean[0]); 45 | 46 | // 在一个上报周期内,老年代的内存变化大小~=新生代晋升大小 47 | GarbageCollectorMXBean majorGCMXBean = gcMXBeanArray[1]; 48 | GCData majorGcData = c.getJvmData(getJmxPort()).getGcData(majorGCMXBean.getName()); 49 | long lastOldGenMemoryUsed = majorGcData.getMemoryUsed(); 50 | long newGenPromotion = oldGenUsed - lastOldGenMemoryUsed; 51 | if (lastOldGenMemoryUsed <= 0 || newGenPromotion < 0) { 52 | newGenPromotion = -1; 53 | } 54 | 55 | // 在一个上报周期内,YGC次数 56 | GarbageCollectorMXBean minorGCMXBean = gcMXBeanArray[0]; 57 | GCData minorGcData = c.getJvmData(getJmxPort()).getGcData(minorGCMXBean.getName()); 58 | long gcCount = minorGcData.getUnitTimeCollectionCount(); 59 | 60 | long newGenAvgPromotion = 0; 61 | if (gcCount > 0 && newGenPromotion > 0) { 62 | newGenAvgPromotion = (long) (newGenPromotion / gcCount); 63 | } 64 | 65 | MemoryUsedInfo memoryUsedInfo = new MemoryUsedInfo(oldGenUsed, oldGenUsedRatio, newGenPromotion, newGenAvgPromotion); 66 | 67 | // update last data 68 | majorGcData.setMemoryUsed(oldGenUsed); 69 | 70 | return memoryUsedInfo; 71 | } 72 | 73 | @Override 74 | public List build(MemoryUsedInfo jmxResultData) throws Exception { 75 | List items = new ArrayList(); 76 | 77 | // 将jvm信息封装成openfalcon格式数据 78 | FalconItem oldGenUsedItem = new FalconItem(); 79 | oldGenUsedItem.setCounterType(CounterType.GAUGE.toString()); 80 | oldGenUsedItem.setEndpoint(Config.I.getHostname()); 81 | oldGenUsedItem.setMetric(StringUtils.lowerCase(Constants.oldGenMemUsed)); 82 | oldGenUsedItem.setStep(Constants.defaultStep); 83 | oldGenUsedItem.setTags(StringUtils.lowerCase("jmxport=" + getJmxPort())); 84 | oldGenUsedItem.setTimestamp(System.currentTimeMillis() / 1000); 85 | oldGenUsedItem.setValue(jmxResultData.getOldGenUsed()); 86 | items.add(oldGenUsedItem); 87 | 88 | FalconItem oldGenUsedRatioItem = new FalconItem(); 89 | oldGenUsedRatioItem.setCounterType(CounterType.GAUGE.toString()); 90 | oldGenUsedRatioItem.setEndpoint(Config.I.getHostname()); 91 | oldGenUsedRatioItem.setMetric(StringUtils.lowerCase(Constants.oldGenMemRatio)); 92 | oldGenUsedRatioItem.setStep(Constants.defaultStep); 93 | oldGenUsedRatioItem.setTags(StringUtils.lowerCase("jmxport=" + getJmxPort())); 94 | oldGenUsedRatioItem.setTimestamp(System.currentTimeMillis() / 1000); 95 | oldGenUsedRatioItem.setValue(jmxResultData.getOldGenUsedRatio()); 96 | items.add(oldGenUsedRatioItem); 97 | 98 | FalconItem newGenPromotionItem = new FalconItem(); 99 | newGenPromotionItem.setCounterType(CounterType.GAUGE.toString()); 100 | newGenPromotionItem.setEndpoint(Config.I.getHostname()); 101 | newGenPromotionItem.setMetric(StringUtils.lowerCase(Constants.newGenPromotion)); 102 | newGenPromotionItem.setStep(Constants.defaultStep); 103 | newGenPromotionItem.setTags(StringUtils.lowerCase("jmxport=" + getJmxPort())); 104 | newGenPromotionItem.setTimestamp(System.currentTimeMillis() / 1000); 105 | newGenPromotionItem.setValue(jmxResultData.getNewGenPromotion()); 106 | items.add(newGenPromotionItem); 107 | 108 | FalconItem newGenAvgPromotionItem = new FalconItem(); 109 | newGenAvgPromotionItem.setCounterType(CounterType.GAUGE.toString()); 110 | newGenAvgPromotionItem.setEndpoint(Config.I.getHostname()); 111 | newGenAvgPromotionItem.setMetric(StringUtils.lowerCase(Constants.newGenAvgPromotion)); 112 | newGenAvgPromotionItem.setStep(Constants.defaultStep); 113 | newGenAvgPromotionItem.setTags(StringUtils.lowerCase("jmxport=" + getJmxPort())); 114 | newGenAvgPromotionItem.setTimestamp(System.currentTimeMillis() / 1000); 115 | newGenAvgPromotionItem.setValue(jmxResultData.getNewGenAvgPromotion()); 116 | items.add(newGenAvgPromotionItem); 117 | 118 | return items; 119 | } 120 | 121 | class MemoryUsedInfo { 122 | private final double oldGenUsedRatio; 123 | private final long oldGenUsed; 124 | private final long newGenPromotion; 125 | private final long newGenAvgPromotion; 126 | 127 | public MemoryUsedInfo(long oldGenUsed, double oldGenUsedRatio, long newGenPromotion, long newGenAvgPromotion) { 128 | this.oldGenUsed = oldGenUsed; 129 | this.oldGenUsedRatio = oldGenUsedRatio; 130 | this.newGenPromotion = newGenPromotion; 131 | this.newGenAvgPromotion = newGenAvgPromotion; 132 | } 133 | 134 | /** 135 | * @return the oldGenUsedRatio 136 | */ 137 | public double getOldGenUsedRatio() { 138 | return oldGenUsedRatio; 139 | } 140 | 141 | /** 142 | * @return the oldGenUsed 143 | */ 144 | public long getOldGenUsed() { 145 | return oldGenUsed; 146 | } 147 | 148 | /** 149 | * @return the newGenPromotion 150 | */ 151 | public long getNewGenPromotion() { 152 | return newGenPromotion; 153 | } 154 | 155 | /** 156 | * @return the newGenAvgPromotion 157 | */ 158 | public long getNewGenAvgPromotion() { 159 | return newGenAvgPromotion; 160 | } 161 | 162 | 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/main/java/com/stephan/tof/jmxmon/JVMThreadExtractor.java: -------------------------------------------------------------------------------- 1 | package com.stephan.tof.jmxmon; 2 | 3 | import java.io.IOException; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | import org.apache.commons.lang.StringUtils; 8 | 9 | import com.stephan.tof.jmxmon.Constants.CounterType; 10 | import com.stephan.tof.jmxmon.JVMThreadExtractor.ThreadInfo; 11 | import com.stephan.tof.jmxmon.bean.FalconItem; 12 | import com.stephan.tof.jmxmon.jmxutil.ProxyClient; 13 | 14 | public class JVMThreadExtractor extends JVMDataExtractor { 15 | 16 | public JVMThreadExtractor(ProxyClient proxyClient, int jmxPort) 17 | throws IOException { 18 | super(proxyClient, jmxPort); 19 | } 20 | 21 | @Override 22 | public ThreadInfo call() throws Exception { 23 | int threadNum = getThreadMXBean().getThreadCount(); 24 | int peakThreadNum = getThreadMXBean().getPeakThreadCount(); 25 | 26 | ThreadInfo threadInfo = new ThreadInfo(threadNum, peakThreadNum); 27 | return threadInfo; 28 | } 29 | 30 | @Override 31 | public List build(ThreadInfo jmxResultData) throws Exception { 32 | List items = new ArrayList(); 33 | 34 | // 将jvm信息封装成openfalcon格式数据 35 | FalconItem threadNumItem = new FalconItem(); 36 | threadNumItem.setCounterType(CounterType.GAUGE.toString()); 37 | threadNumItem.setEndpoint(Config.I.getHostname()); 38 | threadNumItem.setMetric(StringUtils.lowerCase(Constants.threadActiveCount)); 39 | threadNumItem.setStep(Constants.defaultStep); 40 | threadNumItem.setTags(StringUtils.lowerCase("jmxport=" + getJmxPort())); 41 | threadNumItem.setTimestamp(System.currentTimeMillis() / 1000); 42 | threadNumItem.setValue(jmxResultData.getThreadNum()); 43 | items.add(threadNumItem); 44 | 45 | FalconItem peakThreadNumItem = new FalconItem(); 46 | peakThreadNumItem.setCounterType(CounterType.GAUGE.toString()); 47 | peakThreadNumItem.setEndpoint(Config.I.getHostname()); 48 | peakThreadNumItem.setMetric(StringUtils.lowerCase(Constants.threadPeakCount)); 49 | peakThreadNumItem.setStep(Constants.defaultStep); 50 | peakThreadNumItem.setTags(StringUtils.lowerCase("jmxport=" + getJmxPort())); 51 | peakThreadNumItem.setTimestamp(System.currentTimeMillis() / 1000); 52 | peakThreadNumItem.setValue(jmxResultData.getPeakThreadNum()); 53 | items.add(peakThreadNumItem); 54 | 55 | return items; 56 | } 57 | 58 | class ThreadInfo { 59 | private final int threadNum; 60 | private final int peakThreadNum; 61 | public ThreadInfo(int threadNum, int peakThreadNum) { 62 | this.threadNum = threadNum; 63 | this.peakThreadNum = peakThreadNum; 64 | } 65 | 66 | /** 67 | * @return the threadNum 68 | */ 69 | public int getThreadNum() { 70 | return threadNum; 71 | } 72 | 73 | /** 74 | * @return the peakThreadNum 75 | */ 76 | public int getPeakThreadNum() { 77 | return peakThreadNum; 78 | } 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/stephan/tof/jmxmon/Utils.java: -------------------------------------------------------------------------------- 1 | package com.stephan.tof.jmxmon; 2 | 3 | import java.net.InetAddress; 4 | import java.net.UnknownHostException; 5 | 6 | public class Utils { 7 | 8 | private Utils(){} 9 | 10 | public static String getHostNameForLinux() { 11 | try { 12 | return (InetAddress.getLocalHost()).getHostName(); 13 | } catch (UnknownHostException uhe) { 14 | String host = uhe.getMessage(); // host = "hostname: hostname" 15 | if (host != null) { 16 | int colon = host.indexOf(':'); 17 | if (colon > 0) { 18 | return host.substring(0, colon); 19 | } 20 | } 21 | return "UnknownHost"; 22 | } 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/stephan/tof/jmxmon/bean/CustomDoubleSerialize.java: -------------------------------------------------------------------------------- 1 | package com.stephan.tof.jmxmon.bean; 2 | 3 | import java.io.IOException; 4 | import java.text.DecimalFormat; 5 | 6 | import com.fasterxml.jackson.core.JsonGenerator; 7 | import com.fasterxml.jackson.core.JsonProcessingException; 8 | import com.fasterxml.jackson.databind.JsonSerializer; 9 | import com.fasterxml.jackson.databind.SerializerProvider; 10 | 11 | public class CustomDoubleSerialize extends JsonSerializer { 12 | 13 | private DecimalFormat df = new DecimalFormat("##.00"); 14 | 15 | @Override 16 | public void serialize(Double value, JsonGenerator jgen, 17 | SerializerProvider provider) throws IOException, 18 | JsonProcessingException { 19 | 20 | jgen.writeNumber(Double.parseDouble(df.format(value))); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/stephan/tof/jmxmon/bean/FalconItem.java: -------------------------------------------------------------------------------- 1 | package com.stephan.tof.jmxmon.bean; 2 | 3 | import org.apache.commons.lang.builder.ToStringBuilder; 4 | 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 7 | 8 | /** 9 | * { "metric": "metric.demo", "endpoint": "qd-open-falcon-judge01.hd", 10 | * "timestamp": $ts, "step": 60, "value": 9, "counterType": "GAUGE", "tags": 11 | * "project=falcon,module=judge" } 12 | * 13 | * @author Stephan gao 14 | * @since 2016年4月28日 15 | * 16 | */ 17 | public class FalconItem { 18 | 19 | @JsonProperty 20 | private String metric; 21 | 22 | @JsonProperty 23 | private String endpoint; 24 | 25 | @JsonProperty 26 | private long timestamp; 27 | 28 | @JsonProperty 29 | private int step; 30 | 31 | @JsonProperty 32 | @JsonSerialize(using = CustomDoubleSerialize.class) 33 | private double value; 34 | 35 | @JsonProperty 36 | private String counterType; 37 | 38 | @JsonProperty 39 | private String tags; 40 | 41 | public FalconItem() { 42 | } 43 | 44 | public FalconItem(String metric, String endpoint, long timestamp, int step, 45 | double value, String counterType, String tags) { 46 | this.metric = metric; 47 | this.endpoint = endpoint; 48 | this.timestamp = timestamp; 49 | this.step = step; 50 | this.value = value; 51 | this.counterType = counterType; 52 | this.tags = tags; 53 | } 54 | 55 | @Override 56 | public String toString() { 57 | return ToStringBuilder.reflectionToString(this); 58 | } 59 | 60 | /** 61 | * @return the metric 62 | */ 63 | public String getMetric() { 64 | return metric; 65 | } 66 | 67 | /** 68 | * @return the endpoint 69 | */ 70 | public String getEndpoint() { 71 | return endpoint; 72 | } 73 | 74 | /** 75 | * @return the timestamp 76 | */ 77 | public long getTimestamp() { 78 | return timestamp; 79 | } 80 | 81 | /** 82 | * @return the step 83 | */ 84 | public int getStep() { 85 | return step; 86 | } 87 | 88 | /** 89 | * @return the value 90 | */ 91 | public double getValue() { 92 | return value; 93 | } 94 | 95 | /** 96 | * @return the counterType 97 | */ 98 | public String getCounterType() { 99 | return counterType; 100 | } 101 | 102 | /** 103 | * @return the tags 104 | */ 105 | public String getTags() { 106 | return tags; 107 | } 108 | 109 | /** 110 | * @param metric 111 | * the metric to set 112 | */ 113 | public void setMetric(String metric) { 114 | this.metric = metric; 115 | } 116 | 117 | /** 118 | * @param endpoint 119 | * the endpoint to set 120 | */ 121 | public void setEndpoint(String endpoint) { 122 | this.endpoint = endpoint; 123 | } 124 | 125 | /** 126 | * @param timestamp 127 | * the timestamp to set 128 | */ 129 | public void setTimestamp(long timestamp) { 130 | this.timestamp = timestamp; 131 | } 132 | 133 | /** 134 | * @param step 135 | * the step to set 136 | */ 137 | public void setStep(int step) { 138 | this.step = step; 139 | } 140 | 141 | /** 142 | * @param value 143 | * the value to set 144 | */ 145 | public void setValue(double value) { 146 | this.value = value; 147 | } 148 | 149 | /** 150 | * @param counterType 151 | * the counterType to set 152 | */ 153 | public void setCounterType(String counterType) { 154 | this.counterType = counterType; 155 | } 156 | 157 | /** 158 | * @param tags 159 | * the tags to set 160 | */ 161 | public void setTags(String tags) { 162 | this.tags = tags; 163 | } 164 | 165 | } 166 | -------------------------------------------------------------------------------- /src/main/java/com/stephan/tof/jmxmon/bean/GCData.java: -------------------------------------------------------------------------------- 1 | package com.stephan.tof.jmxmon.bean; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | public class GCData { 6 | 7 | @JsonProperty 8 | private long collectionTime; 9 | 10 | @JsonProperty 11 | private long collectionCount; 12 | 13 | @JsonProperty 14 | private long memoryUsed; 15 | 16 | @JsonProperty 17 | private long unitTimeCollectionCount; 18 | 19 | /** 20 | * @return the collectionTime 21 | */ 22 | public long getCollectionTime() { 23 | return collectionTime; 24 | } 25 | 26 | /** 27 | * @return the collectionCount 28 | */ 29 | public long getCollectionCount() { 30 | return collectionCount; 31 | } 32 | 33 | /** 34 | * @param collectionTime the collectionTime to set 35 | */ 36 | public void setCollectionTime(long collectionTime) { 37 | this.collectionTime = collectionTime; 38 | } 39 | 40 | /** 41 | * @param collectionCount the collectionCount to set 42 | */ 43 | public void setCollectionCount(long collectionCount) { 44 | this.collectionCount = collectionCount; 45 | } 46 | 47 | /** 48 | * @return the memoryUsed 49 | */ 50 | public long getMemoryUsed() { 51 | return memoryUsed; 52 | } 53 | 54 | /** 55 | * @param memoryUsed the memoryUsed to set 56 | */ 57 | public void setMemoryUsed(long memoryUsed) { 58 | this.memoryUsed = memoryUsed; 59 | } 60 | 61 | /** 62 | * @return the unitTimeCollectionCount 63 | */ 64 | public long getUnitTimeCollectionCount() { 65 | return unitTimeCollectionCount; 66 | } 67 | 68 | /** 69 | * @param unitTimeCollectionCount the unitTimeCollectionCount to set 70 | */ 71 | public void setUnitTimeCollectionCount(long unitTimeCollectionCount) { 72 | this.unitTimeCollectionCount = unitTimeCollectionCount; 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/stephan/tof/jmxmon/bean/JVMContext.java: -------------------------------------------------------------------------------- 1 | package com.stephan.tof.jmxmon.bean; 2 | 3 | import java.util.LinkedHashMap; 4 | import java.util.Map; 5 | 6 | import com.fasterxml.jackson.annotation.JsonProperty; 7 | 8 | /** 9 | * 用来缓存通过JMX获取到的JVM数据,每次程序退出时需要将此对象序列化到文件中,以便下次启动时能够再次加载到内存中使用 10 | * 11 | * @author Stephan gao 12 | * @since 2016年4月26日 13 | * 14 | */ 15 | public class JVMContext { 16 | 17 | /** 18 | * port -> jvm data 19 | */ 20 | @JsonProperty 21 | private Map jvmDatas = new LinkedHashMap(); 22 | 23 | public JVMData getJvmData(Integer jmxPort) { 24 | if(jvmDatas.containsKey(jmxPort)) { 25 | return jvmDatas.get(jmxPort); 26 | } else { 27 | JVMData jvmData = new JVMData(); 28 | jvmDatas.put(jmxPort, jvmData); 29 | return jvmData; 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/stephan/tof/jmxmon/bean/JVMData.java: -------------------------------------------------------------------------------- 1 | package com.stephan.tof.jmxmon.bean; 2 | 3 | import java.util.LinkedHashMap; 4 | import java.util.Map; 5 | 6 | import com.fasterxml.jackson.annotation.JsonProperty; 7 | 8 | public class JVMData { 9 | 10 | /** 11 | * GCMXBean name -> GCData 12 | */ 13 | @JsonProperty 14 | private Map gcDatas = new LinkedHashMap(); 15 | 16 | public GCData getGcData(String beanName) { 17 | if (gcDatas.containsKey(beanName)) { 18 | return gcDatas.get(beanName); 19 | } else { 20 | GCData gcData = new GCData(); 21 | gcDatas.put(beanName, gcData); 22 | return gcData; 23 | } 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/stephan/tof/jmxmon/bean/JacksonUtil.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package com.stephan.tof.jmxmon.bean; 5 | 6 | import java.io.File; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.util.Map; 10 | 11 | import com.fasterxml.jackson.core.JsonParseException; 12 | import com.fasterxml.jackson.core.JsonProcessingException; 13 | import com.fasterxml.jackson.databind.DeserializationFeature; 14 | import com.fasterxml.jackson.databind.JsonMappingException; 15 | import com.fasterxml.jackson.databind.ObjectMapper; 16 | 17 | /** 18 | * @author Stephan gao 19 | * @since 2015年4月20日 20 | * 21 | */ 22 | public class JacksonUtil { 23 | 24 | private static ObjectMapper om = new ObjectMapper(); 25 | // static { 26 | // // 设置输入时忽略JSON字符串中存在而Java对象实际没有的属性 27 | // om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 28 | // } 29 | 30 | private JacksonUtil() { 31 | } 32 | 33 | public static void setObjectMapper(ObjectMapper om) { 34 | JacksonUtil.om = om; 35 | } 36 | 37 | public static T readBeanFromFile(File srcFile, Class clazz) 38 | throws IOException { 39 | return om.readValue(srcFile, clazz); 40 | } 41 | 42 | public static T readBeanFromString(String jsonStr, Class clazz) 43 | throws IOException { 44 | return om.readValue(jsonStr, clazz); 45 | } 46 | 47 | public static T readBeanFromStream(InputStream input, Class clazz) throws JsonParseException, JsonMappingException, IOException { 48 | return om.readValue(input, clazz); 49 | } 50 | 51 | public static void writeBeanToFile(File dstFile, T t, 52 | boolean withPrettyPrinter) throws IOException { 53 | if (withPrettyPrinter) { 54 | om.writerWithDefaultPrettyPrinter().writeValue(dstFile, t); 55 | } else { 56 | om.writeValue(dstFile, t); 57 | } 58 | } 59 | 60 | public static String writeBeanToString(T t, boolean withPrettyPrinter) 61 | throws JsonProcessingException { 62 | if (withPrettyPrinter) { 63 | return om.writerWithDefaultPrettyPrinter().writeValueAsString(t); 64 | } else { 65 | return om.writeValueAsString(t); 66 | } 67 | } 68 | 69 | public static Map readMapFromString(String jsonStr) 70 | throws JsonParseException, JsonMappingException, IOException { 71 | return om.readValue(jsonStr, Map.class); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/com/stephan/tof/jmxmon/jmxutil/JConsoleContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * %W% %E% 3 | * 4 | * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved. 5 | * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. 6 | */ 7 | 8 | package com.stephan.tof.jmxmon.jmxutil; 9 | 10 | import java.beans.PropertyChangeListener; 11 | 12 | import javax.management.MBeanServerConnection; 13 | 14 | /** 15 | * {@code JConsoleContext} represents a JConsole connection to a target 16 | * application. 17 | *

18 | * {@code JConsoleContext} notifies any {@code PropertyChangeListeners} 19 | * about the {@linkplain #CONNECTION_STATE_PROPERTY ConnectionState} 20 | * property change to {@link ConnectionState#CONNECTED CONNECTED} and 21 | * {@link ConnectionState#DISCONNECTED DISCONNECTED}. 22 | * The {@code JConsoleContext} instance will be the source for 23 | * any generated events. 24 | *

25 | * 26 | * @since 1.6 27 | */ 28 | public interface JConsoleContext { 29 | /** 30 | * The {@link ConnectionState ConnectionState} bound property name. 31 | */ 32 | public static String CONNECTION_STATE_PROPERTY = "connectionState"; 33 | 34 | /** 35 | * Values for the {@linkplain #CONNECTION_STATE_PROPERTY 36 | * ConnectionState} bound property. 37 | */ 38 | public enum ConnectionState { 39 | /** 40 | * The connection has been successfully established. 41 | */ 42 | CONNECTED, 43 | /** 44 | * No connection present. 45 | */ 46 | DISCONNECTED, 47 | /** 48 | * The connection is being attempted. 49 | */ 50 | CONNECTING 51 | } 52 | 53 | /** 54 | * Returns the {@link MBeanServerConnection MBeanServerConnection} for the 55 | * connection to an application. The returned 56 | * {@code MBeanServerConnection} object becomes invalid when 57 | * the connection state is changed to the 58 | * {@link ConnectionState#DISCONNECTED DISCONNECTED} state. 59 | * 60 | * @return the {@code MBeanServerConnection} for the 61 | * connection to an application. 62 | */ 63 | public MBeanServerConnection getMBeanServerConnection(); 64 | 65 | /** 66 | * Returns the current connection state. 67 | * @return the current connection state. 68 | */ 69 | public ConnectionState getConnectionState(); 70 | 71 | /** 72 | * Add a {@link java.beans.PropertyChangeListener PropertyChangeListener} 73 | * to the listener list. 74 | * The listener is registered for all properties. 75 | * The same listener object may be added more than once, and will be called 76 | * as many times as it is added. 77 | * If {@code listener} is {@code null}, no exception is thrown and 78 | * no action is taken. 79 | * 80 | * @param listener The {@code PropertyChangeListener} to be added 81 | */ 82 | public void addPropertyChangeListener(PropertyChangeListener listener); 83 | 84 | /** 85 | * Removes a {@link java.beans.PropertyChangeListener PropertyChangeListener} 86 | * from the listener list. This 87 | * removes a {@code PropertyChangeListener} that was registered for all 88 | * properties. If {@code listener} was added more than once to the same 89 | * event source, it will be notified one less time after being removed. If 90 | * {@code listener} is {@code null}, or was never added, no exception is 91 | * thrown and no action is taken. 92 | * 93 | * @param listener the {@code PropertyChangeListener} to be removed 94 | */ 95 | public void removePropertyChangeListener(PropertyChangeListener listener); 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/com/stephan/tof/jmxmon/jmxutil/LocalVirtualMachine.java: -------------------------------------------------------------------------------- 1 | /* 2 | * %W% %E% 3 | * 4 | * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved. 5 | * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. 6 | */ 7 | 8 | package com.stephan.tof.jmxmon.jmxutil; 9 | 10 | import java.io.File; 11 | import java.io.IOException; 12 | import java.util.HashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | import java.util.Properties; 16 | import java.util.Set; 17 | 18 | import sun.jvmstat.monitor.HostIdentifier; 19 | import sun.jvmstat.monitor.MonitorException; 20 | import sun.jvmstat.monitor.MonitoredHost; 21 | import sun.jvmstat.monitor.MonitoredVm; 22 | import sun.jvmstat.monitor.MonitoredVmUtil; 23 | import sun.jvmstat.monitor.VmIdentifier; 24 | // Sun private 25 | import sun.management.ConnectorAddressLink; 26 | 27 | import com.sun.tools.attach.AgentInitializationException; 28 | import com.sun.tools.attach.AgentLoadException; 29 | import com.sun.tools.attach.AttachNotSupportedException; 30 | // Sun specific 31 | import com.sun.tools.attach.VirtualMachine; 32 | import com.sun.tools.attach.VirtualMachineDescriptor; 33 | 34 | public class LocalVirtualMachine { 35 | private String address; 36 | private String commandLine; 37 | private String displayName; 38 | private int vmid; 39 | private boolean isAttachSupported; 40 | 41 | public LocalVirtualMachine(int vmid, String commandLine, boolean canAttach, String connectorAddress) { 42 | this.vmid = vmid; 43 | this.commandLine = commandLine; 44 | this.address = connectorAddress; 45 | this.isAttachSupported = canAttach; 46 | this.displayName = getDisplayName(commandLine); 47 | } 48 | 49 | private static String getDisplayName(String commandLine) { 50 | // trim the pathname of jar file if it's a jar 51 | String[] res = commandLine.split(" ", 2); 52 | if (res[0].endsWith(".jar")) { 53 | File jarfile = new File(res[0]); 54 | String displayName = jarfile.getName(); 55 | if (res.length == 2) { 56 | displayName += " " + res[1]; 57 | } 58 | return displayName; 59 | } 60 | return commandLine; 61 | } 62 | 63 | public int vmid() { 64 | return vmid; 65 | } 66 | 67 | public boolean isManageable() { 68 | return (address != null); 69 | } 70 | 71 | public boolean isAttachable() { 72 | return isAttachSupported; 73 | } 74 | 75 | public void startManagementAgent() throws IOException { 76 | if (address != null) { 77 | // already started 78 | return; 79 | } 80 | 81 | if (!isAttachable()) { 82 | throw new IOException("This virtual machine \"" + vmid + 83 | "\" does not support dynamic attach."); 84 | } 85 | 86 | loadManagementAgent(); 87 | // fails to load or start the management agent 88 | if (address == null) { 89 | // should never reach here 90 | throw new IOException("Fails to find connector address"); 91 | } 92 | } 93 | 94 | public String connectorAddress() { 95 | // return null if not available or no JMX agent 96 | return address; 97 | } 98 | 99 | public String displayName() { 100 | return displayName; 101 | } 102 | 103 | public String toString() { 104 | return commandLine; 105 | } 106 | 107 | // This method returns the list of all virtual machines currently 108 | // running on the machine 109 | public static Map getAllVirtualMachines() { 110 | Map map = 111 | new HashMap(); 112 | getMonitoredVMs(map); 113 | getAttachableVMs(map); 114 | return map; 115 | } 116 | 117 | private static void getMonitoredVMs(Map map) { 118 | MonitoredHost host; 119 | Set vms; 120 | try { 121 | host = MonitoredHost.getMonitoredHost(new HostIdentifier((String)null)); 122 | vms = host.activeVms(); 123 | } catch (java.net.URISyntaxException sx) { 124 | throw new InternalError(sx.getMessage()); 125 | } catch (MonitorException mx) { 126 | throw new InternalError(mx.getMessage()); 127 | } 128 | for (Object vmid: vms) { 129 | if (vmid instanceof Integer) { 130 | int pid = ((Integer) vmid).intValue(); 131 | String name = vmid.toString(); // default to pid if name not available 132 | boolean attachable = false; 133 | String address = null; 134 | try { 135 | MonitoredVm mvm = host.getMonitoredVm(new VmIdentifier(name)); 136 | // use the command line as the display name 137 | name = MonitoredVmUtil.commandLine(mvm); 138 | attachable = MonitoredVmUtil.isAttachable(mvm); 139 | address = ConnectorAddressLink.importFrom(pid); 140 | mvm.detach(); 141 | } catch (Exception x) { 142 | // ignore 143 | } 144 | map.put((Integer) vmid, 145 | new LocalVirtualMachine(pid, name, attachable, address)); 146 | } 147 | } 148 | } 149 | 150 | private static final String LOCAL_CONNECTOR_ADDRESS_PROP = 151 | "com.sun.management.jmxremote.localConnectorAddress"; 152 | 153 | private static void getAttachableVMs(Map map) { 154 | List vms = VirtualMachine.list(); 155 | for (VirtualMachineDescriptor vmd : vms) { 156 | try { 157 | Integer vmid = Integer.valueOf(vmd.id()); 158 | if (!map.containsKey(vmid)) { 159 | boolean attachable = false; 160 | String address = null; 161 | try { 162 | VirtualMachine vm = VirtualMachine.attach(vmd); 163 | attachable = true; 164 | Properties agentProps = vm.getAgentProperties(); 165 | address = (String) agentProps.get(LOCAL_CONNECTOR_ADDRESS_PROP); 166 | vm.detach(); 167 | } catch (AttachNotSupportedException x) { 168 | // not attachable 169 | } catch (IOException x) { 170 | // ignore 171 | } 172 | map.put(vmid, new LocalVirtualMachine(vmid.intValue(), 173 | vmd.displayName(), 174 | attachable, 175 | address)); 176 | } 177 | } catch (NumberFormatException e) { 178 | // do not support vmid different than pid 179 | } 180 | } 181 | } 182 | 183 | public static LocalVirtualMachine getLocalVirtualMachine(int vmid) { 184 | Map map = getAllVirtualMachines(); 185 | LocalVirtualMachine lvm = map.get(vmid); 186 | if (lvm == null) { 187 | // Check if the VM is attachable but not included in the list 188 | // if it's running with a different security context. 189 | // For example, Windows services running 190 | // local SYSTEM account are attachable if you have Adminstrator 191 | // privileges. 192 | boolean attachable = false; 193 | String address = null; 194 | String name = String.valueOf(vmid); // default display name to pid 195 | try { 196 | VirtualMachine vm = VirtualMachine.attach(name); 197 | attachable = true; 198 | Properties agentProps = vm.getAgentProperties(); 199 | address = (String) agentProps.get(LOCAL_CONNECTOR_ADDRESS_PROP); 200 | vm.detach(); 201 | lvm = new LocalVirtualMachine(vmid, name, attachable, address); 202 | } catch (AttachNotSupportedException x) { 203 | // not attachable 204 | throw new IllegalStateException(x); 205 | } catch (IOException x) { 206 | // ignore 207 | throw new IllegalStateException(x); 208 | } 209 | } 210 | return lvm; 211 | } 212 | 213 | // load the management agent into the target VM 214 | private void loadManagementAgent() throws IOException { 215 | VirtualMachine vm = null; 216 | String name = String.valueOf(vmid); 217 | try { 218 | vm = VirtualMachine.attach(name); 219 | } catch (AttachNotSupportedException x) { 220 | IOException ioe = new IOException(x.getMessage()); 221 | ioe.initCause(x); 222 | throw ioe; 223 | } 224 | 225 | String home = vm.getSystemProperties().getProperty("java.home"); 226 | 227 | // Normally in ${java.home}/jre/lib/management-agent.jar but might 228 | // be in ${java.home}/lib in build environments. 229 | 230 | String agent = home + File.separator + "jre" + File.separator + 231 | "lib" + File.separator + "management-agent.jar"; 232 | File f = new File(agent); 233 | if (!f.exists()) { 234 | agent = home + File.separator + "lib" + File.separator + 235 | "management-agent.jar"; 236 | f = new File(agent); 237 | if (!f.exists()) { 238 | throw new IOException("Management agent not found"); 239 | } 240 | } 241 | 242 | agent = f.getCanonicalPath(); 243 | try { 244 | vm.loadAgent(agent, "com.sun.management.jmxremote"); 245 | } catch (AgentLoadException x) { 246 | IOException ioe = new IOException(x.getMessage()); 247 | ioe.initCause(x); 248 | throw ioe; 249 | } catch (AgentInitializationException x) { 250 | IOException ioe = new IOException(x.getMessage()); 251 | ioe.initCause(x); 252 | throw ioe; 253 | } 254 | 255 | // get the connector address 256 | Properties agentProps = vm.getAgentProperties(); 257 | address = (String) agentProps.get(LOCAL_CONNECTOR_ADDRESS_PROP); 258 | 259 | vm.detach(); 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /src/main/java/com/stephan/tof/jmxmon/jmxutil/MemoryPoolProxy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * %W% %E% 3 | * 4 | * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved. 5 | * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. 6 | */ 7 | 8 | package com.stephan.tof.jmxmon.jmxutil; 9 | 10 | import static java.lang.management.ManagementFactory.GARBAGE_COLLECTOR_MXBEAN_DOMAIN_TYPE; 11 | 12 | import java.lang.management.MemoryPoolMXBean; 13 | import java.lang.management.MemoryUsage; 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | import java.util.Set; 17 | 18 | import javax.management.ObjectName; 19 | 20 | import com.sun.management.GarbageCollectorMXBean; 21 | import com.sun.management.GcInfo; 22 | 23 | public class MemoryPoolProxy { 24 | private String poolName; 25 | private ProxyClient client; 26 | private ObjectName objName; 27 | private MemoryPoolMXBean pool; 28 | private Map gcMBeans; 29 | private GcInfo lastGcInfo; 30 | 31 | public MemoryPoolProxy(ProxyClient client, ObjectName poolName) throws java.io.IOException { 32 | this.client = client; 33 | this.objName = objName; 34 | this.pool = client.getMXBean(poolName, MemoryPoolMXBean.class); 35 | this.poolName = this.pool.getName(); 36 | this.gcMBeans = new HashMap(); 37 | this.lastGcInfo = null; 38 | 39 | String[] mgrNames = pool.getMemoryManagerNames(); 40 | for (String name : mgrNames) { 41 | try { 42 | ObjectName mbeanName = new ObjectName(GARBAGE_COLLECTOR_MXBEAN_DOMAIN_TYPE + 43 | ",name=" + name); 44 | if (client.isRegistered(mbeanName)) { 45 | gcMBeans.put(mbeanName, new Long(0)); 46 | } 47 | } catch (Exception e) { 48 | throw new IllegalStateException(e); 49 | } 50 | 51 | } 52 | } 53 | 54 | public boolean isCollectedMemoryPool() { 55 | return (gcMBeans.size() != 0); 56 | } 57 | 58 | public ObjectName getObjectName() { 59 | return objName; 60 | } 61 | 62 | public MemoryPoolStat getStat() throws java.io.IOException { 63 | long usageThreshold = (pool.isUsageThresholdSupported() 64 | ? pool.getUsageThreshold() 65 | : -1); 66 | long collectThreshold = (pool.isCollectionUsageThresholdSupported() 67 | ? pool.getCollectionUsageThreshold() 68 | : -1); 69 | long lastGcStartTime = 0; 70 | long lastGcEndTime = 0; 71 | MemoryUsage beforeGcUsage = null; 72 | MemoryUsage afterGcUsage = null; 73 | long gcId = 0; 74 | if (lastGcInfo != null) { 75 | gcId = lastGcInfo.getId(); 76 | lastGcStartTime = lastGcInfo.getStartTime(); 77 | lastGcEndTime = lastGcInfo.getEndTime(); 78 | beforeGcUsage = lastGcInfo.getMemoryUsageBeforeGc().get(poolName); 79 | afterGcUsage = lastGcInfo.getMemoryUsageAfterGc().get(poolName); 80 | } 81 | 82 | Set> set = gcMBeans.entrySet(); 83 | for (Map.Entry e : set) { 84 | GarbageCollectorMXBean gc = 85 | client.getMXBean(e.getKey(), 86 | com.sun.management.GarbageCollectorMXBean.class); 87 | Long gcCount = e.getValue(); 88 | Long newCount = gc.getCollectionCount(); 89 | if (newCount > gcCount) { 90 | gcMBeans.put(e.getKey(), new Long(newCount)); 91 | lastGcInfo = gc.getLastGcInfo(); 92 | if (lastGcInfo.getEndTime() > lastGcEndTime) { 93 | gcId = lastGcInfo.getId(); 94 | lastGcStartTime = lastGcInfo.getStartTime(); 95 | lastGcEndTime = lastGcInfo.getEndTime(); 96 | beforeGcUsage = lastGcInfo.getMemoryUsageBeforeGc().get(poolName); 97 | afterGcUsage = lastGcInfo.getMemoryUsageAfterGc().get(poolName); 98 | assert(beforeGcUsage != null); 99 | assert(afterGcUsage != null); 100 | } 101 | } 102 | } 103 | 104 | MemoryUsage usage = pool.getUsage(); 105 | return new MemoryPoolStat(poolName, 106 | usageThreshold, 107 | usage, 108 | gcId, 109 | lastGcStartTime, 110 | lastGcEndTime, 111 | collectThreshold, 112 | beforeGcUsage, 113 | afterGcUsage); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/com/stephan/tof/jmxmon/jmxutil/MemoryPoolStat.java: -------------------------------------------------------------------------------- 1 | /* 2 | * %W% %E% 3 | * 4 | * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved. 5 | * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. 6 | */ 7 | package com.stephan.tof.jmxmon.jmxutil; 8 | 9 | import java.lang.management.MemoryUsage; 10 | 11 | public class MemoryPoolStat { 12 | private String poolName; 13 | private long usageThreshold; 14 | private MemoryUsage usage; 15 | private long lastGcId; 16 | private long lastGcStartTime; 17 | private long lastGcEndTime; 18 | private long collectThreshold; 19 | private MemoryUsage beforeGcUsage; 20 | private MemoryUsage afterGcUsage; 21 | 22 | MemoryPoolStat(String name, 23 | long usageThreshold, 24 | MemoryUsage usage, 25 | long lastGcId, 26 | long lastGcStartTime, 27 | long lastGcEndTime, 28 | long collectThreshold, 29 | MemoryUsage beforeGcUsage, 30 | MemoryUsage afterGcUsage) { 31 | this.poolName = name; 32 | this.usageThreshold = usageThreshold; 33 | this.usage = usage; 34 | this.lastGcId = lastGcId; 35 | this.lastGcStartTime = lastGcStartTime; 36 | this.lastGcEndTime = lastGcEndTime; 37 | this.collectThreshold = collectThreshold; 38 | this.beforeGcUsage = beforeGcUsage; 39 | this.afterGcUsage = afterGcUsage; 40 | } 41 | 42 | /** 43 | * Returns the memory pool name. 44 | */ 45 | public String getPoolName() { 46 | return poolName; 47 | } 48 | 49 | /** 50 | * Returns the current memory usage. 51 | */ 52 | public MemoryUsage getUsage() { 53 | return usage; 54 | } 55 | 56 | /** 57 | * Returns the current usage threshold. 58 | * -1 if not supported. 59 | */ 60 | public long getUsageThreshold() { 61 | return usageThreshold; 62 | } 63 | 64 | /** 65 | * Returns the current collection usage threshold. 66 | * -1 if not supported. 67 | */ 68 | public long getCollectionUsageThreshold() { 69 | return collectThreshold; 70 | } 71 | 72 | /** 73 | * Returns the Id of GC. 74 | */ 75 | public long getLastGcId() { 76 | return lastGcId; 77 | } 78 | 79 | 80 | /** 81 | * Returns the start time of the most recent GC on 82 | * the memory pool for this statistics in milliseconds. 83 | * 84 | * Return 0 if no GC occurs. 85 | */ 86 | public long getLastGcStartTime() { 87 | return lastGcStartTime; 88 | } 89 | 90 | /** 91 | * Returns the end time of the most recent GC on 92 | * the memory pool for this statistics in milliseconds. 93 | * 94 | * Return 0 if no GC occurs. 95 | */ 96 | public long getLastGcEndTime() { 97 | return lastGcEndTime; 98 | } 99 | 100 | /** 101 | * Returns the memory usage before the most recent GC started. 102 | * null if no GC occurs. 103 | */ 104 | public MemoryUsage getBeforeGcUsage() { 105 | return beforeGcUsage; 106 | } 107 | 108 | /** 109 | * Returns the memory usage after the most recent GC finished. 110 | * null if no GC occurs. 111 | */ 112 | public MemoryUsage getAfterGcUsage() { 113 | return beforeGcUsage; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/com/stephan/tof/jmxmon/jmxutil/NamedThreadFactory.java: -------------------------------------------------------------------------------- 1 | package com.stephan.tof.jmxmon.jmxutil; 2 | 3 | import java.util.concurrent.ThreadFactory; 4 | import java.util.concurrent.atomic.AtomicInteger; 5 | 6 | /** 7 | * Help for threadpool to set thread name 8 | * 9 | * @author bluedavy 10 | */ 11 | public class NamedThreadFactory implements ThreadFactory { 12 | 13 | static final AtomicInteger poolNumber = new AtomicInteger(1); 14 | 15 | final AtomicInteger threadNumber = new AtomicInteger(1); 16 | final ThreadGroup group; 17 | final String namePrefix; 18 | final boolean isDaemon; 19 | 20 | public NamedThreadFactory() { 21 | this("pool"); 22 | } 23 | public NamedThreadFactory(String name) { 24 | this(name, false); 25 | } 26 | public NamedThreadFactory(String preffix, boolean daemon) { 27 | SecurityManager s = System.getSecurityManager(); 28 | group = (s != null) ? s.getThreadGroup() : Thread.currentThread() 29 | .getThreadGroup(); 30 | namePrefix = preffix + "-" + poolNumber.getAndIncrement() + "-thread-"; 31 | isDaemon = daemon; 32 | } 33 | 34 | 35 | public Thread newThread(Runnable r) { 36 | Thread t = new Thread(group, r, namePrefix 37 | + threadNumber.getAndIncrement(), 0); 38 | t.setDaemon(isDaemon); 39 | if (t.getPriority() != Thread.NORM_PRIORITY) { 40 | t.setPriority(Thread.NORM_PRIORITY); 41 | } 42 | return t; 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /src/main/java/com/stephan/tof/jmxmon/jmxutil/ProxyClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * %W% %E% 3 | * 4 | * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved. 5 | * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. 6 | */ 7 | 8 | package com.stephan.tof.jmxmon.jmxutil; 9 | 10 | import static java.lang.management.ManagementFactory.CLASS_LOADING_MXBEAN_NAME; 11 | import static java.lang.management.ManagementFactory.COMPILATION_MXBEAN_NAME; 12 | import static java.lang.management.ManagementFactory.GARBAGE_COLLECTOR_MXBEAN_DOMAIN_TYPE; 13 | import static java.lang.management.ManagementFactory.MEMORY_MXBEAN_NAME; 14 | import static java.lang.management.ManagementFactory.MEMORY_POOL_MXBEAN_DOMAIN_TYPE; 15 | import static java.lang.management.ManagementFactory.OPERATING_SYSTEM_MXBEAN_NAME; 16 | import static java.lang.management.ManagementFactory.RUNTIME_MXBEAN_NAME; 17 | import static java.lang.management.ManagementFactory.THREAD_MXBEAN_NAME; 18 | import static java.lang.management.ManagementFactory.newPlatformMXBeanProxy; 19 | 20 | import java.beans.PropertyChangeEvent; 21 | import java.beans.PropertyChangeListener; 22 | import java.io.IOException; 23 | import java.lang.management.BufferPoolMXBean; 24 | import java.lang.management.ClassLoadingMXBean; 25 | import java.lang.management.CompilationMXBean; 26 | import java.lang.management.GarbageCollectorMXBean; 27 | import java.lang.management.ManagementFactory; 28 | import java.lang.management.MemoryMXBean; 29 | import java.lang.management.OperatingSystemMXBean; 30 | import java.lang.management.RuntimeMXBean; 31 | import java.lang.management.ThreadMXBean; 32 | import java.lang.ref.WeakReference; 33 | import java.lang.reflect.InvocationHandler; 34 | import java.lang.reflect.InvocationTargetException; 35 | import java.lang.reflect.Method; 36 | import java.lang.reflect.Proxy; 37 | import java.rmi.NotBoundException; 38 | import java.rmi.Remote; 39 | import java.rmi.registry.LocateRegistry; 40 | import java.rmi.registry.Registry; 41 | import java.rmi.server.RMIClientSocketFactory; 42 | import java.rmi.server.RemoteObject; 43 | import java.rmi.server.RemoteObjectInvocationHandler; 44 | import java.rmi.server.RemoteRef; 45 | import java.util.ArrayList; 46 | import java.util.Arrays; 47 | import java.util.Collection; 48 | import java.util.Collections; 49 | import java.util.HashMap; 50 | import java.util.Iterator; 51 | import java.util.List; 52 | import java.util.Map; 53 | import java.util.Set; 54 | import java.util.TreeSet; 55 | 56 | import javax.management.Attribute; 57 | import javax.management.AttributeList; 58 | import javax.management.AttributeNotFoundException; 59 | import javax.management.InstanceNotFoundException; 60 | import javax.management.IntrospectionException; 61 | import javax.management.InvalidAttributeValueException; 62 | import javax.management.MBeanException; 63 | import javax.management.MBeanInfo; 64 | import javax.management.MBeanOperationInfo; 65 | import javax.management.MBeanServerConnection; 66 | import javax.management.MalformedObjectNameException; 67 | import javax.management.ObjectName; 68 | import javax.management.ReflectionException; 69 | import javax.management.remote.JMXConnector; 70 | import javax.management.remote.JMXConnectorFactory; 71 | import javax.management.remote.JMXServiceURL; 72 | import javax.management.remote.rmi.RMIConnector; 73 | import javax.management.remote.rmi.RMIServer; 74 | import javax.rmi.ssl.SslRMIClientSocketFactory; 75 | import javax.swing.event.SwingPropertyChangeSupport; 76 | 77 | import org.slf4j.Logger; 78 | import org.slf4j.LoggerFactory; 79 | 80 | import sun.rmi.server.UnicastRef2; 81 | import sun.rmi.transport.LiveRef; 82 | 83 | import com.sun.management.HotSpotDiagnosticMXBean; 84 | 85 | public class ProxyClient implements JConsoleContext { 86 | 87 | private static Logger logger = LoggerFactory.getLogger(ProxyClient.class); 88 | 89 | private ConnectionState connectionState = ConnectionState.DISCONNECTED; 90 | 91 | // The SwingPropertyChangeSupport will fire events on the EDT 92 | private SwingPropertyChangeSupport propertyChangeSupport = 93 | new SwingPropertyChangeSupport(this, true); 94 | 95 | private static Map cache = 96 | Collections.synchronizedMap(new HashMap()); 97 | 98 | private volatile boolean isDead = true; 99 | private String hostName = null; 100 | private int port = 0; 101 | private String userName = null; 102 | private String password = null; 103 | private boolean hasPlatformMXBeans = false; 104 | private boolean hasHotSpotDiagnosticMXBean= false; 105 | private boolean hasCompilationMXBean = false; 106 | private boolean supportsLockUsage = false; 107 | 108 | // REVISIT: VMPanel and other places relying using getUrl(). 109 | 110 | // set only if it's created for local monitoring 111 | private LocalVirtualMachine lvm; 112 | 113 | // set only if it's created from a given URL via the Advanced tab 114 | private String advancedUrl = null; 115 | 116 | private JMXServiceURL jmxUrl = null; 117 | private MBeanServerConnection mbsc = null; 118 | private SnapshotMBeanServerConnection server = null; 119 | private JMXConnector jmxc = null; 120 | private RMIServer stub = null; 121 | private static final SslRMIClientSocketFactory sslRMIClientSocketFactory = 122 | new SslRMIClientSocketFactory(); 123 | private String registryHostName = null; 124 | private int registryPort = 0; 125 | private boolean vmConnector = false; 126 | private boolean sslRegistry = false; 127 | private boolean sslStub = false; 128 | final private String connectionName; 129 | final private String displayName; 130 | 131 | private ClassLoadingMXBean classLoadingMBean = null; 132 | private CompilationMXBean compilationMBean = null; 133 | private MemoryMXBean memoryMBean = null; 134 | private OperatingSystemMXBean operatingSystemMBean = null; 135 | private RuntimeMXBean runtimeMBean = null; 136 | private ThreadMXBean threadMBean = null; 137 | 138 | private com.sun.management.OperatingSystemMXBean sunOperatingSystemMXBean = null; 139 | private HotSpotDiagnosticMXBean hotspotDiagnosticMXBean = null; 140 | 141 | private List memoryPoolProxies = null; 142 | private List garbageCollectorMBeans = null; 143 | private List bufferPoolMXBeans = null; 144 | 145 | 146 | final static private String HOTSPOT_DIAGNOSTIC_MXBEAN_NAME = 147 | "com.sun.management:type=HotSpotDiagnostic"; 148 | 149 | private ProxyClient(String hostName, int port, 150 | String userName, String password) throws IOException { 151 | this.connectionName = getConnectionName(hostName, port, userName); 152 | this.displayName = connectionName; 153 | if (hostName.equals("localhost") && port == 0) { 154 | // Monitor self 155 | this.hostName = hostName; 156 | this.port = port; 157 | } else { 158 | // Create an RMI connector client and connect it to 159 | // the RMI connector server 160 | final String urlPath = "/jndi/rmi://" + hostName + ":" + port + 161 | "/jmxrmi"; 162 | JMXServiceURL url = new JMXServiceURL("rmi", "", 0, urlPath); 163 | setParameters(url, userName, password); 164 | vmConnector = true; 165 | registryHostName = hostName; 166 | registryPort = port; 167 | checkSslConfig(); 168 | } 169 | } 170 | 171 | private ProxyClient(String url, 172 | String userName, String password) throws IOException { 173 | this.advancedUrl = url; 174 | this.connectionName = getConnectionName(url, userName); 175 | this.displayName = connectionName; 176 | setParameters(new JMXServiceURL(url), userName, password); 177 | } 178 | 179 | private ProxyClient(LocalVirtualMachine lvm) throws IOException { 180 | this.lvm = lvm; 181 | this.connectionName = getConnectionName(lvm); 182 | this.displayName = "pid: " + lvm.vmid() + " " + lvm.displayName(); 183 | } 184 | 185 | private void setParameters(JMXServiceURL url, String userName, String password) { 186 | this.jmxUrl = url; 187 | this.hostName = jmxUrl.getHost(); 188 | this.port = jmxUrl.getPort(); 189 | this.userName = userName; 190 | this.password = password; 191 | } 192 | 193 | private static void checkStub(Remote stub, 194 | Class stubClass) { 195 | // Check remote stub is from the expected class. 196 | // 197 | if (stub.getClass() != stubClass) { 198 | if (!Proxy.isProxyClass(stub.getClass())) { 199 | throw new SecurityException( 200 | "Expecting a " + stubClass.getName() + " stub!"); 201 | } else { 202 | InvocationHandler handler = Proxy.getInvocationHandler(stub); 203 | if (handler.getClass() != RemoteObjectInvocationHandler.class) { 204 | throw new SecurityException( 205 | "Expecting a dynamic proxy instance with a " + 206 | RemoteObjectInvocationHandler.class.getName() + 207 | " invocation handler!"); 208 | } else { 209 | stub = (Remote) handler; 210 | } 211 | } 212 | } 213 | // Check RemoteRef in stub is from the expected class 214 | // "sun.rmi.server.UnicastRef2". 215 | // 216 | RemoteRef ref = ((RemoteObject)stub).getRef(); 217 | if (ref.getClass() != UnicastRef2.class) { 218 | throw new SecurityException( 219 | "Expecting a " + UnicastRef2.class.getName() + 220 | " remote reference in stub!"); 221 | } 222 | // Check RMIClientSocketFactory in stub is from the expected class 223 | // "javax.rmi.ssl.SslRMIClientSocketFactory". 224 | // 225 | LiveRef liveRef = ((UnicastRef2)ref).getLiveRef(); 226 | RMIClientSocketFactory csf = liveRef.getClientSocketFactory(); 227 | if (csf == null || csf.getClass() != SslRMIClientSocketFactory.class) { 228 | throw new SecurityException( 229 | "Expecting a " + SslRMIClientSocketFactory.class.getName() + 230 | " RMI client socket factory in stub!"); 231 | } 232 | } 233 | 234 | private static final String rmiServerImplStubClassName = 235 | "javax.management.remote.rmi.RMIServerImpl_Stub"; 236 | private static final Class rmiServerImplStubClass; 237 | 238 | static { 239 | // FIXME: RMIServerImpl_Stub is generated at build time 240 | // after jconsole is built. We need to investigate if 241 | // the Makefile can be fixed to build jconsole in the 242 | // right order. As a workaround for now, we dynamically 243 | // load RMIServerImpl_Stub class instead of statically 244 | // referencing it. 245 | Class serverStubClass = null; 246 | try { 247 | serverStubClass = Class.forName(rmiServerImplStubClassName).asSubclass(Remote.class); 248 | } catch (ClassNotFoundException e) { 249 | // should never reach here 250 | throw (InternalError) new InternalError(e.getMessage()).initCause(e); 251 | } 252 | rmiServerImplStubClass = serverStubClass; 253 | } 254 | 255 | private void checkSslConfig() throws IOException { 256 | // Get the reference to the RMI Registry and lookup RMIServer stub 257 | // 258 | Registry registry; 259 | try { 260 | registry = 261 | LocateRegistry.getRegistry(registryHostName, registryPort, 262 | sslRMIClientSocketFactory); 263 | try { 264 | stub = (RMIServer) registry.lookup("jmxrmi"); 265 | } catch (NotBoundException nbe) { 266 | throw (IOException) 267 | new IOException(nbe.getMessage()).initCause(nbe); 268 | } 269 | sslRegistry = true; 270 | } catch (IOException e) { 271 | registry = 272 | LocateRegistry.getRegistry(registryHostName, registryPort); 273 | try { 274 | stub = (RMIServer) registry.lookup("jmxrmi"); 275 | } catch (NotBoundException nbe) { 276 | throw (IOException) 277 | new IOException(nbe.getMessage()).initCause(nbe); 278 | } 279 | sslRegistry = false; 280 | } 281 | // Perform the checks for secure stub 282 | // 283 | try { 284 | checkStub(stub, rmiServerImplStubClass); 285 | sslStub = true; 286 | } catch (SecurityException e) { 287 | sslStub = false; 288 | } 289 | } 290 | 291 | /** 292 | * Returns true if the underlying RMI registry is SSL-protected. 293 | * 294 | * @exception UnsupportedOperationException If this {@code ProxyClient} 295 | * does not denote a JMX connector for a JMX VM agent. 296 | */ 297 | public boolean isSslRmiRegistry() { 298 | // Check for VM connector 299 | // 300 | if (!isVmConnector()) { 301 | throw new UnsupportedOperationException( 302 | "ProxyClient.isSslRmiRegistry() is only supported if this " + 303 | "ProxyClient is a JMX connector for a JMX VM agent"); 304 | } 305 | return sslRegistry; 306 | } 307 | 308 | /** 309 | * Returns true if the retrieved RMI stub is SSL-protected. 310 | * 311 | * @exception UnsupportedOperationException If this {@code ProxyClient} 312 | * does not denote a JMX connector for a JMX VM agent. 313 | */ 314 | public boolean isSslRmiStub() { 315 | // Check for VM connector 316 | // 317 | if (!isVmConnector()) { 318 | throw new UnsupportedOperationException( 319 | "ProxyClient.isSslRmiStub() is only supported if this " + 320 | "ProxyClient is a JMX connector for a JMX VM agent"); 321 | } 322 | return sslStub; 323 | } 324 | 325 | /** 326 | * Returns true if this {@code ProxyClient} denotes 327 | * a JMX connector for a JMX VM agent. 328 | */ 329 | public boolean isVmConnector() { 330 | return vmConnector; 331 | } 332 | 333 | private void setConnectionState(ConnectionState state) { 334 | ConnectionState oldState = this.connectionState; 335 | this.connectionState = state; 336 | propertyChangeSupport.firePropertyChange(CONNECTION_STATE_PROPERTY, 337 | oldState, state); 338 | } 339 | 340 | public ConnectionState getConnectionState() { 341 | return this.connectionState; 342 | } 343 | 344 | void flush() { 345 | if (server != null) { 346 | server.flush(); 347 | } 348 | } 349 | 350 | public void connect() { 351 | setConnectionState(ConnectionState.CONNECTING); 352 | try { 353 | tryConnect(); 354 | setConnectionState(ConnectionState.CONNECTED); 355 | } catch (Exception e) { 356 | setConnectionState(ConnectionState.DISCONNECTED); 357 | throw new IllegalStateException(e); 358 | } 359 | } 360 | 361 | private void tryConnect() throws IOException { 362 | if (jmxUrl == null && "localhost".equals(hostName) && port == 0) { 363 | // Monitor self 364 | this.jmxc = null; 365 | this.mbsc = ManagementFactory.getPlatformMBeanServer(); 366 | this.server = Snapshot.newSnapshot(mbsc); 367 | } else { 368 | // Monitor another process 369 | if (lvm != null) { 370 | if (!lvm.isManageable()) { 371 | lvm.startManagementAgent(); 372 | if (!lvm.isManageable()) { 373 | // FIXME: what to throw 374 | throw new IOException(lvm + "not manageable"); 375 | } 376 | } 377 | if (this.jmxUrl == null) { 378 | this.jmxUrl = new JMXServiceURL(lvm.connectorAddress()); 379 | } 380 | } 381 | // Need to pass in credentials ? 382 | if (userName == null && password == null) { 383 | if (isVmConnector()) { 384 | // Check for SSL config on reconnection only 385 | if (stub == null) { 386 | checkSslConfig(); 387 | } 388 | this.jmxc = new RMIConnector(stub, null); 389 | jmxc.connect(); 390 | } else { 391 | this.jmxc = JMXConnectorFactory.connect(jmxUrl); 392 | } 393 | } else { 394 | Map env = new HashMap(); 395 | env.put(JMXConnector.CREDENTIALS, 396 | new String[] {userName, password}); 397 | if (isVmConnector()) { 398 | // Check for SSL config on reconnection only 399 | if (stub == null) { 400 | checkSslConfig(); 401 | } 402 | this.jmxc = new RMIConnector(stub, null); 403 | jmxc.connect(env); 404 | } else { 405 | this.jmxc = JMXConnectorFactory.connect(jmxUrl, env); 406 | } 407 | } 408 | this.mbsc = jmxc.getMBeanServerConnection(); 409 | this.server = Snapshot.newSnapshot(mbsc); 410 | } 411 | this.isDead = false; 412 | 413 | try { 414 | ObjectName on = new ObjectName(THREAD_MXBEAN_NAME); 415 | this.hasPlatformMXBeans = server.isRegistered(on); 416 | this.hasHotSpotDiagnosticMXBean = 417 | server.isRegistered(new ObjectName(HOTSPOT_DIAGNOSTIC_MXBEAN_NAME)); 418 | // check if it has 6.0 new APIs 419 | if (this.hasPlatformMXBeans) { 420 | MBeanOperationInfo[] mopis = server.getMBeanInfo(on).getOperations(); 421 | // look for findDeadlockedThreads operations; 422 | for (MBeanOperationInfo op : mopis) { 423 | if (op.getName().equals("findDeadlockedThreads")) { 424 | this.supportsLockUsage = true; 425 | break; 426 | } 427 | } 428 | 429 | on = new ObjectName(COMPILATION_MXBEAN_NAME); 430 | this.hasCompilationMXBean = server.isRegistered(on); 431 | } 432 | } catch (MalformedObjectNameException e) { 433 | // should not reach here 434 | throw new InternalError(e.getMessage()); 435 | } catch (IntrospectionException e) { 436 | InternalError ie = new InternalError(e.getMessage()); 437 | ie.initCause(e); 438 | throw ie; 439 | } catch (InstanceNotFoundException e) { 440 | InternalError ie = new InternalError(e.getMessage()); 441 | ie.initCause(e); 442 | throw ie; 443 | } catch (ReflectionException e) { 444 | InternalError ie = new InternalError(e.getMessage()); 445 | ie.initCause(e); 446 | throw ie; 447 | } 448 | 449 | if (hasPlatformMXBeans) { 450 | // WORKAROUND for bug 5056632 451 | // Check if the access role is correct by getting a RuntimeMXBean 452 | getRuntimeMXBean(); 453 | } 454 | } 455 | 456 | /** 457 | * Gets a proxy client for a given local virtual machine. 458 | */ 459 | public static ProxyClient getProxyClient(LocalVirtualMachine lvm) 460 | throws IOException { 461 | final String key = getCacheKey(lvm); 462 | ProxyClient proxyClient = cache.get(key); 463 | if (proxyClient == null) { 464 | proxyClient = new ProxyClient(lvm); 465 | cache.put(key, proxyClient); 466 | } 467 | return proxyClient; 468 | } 469 | 470 | public static String getConnectionName(LocalVirtualMachine lvm) { 471 | return Integer.toString(lvm.vmid()); 472 | } 473 | 474 | private static String getCacheKey(LocalVirtualMachine lvm) { 475 | return Integer.toString(lvm.vmid()); 476 | } 477 | 478 | /** 479 | * Gets a proxy client for a given JMXServiceURL. 480 | */ 481 | public static ProxyClient getProxyClient(String url, 482 | String userName, String password) 483 | throws IOException { 484 | final String key = getCacheKey(url, userName, password); 485 | ProxyClient proxyClient = cache.get(key); 486 | if (proxyClient == null) { 487 | proxyClient = new ProxyClient(url, userName, password); 488 | cache.put(key, proxyClient); 489 | } 490 | return proxyClient; 491 | } 492 | 493 | public static String getConnectionName(String url, 494 | String userName) { 495 | if (userName != null && userName.length() > 0) { 496 | return userName + "@" + url; 497 | } else { 498 | return url; 499 | } 500 | } 501 | 502 | private static String getCacheKey(String url, 503 | String userName, String password) { 504 | return (url == null ? "" : url) + ":" + 505 | (userName == null ? "" : userName) + ":" + 506 | (password == null ? "" : password); 507 | } 508 | 509 | /** 510 | * Gets a proxy client for a given "hostname:port". 511 | */ 512 | public static ProxyClient getProxyClient(String hostName, int port, 513 | String userName, String password) 514 | throws IOException { 515 | final String key = getCacheKey(hostName, port, userName, password); 516 | ProxyClient proxyClient = cache.get(key); 517 | if (proxyClient == null) { 518 | proxyClient = new ProxyClient(hostName, port, userName, password); 519 | cache.put(key, proxyClient); 520 | } 521 | return cache.get(key); 522 | } 523 | 524 | public static String getConnectionName(String hostName, int port, 525 | String userName) { 526 | String name = hostName + ":" + port; 527 | if (userName != null && userName.length() > 0) { 528 | return userName + "@" + name; 529 | } else { 530 | return name; 531 | } 532 | } 533 | 534 | private static String getCacheKey(String hostName, int port, 535 | String userName, String password) { 536 | return (hostName == null ? "" : hostName) + ":" + 537 | port + ":" + 538 | (userName == null ? "" : userName) + ":" + 539 | (password == null ? "" : password); 540 | } 541 | 542 | public String connectionName() { 543 | return connectionName; 544 | } 545 | 546 | public String getDisplayName() { 547 | return displayName; 548 | } 549 | 550 | public String toString() { 551 | if (!isConnected()) { 552 | return null; 553 | //return Resources.getText("ConnectionName (disconnected)", displayName); 554 | } else { 555 | return displayName; 556 | } 557 | } 558 | 559 | public MBeanServerConnection getMBeanServerConnection() { 560 | return mbsc; 561 | } 562 | 563 | public SnapshotMBeanServerConnection getSnapshotMBeanServerConnection() { 564 | return server; 565 | } 566 | 567 | public String getUrl() { 568 | return advancedUrl; 569 | } 570 | 571 | public String getHostName() { 572 | return hostName; 573 | } 574 | 575 | public int getPort() { 576 | return port; 577 | } 578 | 579 | public int getVmid() { 580 | return (lvm != null) ? lvm.vmid() : 0; 581 | } 582 | 583 | public String getUserName() { 584 | return userName; 585 | } 586 | 587 | public String getPassword() { 588 | return password; 589 | } 590 | 591 | public void disconnect() { 592 | // Reset remote stub 593 | stub = null; 594 | // Close MBeanServer connection 595 | if (jmxc != null) { 596 | try { 597 | jmxc.close(); 598 | } catch (IOException e) { 599 | // Ignore ??? 600 | } 601 | } 602 | // Reset platform MBean references 603 | classLoadingMBean = null; 604 | compilationMBean = null; 605 | memoryMBean = null; 606 | operatingSystemMBean = null; 607 | runtimeMBean = null; 608 | threadMBean = null; 609 | sunOperatingSystemMXBean = null; 610 | garbageCollectorMBeans = null; 611 | memoryPoolProxies = null; 612 | // Set connection state to DISCONNECTED 613 | if (!isDead) { 614 | isDead = true; 615 | setConnectionState(ConnectionState.DISCONNECTED); 616 | } 617 | } 618 | 619 | /** 620 | * Returns the list of domains in which any MBean is 621 | * currently registered. 622 | */ 623 | public String[] getDomains() throws IOException { 624 | return server.getDomains(); 625 | } 626 | 627 | /** 628 | * Returns a map of MBeans with ObjectName as the key and MBeanInfo value 629 | * of a given domain. If domain is null, all MBeans 630 | * are returned. If no MBean found, an empty map is returned. 631 | * 632 | */ 633 | public Map getMBeans(String domain) 634 | throws IOException { 635 | 636 | ObjectName name = null; 637 | if (domain != null) { 638 | try { 639 | name = new ObjectName(domain + ":*"); 640 | } catch (MalformedObjectNameException e) { 641 | // should not reach here 642 | assert(false); 643 | } 644 | } 645 | Set mbeans = server.queryNames(name, null); 646 | Map result = 647 | new HashMap(mbeans.size()); 648 | Iterator iterator = mbeans.iterator(); 649 | while (iterator.hasNext()) { 650 | Object object = iterator.next(); 651 | if (object instanceof ObjectName) { 652 | ObjectName o = (ObjectName)object; 653 | try { 654 | MBeanInfo info = server.getMBeanInfo(o); 655 | result.put(o, info); 656 | } catch (IntrospectionException e) { 657 | // TODO: should log the error 658 | logger.error(e.getMessage(), e); 659 | } catch (InstanceNotFoundException e) { 660 | // TODO: should log the error 661 | logger.error(e.getMessage(), e); 662 | } catch (ReflectionException e) { 663 | // TODO: should log the error 664 | logger.error(e.getMessage(), e); 665 | } 666 | } 667 | } 668 | return result; 669 | } 670 | 671 | /** 672 | * Returns a list of attributes of a named MBean. 673 | * 674 | */ 675 | public AttributeList getAttributes(ObjectName name, String[] attributes) 676 | throws IOException { 677 | AttributeList list = null; 678 | try { 679 | list = server.getAttributes(name, attributes); 680 | } catch (InstanceNotFoundException e) { 681 | // TODO: A MBean may have been unregistered. 682 | // need to set up listener to listen for MBeanServerNotification. 683 | logger.error(e.getMessage(), e); 684 | } catch (ReflectionException e) { 685 | // TODO: should log the error 686 | logger.error(e.getMessage(), e); 687 | } 688 | return list; 689 | } 690 | 691 | /** 692 | * Set the value of a specific attribute of a named MBean. 693 | */ 694 | public void setAttribute(ObjectName name, Attribute attribute) 695 | throws InvalidAttributeValueException, 696 | MBeanException, 697 | IOException { 698 | try { 699 | server.setAttribute(name, attribute); 700 | } catch (InstanceNotFoundException e) { 701 | // TODO: A MBean may have been unregistered. 702 | logger.error(e.getMessage(), e); 703 | } catch (AttributeNotFoundException e) { 704 | throw new IllegalStateException(e); 705 | } catch (ReflectionException e) { 706 | // TODO: should log the error 707 | logger.error(e.getMessage(), e); 708 | } 709 | } 710 | 711 | /** 712 | * Invokes an operation of a named MBean. 713 | * 714 | * @throws MBeanException Wraps an exception thrown by 715 | * the MBean's invoked method. 716 | */ 717 | public Object invoke(ObjectName name, String operationName, 718 | Object[] params, String[] signature) 719 | throws IOException, MBeanException { 720 | Object result = null; 721 | try { 722 | result = server.invoke(name, operationName, params, signature); 723 | } catch (InstanceNotFoundException e) { 724 | // TODO: A MBean may have been unregistered. 725 | } catch (ReflectionException e) { 726 | // TODO: should log the error 727 | logger.error(e.getMessage(), e); 728 | } 729 | return result; 730 | } 731 | 732 | public synchronized ClassLoadingMXBean getClassLoadingMXBean() throws IOException { 733 | if (hasPlatformMXBeans && classLoadingMBean == null) { 734 | classLoadingMBean = 735 | newPlatformMXBeanProxy(server, CLASS_LOADING_MXBEAN_NAME, 736 | ClassLoadingMXBean.class); 737 | } 738 | return classLoadingMBean; 739 | } 740 | 741 | public synchronized CompilationMXBean getCompilationMXBean() throws IOException { 742 | if (hasCompilationMXBean && compilationMBean == null) { 743 | compilationMBean = 744 | newPlatformMXBeanProxy(server, COMPILATION_MXBEAN_NAME, 745 | CompilationMXBean.class); 746 | } 747 | return compilationMBean; 748 | } 749 | 750 | public Collection getMemoryPoolProxies() 751 | throws IOException { 752 | 753 | // TODO: How to deal with changes to the list?? 754 | if (memoryPoolProxies == null) { 755 | ObjectName poolName = null; 756 | try { 757 | poolName = new ObjectName(MEMORY_POOL_MXBEAN_DOMAIN_TYPE + ",*"); 758 | } catch (MalformedObjectNameException e) { 759 | // should not reach here 760 | assert(false); 761 | } 762 | Set mbeans = server.queryNames(poolName, null); 763 | if (mbeans != null) { 764 | memoryPoolProxies = new ArrayList(); 765 | Iterator iterator = mbeans.iterator(); 766 | while (iterator.hasNext()) { 767 | ObjectName objName = (ObjectName) iterator.next(); 768 | MemoryPoolProxy p = new MemoryPoolProxy(this, objName); 769 | memoryPoolProxies.add(p); 770 | } 771 | } 772 | } 773 | return memoryPoolProxies; 774 | } 775 | 776 | 777 | public synchronized Collection getBufferPoolMXBeans() 778 | throws IOException { 779 | String oName = "java.nio:type=BufferPool"; 780 | // TODO: How to deal with changes to the list?? 781 | if (bufferPoolMXBeans == null) { 782 | ObjectName bpName = null; 783 | try { 784 | bpName = new ObjectName(oName + ",*"); 785 | } catch (MalformedObjectNameException e) { 786 | // should not reach here 787 | assert(false); 788 | } 789 | Set mbeans = server.queryNames(bpName, null); 790 | if (mbeans != null) { 791 | bufferPoolMXBeans = new ArrayList(); 792 | Iterator iterator = mbeans.iterator(); 793 | while (iterator.hasNext()) { 794 | ObjectName on = (ObjectName) iterator.next(); 795 | String name = oName +",name=" + on.getKeyProperty("name"); 796 | 797 | BufferPoolMXBean mBean = 798 | newPlatformMXBeanProxy(server, name, 799 | BufferPoolMXBean.class); 800 | bufferPoolMXBeans.add(mBean); 801 | } 802 | } 803 | } 804 | return bufferPoolMXBeans; 805 | } 806 | 807 | public synchronized Collection getGarbageCollectorMXBeans() 808 | throws IOException { 809 | 810 | // TODO: How to deal with changes to the list?? 811 | if (garbageCollectorMBeans == null) { 812 | ObjectName gcName = null; 813 | try { 814 | gcName = new ObjectName(GARBAGE_COLLECTOR_MXBEAN_DOMAIN_TYPE + ",*"); 815 | } catch (MalformedObjectNameException e) { 816 | // should not reach here 817 | assert(false); 818 | } 819 | Set mbeans = server.queryNames(gcName, null); 820 | if (mbeans != null) { 821 | garbageCollectorMBeans = new ArrayList(); 822 | Iterator iterator = mbeans.iterator(); 823 | while (iterator.hasNext()) { 824 | ObjectName on = (ObjectName) iterator.next(); 825 | String name = GARBAGE_COLLECTOR_MXBEAN_DOMAIN_TYPE + 826 | ",name=" + on.getKeyProperty("name"); 827 | 828 | GarbageCollectorMXBean mBean = 829 | newPlatformMXBeanProxy(server, name, 830 | GarbageCollectorMXBean.class); 831 | garbageCollectorMBeans.add(mBean); 832 | } 833 | } 834 | } 835 | return garbageCollectorMBeans; 836 | } 837 | 838 | public synchronized MemoryMXBean getMemoryMXBean() throws IOException { 839 | if (hasPlatformMXBeans && memoryMBean == null) { 840 | memoryMBean = 841 | newPlatformMXBeanProxy(server, MEMORY_MXBEAN_NAME, 842 | MemoryMXBean.class); 843 | } 844 | return memoryMBean; 845 | } 846 | 847 | public synchronized RuntimeMXBean getRuntimeMXBean() throws IOException { 848 | if (hasPlatformMXBeans && runtimeMBean == null) { 849 | runtimeMBean = 850 | newPlatformMXBeanProxy(server, RUNTIME_MXBEAN_NAME, 851 | RuntimeMXBean.class); 852 | } 853 | return runtimeMBean; 854 | } 855 | 856 | 857 | public synchronized ThreadMXBean getThreadMXBean() throws IOException { 858 | if (hasPlatformMXBeans && threadMBean == null) { 859 | threadMBean = 860 | newPlatformMXBeanProxy(server, THREAD_MXBEAN_NAME, 861 | ThreadMXBean.class); 862 | } 863 | return threadMBean; 864 | } 865 | 866 | public synchronized OperatingSystemMXBean getOperatingSystemMXBean() throws IOException { 867 | if (hasPlatformMXBeans && operatingSystemMBean == null) { 868 | operatingSystemMBean = 869 | newPlatformMXBeanProxy(server, OPERATING_SYSTEM_MXBEAN_NAME, 870 | OperatingSystemMXBean.class); 871 | } 872 | return operatingSystemMBean; 873 | } 874 | 875 | public synchronized com.sun.management.OperatingSystemMXBean 876 | getSunOperatingSystemMXBean() throws IOException { 877 | 878 | try { 879 | ObjectName on = new ObjectName(OPERATING_SYSTEM_MXBEAN_NAME); 880 | if (sunOperatingSystemMXBean == null) { 881 | if (server.isInstanceOf(on, 882 | "com.sun.management.OperatingSystemMXBean")) { 883 | sunOperatingSystemMXBean = 884 | newPlatformMXBeanProxy(server, 885 | OPERATING_SYSTEM_MXBEAN_NAME, 886 | com.sun.management.OperatingSystemMXBean.class); 887 | } 888 | } 889 | } catch (InstanceNotFoundException e) { 890 | return null; 891 | } catch (MalformedObjectNameException e) { 892 | return null; // should never reach here 893 | } 894 | return sunOperatingSystemMXBean; 895 | } 896 | 897 | public synchronized HotSpotDiagnosticMXBean getHotSpotDiagnosticMXBean() throws IOException { 898 | if (hasHotSpotDiagnosticMXBean && hotspotDiagnosticMXBean == null) { 899 | hotspotDiagnosticMXBean = 900 | newPlatformMXBeanProxy(server, HOTSPOT_DIAGNOSTIC_MXBEAN_NAME, 901 | HotSpotDiagnosticMXBean.class); 902 | } 903 | return hotspotDiagnosticMXBean; 904 | } 905 | 906 | public T getMXBean(ObjectName objName, Class interfaceClass) 907 | throws IOException { 908 | return newPlatformMXBeanProxy(server, 909 | objName.toString(), 910 | interfaceClass); 911 | 912 | } 913 | 914 | // Return thread IDs of deadlocked threads or null if any. 915 | // It finds deadlocks involving only monitors if it's a Tiger VM. 916 | // Otherwise, it finds deadlocks involving both monitors and 917 | // the concurrent locks. 918 | public long[] findDeadlockedThreads() throws IOException { 919 | ThreadMXBean tm = getThreadMXBean(); 920 | if (supportsLockUsage && tm.isSynchronizerUsageSupported()) { 921 | return tm.findDeadlockedThreads(); 922 | } else { 923 | return tm.findMonitorDeadlockedThreads(); 924 | } 925 | } 926 | 927 | public synchronized void markAsDead() { 928 | disconnect(); 929 | } 930 | 931 | public boolean isDead() { 932 | return isDead; 933 | } 934 | 935 | boolean isConnected() { 936 | return !isDead(); 937 | } 938 | 939 | boolean hasPlatformMXBeans() { 940 | return this.hasPlatformMXBeans; 941 | } 942 | 943 | boolean hasHotSpotDiagnosticMXBean() { 944 | return this.hasHotSpotDiagnosticMXBean; 945 | } 946 | 947 | boolean isLockUsageSupported() { 948 | return supportsLockUsage; 949 | } 950 | 951 | public boolean isRegistered(ObjectName name) throws IOException { 952 | return server.isRegistered(name); 953 | } 954 | 955 | public void addPropertyChangeListener(PropertyChangeListener listener) { 956 | propertyChangeSupport.addPropertyChangeListener(listener); 957 | } 958 | 959 | public void addWeakPropertyChangeListener(PropertyChangeListener listener) { 960 | if (!(listener instanceof WeakPCL)) { 961 | listener = new WeakPCL(listener); 962 | } 963 | propertyChangeSupport.addPropertyChangeListener(listener); 964 | } 965 | 966 | public void removePropertyChangeListener(PropertyChangeListener listener) { 967 | if (!(listener instanceof WeakPCL)) { 968 | // Search for the WeakPCL holding this listener (if any) 969 | for (PropertyChangeListener pcl : propertyChangeSupport.getPropertyChangeListeners()) { 970 | if (pcl instanceof WeakPCL && ((WeakPCL)pcl).get() == listener) { 971 | listener = pcl; 972 | break; 973 | } 974 | } 975 | } 976 | propertyChangeSupport.removePropertyChangeListener(listener); 977 | } 978 | 979 | /** 980 | * The PropertyChangeListener is handled via a WeakReference 981 | * so as not to pin down the listener. 982 | */ 983 | private class WeakPCL extends WeakReference 984 | implements PropertyChangeListener { 985 | WeakPCL(PropertyChangeListener referent) { 986 | super(referent); 987 | } 988 | 989 | public void propertyChange(PropertyChangeEvent pce) { 990 | PropertyChangeListener pcl = get(); 991 | 992 | if (pcl == null) { 993 | // The referent listener was GC'ed, we're no longer 994 | // interested in PropertyChanges, remove the listener. 995 | dispose(); 996 | } else { 997 | pcl.propertyChange(pce); 998 | } 999 | } 1000 | 1001 | private void dispose() { 1002 | removePropertyChangeListener(this); 1003 | } 1004 | } 1005 | 1006 | // 1007 | // Snapshot MBeanServerConnection: 1008 | // 1009 | // This is an object that wraps an existing MBeanServerConnection and adds 1010 | // caching to it, as follows: 1011 | // 1012 | // - The first time an attribute is called in a given MBean, the result is 1013 | // cached. Every subsequent time getAttribute is called for that attribute 1014 | // the cached result is returned. 1015 | // 1016 | // - Before every call to VMPanel.update() or when the Refresh button in the 1017 | // Attributes table is pressed down the attributes cache is flushed. Then 1018 | // any subsequent call to getAttribute will retrieve all the values for 1019 | // the attributes that are known to the cache. 1020 | // 1021 | // - The attributes cache uses a learning approach and only the attributes 1022 | // that are in the cache will be retrieved between two subsequent updates. 1023 | // 1024 | 1025 | public interface SnapshotMBeanServerConnection 1026 | extends MBeanServerConnection { 1027 | /** 1028 | * Flush all cached values of attributes. 1029 | */ 1030 | public void flush(); 1031 | } 1032 | 1033 | public static class Snapshot { 1034 | private Snapshot() { 1035 | } 1036 | public static SnapshotMBeanServerConnection 1037 | newSnapshot(MBeanServerConnection mbsc) { 1038 | final InvocationHandler ih = new SnapshotInvocationHandler(mbsc); 1039 | return (SnapshotMBeanServerConnection) Proxy.newProxyInstance( 1040 | Snapshot.class.getClassLoader(), 1041 | new Class[] {SnapshotMBeanServerConnection.class}, 1042 | ih); 1043 | } 1044 | } 1045 | 1046 | static class SnapshotInvocationHandler implements InvocationHandler { 1047 | 1048 | private final MBeanServerConnection conn; 1049 | private Map cachedValues = newMap(); 1050 | private Map> cachedNames = newMap(); 1051 | 1052 | @SuppressWarnings("serial") 1053 | private static final class NameValueMap 1054 | extends HashMap {} 1055 | 1056 | SnapshotInvocationHandler(MBeanServerConnection conn) { 1057 | this.conn = conn; 1058 | } 1059 | 1060 | synchronized void flush() { 1061 | cachedValues = newMap(); 1062 | } 1063 | 1064 | public Object invoke(Object proxy, Method method, Object[] args) 1065 | throws Throwable { 1066 | final String methodName = method.getName(); 1067 | if (methodName.equals("getAttribute")) { 1068 | return getAttribute((ObjectName) args[0], (String) args[1]); 1069 | } else if (methodName.equals("getAttributes")) { 1070 | return getAttributes((ObjectName) args[0], (String[]) args[1]); 1071 | } else if (methodName.equals("flush")) { 1072 | flush(); 1073 | return null; 1074 | } else { 1075 | try { 1076 | return method.invoke(conn, args); 1077 | } catch (InvocationTargetException e) { 1078 | throw e.getCause(); 1079 | } 1080 | } 1081 | } 1082 | 1083 | private Object getAttribute(ObjectName objName, String attrName) 1084 | throws MBeanException, InstanceNotFoundException, 1085 | AttributeNotFoundException, ReflectionException, IOException { 1086 | final NameValueMap values = getCachedAttributes( 1087 | objName, Collections.singleton(attrName)); 1088 | Object value = values.get(attrName); 1089 | if (value != null || values.containsKey(attrName)) { 1090 | return value; 1091 | } 1092 | // Not in cache, presumably because it was omitted from the 1093 | // getAttributes result because of an exception. Following 1094 | // call will probably provoke the same exception. 1095 | return conn.getAttribute(objName, attrName); 1096 | } 1097 | 1098 | private AttributeList getAttributes( 1099 | ObjectName objName, String[] attrNames) throws 1100 | InstanceNotFoundException, ReflectionException, IOException { 1101 | final NameValueMap values = getCachedAttributes( 1102 | objName, 1103 | new TreeSet(Arrays.asList(attrNames))); 1104 | final AttributeList list = new AttributeList(); 1105 | for (String attrName : attrNames) { 1106 | final Object value = values.get(attrName); 1107 | if (value != null || values.containsKey(attrName)) { 1108 | list.add(new Attribute(attrName, value)); 1109 | } 1110 | } 1111 | return list; 1112 | } 1113 | 1114 | private synchronized NameValueMap getCachedAttributes( 1115 | ObjectName objName, Set attrNames) throws 1116 | InstanceNotFoundException, ReflectionException, IOException { 1117 | NameValueMap values = cachedValues.get(objName); 1118 | if (values != null && values.keySet().containsAll(attrNames)) { 1119 | return values; 1120 | } 1121 | attrNames = new TreeSet(attrNames); 1122 | Set oldNames = cachedNames.get(objName); 1123 | if (oldNames != null) { 1124 | attrNames.addAll(oldNames); 1125 | } 1126 | values = new NameValueMap(); 1127 | final AttributeList attrs = conn.getAttributes( 1128 | objName, 1129 | attrNames.toArray(new String[attrNames.size()])); 1130 | for (Attribute attr : attrs.asList()) { 1131 | values.put(attr.getName(), attr.getValue()); 1132 | } 1133 | cachedValues.put(objName, values); 1134 | cachedNames.put(objName, attrNames); 1135 | return values; 1136 | } 1137 | 1138 | // See http://www.artima.com/weblogs/viewpost.jsp?thread=79394 1139 | private static Map newMap() { 1140 | return new HashMap(); 1141 | } 1142 | } 1143 | } 1144 | -------------------------------------------------------------------------------- /src/main/resources/conf.properties: -------------------------------------------------------------------------------- 1 | # the working dir 2 | workDir=./ 3 | 4 | # localhost jmx ports, split by comma 5 | jmx.ports=10000,10001,10002,10003 6 | 7 | # agent port url 8 | agent.posturl=http://localhost:1988/v1/push 9 | -------------------------------------------------------------------------------- /src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | # Output pattern : date [thread] priority category - message 2 | #log4j.rootLogger=INFO, Console, RollingFile 3 | log4j.rootLogger=INFO, Console 4 | 5 | #Console 6 | log4j.appender.Console=org.apache.log4j.ConsoleAppender 7 | log4j.appender.Console.layout=org.apache.log4j.PatternLayout 8 | log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n 9 | -------------------------------------------------------------------------------- /src/test/java/com/stephan/tof/jmxmon/bean/FalconPostDataTest.java: -------------------------------------------------------------------------------- 1 | package com.stephan.tof.jmxmon.bean; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | import static org.assertj.core.api.Assertions.tuple; 5 | 6 | import java.io.File; 7 | import java.io.IOException; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | import org.junit.Before; 12 | import org.junit.BeforeClass; 13 | import org.junit.Test; 14 | 15 | import com.stephan.tof.jmxmon.Constants.CounterType; 16 | 17 | public class FalconPostDataTest { 18 | 19 | @BeforeClass 20 | public static void setUpBeforeClass() throws Exception { 21 | } 22 | 23 | @Before 24 | public void setUp() throws Exception { 25 | } 26 | 27 | @Test 28 | public void testJsonTransfer() throws IOException { 29 | FalconItem item1 = new FalconItem("m1", "ep1", 30 | System.currentTimeMillis() / 1000, 60, 123, 31 | CounterType.COUNTER.toString(), "tag1=a,tag2=b"); 32 | FalconItem item2 = new FalconItem("m2", "ep2", 33 | System.currentTimeMillis() / 1000, 60, 156789.123456F, 34 | CounterType.GAUGE.toString(), "tag3=a,tag4=b"); 35 | List writeItems = new ArrayList(); 36 | writeItems.add(item1); 37 | writeItems.add(item2); 38 | 39 | File f = new File("test.falconPostData.json"); 40 | JacksonUtil.writeBeanToFile(f, writeItems, true); 41 | System.out.println("json file path=" + f.getAbsolutePath()); 42 | 43 | @SuppressWarnings("unchecked") 44 | List readItems = JacksonUtil 45 | .readBeanFromFile(f, List.class); 46 | assertThat(readItems).hasSize(2); 47 | assertThat(readItems).extracting( 48 | "metric", "endpoint", "step", "value", "counterType", "tags").containsExactly( 49 | tuple("m1", "ep1", 60, 123D, CounterType.COUNTER.toString(), "tag1=a,tag2=b"), 50 | tuple("m2", "ep2", 60, 156789.12D, CounterType.GAUGE.toString(), "tag3=a,tag4=b")); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/com/stephan/tof/jmxmon/bean/JVMContextTest.java: -------------------------------------------------------------------------------- 1 | package com.stephan.tof.jmxmon.bean; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | 8 | import org.junit.Before; 9 | import org.junit.BeforeClass; 10 | import org.junit.Test; 11 | 12 | public class JVMContextTest { 13 | 14 | @BeforeClass 15 | public static void setUpBeforeClass() throws Exception { 16 | } 17 | 18 | @Before 19 | public void setUp() throws Exception { 20 | } 21 | 22 | @Test 23 | public void testJsonTransfer() throws IOException { 24 | JVMContext context1 = new JVMContext(); 25 | context1.getJvmData(1111).getGcData("1a").setCollectionCount(11); 26 | context1.getJvmData(1111).getGcData("1b").setCollectionCount(12); 27 | context1.getJvmData(2222).getGcData("2a").setCollectionCount(21); 28 | context1.getJvmData(2222).getGcData("2b").setCollectionCount(22); 29 | context1.getJvmData(2222).getGcData("2c").setCollectionCount(23); 30 | File f = new File("test.jvmcontext.json"); 31 | JacksonUtil.writeBeanToFile(f, context1, true); 32 | System.out.println("json file path=" + f.getAbsolutePath()); 33 | 34 | JVMContext context2 = JacksonUtil.readBeanFromFile(f, JVMContext.class); 35 | assertThat(context2.getJvmData(1111).getGcData("1b").getCollectionCount()).isEqualTo(12); 36 | assertThat(context2.getJvmData(2222).getGcData("2a").getCollectionCount()).isEqualTo(21); 37 | assertThat(context2.getJvmData(2222).getGcData("2c").getCollectionCount()).isEqualTo(23); 38 | assertThat(context2.getJvmData(2222).getGcData("2a").getCollectionTime()).isEqualTo(0); 39 | assertThat(context2.getJvmData(2222).getGcData("2b").getCollectionTime()).isEqualTo(0); 40 | assertThat(context2.getJvmData(2222).getGcData("2c").getCollectionTime()).isEqualTo(0); 41 | } 42 | 43 | } 44 | --------------------------------------------------------------------------------