├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── images ├── screen_shot_1.png └── screen_shot_2.png ├── mvnw ├── mvnw.cmd ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── kakao │ │ └── infra │ │ ├── JavaThreadDumpAnalyzerApplication.java │ │ ├── ScheduledTasks.java │ │ ├── controllers │ │ ├── JavaController.java │ │ └── JavaRestAPIController.java │ │ ├── mappers │ │ ├── DumpInfoMapper.java │ │ ├── LockInfoMapper.java │ │ ├── LockThreadInfoMapper.java │ │ └── ThreadInfoMapper.java │ │ ├── models │ │ ├── DumpInfo.java │ │ ├── LockInfo.java │ │ ├── LockList.java │ │ ├── LockThreadInfo.java │ │ └── ThreadInfo.java │ │ ├── utils │ │ └── SqlHelper.java │ │ └── workers │ │ └── JavaDumpParser.java └── resources │ ├── application-devel.properties │ ├── application-production.properties │ ├── application.properties │ ├── static │ ├── css │ │ └── bootstrap.css │ └── js │ │ ├── bootstrap.min.js │ │ ├── echarts-all.js │ │ └── echarts.js │ └── templates │ ├── dumpIndex.html │ ├── showDump.html │ ├── uploadComplete.html │ └── uploadForm.html └── test └── java └── com └── kakao └── infra └── JavaThreadAnalyzerApplicationTests.java /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Submitting Pull Requests 2 | 3 | Before changes can be accepted a Contributor Licensing Agreement for [Individual](https://docs.google.com/forms/d/1d2R9aVafHTasTYirq-qZx_lx20Obss0ufUS-OtLpu20/viewform) | [Corporate](https://docs.google.com/forms/d/1hj5s1bkbmO1OP-UKnSNS6dsDkL1jFDL2XZ5d4IIFdb4/viewform) must be completed. 4 | 5 | * If you are an individual writing original source code and you're sure you own the intellectual property, then you'll need to sign an [Individual CLA](https://docs.google.com/forms/d/1d2R9aVafHTasTYirq-qZx_lx20Obss0ufUS-OtLpu20/viewform). 6 | * If you work for a company that wants to allow you to contribute your work, then you'll need to sign a [Corporate CLA](https://docs.google.com/forms/d/1hj5s1bkbmO1OP-UKnSNS6dsDkL1jFDL2XZ5d4IIFdb4/viewform). 7 | 8 | *!Caution : due to legal obligations, this CLA does not apply to korean users, see below for the details.* 9 | 10 | ### For Koreans 11 | 12 | If you are Korean, please e-mail at oss@kakaocorp.com for agreeing to the our privacy policy. Then we will send you a CLA copy. 13 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 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 | # Java Thread Dump Analyzer 2 | 3 | ## About 4 | 5 | You can analyze your java thread dump file with this project. 6 | It shows all thread's status and link about lock information. 7 | 8 | ## Configuration 9 | 10 | ### Set properties files 11 | 12 | #### Common 13 | - Filename : src/main/resources/application.properties 14 | ``` 15 | spring.datasource.driverClassName=com.mysql.jdbc.Driver 16 | spring.datasource.username= 17 | spring.datasource.password= 18 | spring.datasource.validation-query=SELECT 1 19 | 20 | multipart.maxFileSize: 1024MB 21 | multipart.maxRequestSize: 1024MB 22 | ``` 23 | ex) 24 | ``` 25 | spring.datasource.driverClassName=com.mysql.jdbc.Driver 26 | spring.datasource.username=username 27 | spring.datasource.password=passwd 28 | spring.datasource.validation-query=SELECT 1 29 | 30 | multipart.maxFileSize: 1024MB 31 | multipart.maxRequestSize: 1024MB 32 | ``` 33 | 34 | #### Development 35 | - Filename : src/main/resources/application-devel.properties 36 | ``` 37 | spring.datasource.url= 38 | config.baseurl= 39 | ``` 40 | ex) 41 | ``` 42 | spring.datasource.url=jdbc:mysql://dev.db.com/data 43 | config.baseurl=http://infra.domain.com 44 | ``` 45 | 46 | 47 | #### Production 48 | - Filename : src/main/resources/application-production.properties 49 | ``` 50 | spring.datasource.url= 51 | config.baseurl= 52 | ``` 53 | 54 | ### Create tables 55 | 56 | #### thread_info 57 | You have to create thread_info table in mysql 58 | ``` 59 | mysql> CREATE TABLE thread_info (hostname varchar(100), timestamp varchar(50), date timestamp, name varchar(100), tid varchar(50), nid varchar(50), state varchar(50), raw_data text, primary key (hostname, timestamp, tid, nid)); 60 | ``` 61 | 62 | #### lock_info 63 | You have to create lock_info table in mysql 64 | ``` 65 | mysql> CREATE TABLE lock_info (hostname varchar(100), timestamp varchar(50), lock_if varchar(50), tid varchar(50), nid varchar(50), state varchar(50), owned int, primary key (hostname, timestamp, tid, nid)); 66 | ``` 67 | 68 | ## How to Build 69 | 70 | You make jar file from mvn package. And just execute it. 71 | ``` 72 | mvn package 73 | ``` 74 | 75 | ## How to Run 76 | 77 | Just execute jar file. 78 | ``` 79 | java -jar java-thread-analyzer-0.0.1-SNAPSHOT.jar -Dspring.profiles.active= 80 | ``` 81 | ex) 82 | ``` 83 | java -jar -Dspring.profiles.active=production -Xms4096m -Xmx4096m ./java-thread-analyzer-0.0.1-SNAPSHOT.jar 84 | ``` 85 | 86 | ## How to Use 87 | 88 | ### Web Mode 89 | 90 | You can use this project with web interface. 91 | 92 | #### Screen Shots 93 | 1. Main Page 94 | ![screen shot 2](images/screen_shot_2.png) 95 | 96 | 2. View Page 97 | ![screen shot 1](images/screen_shot_1.png) 98 | 99 | ### API Mode 100 | 101 | You make java thread dump with jstack and use curl command. 102 | ``` 103 | [root@server ~]# jstack -l 12345 > /tmp/$(hostname)_dump 104 | [root@server ~]# curl -F "dumpFile=@/tmp/$(hostname)_dump" -F "hostname=$(hostname)" http://infra.domain.com/api/v1/dump/java/upload 105 | http://infra.domain.com/dump/java/$(hostname)/1451972358000 106 | ``` 107 | And you open response url with web brower. 108 | 109 | ## License 110 | 111 | This software is licensed under the [Apache 2 license](https://github.com/kakao/hbase-tools/blob/master/LICENSE.txt), quoted below. 112 | 113 | Copyright 2015 Kakao Corp. http://www.kakaocorp.com 114 | 115 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this project except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. 116 | 117 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 118 | -------------------------------------------------------------------------------- /images/screen_shot_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kakao/java_thread_dump_analyzer/23172b721b690667994f91b936f5033a5b713d57/images/screen_shot_1.png -------------------------------------------------------------------------------- /images/screen_shot_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kakao/java_thread_dump_analyzer/23172b721b690667994f91b936f5033a5b713d57/images/screen_shot_2.png -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # 58 | # Look for the Apple JDKs first to preserve the existing behaviour, and then look 59 | # for the new JDKs provided by Oracle. 60 | # 61 | if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then 62 | # 63 | # Apple JDKs 64 | # 65 | export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home 66 | fi 67 | 68 | if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then 69 | # 70 | # Apple JDKs 71 | # 72 | export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home 73 | fi 74 | 75 | if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then 76 | # 77 | # Oracle JDKs 78 | # 79 | export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home 80 | fi 81 | 82 | if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then 83 | # 84 | # Apple JDKs 85 | # 86 | export JAVA_HOME=`/usr/libexec/java_home` 87 | fi 88 | ;; 89 | esac 90 | 91 | if [ -z "$JAVA_HOME" ] ; then 92 | if [ -r /etc/gentoo-release ] ; then 93 | JAVA_HOME=`java-config --jre-home` 94 | fi 95 | fi 96 | 97 | if [ -z "$M2_HOME" ] ; then 98 | ## resolve links - $0 may be a link to maven's home 99 | PRG="$0" 100 | 101 | # need this for relative symlinks 102 | while [ -h "$PRG" ] ; do 103 | ls=`ls -ld "$PRG"` 104 | link=`expr "$ls" : '.*-> \(.*\)$'` 105 | if expr "$link" : '/.*' > /dev/null; then 106 | PRG="$link" 107 | else 108 | PRG="`dirname "$PRG"`/$link" 109 | fi 110 | done 111 | 112 | saveddir=`pwd` 113 | 114 | M2_HOME=`dirname "$PRG"`/.. 115 | 116 | # make it fully qualified 117 | M2_HOME=`cd "$M2_HOME" && pwd` 118 | 119 | cd "$saveddir" 120 | # echo Using m2 at $M2_HOME 121 | fi 122 | 123 | # For Cygwin, ensure paths are in UNIX format before anything is touched 124 | if $cygwin ; then 125 | [ -n "$M2_HOME" ] && 126 | M2_HOME=`cygpath --unix "$M2_HOME"` 127 | [ -n "$JAVA_HOME" ] && 128 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 129 | [ -n "$CLASSPATH" ] && 130 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 131 | fi 132 | 133 | # For Migwn, ensure paths are in UNIX format before anything is touched 134 | if $mingw ; then 135 | [ -n "$M2_HOME" ] && 136 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 137 | [ -n "$JAVA_HOME" ] && 138 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 139 | # TODO classpath? 140 | fi 141 | 142 | if [ -z "$JAVA_HOME" ]; then 143 | javaExecutable="`which javac`" 144 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 145 | # readlink(1) is not available as standard on Solaris 10. 146 | readLink=`which readlink` 147 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 148 | if $darwin ; then 149 | javaHome="`dirname \"$javaExecutable\"`" 150 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 151 | else 152 | javaExecutable="`readlink -f \"$javaExecutable\"`" 153 | fi 154 | javaHome="`dirname \"$javaExecutable\"`" 155 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 156 | JAVA_HOME="$javaHome" 157 | export JAVA_HOME 158 | fi 159 | fi 160 | fi 161 | 162 | if [ -z "$JAVACMD" ] ; then 163 | if [ -n "$JAVA_HOME" ] ; then 164 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 165 | # IBM's JDK on AIX uses strange locations for the executables 166 | JAVACMD="$JAVA_HOME/jre/sh/java" 167 | else 168 | JAVACMD="$JAVA_HOME/bin/java" 169 | fi 170 | else 171 | JAVACMD="`which java`" 172 | fi 173 | fi 174 | 175 | if [ ! -x "$JAVACMD" ] ; then 176 | echo "Error: JAVA_HOME is not defined correctly." >&2 177 | echo " We cannot execute $JAVACMD" >&2 178 | exit 1 179 | fi 180 | 181 | if [ -z "$JAVA_HOME" ] ; then 182 | echo "Warning: JAVA_HOME environment variable is not set." 183 | fi 184 | 185 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 186 | 187 | # For Cygwin, switch paths to Windows format before running java 188 | if $cygwin; then 189 | [ -n "$M2_HOME" ] && 190 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 191 | [ -n "$JAVA_HOME" ] && 192 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 193 | [ -n "$CLASSPATH" ] && 194 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 195 | fi 196 | 197 | # traverses directory structure from process work directory to filesystem root 198 | # first directory with .mvn subdirectory is considered project base directory 199 | find_maven_basedir() { 200 | local basedir=$(pwd) 201 | local wdir=$(pwd) 202 | while [ "$wdir" != '/' ] ; do 203 | if [ -d "$wdir"/.mvn ] ; then 204 | basedir=$wdir 205 | break 206 | fi 207 | wdir=$(cd "$wdir/.."; pwd) 208 | done 209 | echo "${basedir}" 210 | } 211 | 212 | # concatenates all lines of a file 213 | concat_lines() { 214 | if [ -f "$1" ]; then 215 | echo "$(tr -s '\n' ' ' < "$1")" 216 | fi 217 | } 218 | 219 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)} 220 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 221 | 222 | # Provide a "standardized" way to retrieve the CLI args that will 223 | # work with both Windows and non-Windows executions. 224 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 225 | export MAVEN_CMD_LINE_ARGS 226 | 227 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 228 | 229 | exec "$JAVACMD" \ 230 | $MAVEN_OPTS \ 231 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 232 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 233 | ${WRAPPER_LAUNCHER} "$@" 234 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 40 | 41 | @REM set %HOME% to equivalent of $HOME 42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 43 | 44 | @REM Execute a user defined script before this one 45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 49 | :skipRcPre 50 | 51 | @setlocal 52 | 53 | set ERROR_CODE=0 54 | 55 | @REM To isolate internal variables from possible post scripts, we use another setlocal 56 | @setlocal 57 | 58 | @REM ==== START VALIDATION ==== 59 | if not "%JAVA_HOME%" == "" goto OkJHome 60 | 61 | echo. 62 | echo Error: JAVA_HOME not found in your environment. >&2 63 | echo Please set the JAVA_HOME variable in your environment to match the >&2 64 | echo location of your Java installation. >&2 65 | echo. 66 | goto error 67 | 68 | :OkJHome 69 | if exist "%JAVA_HOME%\bin\java.exe" goto init 70 | 71 | echo. 72 | echo Error: JAVA_HOME is set to an invalid directory. >&2 73 | echo JAVA_HOME = "%JAVA_HOME%" >&2 74 | echo Please set the JAVA_HOME variable in your environment to match the >&2 75 | echo location of your Java installation. >&2 76 | echo. 77 | goto error 78 | 79 | @REM ==== END VALIDATION ==== 80 | 81 | :init 82 | 83 | set MAVEN_CMD_LINE_ARGS=%* 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | 121 | set WRAPPER_JAR="".\.mvn\wrapper\maven-wrapper.jar"" 122 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 123 | 124 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% 125 | if ERRORLEVEL 1 goto error 126 | goto end 127 | 128 | :error 129 | set ERROR_CODE=1 130 | 131 | :end 132 | @endlocal & set ERROR_CODE=%ERROR_CODE% 133 | 134 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 135 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 136 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 137 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 138 | :skipRcPost 139 | 140 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 141 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 142 | 143 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 144 | 145 | exit /B %ERROR_CODE% -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.kakao.infra 7 | java-thread-analyzer 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | java-thread-analyzer 12 | java-thread-analyzer 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 1.3.3.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | 1.8 24 | 25 | 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-starter-redis 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-web 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter-thymeleaf 38 | 39 | 40 | mysql 41 | mysql-connector-java 42 | runtime 43 | 44 | 45 | org.springframework 46 | spring-jdbc 47 | 48 | 49 | commons-dbcp 50 | commons-dbcp 51 | 52 | 53 | org.springframework.boot 54 | spring-boot-starter-test 55 | test 56 | 57 | 58 | org.projectlombok 59 | lombok 60 | 1.16.8 61 | provided 62 | 63 | 64 | mysql 65 | mysql-connector-java 66 | 5.1.38 67 | 68 | 69 | com.h2database 70 | h2 71 | 1.4.191 72 | test 73 | 74 | 75 | 76 | 77 | 78 | 79 | org.springframework.boot 80 | spring-boot-maven-plugin 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /src/main/java/com/kakao/infra/JavaThreadDumpAnalyzerApplication.java: -------------------------------------------------------------------------------- 1 | package com.kakao.infra; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.scheduling.annotation.EnableScheduling; 6 | 7 | @SpringBootApplication 8 | @EnableScheduling 9 | public class JavaThreadDumpAnalyzerApplication { 10 | 11 | 12 | public static void main(String[] args) { 13 | SpringApplication.run(JavaThreadDumpAnalyzerApplication.class, args); 14 | } 15 | 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/kakao/infra/ScheduledTasks.java: -------------------------------------------------------------------------------- 1 | package com.kakao.infra; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.scheduling.annotation.Scheduled; 7 | import org.springframework.stereotype.Component; 8 | 9 | import com.kakao.infra.utils.SqlHelper; 10 | 11 | @Component 12 | public class ScheduledTasks { 13 | 14 | private static final Logger logger = LoggerFactory.getLogger(ScheduledTasks.class); 15 | 16 | @Autowired 17 | SqlHelper sqlHelper; 18 | 19 | @Scheduled(fixedRate = 60000) 20 | public void selfDBConnectionCheck(){ 21 | logger.info("pingDB"); 22 | sqlHelper.pingDB(); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/kakao/infra/controllers/JavaController.java: -------------------------------------------------------------------------------- 1 | package com.kakao.infra.controllers; 2 | 3 | import java.sql.Date; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.stereotype.Controller; 10 | import org.springframework.ui.Model; 11 | import org.springframework.web.bind.annotation.PathVariable; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | import org.springframework.web.bind.annotation.RequestMethod; 14 | import org.springframework.web.bind.annotation.RequestParam; 15 | import org.springframework.web.multipart.MultipartFile; 16 | 17 | import com.kakao.infra.models.DumpInfo; 18 | import com.kakao.infra.models.LockInfo; 19 | import com.kakao.infra.models.LockList; 20 | import com.kakao.infra.models.LockThreadInfo; 21 | import com.kakao.infra.models.ThreadInfo; 22 | import com.kakao.infra.utils.SqlHelper; 23 | import com.kakao.infra.workers.JavaDumpParser; 24 | 25 | @Controller 26 | @RequestMapping("/dump/java") 27 | public class JavaController { 28 | 29 | @Value("${config.baseurl}") 30 | String baseUrl; 31 | 32 | @Autowired 33 | SqlHelper sqlHelper; 34 | 35 | @Autowired 36 | JavaDumpParser javaDumpParser; 37 | 38 | @RequestMapping(method = RequestMethod.GET, value="") 39 | public String dumpIndex(Model model){ 40 | 41 | List dumpInfos = sqlHelper.getDumpInfo(); 42 | model.addAttribute("dumpInfos", dumpInfos); 43 | 44 | return "dumpIndex"; 45 | } 46 | 47 | @RequestMapping(method = RequestMethod.GET, value="/upload") 48 | public String uploadForm(Model model) { 49 | return "uploadForm"; 50 | } 51 | 52 | @RequestMapping(method = RequestMethod.POST, value="/upload") 53 | public String uploadFile(@RequestParam("hostname") String hostname, 54 | @RequestParam("dumpFile") MultipartFile dumpFile, Model model) { 55 | 56 | String response = javaDumpParser.parseData(dumpFile, hostname); 57 | String responseUrl = baseUrl + response; 58 | 59 | model.addAttribute("response", responseUrl); 60 | 61 | return "uploadComplete"; 62 | } 63 | 64 | @RequestMapping(method = RequestMethod.GET, value="/{hostname}/{timeStamp}") 65 | public String showDump(@PathVariable String hostname, @PathVariable String timeStamp, Model model) { 66 | 67 | List runnableThreads = sqlHelper.getThreadInfo(hostname, timeStamp, "RUNNABLE"); 68 | List waitingThreads = sqlHelper.getThreadInfo(hostname, timeStamp, "WAITING"); 69 | List timedWaitingThreads = sqlHelper.getThreadInfo(hostname, timeStamp, "TIMED_WAITING"); 70 | List blockedThreads = sqlHelper.getThreadInfo(hostname, timeStamp, "BLOCKED"); 71 | 72 | List lockInfos = sqlHelper.getLockInfo(hostname, timeStamp); 73 | 74 | List lockLists = new ArrayList(); 75 | 76 | List defaultThreadInfos = new ArrayList(); 77 | LockThreadInfo defaultLockThreadInfo = new LockThreadInfo("N/A","N/A","N/A"); 78 | defaultThreadInfos.add(defaultLockThreadInfo); 79 | 80 | for( int i=0 ; i{ 11 | 12 | @Override 13 | public DumpInfo mapRow(ResultSet rs, int rowNum) throws SQLException { 14 | // TODO Auto-generated method stub 15 | DumpInfo dumpInfo = new DumpInfo(rs.getString("hostname"), rs.getString("date"), rs.getString("timestamp")); 16 | return dumpInfo; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/kakao/infra/mappers/LockInfoMapper.java: -------------------------------------------------------------------------------- 1 | package com.kakao.infra.mappers; 2 | 3 | import java.sql.ResultSet; 4 | import java.sql.SQLException; 5 | 6 | import org.springframework.jdbc.core.RowMapper; 7 | 8 | import com.kakao.infra.models.LockInfo; 9 | 10 | public class LockInfoMapper implements RowMapper { 11 | 12 | @Override 13 | public LockInfo mapRow(ResultSet rs, int rowNum) throws SQLException { 14 | // TODO Auto-generated method stub 15 | LockInfo lockInfo = new LockInfo(rs.getString("lock_id")); 16 | return lockInfo; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/kakao/infra/mappers/LockThreadInfoMapper.java: -------------------------------------------------------------------------------- 1 | package com.kakao.infra.mappers; 2 | 3 | import java.sql.ResultSet; 4 | import java.sql.SQLException; 5 | 6 | import org.springframework.jdbc.core.RowMapper; 7 | 8 | import com.kakao.infra.models.LockThreadInfo; 9 | 10 | public class LockThreadInfoMapper implements RowMapper { 11 | 12 | @Override 13 | public LockThreadInfo mapRow(ResultSet rs, int rowNum) throws SQLException { 14 | // TODO Auto-generated method stub 15 | LockThreadInfo lockThreadInfo = new LockThreadInfo(rs.getString("tid"), rs.getString("nid"), rs.getString("state")); 16 | return lockThreadInfo; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/kakao/infra/mappers/ThreadInfoMapper.java: -------------------------------------------------------------------------------- 1 | package com.kakao.infra.mappers; 2 | 3 | import java.sql.ResultSet; 4 | import java.sql.SQLException; 5 | 6 | import org.springframework.jdbc.core.RowMapper; 7 | 8 | import com.kakao.infra.models.ThreadInfo; 9 | 10 | public class ThreadInfoMapper implements RowMapper { 11 | 12 | @Override 13 | public ThreadInfo mapRow(ResultSet rs, int rowNum) throws SQLException { 14 | // TODO Auto-generated method stub 15 | ThreadInfo threadInfo = new ThreadInfo(rs.getString("name"), rs.getString("tid"), rs.getString("nid"), rs.getString("state"), rs.getString("raw_data")); 16 | return threadInfo; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/kakao/infra/models/DumpInfo.java: -------------------------------------------------------------------------------- 1 | package com.kakao.infra.models; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | public class DumpInfo { 7 | 8 | @Getter @Setter private String hostname; 9 | @Getter @Setter private String dateTime; 10 | @Getter @Setter private String timeStamp; 11 | 12 | public DumpInfo(String hostname, String dateTime, String timeStamp){ 13 | this.hostname = hostname; 14 | this.dateTime = dateTime; 15 | this.timeStamp = timeStamp; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/kakao/infra/models/LockInfo.java: -------------------------------------------------------------------------------- 1 | package com.kakao.infra.models; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | 8 | @JsonIgnoreProperties(ignoreUnknown = true) 9 | public class LockInfo { 10 | 11 | @Getter @Setter private String id; 12 | @Getter @Setter private String tid; 13 | @Getter @Setter private String nid; 14 | @Getter @Setter private String state; 15 | @Getter @Setter private int owned; 16 | 17 | public LockInfo(String id){ 18 | this.id = id; 19 | } 20 | 21 | public LockInfo() { 22 | // TODO Auto-generated constructor stub 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/kakao/infra/models/LockList.java: -------------------------------------------------------------------------------- 1 | package com.kakao.infra.models; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | 9 | public class LockList { 10 | 11 | @Getter @Setter private String id; 12 | @Getter @Setter private List lockedThreads; 13 | @Getter @Setter private List waitingThreads; 14 | 15 | public LockList(String id){ 16 | this.id = id; 17 | this.lockedThreads = new ArrayList(); 18 | this.waitingThreads = new ArrayList(); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/kakao/infra/models/LockThreadInfo.java: -------------------------------------------------------------------------------- 1 | package com.kakao.infra.models; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | public class LockThreadInfo { 7 | 8 | @Getter @Setter private String tid; 9 | @Getter @Setter private String nid; 10 | @Getter @Setter private String state; 11 | 12 | public LockThreadInfo(String tid, String nid, String state){ 13 | this.tid = tid; 14 | this.nid = nid; 15 | this.state = state; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/kakao/infra/models/ThreadInfo.java: -------------------------------------------------------------------------------- 1 | package com.kakao.infra.models; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | 8 | @JsonIgnoreProperties(ignoreUnknown = true) 9 | public class ThreadInfo { 10 | 11 | @Getter @Setter private String name; 12 | @Getter @Setter private String tid; 13 | @Getter @Setter private String nid; 14 | @Getter @Setter private String state; 15 | @Getter @Setter private String raw_data; 16 | 17 | public ThreadInfo(String name, String tid, String nid, String state, String raw_data){ 18 | this.name = name; 19 | this.tid = tid; 20 | this.nid = nid; 21 | this.state = state; 22 | this.raw_data = raw_data; 23 | } 24 | 25 | public ThreadInfo() { 26 | // TODO Auto-generated constructor stub 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/kakao/infra/utils/SqlHelper.java: -------------------------------------------------------------------------------- 1 | package com.kakao.infra.utils; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.jdbc.core.JdbcTemplate; 7 | import org.springframework.stereotype.Service; 8 | 9 | import com.kakao.infra.mappers.DumpInfoMapper; 10 | import com.kakao.infra.mappers.LockInfoMapper; 11 | import com.kakao.infra.mappers.LockThreadInfoMapper; 12 | import com.kakao.infra.mappers.ThreadInfoMapper; 13 | import com.kakao.infra.models.DumpInfo; 14 | import com.kakao.infra.models.LockInfo; 15 | import com.kakao.infra.models.LockThreadInfo; 16 | import com.kakao.infra.models.ThreadInfo; 17 | 18 | @Service 19 | public class SqlHelper { 20 | 21 | @Autowired 22 | JdbcTemplate jdbcTemplate; 23 | 24 | public void pingDB(){ 25 | String sql = "SELECT 1"; 26 | jdbcTemplate.queryForRowSet(sql); 27 | } 28 | 29 | public List getThreadInfo(String hostname, String timeStamp, String state){ 30 | String sql = "SELECT name, tid, nid, state, raw_data FROM thread_info WHERE hostname = '" + hostname + "' AND timestamp = '" + timeStamp + "' AND state = '" + state + "'"; 31 | return jdbcTemplate.query(sql, new ThreadInfoMapper()); 32 | } 33 | 34 | public void setThreadInfo(String hostname, String timeStamp, String dateTime, ThreadInfo threadInfo){ 35 | String sql = "INSERT INTO thread_info (hostname, timestamp, date, name, tid, nid, state, raw_data) VALUES "; 36 | sql += "('" + hostname + "','" + timeStamp + "','" + dateTime + "','" + threadInfo.getName() + "','" + threadInfo.getTid() + "','" + threadInfo.getNid() + "','" + threadInfo.getState() + "','" + threadInfo.getRaw_data() + "')"; 37 | sql += " ON DUPLICATE KEY UPDATE name = VALUES(name), state = VALUES(state), raw_data = VALUES(raw_data), date = VALUES(date)"; 38 | jdbcTemplate.batchUpdate(sql); 39 | } 40 | 41 | public List getLockInfo(String hostname, String timeStamp) { 42 | String sql = "SELECT DISTINCT(lock_id) FROM lock_info WHERE hostname = '" + hostname + "' AND timestamp = '" + timeStamp + "'"; 43 | return jdbcTemplate.query(sql, new LockInfoMapper()); 44 | } 45 | 46 | public void setLockInfo(String hostname, String timeStamp, LockInfo lockInfo){ 47 | String sql = "INSERT INTO lock_info (hostname, timestamp, lock_id, tid, nid, state, owned) VALUES"; 48 | sql += "('" + hostname + "','" + timeStamp + "','" + lockInfo.getId() + "','" + lockInfo.getTid() + "','" + lockInfo.getNid() + "','" + lockInfo.getState() + "','" + lockInfo.getOwned() + "')"; 49 | sql += " ON DUPLICATE KEY UPDATE lock_id = VALUES(lock_id), state = VALUES(state), owned = VALUES(owned)"; 50 | jdbcTemplate.batchUpdate(sql); 51 | } 52 | 53 | public List getLockThreadInfo(String hostname, String timeStamp, String id, String owned){ 54 | String sql = "SELECT tid, nid, state, owned FROM lock_info WHERE hostname = '" + hostname + "' AND timestamp = '" + timeStamp + "' AND lock_id = '" + id +"' AND owned = '" + owned + "'"; 55 | return jdbcTemplate.query(sql, new LockThreadInfoMapper()); 56 | } 57 | 58 | public List getDumpInfo(){ 59 | String sql = "SELECT hostname, timestamp, date FROM thread_info GROUP BY hostname, timestamp ORDER BY date DESC"; 60 | return jdbcTemplate.query(sql, new DumpInfoMapper()); 61 | } 62 | 63 | public List getDumpInfo(String hostname, String timeStamp){ 64 | String sql = "SELECT hostname, timestamp, date FROM thread_info WHERE hostname = '" + hostname + "' AND timeStamp = '" + timeStamp + "' LIMIT 1"; 65 | return jdbcTemplate.query(sql, new DumpInfoMapper()); 66 | } 67 | 68 | public void deleteDumpInfo(String hostname, String timeStamp){ 69 | String sql = "DELETE FROM thread_info WHERE hostname = '" + hostname + "' AND timeStamp='" + timeStamp + "'"; 70 | jdbcTemplate.batchUpdate(sql); 71 | 72 | sql = "DELETE FROM lock_info WHERE hostname = '" + hostname + "' AND timeStamp='" + timeStamp + "'"; 73 | jdbcTemplate.batchUpdate(sql); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/kakao/infra/workers/JavaDumpParser.java: -------------------------------------------------------------------------------- 1 | package com.kakao.infra.workers; 2 | 3 | 4 | import java.io.BufferedReader; 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.io.InputStreamReader; 8 | import java.text.ParseException; 9 | import java.text.SimpleDateFormat; 10 | import java.util.regex.Matcher; 11 | import java.util.regex.Pattern; 12 | 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.stereotype.Service; 15 | import org.springframework.web.multipart.MultipartFile; 16 | 17 | import com.kakao.infra.models.LockInfo; 18 | import com.kakao.infra.models.ThreadInfo; 19 | import com.kakao.infra.utils.SqlHelper; 20 | 21 | @Service 22 | public class JavaDumpParser { 23 | 24 | @Autowired 25 | SqlHelper sqlHelper; 26 | 27 | public boolean deleteData(String hostname, String timeStamp){ 28 | sqlHelper.deleteDumpInfo(hostname, timeStamp); 29 | return true; 30 | } 31 | 32 | public String parseData(MultipartFile dumpFile, String hostname){ 33 | 34 | InputStream inputStream = null; 35 | 36 | ThreadInfo threadInfo = null; 37 | 38 | String dateTime = ""; 39 | String timeStamp = ""; 40 | String tid = ""; 41 | String nid = ""; 42 | String state = ""; 43 | 44 | try { 45 | inputStream = dumpFile.getInputStream(); 46 | BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); 47 | String line = null; 48 | String rawData = ""; 49 | 50 | Pattern namePattern = Pattern.compile("^\"(.*)\".*prio=[0-9]+ tid=(\\w*) nid=(\\w*)\\s\\w*"); 51 | Pattern statePattern = Pattern.compile("\\s+java.lang.Thread.State: (.*)"); 52 | Pattern lockWaitPattern = Pattern.compile("\\s+- parking to wait for\\s+<(.*)>\\s+\\(.*\\)"); 53 | Pattern lockedPattern = Pattern.compile("\\s+- locked\\s+<(.*)>\\s+\\(.*\\)"); 54 | 55 | line = bufferedReader.readLine(); 56 | try { 57 | dateTime = line; 58 | timeStamp = Long.toString(new SimpleDateFormat("yyyy-mm-dd kk:mm:ss").parse(line).getTime()); 59 | } catch (ParseException e) { 60 | // TODO Auto-generated catch block 61 | e.printStackTrace(); 62 | } 63 | 64 | while ((line = bufferedReader.readLine()) != null) { 65 | if (line.startsWith("\"")){ 66 | 67 | if (threadInfo != null) { 68 | threadInfo.setRaw_data(rawData); 69 | sqlHelper.setThreadInfo(hostname, timeStamp, dateTime, threadInfo); 70 | rawData = ""; 71 | } 72 | 73 | Matcher matcher = namePattern.matcher(line); 74 | matcher.find(); 75 | 76 | threadInfo = new ThreadInfo(); 77 | threadInfo.setName(matcher.group(1)); 78 | threadInfo.setTid(matcher.group(2)); 79 | threadInfo.setNid(matcher.group(3)); 80 | 81 | tid = matcher.group(2); 82 | nid = matcher.group(3); 83 | 84 | } 85 | if (line.contains("Thread.State:")){ 86 | Matcher matcher = statePattern.matcher(line); 87 | matcher.find(); 88 | 89 | state = matcher.group(1); 90 | state = state.split(" ")[0]; 91 | 92 | threadInfo.setState(state); 93 | } 94 | if (line.contains("parking to wait for")){ 95 | Matcher matcher = lockWaitPattern.matcher(line); 96 | matcher.find(); 97 | 98 | LockInfo lockInfo = new LockInfo(); 99 | lockInfo.setId(matcher.group(1)); 100 | lockInfo.setNid(nid); 101 | lockInfo.setTid(tid); 102 | lockInfo.setState(state); 103 | lockInfo.setOwned(0); 104 | 105 | sqlHelper.setLockInfo(hostname, timeStamp, lockInfo); 106 | } 107 | if (line.contains("- locked")){ 108 | Matcher matcher = lockedPattern.matcher(line); 109 | matcher.find(); 110 | 111 | LockInfo lockInfo = new LockInfo(); 112 | lockInfo.setId(matcher.group(1)); 113 | lockInfo.setNid(nid); 114 | lockInfo.setTid(tid); 115 | lockInfo.setState(state); 116 | lockInfo.setOwned(1); 117 | 118 | sqlHelper.setLockInfo(hostname, timeStamp, lockInfo); 119 | } 120 | 121 | rawData += line + "\n"; 122 | } 123 | 124 | } catch (IOException e1) { 125 | // TODO Auto-generated catch block 126 | e1.printStackTrace(); 127 | } 128 | 129 | return "/dump/java/"+hostname+"/"+timeStamp; 130 | 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /src/main/resources/application-devel.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url= 2 | config.baseurl= -------------------------------------------------------------------------------- /src/main/resources/application-production.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url= 2 | config.baseurl= -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.driverClassName=com.mysql.jdbc.Driver 2 | spring.datasource.username= 3 | spring.datasource.password= 4 | spring.datasource.validation-query=SELECT 1 5 | 6 | multipart.maxFileSize: 1024MB 7 | multipart.maxRequestSize: 1024MB -------------------------------------------------------------------------------- /src/main/resources/static/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.6 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under the MIT license 5 | */ 6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>2)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 3")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.6",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.6",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),a(c.target).is('input[type="radio"]')||a(c.target).is('input[type="checkbox"]')||c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.6",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.6",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.6",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),c.isInStateTrue()?void 0:(clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide())},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.6",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.6",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.6",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); -------------------------------------------------------------------------------- /src/main/resources/static/js/echarts.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * ECharts, a javascript interactive chart library. 3 | * 4 | * Copyright (c) 2015, Baidu Inc. 5 | * All rights reserved. 6 | * 7 | * LICENSE 8 | * https://github.com/ecomfe/echarts/blob/master/LICENSE.txt 9 | */ 10 | 11 | /** 12 | * echarts 13 | * 14 | * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 15 | * @author Kener (@Kener-林峰, kener.linfeng@gmail.com) 16 | * 17 | */ 18 | define(function (require) { 19 | var ecConfig = require('./config'); 20 | var zrUtil = require('zrender/tool/util'); 21 | var zrEvent = require('zrender/tool/event'); 22 | 23 | var self = {}; 24 | 25 | var _canvasSupported = require('zrender/tool/env').canvasSupported; 26 | var _idBase = new Date() - 0; 27 | var _instances = {}; // ECharts实例map索引 28 | var DOM_ATTRIBUTE_KEY = '_echarts_instance_'; 29 | 30 | self.version = '2.2.7'; 31 | self.dependencies = { 32 | zrender: '2.1.1' 33 | }; 34 | /** 35 | * 入口方法 36 | */ 37 | self.init = function (dom, theme) { 38 | var zrender = require('zrender'); 39 | if ((zrender.version.replace('.', '') - 0) < (self.dependencies.zrender.replace('.', '') - 0)) { 40 | console.error( 41 | 'ZRender ' + zrender.version 42 | + ' is too old for ECharts ' + self.version 43 | + '. Current version need ZRender ' 44 | + self.dependencies.zrender + '+' 45 | ); 46 | } 47 | 48 | dom = dom instanceof Array ? dom[0] : dom; 49 | 50 | // dom与echarts实例映射索引 51 | var key = dom.getAttribute(DOM_ATTRIBUTE_KEY); 52 | if (!key) { 53 | key = _idBase++; 54 | dom.setAttribute(DOM_ATTRIBUTE_KEY, key); 55 | } 56 | 57 | if (_instances[key]) { 58 | // 同一个dom上多次init,自动释放已有实例 59 | _instances[key].dispose(); 60 | } 61 | _instances[key] = new Echarts(dom); 62 | _instances[key].id = key; 63 | _instances[key].canvasSupported = _canvasSupported; 64 | _instances[key].setTheme(theme); 65 | 66 | return _instances[key]; 67 | }; 68 | 69 | /** 70 | * 通过id获得ECharts实例,id可在实例化后读取 71 | */ 72 | self.getInstanceById = function (key) { 73 | return _instances[key]; 74 | }; 75 | 76 | /** 77 | * 消息中心 78 | */ 79 | function MessageCenter() { 80 | zrEvent.Dispatcher.call(this); 81 | } 82 | zrUtil.merge(MessageCenter.prototype, zrEvent.Dispatcher.prototype, true); 83 | 84 | /** 85 | * 基于zrender实现Echarts接口层 86 | * @param {HtmlElement} dom 必要 87 | */ 88 | function Echarts(dom) { 89 | // Fxxk IE11 for breaking initialization without a warrant; 90 | // Just set something to let it be! 91 | // by kener 2015-01-09 92 | dom.innerHTML = ''; 93 | this._themeConfig = {}; // zrUtil.clone(ecConfig); 94 | 95 | this.dom = dom; 96 | // this._zr; 97 | // this._option; // curOption clone 98 | // this._optionRestore; // for restore; 99 | // this._island; 100 | // this._toolbox; 101 | // this._timeline; 102 | // this._refreshInside; // 内部刷新标志位 103 | 104 | this._connected = false; 105 | this._status = { // 用于图表间通信 106 | dragIn: false, 107 | dragOut: false, 108 | needRefresh: false 109 | }; 110 | this._curEventType = false; // 破循环信号灯 111 | this._chartList = []; // 图表实例 112 | 113 | this._messageCenter = new MessageCenter(); 114 | 115 | this._messageCenterOutSide = new MessageCenter(); // Echarts层的外部消息中心,做Echarts层的消息转发 116 | 117 | // resize方法经常被绑定到window.resize上,闭包一个this 118 | this.resize = this.resize(); 119 | 120 | // 初始化::构造函数 121 | this._init(); 122 | } 123 | 124 | /** 125 | * ZRender EVENT 126 | * 127 | * @inner 128 | * @const 129 | * @type {Object} 130 | */ 131 | var ZR_EVENT = require('zrender/config').EVENT; 132 | 133 | /** 134 | * 要绑定监听的zrender事件列表 135 | * 136 | * @const 137 | * @inner 138 | * @type {Array} 139 | */ 140 | var ZR_EVENT_LISTENS = [ 141 | 'CLICK', 'DBLCLICK', 'MOUSEOVER', 'MOUSEOUT', 142 | 'DRAGSTART', 'DRAGEND', 'DRAGENTER', 'DRAGOVER', 'DRAGLEAVE', 'DROP' 143 | ]; 144 | 145 | /** 146 | * 对echarts的实例中的chartList属性成员,逐个进行方法调用,遍历顺序为逆序 147 | * 由于在事件触发的默认行为处理中,多次用到相同逻辑,所以抽象了该方法 148 | * 由于所有的调用场景里,最多只有两个参数,基于性能和体积考虑,这里就不使用call或者apply了 149 | * 150 | * @inner 151 | * @param {ECharts} ecInstance ECharts实例 152 | * @param {string} methodName 要调用的方法名 153 | * @param {*} arg0 调用参数1 154 | * @param {*} arg1 调用参数2 155 | * @param {*} arg2 调用参数3 156 | */ 157 | function callChartListMethodReverse(ecInstance, methodName, arg0, arg1, arg2) { 158 | var chartList = ecInstance._chartList; 159 | var len = chartList.length; 160 | 161 | while (len--) { 162 | var chart = chartList[len]; 163 | if (typeof chart[methodName] === 'function') { 164 | chart[methodName](arg0, arg1, arg2); 165 | } 166 | } 167 | } 168 | 169 | Echarts.prototype = { 170 | /** 171 | * 初始化::构造函数 172 | */ 173 | _init: function () { 174 | var self = this; 175 | var _zr = require('zrender').init(this.dom); 176 | this._zr = _zr; 177 | 178 | // wrap: n,e,d,t for name event data this 179 | this._messageCenter.dispatch = function(type, event, eventPackage, that) { 180 | eventPackage = eventPackage || {}; 181 | eventPackage.type = type; 182 | eventPackage.event = event; 183 | 184 | self._messageCenter.dispatchWithContext(type, eventPackage, that); 185 | self._messageCenterOutSide.dispatchWithContext(type, eventPackage, that); 186 | 187 | // 如下注掉的代码,@see: https://github.com/ecomfe/echarts-discuss/issues/3 188 | // if (type != 'HOVER' && type != 'MOUSEOUT') { // 频繁事件直接抛出 189 | // setTimeout(function(){ 190 | // self._messageCenterOutSide.dispatchWithContext( 191 | // type, eventPackage, that 192 | // ); 193 | // },50); 194 | // } 195 | // else { 196 | // self._messageCenterOutSide.dispatchWithContext( 197 | // type, eventPackage, that 198 | // ); 199 | // } 200 | }; 201 | 202 | this._onevent = function(param){ 203 | return self.__onevent(param); 204 | }; 205 | for (var e in ecConfig.EVENT) { 206 | if (e != 'CLICK' && e != 'DBLCLICK' 207 | && e != 'HOVER' && e != 'MOUSEOUT' && e != 'MAP_ROAM' 208 | ) { 209 | this._messageCenter.bind(ecConfig.EVENT[e], this._onevent, this); 210 | } 211 | } 212 | 213 | 214 | var eventBehaviors = {}; 215 | this._onzrevent = function (param) { 216 | return self[eventBehaviors[ param.type ]](param); 217 | }; 218 | 219 | // 挂载关心的事件 220 | for (var i = 0, len = ZR_EVENT_LISTENS.length; i < len; i++) { 221 | var eventName = ZR_EVENT_LISTENS[i]; 222 | var eventValue = ZR_EVENT[eventName]; 223 | eventBehaviors[eventValue] = '_on' + eventName.toLowerCase(); 224 | _zr.on(eventValue, this._onzrevent); 225 | } 226 | 227 | this.chart = {}; // 图表索引 228 | this.component = {}; // 组件索引 229 | 230 | // 内置图表 231 | // 孤岛 232 | var Island = require('./chart/island'); 233 | this._island = new Island(this._themeConfig, this._messageCenter, _zr, {}, this); 234 | this.chart.island = this._island; 235 | 236 | // 内置通用组件 237 | // 工具箱 238 | var Toolbox = require('./component/toolbox'); 239 | this._toolbox = new Toolbox(this._themeConfig, this._messageCenter, _zr, {}, this); 240 | this.component.toolbox = this._toolbox; 241 | 242 | var componentLibrary = require('./component'); 243 | componentLibrary.define('title', require('./component/title')); 244 | componentLibrary.define('tooltip', require('./component/tooltip')); 245 | componentLibrary.define('legend', require('./component/legend')); 246 | 247 | if (_zr.getWidth() === 0 || _zr.getHeight() === 0) { 248 | console.error('Dom’s width & height should be ready before init.'); 249 | } 250 | }, 251 | 252 | /** 253 | * ECharts事件处理中心 254 | */ 255 | __onevent: function (param){ 256 | param.__echartsId = param.__echartsId || this.id; 257 | 258 | // 来自其他联动图表的事件 259 | var fromMyself = (param.__echartsId === this.id); 260 | 261 | if (!this._curEventType) { 262 | this._curEventType = param.type; 263 | } 264 | 265 | switch (param.type) { 266 | case ecConfig.EVENT.LEGEND_SELECTED : 267 | this._onlegendSelected(param); 268 | break; 269 | case ecConfig.EVENT.DATA_ZOOM : 270 | if (!fromMyself) { 271 | var dz = this.component.dataZoom; 272 | if (dz) { 273 | dz.silence(true); 274 | dz.absoluteZoom(param.zoom); 275 | dz.silence(false); 276 | } 277 | } 278 | this._ondataZoom(param); 279 | break; 280 | case ecConfig.EVENT.DATA_RANGE : 281 | fromMyself && this._ondataRange(param); 282 | break; 283 | case ecConfig.EVENT.MAGIC_TYPE_CHANGED : 284 | if (!fromMyself) { 285 | var tb = this.component.toolbox; 286 | if (tb) { 287 | tb.silence(true); 288 | tb.setMagicType(param.magicType); 289 | tb.silence(false); 290 | } 291 | } 292 | this._onmagicTypeChanged(param); 293 | break; 294 | case ecConfig.EVENT.DATA_VIEW_CHANGED : 295 | fromMyself && this._ondataViewChanged(param); 296 | break; 297 | case ecConfig.EVENT.TOOLTIP_HOVER : 298 | fromMyself && this._tooltipHover(param); 299 | break; 300 | case ecConfig.EVENT.RESTORE : 301 | this._onrestore(); 302 | break; 303 | case ecConfig.EVENT.REFRESH : 304 | fromMyself && this._onrefresh(param); 305 | break; 306 | // 鼠标同步 307 | case ecConfig.EVENT.TOOLTIP_IN_GRID : 308 | case ecConfig.EVENT.TOOLTIP_OUT_GRID : 309 | if (!fromMyself) { 310 | // 只处理来自外部的鼠标同步 311 | var grid = this.component.grid; 312 | if (grid) { 313 | this._zr.trigger( 314 | 'mousemove', 315 | { 316 | connectTrigger: true, 317 | zrenderX: grid.getX() + param.x * grid.getWidth(), 318 | zrenderY: grid.getY() + param.y * grid.getHeight() 319 | } 320 | ); 321 | } 322 | } 323 | else if (this._connected) { 324 | // 来自自己,并且存在多图联动,空间坐标映射修改参数分发 325 | var grid = this.component.grid; 326 | if (grid) { 327 | param.x = (param.event.zrenderX - grid.getX()) / grid.getWidth(); 328 | param.y = (param.event.zrenderY - grid.getY()) / grid.getHeight(); 329 | } 330 | } 331 | break; 332 | /* 333 | case ecConfig.EVENT.RESIZE : 334 | case ecConfig.EVENT.DATA_CHANGED : 335 | case ecConfig.EVENT.PIE_SELECTED : 336 | case ecConfig.EVENT.MAP_SELECTED : 337 | break; 338 | */ 339 | } 340 | 341 | // 多图联动,只做自己的一级事件分发,避免级联事件循环 342 | if (this._connected && fromMyself && this._curEventType === param.type) { 343 | for (var c in this._connected) { 344 | this._connected[c].connectedEventHandler(param); 345 | } 346 | // 分发完毕后复位 347 | this._curEventType = null; 348 | } 349 | 350 | if (!fromMyself || (!this._connected && fromMyself)) { // 处理了完联动事件复位 351 | this._curEventType = null; 352 | } 353 | }, 354 | 355 | /** 356 | * 点击事件,响应zrender事件,包装后分发到Echarts层 357 | */ 358 | _onclick: function (param) { 359 | callChartListMethodReverse(this, 'onclick', param); 360 | 361 | if (param.target) { 362 | var ecData = this._eventPackage(param.target); 363 | if (ecData && ecData.seriesIndex != null) { 364 | this._messageCenter.dispatch( 365 | ecConfig.EVENT.CLICK, 366 | param.event, 367 | ecData, 368 | this 369 | ); 370 | } 371 | } 372 | }, 373 | 374 | /** 375 | * 双击事件,响应zrender事件,包装后分发到Echarts层 376 | */ 377 | _ondblclick: function (param) { 378 | callChartListMethodReverse(this, 'ondblclick', param); 379 | 380 | if (param.target) { 381 | var ecData = this._eventPackage(param.target); 382 | if (ecData && ecData.seriesIndex != null) { 383 | this._messageCenter.dispatch( 384 | ecConfig.EVENT.DBLCLICK, 385 | param.event, 386 | ecData, 387 | this 388 | ); 389 | } 390 | } 391 | }, 392 | 393 | /** 394 | * 鼠标移入事件,响应zrender事件,包装后分发到Echarts层 395 | */ 396 | _onmouseover: function (param) { 397 | if (param.target) { 398 | var ecData = this._eventPackage(param.target); 399 | if (ecData && ecData.seriesIndex != null) { 400 | this._messageCenter.dispatch( 401 | ecConfig.EVENT.HOVER, 402 | param.event, 403 | ecData, 404 | this 405 | ); 406 | } 407 | } 408 | }, 409 | 410 | /** 411 | * 鼠标移出事件,响应zrender事件,包装后分发到Echarts层 412 | */ 413 | _onmouseout: function (param) { 414 | if (param.target) { 415 | var ecData = this._eventPackage(param.target); 416 | if (ecData && ecData.seriesIndex != null) { 417 | this._messageCenter.dispatch( 418 | ecConfig.EVENT.MOUSEOUT, 419 | param.event, 420 | ecData, 421 | this 422 | ); 423 | } 424 | } 425 | }, 426 | 427 | /** 428 | * dragstart回调,可计算特性实现 429 | */ 430 | _ondragstart: function (param) { 431 | // 复位用于图表间通信拖拽标识 432 | this._status = { 433 | dragIn: false, 434 | dragOut: false, 435 | needRefresh: false 436 | }; 437 | 438 | callChartListMethodReverse(this, 'ondragstart', param); 439 | }, 440 | 441 | /** 442 | * dragging回调,可计算特性实现 443 | */ 444 | _ondragenter: function (param) { 445 | callChartListMethodReverse(this, 'ondragenter', param); 446 | }, 447 | 448 | /** 449 | * dragstart回调,可计算特性实现 450 | */ 451 | _ondragover: function (param) { 452 | callChartListMethodReverse(this, 'ondragover', param); 453 | }, 454 | 455 | /** 456 | * dragstart回调,可计算特性实现 457 | */ 458 | _ondragleave: function (param) { 459 | callChartListMethodReverse(this, 'ondragleave', param); 460 | }, 461 | 462 | /** 463 | * dragstart回调,可计算特性实现 464 | */ 465 | _ondrop: function (param) { 466 | callChartListMethodReverse(this, 'ondrop', param, this._status); 467 | this._island.ondrop(param, this._status); 468 | }, 469 | 470 | /** 471 | * dragdone回调 ,可计算特性实现 472 | */ 473 | _ondragend: function (param) { 474 | callChartListMethodReverse(this, 'ondragend', param, this._status); 475 | 476 | this._timeline && this._timeline.ondragend(param, this._status); 477 | this._island.ondragend(param, this._status); 478 | 479 | // 发生过重计算 480 | if (this._status.needRefresh) { 481 | this._syncBackupData(this._option); 482 | 483 | var messageCenter = this._messageCenter; 484 | messageCenter.dispatch( 485 | ecConfig.EVENT.DATA_CHANGED, 486 | param.event, 487 | this._eventPackage(param.target), 488 | this 489 | ); 490 | messageCenter.dispatch(ecConfig.EVENT.REFRESH, null, null, this); 491 | } 492 | }, 493 | 494 | /** 495 | * 图例选择响应 496 | */ 497 | _onlegendSelected: function (param) { 498 | // 用于图表间通信 499 | this._status.needRefresh = false; 500 | callChartListMethodReverse(this, 'onlegendSelected', param, this._status); 501 | 502 | if (this._status.needRefresh) { 503 | this._messageCenter.dispatch(ecConfig.EVENT.REFRESH, null, null, this); 504 | } 505 | }, 506 | 507 | /** 508 | * 数据区域缩放响应 509 | */ 510 | _ondataZoom: function (param) { 511 | // 用于图表间通信 512 | this._status.needRefresh = false; 513 | callChartListMethodReverse(this, 'ondataZoom', param, this._status); 514 | 515 | if (this._status.needRefresh) { 516 | this._messageCenter.dispatch(ecConfig.EVENT.REFRESH, null, null, this); 517 | } 518 | }, 519 | 520 | /** 521 | * 值域漫游响应 522 | */ 523 | _ondataRange: function (param) { 524 | this._clearEffect(); 525 | // 用于图表间通信 526 | this._status.needRefresh = false; 527 | callChartListMethodReverse(this, 'ondataRange', param, this._status); 528 | 529 | // 没有相互影响,直接刷新即可 530 | if (this._status.needRefresh) { 531 | this._zr.refreshNextFrame(); 532 | } 533 | }, 534 | 535 | /** 536 | * 动态类型切换响应 537 | */ 538 | _onmagicTypeChanged: function () { 539 | this._clearEffect(); 540 | this._render(this._toolbox.getMagicOption()); 541 | }, 542 | 543 | /** 544 | * 数据视图修改响应 545 | */ 546 | _ondataViewChanged: function (param) { 547 | this._syncBackupData(param.option); 548 | this._messageCenter.dispatch( 549 | ecConfig.EVENT.DATA_CHANGED, 550 | null, 551 | param, 552 | this 553 | ); 554 | this._messageCenter.dispatch(ecConfig.EVENT.REFRESH, null, null, this); 555 | }, 556 | 557 | /** 558 | * tooltip与图表间通信 559 | */ 560 | _tooltipHover: function (param) { 561 | var tipShape = []; 562 | callChartListMethodReverse(this, 'ontooltipHover', param, tipShape); 563 | }, 564 | 565 | /** 566 | * 还原 567 | */ 568 | _onrestore: function () { 569 | this.restore(); 570 | }, 571 | 572 | /** 573 | * 刷新 574 | */ 575 | _onrefresh: function (param) { 576 | this._refreshInside = true; 577 | this.refresh(param); 578 | this._refreshInside = false; 579 | }, 580 | 581 | /** 582 | * 数据修改后的反向同步dataZoom持有的备份数据 583 | */ 584 | _syncBackupData: function (curOption) { 585 | this.component.dataZoom && this.component.dataZoom.syncBackupData(curOption); 586 | }, 587 | 588 | /** 589 | * 打包Echarts层的事件附件 590 | */ 591 | _eventPackage: function (target) { 592 | if (target) { 593 | var ecData = require('./util/ecData'); 594 | 595 | var seriesIndex = ecData.get(target, 'seriesIndex'); 596 | var dataIndex = ecData.get(target, 'dataIndex'); 597 | 598 | dataIndex = seriesIndex != -1 && this.component.dataZoom 599 | ? this.component.dataZoom.getRealDataIndex( 600 | seriesIndex, 601 | dataIndex 602 | ) 603 | : dataIndex; 604 | return { 605 | seriesIndex: seriesIndex, 606 | seriesName: (ecData.get(target, 'series') || {}).name, 607 | dataIndex: dataIndex, 608 | data: ecData.get(target, 'data'), 609 | name: ecData.get(target, 'name'), 610 | value: ecData.get(target, 'value'), 611 | special: ecData.get(target, 'special') 612 | }; 613 | } 614 | return; 615 | }, 616 | 617 | _noDataCheck: function(magicOption) { 618 | var series = magicOption.series; 619 | 620 | for (var i = 0, l = series.length; i < l; i++) { 621 | if (series[i].type == ecConfig.CHART_TYPE_MAP 622 | || (series[i].data && series[i].data.length > 0) 623 | || (series[i].markPoint && series[i].markPoint.data && series[i].markPoint.data.length > 0) 624 | || (series[i].markLine && series[i].markLine.data && series[i].markLine.data.length > 0) 625 | || (series[i].nodes && series[i].nodes.length > 0) 626 | || (series[i].links && series[i].links.length > 0) 627 | || (series[i].matrix && series[i].matrix.length > 0) 628 | || (series[i].eventList && series[i].eventList.length > 0) 629 | ) { 630 | return false; // 存在任意数据则为非空数据 631 | } 632 | } 633 | var loadOption = (this._option && this._option.noDataLoadingOption) 634 | || this._themeConfig.noDataLoadingOption 635 | || ecConfig.noDataLoadingOption 636 | || { 637 | text: (this._option && this._option.noDataText) 638 | || this._themeConfig.noDataText 639 | || ecConfig.noDataText, 640 | effect: (this._option && this._option.noDataEffect) 641 | || this._themeConfig.noDataEffect 642 | || ecConfig.noDataEffect 643 | }; 644 | // 空数据 645 | this.clear(); 646 | this.showLoading(loadOption); 647 | return true; 648 | }, 649 | 650 | /** 651 | * 图表渲染 652 | */ 653 | _render: function (magicOption) { 654 | this._mergeGlobalConifg(magicOption); 655 | 656 | if (this._noDataCheck(magicOption)) { 657 | return; 658 | } 659 | 660 | var bgColor = magicOption.backgroundColor; 661 | if (bgColor) { 662 | if (!_canvasSupported 663 | && bgColor.indexOf('rgba') != -1 664 | ) { 665 | // IE6~8对RGBA的处理,filter会带来其他颜色的影响 666 | var cList = bgColor.split(','); 667 | this.dom.style.filter = 'alpha(opacity=' + 668 | cList[3].substring(0, cList[3].lastIndexOf(')')) * 100 669 | + ')'; 670 | cList.length = 3; 671 | cList[0] = cList[0].replace('a', ''); 672 | this.dom.style.backgroundColor = cList.join(',') + ')'; 673 | } 674 | else { 675 | this.dom.style.backgroundColor = bgColor; 676 | } 677 | } 678 | 679 | this._zr.clearAnimation(); 680 | this._chartList = []; 681 | 682 | var chartLibrary = require('./chart'); 683 | var componentLibrary = require('./component'); 684 | 685 | if (magicOption.xAxis || magicOption.yAxis) { 686 | magicOption.grid = magicOption.grid || {}; 687 | magicOption.dataZoom = magicOption.dataZoom || {}; 688 | } 689 | 690 | var componentList = [ 691 | 'title', 'legend', 'tooltip', 'dataRange', 'roamController', 692 | 'grid', 'dataZoom', 'xAxis', 'yAxis', 'polar' 693 | ]; 694 | 695 | var ComponentClass; 696 | var componentType; 697 | var component; 698 | for (var i = 0, l = componentList.length; i < l; i++) { 699 | componentType = componentList[i]; 700 | component = this.component[componentType]; 701 | 702 | if (magicOption[componentType]) { 703 | if (component) { 704 | component.refresh && component.refresh(magicOption); 705 | } 706 | else { 707 | ComponentClass = componentLibrary.get( 708 | /^[xy]Axis$/.test(componentType) ? 'axis' : componentType 709 | ); 710 | component = new ComponentClass( 711 | this._themeConfig, this._messageCenter, this._zr, 712 | magicOption, this, componentType 713 | ); 714 | this.component[componentType] = component; 715 | } 716 | this._chartList.push(component); 717 | } 718 | else if (component) { 719 | component.dispose(); 720 | this.component[componentType] = null; 721 | delete this.component[componentType]; 722 | } 723 | } 724 | 725 | var ChartClass; 726 | var chartType; 727 | var chart; 728 | var chartMap = {}; // 记录已经初始化的图表 729 | for (var i = 0, l = magicOption.series.length; i < l; i++) { 730 | chartType = magicOption.series[i].type; 731 | if (!chartType) { 732 | console.error('series[' + i + '] chart type has not been defined.'); 733 | continue; 734 | } 735 | 736 | if (!chartMap[chartType]) { 737 | chartMap[chartType] = true; 738 | ChartClass = chartLibrary.get(chartType); 739 | if (ChartClass) { 740 | if (this.chart[chartType]) { 741 | chart = this.chart[chartType]; 742 | chart.refresh(magicOption); 743 | } 744 | else { 745 | chart = new ChartClass( 746 | this._themeConfig, this._messageCenter, this._zr, 747 | magicOption, this 748 | ); 749 | } 750 | this._chartList.push(chart); 751 | this.chart[chartType] = chart; 752 | } 753 | else { 754 | console.error(chartType + ' has not been required.'); 755 | } 756 | } 757 | } 758 | 759 | // 已有实例但新option不带这类图表的实例释放 760 | for (chartType in this.chart) { 761 | if (chartType != ecConfig.CHART_TYPE_ISLAND && !chartMap[chartType]) { 762 | this.chart[chartType].dispose(); 763 | this.chart[chartType] = null; 764 | delete this.chart[chartType]; 765 | } 766 | } 767 | 768 | this.component.grid && this.component.grid.refixAxisShape(this.component); 769 | 770 | this._island.refresh(magicOption); 771 | this._toolbox.refresh(magicOption); 772 | 773 | magicOption.animation && !magicOption.renderAsImage 774 | ? this._zr.refresh() 775 | : this._zr.render(); 776 | 777 | var imgId = 'IMG' + this.id; 778 | var img = document.getElementById(imgId); 779 | if (magicOption.renderAsImage && _canvasSupported) { 780 | // IE8- 不支持图片渲染形式 781 | if (img) { 782 | // 已经渲染过则更新显示 783 | img.src = this.getDataURL(magicOption.renderAsImage); 784 | } 785 | else { 786 | // 没有渲染过插入img dom 787 | img = this.getImage(magicOption.renderAsImage); 788 | img.id = imgId; 789 | img.style.position = 'absolute'; 790 | img.style.left = 0; 791 | img.style.top = 0; 792 | this.dom.firstChild.appendChild(img); 793 | } 794 | this.un(); 795 | this._zr.un(); 796 | this._disposeChartList(); 797 | this._zr.clear(); 798 | } 799 | else if (img) { 800 | // 删除可能存在的img 801 | img.parentNode.removeChild(img); 802 | } 803 | img = null; 804 | 805 | this._option = magicOption; 806 | }, 807 | 808 | /** 809 | * 还原 810 | */ 811 | restore: function () { 812 | this._clearEffect(); 813 | this._option = zrUtil.clone(this._optionRestore); 814 | this._disposeChartList(); 815 | this._island.clear(); 816 | this._toolbox.reset(this._option, true); 817 | this._render(this._option); 818 | }, 819 | 820 | /** 821 | * 刷新 822 | * @param {Object=} param,可选参数,用于附带option,内部同步用,外部不建议带入数据修改,无法同步 823 | */ 824 | refresh: function (param) { 825 | this._clearEffect(); 826 | param = param || {}; 827 | var magicOption = param.option; 828 | 829 | // 外部调用的refresh且有option带入 830 | if (!this._refreshInside && magicOption) { 831 | // 做简单的差异合并去同步内部持有的数据克隆,不建议带入数据 832 | // 开启数据区域缩放、拖拽重计算、数据视图可编辑模式情况下,当用户产生了数据变化后无法同步 833 | // 如有带入option存在数据变化,请重新setOption 834 | magicOption = this.getOption(); 835 | zrUtil.merge(magicOption, param.option, true); 836 | zrUtil.merge(this._optionRestore, param.option, true); 837 | this._toolbox.reset(magicOption); 838 | } 839 | 840 | this._island.refresh(magicOption); 841 | this._toolbox.refresh(magicOption); 842 | 843 | // 停止动画 844 | this._zr.clearAnimation(); 845 | // 先来后到,安顺序刷新各种图表,图表内部refresh优化检查magicOption,无需更新则不更新~ 846 | for (var i = 0, l = this._chartList.length; i < l; i++) { 847 | this._chartList[i].refresh && this._chartList[i].refresh(magicOption); 848 | } 849 | this.component.grid && this.component.grid.refixAxisShape(this.component); 850 | this._zr.refresh(); 851 | }, 852 | 853 | /** 854 | * 释放图表实例 855 | */ 856 | _disposeChartList: function () { 857 | this._clearEffect(); 858 | 859 | // 停止动画 860 | this._zr.clearAnimation(); 861 | 862 | var len = this._chartList.length; 863 | while (len--) { 864 | var chart = this._chartList[len]; 865 | 866 | if (chart) { 867 | var chartType = chart.type; 868 | this.chart[chartType] && delete this.chart[chartType]; 869 | this.component[chartType] && delete this.component[chartType]; 870 | chart.dispose && chart.dispose(); 871 | } 872 | } 873 | 874 | this._chartList = []; 875 | }, 876 | 877 | /** 878 | * 非图表全局属性merge~~ 879 | */ 880 | _mergeGlobalConifg: function (magicOption) { 881 | var mergeList = [ 882 | // 背景颜色 883 | 'backgroundColor', 884 | 885 | // 拖拽重计算相关 886 | 'calculable', 'calculableColor', 'calculableHolderColor', 887 | 888 | // 孤岛显示连接符 889 | 'nameConnector', 'valueConnector', 890 | 891 | // 动画相关 892 | 'animation', 'animationThreshold', 893 | 'animationDuration', 'animationDurationUpdate', 894 | 'animationEasing', 'addDataAnimation', 895 | 896 | // 默认标志图形类型列表 897 | 'symbolList', 898 | 899 | // 降低图表内元素拖拽敏感度,单位ms,不建议外部干预 900 | 'DRAG_ENABLE_TIME' 901 | ]; 902 | 903 | var len = mergeList.length; 904 | while (len--) { 905 | var mergeItem = mergeList[len]; 906 | if (magicOption[mergeItem] == null) { 907 | magicOption[mergeItem] = this._themeConfig[mergeItem] != null 908 | ? this._themeConfig[mergeItem] 909 | : ecConfig[mergeItem]; 910 | } 911 | } 912 | 913 | // 数值系列的颜色列表,不传则采用内置颜色,可配数组,借用zrender实例注入,会有冲突风险,先这样 914 | var themeColor = magicOption.color; 915 | if (!(themeColor && themeColor.length)) { 916 | themeColor = this._themeConfig.color || ecConfig.color; 917 | } 918 | 919 | this._zr.getColor = function (idx) { 920 | var zrColor = require('zrender/tool/color'); 921 | return zrColor.getColor(idx, themeColor); 922 | }; 923 | 924 | if (!_canvasSupported) { 925 | // 不支持Canvas的强制关闭动画 926 | magicOption.animation = false; 927 | magicOption.addDataAnimation = false; 928 | } 929 | }, 930 | 931 | /** 932 | * 万能接口,配置图表实例任何可配置选项,多次调用时option选项做merge处理 933 | * @param {Object} option 934 | * @param {boolean=} notMerge 多次调用时option选项是默认是合并(merge)的, 935 | * 如果不需求,可以通过notMerger参数为true阻止与上次option的合并 936 | */ 937 | setOption: function (option, notMerge) { 938 | if (!option.timeline) { 939 | return this._setOption(option, notMerge); 940 | } 941 | else { 942 | return this._setTimelineOption(option); 943 | } 944 | }, 945 | 946 | /** 947 | * 万能接口,配置图表实例任何可配置选项,多次调用时option选项做merge处理 948 | * @param {Object} option 949 | * @param {boolean=} notMerge 多次调用时option选项是默认是合并(merge)的, 950 | * 如果不需求,可以通过notMerger参数为true阻止与上次option的合并 951 | * @param {boolean=} 默认false。keepTimeLine 表示从timeline组件调用而来, 952 | * 表示当前行为是timeline的数据切换,保持timeline, 953 | * 反之销毁timeline。 详见Issue #1601 954 | */ 955 | _setOption: function (option, notMerge, keepTimeLine) { 956 | if (!notMerge && this._option) { 957 | this._option = zrUtil.merge( 958 | this.getOption(), 959 | zrUtil.clone(option), 960 | true 961 | ); 962 | } 963 | else { 964 | this._option = zrUtil.clone(option); 965 | !keepTimeLine && this._timeline && this._timeline.dispose(); 966 | } 967 | 968 | this._optionRestore = zrUtil.clone(this._option); 969 | 970 | if (!this._option.series || this._option.series.length === 0) { 971 | this._zr.clear(); 972 | return; 973 | } 974 | 975 | if (this.component.dataZoom // 存在dataZoom控件 976 | && (this._option.dataZoom // 并且新option也存在 977 | || (this._option.toolbox 978 | && this._option.toolbox.feature 979 | && this._option.toolbox.feature.dataZoom 980 | && this._option.toolbox.feature.dataZoom.show 981 | ) 982 | ) 983 | ) { 984 | // dataZoom同步数据 985 | this.component.dataZoom.syncOption(this._option); 986 | } 987 | this._toolbox.reset(this._option); 988 | this._render(this._option); 989 | return this; 990 | }, 991 | 992 | /** 993 | * 返回内部持有的当前显示option克隆 994 | */ 995 | getOption: function () { 996 | var magicOption = zrUtil.clone(this._option); 997 | 998 | var self = this; 999 | function restoreOption(prop) { 1000 | var restoreSource = self._optionRestore[prop]; 1001 | 1002 | if (restoreSource) { 1003 | if (restoreSource instanceof Array) { 1004 | var len = restoreSource.length; 1005 | while (len--) { 1006 | magicOption[prop][len].data = zrUtil.clone( 1007 | restoreSource[len].data 1008 | ); 1009 | } 1010 | } 1011 | else { 1012 | magicOption[prop].data = zrUtil.clone(restoreSource.data); 1013 | } 1014 | } 1015 | } 1016 | 1017 | // 横轴数据还原 1018 | restoreOption('xAxis'); 1019 | 1020 | // 纵轴数据还原 1021 | restoreOption('yAxis'); 1022 | 1023 | // 系列数据还原 1024 | restoreOption('series'); 1025 | 1026 | return magicOption; 1027 | }, 1028 | 1029 | /** 1030 | * 数据设置快捷接口 1031 | * @param {Array} series 1032 | * @param {boolean=} notMerge 多次调用时option选项是默认是合并(merge)的, 1033 | * 如果不需求,可以通过notMerger参数为true阻止与上次option的合并。 1034 | */ 1035 | setSeries: function (series, notMerge) { 1036 | if (!notMerge) { 1037 | this.setOption({series: series}); 1038 | } 1039 | else { 1040 | this._option.series = series; 1041 | this.setOption(this._option, notMerge); 1042 | } 1043 | return this; 1044 | }, 1045 | 1046 | /** 1047 | * 返回内部持有的当前显示series克隆 1048 | */ 1049 | getSeries: function () { 1050 | return this.getOption().series; 1051 | }, 1052 | 1053 | /** 1054 | * timelineOption接口,配置图表实例任何可配置选项 1055 | * @param {Object} option 1056 | */ 1057 | _setTimelineOption: function(option) { 1058 | this._timeline && this._timeline.dispose(); 1059 | var Timeline = require('./component/timeline'); 1060 | var timeline = new Timeline( 1061 | this._themeConfig, this._messageCenter, this._zr, option, this 1062 | ); 1063 | this._timeline = timeline; 1064 | this.component.timeline = this._timeline; 1065 | 1066 | return this; 1067 | }, 1068 | 1069 | /** 1070 | * 动态数据添加 1071 | * 形参为单组数据参数,多组时为数据,内容同[seriesIdx, data, isShift, additionData] 1072 | * @param {number} seriesIdx 系列索引 1073 | * @param {number | Object} data 增加数据 1074 | * @param {boolean=} isHead 是否队头加入,默认,不指定或false时为队尾插入 1075 | * @param {boolean=} dataGrow 是否增长数据队列长度,默认,不指定或false时移出目标数组对位数据 1076 | * @param {string=} additionData 是否增加类目轴(饼图为图例)数据,附加操作同isHead和dataGrow 1077 | */ 1078 | addData: function (seriesIdx, data, isHead, dataGrow, additionData) { 1079 | var params = seriesIdx instanceof Array 1080 | ? seriesIdx 1081 | : [[seriesIdx, data, isHead, dataGrow, additionData]]; 1082 | 1083 | //this._optionRestore 和 magicOption 都要同步 1084 | var magicOption = this.getOption(); 1085 | var optionRestore = this._optionRestore; 1086 | var self = this; 1087 | for (var i = 0, l = params.length; i < l; i++) { 1088 | seriesIdx = params[i][0]; 1089 | data = params[i][1]; 1090 | isHead = params[i][2]; 1091 | dataGrow = params[i][3]; 1092 | additionData = params[i][4]; 1093 | 1094 | var seriesItem = optionRestore.series[seriesIdx]; 1095 | var inMethod = isHead ? 'unshift' : 'push'; 1096 | var outMethod = isHead ? 'pop' : 'shift'; 1097 | if (seriesItem) { 1098 | var seriesItemData = seriesItem.data; 1099 | var mSeriesItemData = magicOption.series[seriesIdx].data; 1100 | 1101 | seriesItemData[inMethod](data); 1102 | mSeriesItemData[inMethod](data); 1103 | if (!dataGrow) { 1104 | seriesItemData[outMethod](); 1105 | data = mSeriesItemData[outMethod](); 1106 | } 1107 | 1108 | if (additionData != null) { 1109 | var legend; 1110 | var legendData; 1111 | 1112 | if (seriesItem.type === ecConfig.CHART_TYPE_PIE 1113 | && (legend = optionRestore.legend) 1114 | && (legendData = legend.data) 1115 | ) { 1116 | var mLegendData = magicOption.legend.data; 1117 | legendData[inMethod](additionData); 1118 | mLegendData[inMethod](additionData); 1119 | 1120 | if (!dataGrow) { 1121 | var legendDataIdx = zrUtil.indexOf(legendData, data.name); 1122 | legendDataIdx != -1 && legendData.splice(legendDataIdx, 1); 1123 | 1124 | legendDataIdx = zrUtil.indexOf(mLegendData, data.name); 1125 | legendDataIdx != -1 && mLegendData.splice(legendDataIdx, 1); 1126 | } 1127 | } 1128 | else if (optionRestore.xAxis != null && optionRestore.yAxis != null) { 1129 | // x轴类目 1130 | var axisData; 1131 | var mAxisData; 1132 | var axisIdx = seriesItem.xAxisIndex || 0; 1133 | 1134 | if (optionRestore.xAxis[axisIdx].type == null 1135 | || optionRestore.xAxis[axisIdx].type === 'category' 1136 | ) { 1137 | axisData = optionRestore.xAxis[axisIdx].data; 1138 | mAxisData = magicOption.xAxis[axisIdx].data; 1139 | 1140 | axisData[inMethod](additionData); 1141 | mAxisData[inMethod](additionData); 1142 | if (!dataGrow) { 1143 | axisData[outMethod](); 1144 | mAxisData[outMethod](); 1145 | } 1146 | } 1147 | 1148 | // y轴类目 1149 | axisIdx = seriesItem.yAxisIndex || 0; 1150 | if (optionRestore.yAxis[axisIdx].type === 'category') { 1151 | axisData = optionRestore.yAxis[axisIdx].data; 1152 | mAxisData = magicOption.yAxis[axisIdx].data; 1153 | 1154 | axisData[inMethod](additionData); 1155 | mAxisData[inMethod](additionData); 1156 | if (!dataGrow) { 1157 | axisData[outMethod](); 1158 | mAxisData[outMethod](); 1159 | } 1160 | } 1161 | } 1162 | } 1163 | 1164 | // 同步图表内状态,动画需要 1165 | this._option.series[seriesIdx].data = magicOption.series[seriesIdx].data; 1166 | } 1167 | } 1168 | 1169 | this._zr.clearAnimation(); 1170 | var chartList = this._chartList; 1171 | var chartAnimationCount = 0; 1172 | var chartAnimationDone = function () { 1173 | chartAnimationCount--; 1174 | if (chartAnimationCount === 0) { 1175 | animationDone(); 1176 | } 1177 | }; 1178 | for (var i = 0, l = chartList.length; i < l; i++) { 1179 | if (magicOption.addDataAnimation && chartList[i].addDataAnimation) { 1180 | chartAnimationCount++; 1181 | chartList[i].addDataAnimation(params, chartAnimationDone); 1182 | } 1183 | } 1184 | 1185 | // dataZoom同步数据 1186 | this.component.dataZoom && this.component.dataZoom.syncOption(magicOption); 1187 | 1188 | this._option = magicOption; 1189 | function animationDone() { 1190 | if (!self._zr) { 1191 | return; // 已经被释放 1192 | } 1193 | self._zr.clearAnimation(); 1194 | for (var i = 0, l = chartList.length; i < l; i++) { 1195 | // 有addData动画就去掉过渡动画 1196 | chartList[i].motionlessOnce = 1197 | magicOption.addDataAnimation && chartList[i].addDataAnimation; 1198 | } 1199 | self._messageCenter.dispatch( 1200 | ecConfig.EVENT.REFRESH, 1201 | null, 1202 | {option: magicOption}, 1203 | self 1204 | ); 1205 | } 1206 | 1207 | if (!magicOption.addDataAnimation) { 1208 | setTimeout(animationDone, 0); 1209 | } 1210 | return this; 1211 | }, 1212 | 1213 | /** 1214 | * 动态[标注 | 标线]添加 1215 | * @param {number} seriesIdx 系列索引 1216 | * @param {Object} markData [标注 | 标线]对象,支持多个 1217 | */ 1218 | addMarkPoint: function (seriesIdx, markData) { 1219 | return this._addMark(seriesIdx, markData, 'markPoint'); 1220 | }, 1221 | 1222 | addMarkLine: function (seriesIdx, markData) { 1223 | return this._addMark(seriesIdx, markData, 'markLine'); 1224 | }, 1225 | 1226 | _addMark: function (seriesIdx, markData, markType) { 1227 | var series = this._option.series; 1228 | var seriesItem; 1229 | 1230 | if (series && (seriesItem = series[seriesIdx])) { 1231 | var seriesR = this._optionRestore.series; 1232 | var seriesRItem = seriesR[seriesIdx]; 1233 | var markOpt = seriesItem[markType]; 1234 | var markOptR = seriesRItem[markType]; 1235 | 1236 | markOpt = seriesItem[markType] = markOpt || {data: []}; 1237 | markOptR = seriesRItem[markType] = markOptR || {data: []}; 1238 | 1239 | for (var key in markData) { 1240 | if (key === 'data') { 1241 | // 数据concat 1242 | markOpt.data = markOpt.data.concat(markData.data); 1243 | markOptR.data = markOptR.data.concat(markData.data); 1244 | } 1245 | else if (typeof markData[key] != 'object' || markOpt[key] == null) { 1246 | // 简单类型或新值直接赋值 1247 | markOpt[key] = markOptR[key] = markData[key]; 1248 | } 1249 | else { 1250 | // 非数据的复杂对象merge 1251 | zrUtil.merge(markOpt[key], markData[key], true); 1252 | zrUtil.merge(markOptR[key], markData[key], true); 1253 | } 1254 | } 1255 | 1256 | var chart = this.chart[seriesItem.type]; 1257 | chart && chart.addMark(seriesIdx, markData, markType); 1258 | } 1259 | 1260 | return this; 1261 | }, 1262 | 1263 | /** 1264 | * 动态[标注 | 标线]删除 1265 | * @param {number} seriesIdx 系列索引 1266 | * @param {string} markName [标注 | 标线]名称 1267 | */ 1268 | delMarkPoint: function (seriesIdx, markName) { 1269 | return this._delMark(seriesIdx, markName, 'markPoint'); 1270 | }, 1271 | 1272 | delMarkLine: function (seriesIdx, markName) { 1273 | return this._delMark(seriesIdx, markName, 'markLine'); 1274 | }, 1275 | 1276 | _delMark: function (seriesIdx, markName, markType) { 1277 | var series = this._option.series; 1278 | var seriesItem; 1279 | var mark; 1280 | var dataArray; 1281 | 1282 | if (!( 1283 | series 1284 | && (seriesItem = series[seriesIdx]) 1285 | && (mark = seriesItem[markType]) 1286 | && (dataArray = mark.data) 1287 | ) 1288 | ) { 1289 | return this; 1290 | } 1291 | 1292 | markName = markName.split(' > '); 1293 | var targetIndex = -1; 1294 | 1295 | for (var i = 0, l = dataArray.length; i < l; i++) { 1296 | var dataItem = dataArray[i]; 1297 | if (dataItem instanceof Array) { 1298 | if (dataItem[0].name === markName[0] 1299 | && dataItem[1].name === markName[1] 1300 | ) { 1301 | targetIndex = i; 1302 | break; 1303 | } 1304 | } 1305 | else if (dataItem.name === markName[0]) { 1306 | targetIndex = i; 1307 | break; 1308 | } 1309 | } 1310 | 1311 | if (targetIndex > -1) { 1312 | dataArray.splice(targetIndex, 1); 1313 | this._optionRestore.series[seriesIdx][markType].data.splice(targetIndex, 1); 1314 | 1315 | var chart = this.chart[seriesItem.type]; 1316 | chart && chart.delMark(seriesIdx, markName.join(' > '), markType); 1317 | } 1318 | 1319 | return this; 1320 | }, 1321 | 1322 | /** 1323 | * 获取当前dom 1324 | */ 1325 | getDom: function () { 1326 | return this.dom; 1327 | }, 1328 | 1329 | /** 1330 | * 获取当前zrender实例,可用于添加额为的shape和深度控制 1331 | */ 1332 | getZrender: function () { 1333 | return this._zr; 1334 | }, 1335 | 1336 | /** 1337 | * 获取Base64图片dataURL 1338 | * @param {string} imgType 图片类型,支持png|jpeg,默认为png 1339 | * @return imgDataURL 1340 | */ 1341 | getDataURL: function (imgType) { 1342 | if (!_canvasSupported) { 1343 | return ''; 1344 | } 1345 | 1346 | if (this._chartList.length === 0) { 1347 | // 渲染为图片 1348 | var imgId = 'IMG' + this.id; 1349 | var img = document.getElementById(imgId); 1350 | if (img) { 1351 | return img.src; 1352 | } 1353 | } 1354 | 1355 | // 清除可能存在的tooltip元素 1356 | var tooltip = this.component.tooltip; 1357 | tooltip && tooltip.hideTip(); 1358 | 1359 | switch (imgType) { 1360 | case 'jpeg': 1361 | break; 1362 | default: 1363 | imgType = 'png'; 1364 | } 1365 | 1366 | var bgColor = this._option.backgroundColor; 1367 | if (bgColor && bgColor.replace(' ','') === 'rgba(0,0,0,0)') { 1368 | bgColor = '#fff'; 1369 | } 1370 | 1371 | return this._zr.toDataURL('image/' + imgType, bgColor); 1372 | }, 1373 | 1374 | /** 1375 | * 获取img 1376 | * @param {string} imgType 图片类型,支持png|jpeg,默认为png 1377 | * @return img dom 1378 | */ 1379 | getImage: function (imgType) { 1380 | var title = this._optionRestore.title; 1381 | var imgDom = document.createElement('img'); 1382 | imgDom.src = this.getDataURL(imgType); 1383 | imgDom.title = (title && title.text) || 'ECharts'; 1384 | return imgDom; 1385 | }, 1386 | 1387 | /** 1388 | * 获取多图联动的Base64图片dataURL 1389 | * @param {string} imgType 图片类型,支持png|jpeg,默认为png 1390 | * @return imgDataURL 1391 | */ 1392 | getConnectedDataURL: function (imgType) { 1393 | if (!this.isConnected()) { 1394 | return this.getDataURL(imgType); 1395 | } 1396 | 1397 | var tempDom = this.dom; 1398 | var imgList = { 1399 | 'self': { 1400 | img: this.getDataURL(imgType), 1401 | left: tempDom.offsetLeft, 1402 | top: tempDom.offsetTop, 1403 | right: tempDom.offsetLeft + tempDom.offsetWidth, 1404 | bottom: tempDom.offsetTop + tempDom.offsetHeight 1405 | } 1406 | }; 1407 | 1408 | var minLeft = imgList.self.left; 1409 | var minTop = imgList.self.top; 1410 | var maxRight = imgList.self.right; 1411 | var maxBottom = imgList.self.bottom; 1412 | 1413 | for (var c in this._connected) { 1414 | tempDom = this._connected[c].getDom(); 1415 | imgList[c] = { 1416 | img: this._connected[c].getDataURL(imgType), 1417 | left: tempDom.offsetLeft, 1418 | top: tempDom.offsetTop, 1419 | right: tempDom.offsetLeft + tempDom.offsetWidth, 1420 | bottom: tempDom.offsetTop + tempDom.offsetHeight 1421 | }; 1422 | 1423 | minLeft = Math.min(minLeft, imgList[c].left); 1424 | minTop = Math.min(minTop, imgList[c].top); 1425 | maxRight = Math.max(maxRight, imgList[c].right); 1426 | maxBottom = Math.max(maxBottom, imgList[c].bottom); 1427 | } 1428 | 1429 | var zrDom = document.createElement('div'); 1430 | zrDom.style.position = 'absolute'; 1431 | zrDom.style.left = '-4000px'; 1432 | zrDom.style.width = (maxRight - minLeft) + 'px'; 1433 | zrDom.style.height = (maxBottom - minTop) + 'px'; 1434 | document.body.appendChild(zrDom); 1435 | 1436 | var zrImg = require('zrender').init(zrDom); 1437 | 1438 | var ImageShape = require('zrender/shape/Image'); 1439 | for (var c in imgList) { 1440 | zrImg.addShape(new ImageShape({ 1441 | style: { 1442 | x: imgList[c].left - minLeft, 1443 | y: imgList[c].top - minTop, 1444 | image: imgList[c].img 1445 | } 1446 | })); 1447 | } 1448 | 1449 | zrImg.render(); 1450 | var bgColor = this._option.backgroundColor; 1451 | if (bgColor && bgColor.replace(/ /g, '') === 'rgba(0,0,0,0)') { 1452 | bgColor = '#fff'; 1453 | } 1454 | 1455 | var image = zrImg.toDataURL('image/png', bgColor); 1456 | 1457 | setTimeout(function () { 1458 | zrImg.dispose(); 1459 | zrDom.parentNode.removeChild(zrDom); 1460 | zrDom = null; 1461 | }, 100); 1462 | 1463 | return image; 1464 | }, 1465 | 1466 | /** 1467 | * 获取多图联动的img 1468 | * @param {string} imgType 图片类型,支持png|jpeg,默认为png 1469 | * @return img dom 1470 | */ 1471 | getConnectedImage: function (imgType) { 1472 | var title = this._optionRestore.title; 1473 | var imgDom = document.createElement('img'); 1474 | imgDom.src = this.getConnectedDataURL(imgType); 1475 | imgDom.title = (title && title.text) || 'ECharts'; 1476 | return imgDom; 1477 | }, 1478 | 1479 | /** 1480 | * 外部接口绑定事件 1481 | * @param {Object} eventName 事件名称 1482 | * @param {Object} eventListener 事件响应函数 1483 | */ 1484 | on: function (eventName, eventListener) { 1485 | this._messageCenterOutSide.bind(eventName, eventListener, this); 1486 | return this; 1487 | }, 1488 | 1489 | /** 1490 | * 外部接口解除事件绑定 1491 | * @param {Object} eventName 事件名称 1492 | * @param {Object} eventListener 事件响应函数 1493 | */ 1494 | un: function (eventName, eventListener) { 1495 | this._messageCenterOutSide.unbind(eventName, eventListener); 1496 | return this; 1497 | }, 1498 | 1499 | /** 1500 | * 多图联动 1501 | * @param connectTarget{ECharts | Array } connectTarget 联动目标 1502 | */ 1503 | connect: function (connectTarget) { 1504 | if (!connectTarget) { 1505 | return this; 1506 | } 1507 | 1508 | if (!this._connected) { 1509 | this._connected = {}; 1510 | } 1511 | 1512 | if (connectTarget instanceof Array) { 1513 | for (var i = 0, l = connectTarget.length; i < l; i++) { 1514 | this._connected[connectTarget[i].id] = connectTarget[i]; 1515 | } 1516 | } 1517 | else { 1518 | this._connected[connectTarget.id] = connectTarget; 1519 | } 1520 | 1521 | return this; 1522 | }, 1523 | 1524 | /** 1525 | * 解除多图联动 1526 | * @param connectTarget{ECharts | Array } connectTarget 解除联动目标 1527 | */ 1528 | disConnect: function (connectTarget) { 1529 | if (!connectTarget || !this._connected) { 1530 | return this; 1531 | } 1532 | 1533 | if (connectTarget instanceof Array) { 1534 | for (var i = 0, l = connectTarget.length; i < l; i++) { 1535 | delete this._connected[connectTarget[i].id]; 1536 | } 1537 | } 1538 | else { 1539 | delete this._connected[connectTarget.id]; 1540 | } 1541 | 1542 | for (var k in this._connected) { 1543 | return k, this; // 非空 1544 | } 1545 | 1546 | // 空,转为标志位 1547 | this._connected = false; 1548 | return this; 1549 | }, 1550 | 1551 | /** 1552 | * 联动事件响应 1553 | */ 1554 | connectedEventHandler: function (param) { 1555 | if (param.__echartsId != this.id) { 1556 | // 来自其他联动图表的事件 1557 | this._onevent(param); 1558 | } 1559 | }, 1560 | 1561 | /** 1562 | * 是否存在多图联动 1563 | */ 1564 | isConnected: function () { 1565 | return !!this._connected; 1566 | }, 1567 | 1568 | /** 1569 | * 显示loading过渡 1570 | * @param {Object} loadingOption 1571 | */ 1572 | showLoading: function (loadingOption) { 1573 | var effectList = { 1574 | bar: require('zrender/loadingEffect/Bar'), 1575 | bubble: require('zrender/loadingEffect/Bubble'), 1576 | dynamicLine: require('zrender/loadingEffect/DynamicLine'), 1577 | ring: require('zrender/loadingEffect/Ring'), 1578 | spin: require('zrender/loadingEffect/Spin'), 1579 | whirling: require('zrender/loadingEffect/Whirling') 1580 | }; 1581 | this._toolbox.hideDataView(); 1582 | 1583 | loadingOption = loadingOption || {}; 1584 | 1585 | var textStyle = loadingOption.textStyle || {}; 1586 | loadingOption.textStyle = textStyle; 1587 | 1588 | var finalTextStyle = zrUtil.merge( 1589 | zrUtil.merge( 1590 | zrUtil.clone(textStyle), 1591 | this._themeConfig.textStyle 1592 | ), 1593 | ecConfig.textStyle 1594 | ); 1595 | 1596 | textStyle.textFont = finalTextStyle.fontStyle + ' ' 1597 | + finalTextStyle.fontWeight + ' ' 1598 | + finalTextStyle.fontSize + 'px ' 1599 | + finalTextStyle.fontFamily; 1600 | 1601 | textStyle.text = loadingOption.text 1602 | || (this._option && this._option.loadingText) 1603 | || this._themeConfig.loadingText 1604 | || ecConfig.loadingText; 1605 | 1606 | if (loadingOption.x != null) { 1607 | textStyle.x = loadingOption.x; 1608 | } 1609 | if (loadingOption.y != null) { 1610 | textStyle.y = loadingOption.y; 1611 | } 1612 | 1613 | loadingOption.effectOption = loadingOption.effectOption || {}; 1614 | loadingOption.effectOption.textStyle = textStyle; 1615 | 1616 | var Effect = loadingOption.effect; 1617 | if (typeof Effect === 'string' || Effect == null) { 1618 | Effect = effectList[ 1619 | loadingOption.effect 1620 | || (this._option && this._option.loadingEffect) 1621 | || this._themeConfig.loadingEffect 1622 | || ecConfig.loadingEffect 1623 | ] 1624 | || effectList.spin; 1625 | } 1626 | this._zr.showLoading(new Effect(loadingOption.effectOption)); 1627 | return this; 1628 | }, 1629 | 1630 | /** 1631 | * 隐藏loading过渡 1632 | */ 1633 | hideLoading: function () { 1634 | this._zr.hideLoading(); 1635 | return this; 1636 | }, 1637 | 1638 | /** 1639 | * 主题设置 1640 | */ 1641 | setTheme: function (theme) { 1642 | if (theme) { 1643 | if (typeof theme === 'string') { 1644 | // 默认主题 1645 | switch (theme) { 1646 | case 'macarons': 1647 | theme = require('./theme/macarons'); 1648 | break; 1649 | case 'infographic': 1650 | theme = require('./theme/infographic'); 1651 | break; 1652 | default: 1653 | theme = {}; // require('./theme/default'); 1654 | } 1655 | } 1656 | else { 1657 | theme = theme || {}; 1658 | } 1659 | 1660 | // // 复位默认配置 1661 | // // this._themeConfig会被别的对象引用持有 1662 | // // 所以不能改成this._themeConfig = {}; 1663 | // for (var key in this._themeConfig) { 1664 | // delete this._themeConfig[key]; 1665 | // } 1666 | // for (var key in ecConfig) { 1667 | // this._themeConfig[key] = zrUtil.clone(ecConfig[key]); 1668 | // } 1669 | 1670 | // // 颜色数组随theme,不merge 1671 | // theme.color && (this._themeConfig.color = []); 1672 | 1673 | // // 默认标志图形类型列表,不merge 1674 | // theme.symbolList && (this._themeConfig.symbolList = []); 1675 | 1676 | // // 应用新主题 1677 | // zrUtil.merge(this._themeConfig, zrUtil.clone(theme), true); 1678 | this._themeConfig = theme; 1679 | } 1680 | 1681 | if (!_canvasSupported) { // IE8- 1682 | var textStyle = this._themeConfig.textStyle; 1683 | textStyle && textStyle.fontFamily && textStyle.fontFamily2 1684 | && (textStyle.fontFamily = textStyle.fontFamily2); 1685 | 1686 | textStyle = ecConfig.textStyle; 1687 | textStyle.fontFamily = textStyle.fontFamily2; 1688 | } 1689 | 1690 | this._timeline && this._timeline.setTheme(true); 1691 | this._optionRestore && this.restore(); 1692 | }, 1693 | 1694 | /** 1695 | * 视图区域大小变化更新,不默认绑定,供使用方按需调用 1696 | */ 1697 | resize: function () { 1698 | var self = this; 1699 | return function(){ 1700 | self._clearEffect(); 1701 | self._zr.resize(); 1702 | if (self._option && self._option.renderAsImage && _canvasSupported) { 1703 | // 渲染为图片重走render模式 1704 | self._render(self._option); 1705 | return self; 1706 | } 1707 | // 停止动画 1708 | self._zr.clearAnimation(); 1709 | self._island.resize(); 1710 | self._toolbox.resize(); 1711 | self._timeline && self._timeline.resize(); 1712 | // 先来后到,不能仅刷新自己,也不能在上一个循环中刷新,如坐标系数据改变会影响其他图表的大小 1713 | // 所以安顺序刷新各种图表,图表内部refresh优化无需更新则不更新~ 1714 | for (var i = 0, l = self._chartList.length; i < l; i++) { 1715 | self._chartList[i].resize && self._chartList[i].resize(); 1716 | } 1717 | self.component.grid && self.component.grid.refixAxisShape(self.component); 1718 | self._zr.refresh(); 1719 | self._messageCenter.dispatch(ecConfig.EVENT.RESIZE, null, null, self); 1720 | return self; 1721 | }; 1722 | }, 1723 | 1724 | _clearEffect: function() { 1725 | this._zr.modLayer(ecConfig.EFFECT_ZLEVEL, { motionBlur: false }); 1726 | this._zr.painter.clearLayer(ecConfig.EFFECT_ZLEVEL); 1727 | }, 1728 | 1729 | /** 1730 | * 清除已渲染内容 ,clear后echarts实例可用 1731 | */ 1732 | clear: function () { 1733 | this._disposeChartList(); 1734 | this._zr.clear(); 1735 | this._option = {}; 1736 | this._optionRestore = {}; 1737 | this.dom.style.backgroundColor = null; 1738 | return this; 1739 | }, 1740 | 1741 | /** 1742 | * 释放,dispose后echarts实例不可用 1743 | */ 1744 | dispose: function () { 1745 | var key = this.dom.getAttribute(DOM_ATTRIBUTE_KEY); 1746 | key && delete _instances[key]; 1747 | 1748 | this._island.dispose(); 1749 | this._toolbox.dispose(); 1750 | this._timeline && this._timeline.dispose(); 1751 | this._messageCenter.unbind(); 1752 | this.clear(); 1753 | this._zr.dispose(); 1754 | this._zr = null; 1755 | } 1756 | }; 1757 | 1758 | return self; 1759 | }); -------------------------------------------------------------------------------- /src/main/resources/templates/dumpIndex.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 28 | 29 | 30 | 47 | 48 |
49 |
50 |
51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 |
HostnameDump DateAction
ViewDel
66 |
67 |
68 |
69 | 70 | -------------------------------------------------------------------------------- /src/main/resources/templates/showDump.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 17 | 18 | 19 | 36 | 37 |
38 | 39 |
40 |
41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 |
Hostname
Date
53 |
54 |
55 | 56 |
57 |
58 | 61 |
62 |
63 | 64 |
65 |
66 |
67 |
68 |

RUNNABLE

69 |
70 |
71 |

72 |
73 |
74 |
75 |
76 |
77 |
78 |

WAITING

79 |
80 |
81 |

82 |
83 |
84 |
85 |
86 |
87 |
88 |

TIMED_WAITING

89 |
90 |
91 |

92 |
93 |
94 |
95 |
96 |
97 |
98 |

BLOCKED

99 |
100 |
101 |

102 |
103 |
104 |
105 |
106 | 107 |
108 |
109 | 112 |
113 |
114 | 115 |
116 |
117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 132 | 143 | 154 | 155 | 156 |
Lock IDLock Owned Thread ID(s)Lock Waiting Thread ID(s)
128 |

129 |

130 |

131 |
133 |

134 |

135 | 136 | RUNNABLE 137 | WAITING 138 | TIMED_WAITING 139 | BLOCKED 140 |
141 |

142 |
144 |

145 |

146 | 147 | RUNNABLE 148 | WAITING 149 | TIMED_WAITING 150 | BLOCKED 151 |
152 |

153 |
157 |
158 |
159 | 160 |
161 |
162 | 165 |
166 |
167 | 168 |
169 |
170 | 176 |
177 |
178 |
179 |

180 |

181 |                 	
182 |
183 |
184 |
185 |

186 |

187 |                 	
188 |
189 |
190 |
191 |

192 |

193 |                 	
194 |
195 |
196 |
197 |

198 |

199 |                 	
200 |
201 |
202 |
203 |
204 |
205 | 206 | -------------------------------------------------------------------------------- /src/main/resources/templates/uploadComplete.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 25 | 26 |
27 | 28 |
29 |
30 |
31 | 업로드 완료 되었습니다. 32 |
33 | 바로가기 34 |
35 |
36 | 37 |
38 | 39 | 40 | -------------------------------------------------------------------------------- /src/main/resources/templates/uploadForm.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 25 | 26 |
27 | 28 |
29 |
30 |
31 |
32 |
33 | 34 |
35 | 36 |
37 |
38 |
39 | 40 |
41 | 42 |
43 |
44 |
45 |
46 | 47 | 48 |
49 |
50 |
51 |
52 |
53 |
54 | 55 |
56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/test/java/com/kakao/infra/JavaThreadAnalyzerApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.kakao.infra; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.WebIntegrationTest; 6 | import org.springframework.test.context.web.WebAppConfiguration; 7 | import org.springframework.boot.test.SpringApplicationConfiguration; 8 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 9 | 10 | @RunWith(SpringJUnit4ClassRunner.class) 11 | @SpringApplicationConfiguration(classes = JavaThreadDumpAnalyzerApplication.class) 12 | @WebIntegrationTest({"config.baseurl=http://localhost:9000", 13 | "spring.datasource.url=jdbc:h2:mem", 14 | "spring.datasource.driverClassName=org.h2.Driver", 15 | "spring.datasource.username=sa", 16 | "spring.datasource.password=", 17 | "server.port=9000" 18 | }) 19 | public class JavaThreadAnalyzerApplicationTests { 20 | 21 | @Test 22 | public void contextLoads() { 23 | } 24 | 25 | } 26 | --------------------------------------------------------------------------------