├── .editorconfig ├── .github └── workflows │ └── codeql.yml ├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── doc ├── config-sample-file.yaml ├── config-sample-helloworld-udp.yaml ├── config-sample-helloworld.yaml ├── helloworld-loop.jar ├── helloworldloop_overview.png └── logstash.conf ├── notes.txt ├── pom.xml └── src ├── main ├── java │ └── de │ │ └── marcelsauer │ │ └── profiler │ │ ├── agent │ │ └── Agent.java │ │ ├── config │ │ ├── Classes.java │ │ ├── Config.java │ │ ├── FileUtils.java │ │ ├── Processor.java │ │ └── Recorder.java │ │ ├── instrumenter │ │ ├── DefaultInstrumentationCallback.java │ │ └── Instrumenter.java │ │ ├── processor │ │ ├── AbstractAsyncStackProcessor.java │ │ ├── RecordingEvent.java │ │ ├── Stack.java │ │ ├── StackProcessor.java │ │ ├── StackProcessorFactory.java │ │ ├── file │ │ │ └── AsyncFileWritingStackProcessor.java │ │ ├── inmemory │ │ │ ├── InMemoryCountingCollector.java │ │ │ ├── InMemoryStackProcessor.java │ │ │ └── server │ │ │ │ ├── MapUtil.java │ │ │ │ └── Server.java │ │ ├── noop │ │ │ └── AsyncNoopStackProcessor.java │ │ └── udp │ │ │ └── AsyncUdpStackProcessor.java │ │ ├── recorder │ │ ├── Recorder.java │ │ └── Statistics.java │ │ ├── transformer │ │ ├── Transformer.java │ │ └── filter │ │ │ ├── CombinedFilter.java │ │ │ ├── Filter.java │ │ │ └── RegexFilter.java │ │ └── util │ │ └── Util.java └── resources │ ├── META-INF │ ├── MANIFEST.MF │ └── config.yaml │ └── log4j.properties └── test ├── java ├── de │ └── marcelsauer │ │ └── profiler │ │ ├── config │ │ └── ConfigCreatorTest.java │ │ ├── processor │ │ └── RecordingEventTest.java │ │ ├── recorder │ │ └── RecorderTest.java │ │ └── transformer │ │ ├── TransformerTest.java │ │ └── filter │ │ ├── CombinedFilterTest.java │ │ └── RegexFilterTest.java └── integration │ ├── AbtractAsyncIntegrationTest.java │ ├── AfterVmStartupAgentLoader.java │ ├── AsyncFileIntegrationTest.java │ ├── AsyncUdpIntegrationTest.java │ ├── CountingInMemoryStackProcessor.java │ ├── InMemoryCountingIntegrationTest.java │ ├── WaitOrTimeout.java │ ├── package1 │ ├── A.java │ ├── AbstractClass.java │ ├── B.java │ ├── ClassWithInnerClasses.java │ ├── InterfaceA.java │ ├── SubClass.java │ ├── SubInterface.java │ ├── SuperClass.java │ └── Superinterface.java │ └── package2 │ ├── Collaborator1.java │ └── Collaborator2.java └── resources ├── integration ├── test-config-asyncfile.yaml ├── test-config-asyncudp.yaml └── test-config.yaml └── log4j.properties /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | indent_style = tab 15 | 16 | [{*.json,*.yml}] 17 | indent_size = 4 18 | indent_style = space -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "master" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "master" ] 20 | schedule: 21 | - cron: '31 10 * * 5' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'java' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v2 73 | with: 74 | category: "/language:${{matrix.language}}" 75 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | .classpath 4 | .classpath-e 5 | .clover 6 | .project 7 | .tmp 8 | .settings/ 9 | .metadata 10 | eclipse-build/ 11 | target/ 12 | 13 | emn-config.yaml 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | jdk: 4 | - openjdk7 5 | - oraclejdk8 6 | 7 | install: 8 | - echo "Downloading Maven 3.0"; 9 | - wget https://archive.apache.org/dist/maven/binaries/apache-maven-3.0-bin.zip || travis_terminate 1 10 | - unzip -qq apache-maven-3.0-bin.zip || travis_terminate 1 11 | - export M2_HOME=$PWD/apache-maven-3.0 12 | - export PATH=$M2_HOME/bin:$PATH 13 | - mvn -version 14 | - mvn clean package install -DskipTests -Dgpg.skip 15 | script: 16 | - mvn test 17 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Marcel Sauer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [](http://travis-ci.org/niesfisch/java-code-tracer/builds) 2 | 3 | # JCT = Java Code Tracer 4 | 5 | JCT was born out of the idea to collect runtime information from a big monolithic (legacy) application that was running for several years. from time to time there was the same question over and over again: 6 | 7 | > do we still need this code? nobody seems to be calling it .... 8 | 9 | > but i am still scared to remove it :-( 10 | 11 | > maybe the code is called in production? 12 | 13 | JCT helps you answering these questions by collecting information about your running application. 14 | 15 | # Installing JCT for your application 16 | 17 | first clone or download this project 18 | 19 | ## create config 20 | 21 | the config file is the place where you configure which classes and packages of your application should be 'monitored' for calls (aka instrumented). by default nothing will be instrumented as this would produce massive amounts of data. start by choosing a sensible package of your application to begin with. everything is based on regex(s), so there should be a lot of freedom for you. see the config-sample.yml that you'll be copying to start with. 22 | 23 | ```bash 24 | mkdir ${HOME}/.jct/ 25 | cp doc/config-sample-file.yaml ${HOME}/.jct/ 26 | ``` 27 | 28 | the recorded stacks will be save to the folder specified in the config file. 29 | 30 | ## built the agent jar that will be used 31 | 32 | ```bash 33 | mvn clean package 34 | ls -al ./target/ 35 | ``` 36 | 37 | ## start application with agent 38 | 39 | ```bash 40 | # (multiline for readability) 41 | java -jar someApplication.jar 42 | -javaagent:"/path_to/target/java-code-tracer-1.0-SNAPSHOT-jar-with-dependencies.jar" 43 | -Djct.loglevel=INFO 44 | -Djct.config=${HOME}/.jct/config-sample-file.yaml (optional, default is under /src/main/resources/META-INF/config.yaml, will be merged) 45 | -Djct.logDir=/tmp/jct 46 | -noverify (needed for the moment) 47 | ``` 48 | 49 | ## Output Processor(s)) 50 | 51 | there are multiple ways to handle the captured data. the following processor are currently available: 52 | 53 | - **AsyncFileWritingStackProcessor**, writes stacks to dedicated files (see [config.yaml](src/test/resources/integration/test-config-asyncfile.yaml)) 54 | - **AsyncUdpStackProcessor**, sends each stack to configured UDP port for further processing (see [config.yaml](src/test/resources/integration/test-config-asyncudp.yaml)) 55 | 56 | see further down for a fullblown hello world example. 57 | 58 | ## Message Format 59 | 60 | ```json 61 | { 62 | "stack": [ 63 | "de.marcelsauer.helloworld.subA.InSubA.a_1()", 64 | "de.marcelsauer.helloworld.subA.InSubA_InnerClass.a_inner_1()", 65 | "de.marcelsauer.helloworld.subA.InSubA_InnerClass.a_inner_2()", 66 | "de.marcelsauer.helloworld.subA.InSubA_InnerClass.a_inner_2()" 67 | ], 68 | "timestampMillis": "1528120883697" 69 | } 70 | ``` 71 | 72 | ``` 73 | timestampMillis: timestamp milliseconds the first stack entry was started 74 | stack: all captured stack elements during the call, where stack[0] is the entry and stack[n] is the end 75 | ``` 76 | 77 | ## Logging 78 | 79 | Files can be found in `jct.logDir` 80 | 81 | ## Debugging 82 | 83 | if you need more information about what will be instrumented etc. tune the loglevel. 84 | 85 | ```bash 86 | -Djct.loglevel=DEBUG 87 | ``` 88 | 89 | and check the logs ... 90 | 91 | # How it works 92 | 93 | JCT uses byte code instrumentation. it "magically" weaves tracing information into each instrumented class/method to record calls. it keeps a map of call stacks that it collected and their counts. this is done as long as the instrumented process is running and kept in memory. as soon as the process shuts down, the collected information is gone. 94 | 95 | ## Example Hello World walkthrough 96 | 97 | In this walkthrough you will start a simple loop that prints "Hello World ..." to the console. the program will be instrumtented with JCT and some data will be collected. 98 | 99 | hint: we explicitly excluded the "HelloWorldLoop" in the config otherwise we would never get results as the loop never leaves and JCT never reaches the end of the stack. 100 | 101 | now it's time to start it locally ... 102 | 103 | ```bash 104 | # produce jar with agent 105 | mvn clean package 106 | # start hello world jar with agent attached to it, (multiline for readability) 107 | java -javaagent:"${PWD}/target/java-code-tracer-1.0-SNAPSHOT-jar-with-dependencies.jar" 108 | -Djct.loglevel=INFO 109 | -Djct.config=./doc/config-sample-helloworld.yaml 110 | -Djct.logDir=/tmp/jct -noverify 111 | -jar "${PWD}/doc/helloworld-loop.jar" 112 | ``` 113 | 114 | you should see "Hello World .." printed to the console multiple times. 115 | 116 | open another tab ... 117 | 118 | check the logs ... 119 | 120 | ```bash 121 | cat /tmp/jct/jct_agent.log 122 | ``` 123 | 124 | every x seconds the collected stacks will be dumped to a file. you need to aggregate these files yourself however you see fit. 125 | 126 | ```bash 127 | cat /tmp/stacks/jct_xxxx_xx_xx_xxxxxx_xxx.log 128 | 129 | {"timestampMillis" : "1528120452903", "stack" : ["de.marcelsauer.helloworld.subA.InSubA.a_1()","de.marcelsauer.helloworld.subA.InSubA_InnerClass.a_inner_1()","de.marcelsauer.helloworld.subA.InSubA_InnerClass.a_inner_2()","de.marcelsauer.helloworld.subA.InSubA_InnerClass.a_inner_2()"]} 130 | {"timestampMillis" : "1528120452903", "stack" : ["de.marcelsauer.helloworld.subA.InSubA.a_2()"]} 131 | {"timestampMillis" : "1528120452903", "stack" : ["de.marcelsauer.helloworld.subB.InSubB.b_1()","de.marcelsauer.helloworld.subB.InSubB_InnerClass.b_inner_1()","de.marcelsauer.helloworld.subB.InSubB_InnerClass.b_inner_2()","de.marcelsauer.helloworld.subB.InSubB_InnerClass.b_inner_2()"]} 132 | {"timestampMillis" : "1528120452903", "stack" : ["de.marcelsauer.helloworld.subB.InSubB.b_2()","de.marcelsauer.helloworld.subB.InSubB_InnerClass.b_inner_1()","de.marcelsauer.helloworld.subB.InSubB_InnerClass.b_inner_2()","de.marcelsauer.helloworld.subB.InSubB_InnerClass.b_inner_2()"]} 133 | ``` 134 | 135 | you could use some simple aggregation like this 136 | 137 | ```bash 138 | cd /tmp/stacks/ 139 | for file in `find . -name "jct*"`; do cat $file >> all.log; done 140 | cat all.log | perl -pe 's/.*\[(.*)\].*/\1/' | sort | uniq -c 141 | 142 | 5 "de.marcelsauer.helloworld.subA.InSubA.a_1()","de.marcelsauer.helloworld.subA.InSubA_InnerClass.a_inner_1()","de.marcelsauer.helloworld.subA.InSubA_InnerClass.a_inner_2()","de.marcelsauer.helloworld.subA.InSubA_InnerClass.a_inner_2()" 143 | 5 "de.marcelsauer.helloworld.subA.InSubA.a_2()" 144 | 5 "de.marcelsauer.helloworld.subB.InSubB.b_1()","de.marcelsauer.helloworld.subB.InSubB_InnerClass.b_inner_1()","de.marcelsauer.helloworld.subB.InSubB_InnerClass.b_inner_2()","de.marcelsauer.helloworld.subB.InSubB_InnerClass.b_inner_2()" 145 | 5 "de.marcelsauer.helloworld.subB.InSubB.b_2()","de.marcelsauer.helloworld.subB.InSubB_InnerClass.b_inner_1()","de.marcelsauer.helloworld.subB.InSubB_InnerClass.b_inner_2()","de.marcelsauer.helloworld.subB.InSubB_InnerClass.b_inner_2()" 146 | 147 | ``` 148 | 149 | ### JCT -> UDP -> Logstash -> Elasticsearch example 150 | 151 | here is a full example to use UDP + logstash + elasticsearch (assumes you have [logstash](https://www.elastic.co/de/downloads/logstash)/[docker](https://www.docker.com/community-edition#/download) on your machine and the UDP processor sends to port 9999) 152 | 153 | ```bash 154 | 155 | # tab1, start logstash with out UDP input and elasticsearch output 156 | logstash -f doc/logstash.conf 157 | 158 | #tab2, start elasticsearch 159 | docker run -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:6.2.4 160 | 161 | #tab3 (multiline for readability), start helloworld loop that will send captured stacks to UDP->logstash->elastisearch 162 | java -javaagent:"${PWD}/target/java-code-tracer-1.0-SNAPSHOT-jar-with-dependencies.jar" 163 | -Djct.loglevel=INFO 164 | -Djct.config=./doc/config-sample-helloworld-udp.yaml 165 | -Djct.logDir=/tmp/jct 166 | -noverify 167 | -jar "${PWD}/doc/helloworld-loop.jar" 168 | 169 | # query captured data 170 | curl -XGET http://127.0.0.1:9200/jct_stacks/_search 171 | ``` 172 | 173 | # Similar Projects/Ideas 174 | 175 | - [Datadog dd-trace-java](https://github.com/DataDog/dd-trace-java/) 176 | - [NewRelic](https://newrelic.com/) 177 | - [Call Graph](https://github.com/gousiosg/java-callgraph) 178 | 179 | # ToDos, Ideas 180 | 181 | - use proper dependency injection instead of static calls 182 | 183 | # License 184 | 185 | [MIT](LICENSE.txt) 186 | -------------------------------------------------------------------------------- /doc/config-sample-file.yaml: -------------------------------------------------------------------------------- 1 | # some comment 2 | classes: 3 | included: 4 | # include one explicit class 5 | - ^some.package.with.ClassA 6 | # include some classes in certain packages that match regex 7 | - ^some.package.with.more.classes.* 8 | # exlude nothing, as per default nothing will be included, use this to tune to wide includes ... 9 | excluded: [] 10 | processor: 11 | fullQualifiedClass: de.marcelsauer.profiler.processor.file.AsyncFileWritingStackProcessor 12 | stackFolderName: /tmp/stacks/ 13 | -------------------------------------------------------------------------------- /doc/config-sample-helloworld-udp.yaml: -------------------------------------------------------------------------------- 1 | # some comment 2 | classes: 3 | included: 4 | - ^.*hell.* 5 | excluded: 6 | - .*HelloWorldLoop.* 7 | processor: 8 | fullQualifiedClass: de.marcelsauer.profiler.processor.udp.AsyncUdpStackProcessor 9 | udpHost: localhost 10 | udpPort: 9999 11 | -------------------------------------------------------------------------------- /doc/config-sample-helloworld.yaml: -------------------------------------------------------------------------------- 1 | # some comment 2 | classes: 3 | included: 4 | - ^.*hell.* 5 | excluded: 6 | - .*HelloWorldLoop.* 7 | processor: 8 | fullQualifiedClass: de.marcelsauer.profiler.processor.file.AsyncFileWritingStackProcessor 9 | stackFolderName: /tmp/stacks/ 10 | -------------------------------------------------------------------------------- /doc/helloworld-loop.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niesfisch/java-code-tracer/0225f7808cda894bb96efe6632c1f39cef15f0ae/doc/helloworld-loop.jar -------------------------------------------------------------------------------- /doc/helloworldloop_overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niesfisch/java-code-tracer/0225f7808cda894bb96efe6632c1f39cef15f0ae/doc/helloworldloop_overview.png -------------------------------------------------------------------------------- /doc/logstash.conf: -------------------------------------------------------------------------------- 1 | input { 2 | udp { 3 | port => 9999 4 | codec => json 5 | } 6 | } 7 | 8 | output { 9 | elasticsearch { 10 | hosts => ["localhost:9200"] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /notes.txt: -------------------------------------------------------------------------------- 1 | just for me ... 2 | =============== 3 | 4 | aggregation by stack ... 5 | 6 | cat target/stacks/jct_xxx.log | perl -pe 's/.*\[(.*)\].*/\1/' | sort | uniq -c 7 | 8 | { 9 | "aggs": { 10 | "group_by_method": { 11 | "terms": { 12 | "field": "stack.keyword" 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | de.marcel 6 | java-code-tracer 7 | 1.0-SNAPSHOT 8 | jar 9 | 10 | java-code-tracer 11 | 12 | 13 | UTF-8 14 | 15 | 16 | 17 | 18 | marcel sauer 19 | 20 | 21 | 22 | 23 | 24 | MIT Licence 25 | see LICENCE.txt 26 | 27 | 28 | 29 | 30 | 31 | Sonatype-public 32 | SnakeYAML repository 33 | http://oss.sonatype.org/content/groups/public/ 34 | 35 | 36 | 37 | 38 | 39 | com.sun 40 | tools 41 | 1.6.0 42 | system 43 | ${java.home}/../lib/tools.jar 44 | 45 | 46 | org.yaml 47 | snakeyaml 48 | 1.15 49 | 50 | 51 | org.javassist 52 | javassist 53 | 3.19.0-GA 54 | jar 55 | 56 | 57 | junit 58 | junit 59 | 4.11 60 | jar 61 | test 62 | 63 | 64 | org.mockito 65 | mockito-all 66 | 1.9.5 67 | test 68 | 69 | 70 | log4j 71 | log4j 72 | 1.2.17 73 | jar 74 | 75 | 76 | 77 | 78 | 79 | org.apache.maven.plugins 80 | maven-surefire-plugin 81 | 2.19 82 | 83 | 84 | **/*Integration*Test.java 85 | 86 | 87 | 88 | 89 | org.apache.maven.plugins 90 | maven-compiler-plugin 91 | 2.5.1 92 | 93 | 1.7 94 | 1.7 95 | 96 | 97 | 98 | 99 | maven-assembly-plugin 100 | 101 | 102 | 103 | de.marcelsauer.profiler.agent.Agent 104 | de.marcelsauer.profiler.agent.Agent 105 | true 106 | true 107 | true 108 | 109 | 110 | 111 | jar-with-dependencies 112 | 113 | 114 | 115 | 116 | make-assembly 117 | package 118 | 119 | single 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /src/main/java/de/marcelsauer/profiler/agent/Agent.java: -------------------------------------------------------------------------------- 1 | package de.marcelsauer.profiler.agent; 2 | 3 | import java.lang.instrument.Instrumentation; 4 | 5 | import org.apache.log4j.Logger; 6 | 7 | import de.marcelsauer.profiler.config.Config; 8 | import de.marcelsauer.profiler.processor.inmemory.server.Server; 9 | import de.marcelsauer.profiler.transformer.Transformer; 10 | 11 | /** 12 | * start with 13 | *

14 | * -javaagent: /path-to-jar/code-tracer/target/javaagent-sample-1.0-SNAPSHOT-jar-with-dependencies.jar 15 | * -Dconfig=test-config.yaml (optional) 16 | * -Dtracer.server.port=9002 (default=9001) 17 | * * 18 | * https://web.archive.org/web/20141014195801/http://dhruba.name/2010/02/07/creation-dynamic-loading-and-instrumentation-with-javaagents/ 19 | * 20 | * @author msauer 21 | */ 22 | public class Agent { 23 | 24 | private static final Logger logger = Logger.getLogger(Agent.class); 25 | 26 | public static Instrumentation INSTRUMENTATION; 27 | 28 | public static void agentmain(String args, Instrumentation inst) { 29 | init(inst); 30 | } 31 | 32 | public static void premain(String agentArgs, Instrumentation inst) { 33 | init(inst); 34 | } 35 | 36 | private static void init(Instrumentation inst) { 37 | INSTRUMENTATION = inst; 38 | 39 | logger.info("registering instrumentation transformer"); 40 | inst.addTransformer(new Transformer(Config.get())); 41 | 42 | // new Server().start(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/de/marcelsauer/profiler/config/Classes.java: -------------------------------------------------------------------------------- 1 | package de.marcelsauer.profiler.config; 2 | 3 | import java.util.HashSet; 4 | import java.util.Set; 5 | 6 | /** 7 | * @author msauer 8 | */ 9 | public class Classes { 10 | public Set included = new HashSet<>(); 11 | public Set excluded = new HashSet<>(); 12 | 13 | @Override 14 | public String toString() { 15 | return "Classes{" + 16 | "included=" + included + 17 | ", excluded=" + excluded + 18 | '}'; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/de/marcelsauer/profiler/config/Config.java: -------------------------------------------------------------------------------- 1 | package de.marcelsauer.profiler.config; 2 | 3 | import java.io.FileInputStream; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.util.Set; 7 | 8 | import org.apache.log4j.Logger; 9 | import org.yaml.snakeyaml.Yaml; 10 | import org.yaml.snakeyaml.constructor.Constructor; 11 | 12 | /** 13 | * yaml bean 14 | * 15 | * @author msauer 16 | */ 17 | public class Config { 18 | private static final Logger logger = Logger.getLogger(Config.class); 19 | private static final String DEFAULT_CONFIG_FILE = "META-INF/config.yaml"; 20 | 21 | public Classes classes = new Classes(); 22 | public Recorder recorder = new Recorder(); 23 | public Processor processor = new Processor(); 24 | 25 | private static Config currentConfig; 26 | 27 | public static Config get() { 28 | if (currentConfig == null) { 29 | currentConfig = init(); 30 | } 31 | return currentConfig; 32 | } 33 | 34 | private static Config init() { 35 | Config config = Config.initDefaultFromYamlFile(); 36 | config.merge(Config.loadCustomFromYamlFile()); 37 | logger.info("merged config config" + config); 38 | return config; 39 | } 40 | 41 | static Config initDefaultFromYamlFile() { 42 | Yaml yaml = new Yaml(new Constructor(Config.class)); 43 | Config config = (Config) yaml.load(FileUtils.getLocalResource(DEFAULT_CONFIG_FILE)); 44 | logger.info("using default config: " + config); 45 | return config; 46 | } 47 | 48 | static Config loadCustomFromYamlFile() { 49 | String yamlFile = System.getProperty("jct.config"); 50 | if (yamlFile != null && !"".equals(yamlFile.trim())) { 51 | 52 | Yaml yaml = new Yaml(new Constructor(Config.class)); 53 | Config configFromCustomFile = null; 54 | try { 55 | InputStream result = new FileInputStream(yamlFile); 56 | configFromCustomFile = (Config) yaml.load(result); 57 | } catch (IOException e) { 58 | throw new RuntimeException("could not load " + yamlFile); 59 | } 60 | 61 | logger.info(String.format("using custom config from file '%s' with config '%s'", yamlFile, configFromCustomFile)); 62 | return configFromCustomFile; 63 | } 64 | return null; 65 | } 66 | 67 | @Override 68 | public String toString() { 69 | return "Config{" + 70 | "classes=" + classes + 71 | ", recorder=" + recorder + 72 | '}'; 73 | } 74 | 75 | private void merge(Config otherConfig) { 76 | if (otherConfig == null) { 77 | return; 78 | } 79 | mergeClasses(otherConfig); 80 | mergeRecorder(otherConfig); 81 | this.processor = otherConfig.processor; 82 | } 83 | 84 | private void mergeRecorder(Config otherConfig) { 85 | addNullSafe(otherConfig.recorder.superClasses, this.recorder.superClasses); 86 | addNullSafe(otherConfig.recorder.interfaces, this.recorder.interfaces); 87 | addNullSafe(otherConfig.recorder.methodLevelAnnotations, this.recorder.methodLevelAnnotations); 88 | addNullSafe(otherConfig.recorder.classLevelAnnotations, this.recorder.classLevelAnnotations); 89 | } 90 | 91 | private void mergeClasses(Config otherConfig) { 92 | addNullSafe(otherConfig.classes.included, this.classes.included); 93 | addNullSafe(otherConfig.classes.excluded, this.classes.excluded); 94 | } 95 | 96 | private void addNullSafe(Set src, Set target) { 97 | if (src != null) { 98 | target.addAll(src); 99 | } 100 | } 101 | 102 | public boolean isInclusionConfigured() { 103 | return !this.classes.included.isEmpty(); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/de/marcelsauer/profiler/config/FileUtils.java: -------------------------------------------------------------------------------- 1 | package de.marcelsauer.profiler.config; 2 | 3 | import java.io.BufferedInputStream; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | 7 | /** 8 | * @author msauer 9 | */ 10 | class FileUtils { 11 | 12 | static String getLocalResource(String file) { 13 | try { 14 | InputStream input = FileUtils.class.getClassLoader().getResourceAsStream(file); 15 | if (input == null) { 16 | throw new RuntimeException("Can not load " + file); 17 | } 18 | BufferedInputStream is = new BufferedInputStream(input); 19 | StringBuilder buf = new StringBuilder(3000); 20 | int i; 21 | try { 22 | while ((i = is.read()) != -1) { 23 | buf.append((char) i); 24 | } 25 | } finally { 26 | is.close(); 27 | } 28 | String resource = buf.toString(); 29 | // convert EOLs 30 | String[] lines = resource.split("\\r?\\n"); 31 | StringBuilder buffer = new StringBuilder(); 32 | for (String line : lines) { 33 | buffer.append(line); 34 | buffer.append("\n"); 35 | } 36 | return buffer.toString(); 37 | } catch (IOException e) { 38 | throw new RuntimeException(e); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/de/marcelsauer/profiler/config/Processor.java: -------------------------------------------------------------------------------- 1 | package de.marcelsauer.profiler.config; 2 | 3 | /** 4 | * yaml bean 5 | * @author msauer 6 | */ 7 | public class Processor { 8 | public String fullQualifiedClass; 9 | 10 | public String udpHost; 11 | public int udpPort; 12 | 13 | public String stackFolderName; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/de/marcelsauer/profiler/config/Recorder.java: -------------------------------------------------------------------------------- 1 | package de.marcelsauer.profiler.config; 2 | 3 | import java.util.HashSet; 4 | import java.util.Set; 5 | 6 | /** 7 | * yaml bean 8 | * 9 | * @author msauer 10 | */ 11 | public class Recorder { 12 | public Set interfaces = new HashSet<>(); 13 | public Set superClasses = new HashSet<>(); 14 | public Set classLevelAnnotations = new HashSet<>(); 15 | public Set methodLevelAnnotations = new HashSet<>(); 16 | 17 | @Override 18 | public String toString() { 19 | return "Recorder{" + 20 | "interfaces=" + interfaces + 21 | ", superClasses=" + superClasses + 22 | ", classLevelAnnotations=" + classLevelAnnotations + 23 | ", methodLevelAnnotations=" + methodLevelAnnotations + 24 | '}'; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/de/marcelsauer/profiler/instrumenter/DefaultInstrumentationCallback.java: -------------------------------------------------------------------------------- 1 | package de.marcelsauer.profiler.instrumenter; 2 | 3 | import org.apache.log4j.Logger; 4 | 5 | import de.marcelsauer.profiler.config.Config; 6 | import de.marcelsauer.profiler.recorder.Recorder; 7 | import javassist.CtMethod; 8 | 9 | /** 10 | * @author msauer 11 | */ 12 | public class DefaultInstrumentationCallback implements Instrumenter.InstrumentationCallback { 13 | 14 | private static final Logger logger = Logger.getLogger(DefaultInstrumentationCallback.class); 15 | private final Config config; 16 | 17 | public DefaultInstrumentationCallback(Config config) { 18 | this.config = config; 19 | 20 | // preload recorder class 21 | Recorder.touch(); 22 | } 23 | 24 | public void instrument(CtMethod declaredMethod) { 25 | String methodNameLong = declaredMethod.getLongName(); 26 | 27 | logger.debug("[will record] method '" + declaredMethod.getLongName() + "'"); 28 | try { 29 | declaredMethod.insertBefore(String.format("%s.start(\"%s\");", Recorder.class.getName(), methodNameLong)); 30 | declaredMethod.insertAfter(String.format("%s.stop();", Recorder.class.getName()), true); 31 | } catch (Exception e) { 32 | logger.warn(String.format("could not instrument method %s: %s", declaredMethod.getLongName(), e.getMessage())); 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/de/marcelsauer/profiler/instrumenter/Instrumenter.java: -------------------------------------------------------------------------------- 1 | package de.marcelsauer.profiler.instrumenter; 2 | 3 | import org.apache.log4j.Logger; 4 | 5 | import de.marcelsauer.profiler.recorder.Statistics; 6 | import de.marcelsauer.profiler.util.Util; 7 | import javassist.ClassPool; 8 | import javassist.CtClass; 9 | import javassist.CtMethod; 10 | import javassist.LoaderClassPath; 11 | 12 | /** 13 | * @author msauer 14 | */ 15 | public class Instrumenter { 16 | 17 | private static final Logger logger = Logger.getLogger(Instrumenter.class); 18 | private final InstrumentationCallback callback; 19 | private boolean done = false; 20 | 21 | public Instrumenter(InstrumentationCallback callback) { 22 | this.callback = callback; 23 | } 24 | 25 | public interface InstrumentationCallback { 26 | void instrument(CtMethod declaredMethod); 27 | } 28 | 29 | public byte[] instrument(String className, ClassLoader loader, Class klass) { 30 | try { 31 | 32 | ClassPool cp = getClassPool(loader); 33 | 34 | CtClass cc = cp.get(Util.fromJVM(className)); 35 | boolean instrument = !cc.isAnnotation() && !cc.isArray() && !cc.isInterface() && !cc.isEnum(); 36 | 37 | if (!instrument) { 38 | return null; 39 | } 40 | 41 | CtMethod[] declaredMethods = cc.getDeclaredMethods(); 42 | 43 | for (CtMethod declaredMethod : declaredMethods) { 44 | try { 45 | this.callback.instrument(declaredMethod); 46 | Statistics.addInstrumentedMethod(declaredMethod.getLongName()); 47 | } catch (Exception e) { 48 | logger.warn(String.format("could not instrument method '%s': %s", className, e.getMessage())); 49 | return null; 50 | } 51 | } 52 | 53 | byte[] bytes = cc.toBytecode(); 54 | cc.detach(); 55 | 56 | Statistics.addInstrumentedClass(className); 57 | 58 | return bytes; 59 | } catch (Exception ex) { 60 | logger.warn(String.format("could not instrument class '%s': %s", className, ex.getMessage())); 61 | return null; 62 | } 63 | } 64 | 65 | private ClassPool getClassPool(ClassLoader loader) { 66 | ClassPool cp = ClassPool.getDefault(); 67 | if (!done) { 68 | cp.insertClassPath(new LoaderClassPath(loader)); 69 | done = true; 70 | } 71 | return cp; 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/de/marcelsauer/profiler/processor/AbstractAsyncStackProcessor.java: -------------------------------------------------------------------------------- 1 | package de.marcelsauer.profiler.processor; 2 | 3 | import java.util.Collection; 4 | import java.util.concurrent.ArrayBlockingQueue; 5 | import java.util.concurrent.Executors; 6 | import java.util.concurrent.ScheduledExecutorService; 7 | import java.util.concurrent.ThreadFactory; 8 | import java.util.concurrent.TimeUnit; 9 | import java.util.concurrent.atomic.AtomicInteger; 10 | 11 | import org.apache.log4j.Logger; 12 | 13 | /** 14 | * handles async stuff and buffer to worker queue 15 | */ 16 | public abstract class AbstractAsyncStackProcessor implements StackProcessor { 17 | 18 | private static final Logger logger = Logger.getLogger(StackProcessor.class); 19 | private static final String JCA_PROCESSOR_THREAD = "jca-processor-thread"; 20 | private static final int CAPACITY = 10000; 21 | 22 | /** 23 | * counters 24 | */ 25 | private static AtomicInteger stacksCapturedCounter = new AtomicInteger(0); 26 | private static AtomicInteger successfullyProcessedStacksCounter = new AtomicInteger(0); 27 | 28 | private ArrayBlockingQueue workerQueue = new ArrayBlockingQueue<>(CAPACITY); 29 | 30 | /** 31 | * acting like cron 32 | */ 33 | private final ScheduledExecutorService scheduledExecutor = 34 | Executors.newScheduledThreadPool(1, new ThreadFactory() { 35 | @Override 36 | public Thread newThread(final Runnable r) { 37 | final Thread thread = new Thread(r, JCA_PROCESSOR_THREAD); 38 | thread.setDaemon(true); 39 | return thread; 40 | } 41 | }); 42 | 43 | public void process(RecordingEvent event) { 44 | stacksCapturedCounter.incrementAndGet(); 45 | try { 46 | workerQueue.add(event); 47 | } catch (IllegalStateException e) { 48 | // workerQueue full, discarding 49 | } 50 | } 51 | 52 | 53 | protected abstract void doStart(); 54 | 55 | protected abstract void doStop(); 56 | 57 | protected abstract void doProcess(Collection snapshots); 58 | 59 | @Override 60 | public void start() { 61 | logger.info("starting " + this.getClass().getName()); 62 | startScheduler(); 63 | doStart(); 64 | } 65 | 66 | @Override 67 | public void stop() { 68 | logger.info("shutting down " + this.getClass().getName()); 69 | try { 70 | scheduledExecutor.shutdownNow(); 71 | } catch (Exception e) { 72 | // ignore 73 | } 74 | try { 75 | scheduledExecutor.awaitTermination(500, TimeUnit.MILLISECONDS); 76 | } catch (InterruptedException e) { 77 | // ignore 78 | } 79 | try { 80 | doStop(); 81 | } catch (Exception e) { 82 | // ignore 83 | } 84 | } 85 | 86 | public static int getStacksCapturedCounter() { 87 | return stacksCapturedCounter.intValue(); 88 | } 89 | 90 | public static int getSuccessfullyProcessedStacksCounter() { 91 | return successfullyProcessedStacksCounter.intValue(); 92 | } 93 | 94 | private void startScheduler() { 95 | scheduledExecutor.scheduleAtFixedRate(new WorkQueueProcessorTask(), 0, 1, TimeUnit.SECONDS); 96 | } 97 | 98 | class WorkQueueProcessorTask implements Runnable { 99 | 100 | @Override 101 | public void run() { 102 | try { 103 | ArrayBlockingQueue snapshot; 104 | synchronized (workerQueue) { 105 | snapshot = workerQueue; 106 | workerQueue = new ArrayBlockingQueue<>(CAPACITY); 107 | } 108 | if (!snapshot.isEmpty()) { 109 | doProcess(snapshot); 110 | successfullyProcessedStacksCounter.addAndGet(snapshot.size()); 111 | logger.info( 112 | String.format("delegating %d stacks. successfully processed so far %d", 113 | snapshot.size(), 114 | successfullyProcessedStacksCounter.intValue())); 115 | } 116 | } catch (Exception e) { 117 | logger.warn("problem while async handling. " + e.getMessage()); 118 | } 119 | 120 | } 121 | } 122 | 123 | 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/de/marcelsauer/profiler/processor/RecordingEvent.java: -------------------------------------------------------------------------------- 1 | package de.marcelsauer.profiler.processor; 2 | 3 | import java.util.Arrays; 4 | import java.util.Collections; 5 | import java.util.Date; 6 | import java.util.List; 7 | 8 | public class RecordingEvent { 9 | public final Stack stack; 10 | public final long timestampMillis; 11 | 12 | public RecordingEvent(Stack stack) { 13 | this.stack = stack; 14 | this.timestampMillis = new Date().getTime(); 15 | } 16 | 17 | List getStackEntries() { 18 | return Collections.unmodifiableList(stack.stackEntries); 19 | } 20 | 21 | public String asJson() { 22 | StringBuilder sb = new StringBuilder("{"); 23 | sb.append(String.format("\"timestampMillis\" : \"%d\", ", timestampMillis)); 24 | 25 | StringBuilder stackSb = new StringBuilder("["); 26 | for (Stack.StackEntry call : getStackEntries()) { 27 | stackSb.append(String.format(",\"%s\"", call.methodName)); 28 | // stackSb.append(String.format(",\"%s%s\"", nSpaces(call), call.methodName)); 29 | } 30 | stackSb.append("]"); 31 | sb.append(String.format("\"stack\" : %s", stackSb.toString().replaceFirst(",", ""))); 32 | sb.append("}"); 33 | return sb.toString(); 34 | } 35 | 36 | private String nSpaces(Stack.StackEntry call) { 37 | int n = call.level; 38 | char[] chars = new char[n * 2]; 39 | Arrays.fill(chars, ' '); 40 | return new String(chars); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/de/marcelsauer/profiler/processor/Stack.java: -------------------------------------------------------------------------------- 1 | package de.marcelsauer.profiler.processor; 2 | 3 | import java.util.Date; 4 | import java.util.List; 5 | 6 | public class Stack { 7 | final List stackEntries; 8 | 9 | public Stack(List stackEntries) { 10 | this.stackEntries = stackEntries; 11 | } 12 | 13 | public static class StackEntry { 14 | public final String methodName; 15 | public final long startNanos; 16 | public final long timestampMillis; 17 | public long endNanos; 18 | public int level; 19 | 20 | public StackEntry(String methodName, long startNanos, int level) { 21 | this.methodName = methodName; 22 | this.startNanos = startNanos; 23 | this.level = level; 24 | this.timestampMillis = new Date().getTime(); 25 | } 26 | 27 | public void end() { 28 | this.endNanos = System.nanoTime(); 29 | } 30 | 31 | public long getDuration() { 32 | return this.endNanos - this.startNanos; 33 | } 34 | 35 | @Override 36 | public String toString() { 37 | return "StackEntry{" + 38 | "methodName='" + methodName + '\'' + 39 | '}'; 40 | } 41 | } 42 | 43 | @Override 44 | public String toString() { 45 | return stackEntries.toString(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/de/marcelsauer/profiler/processor/StackProcessor.java: -------------------------------------------------------------------------------- 1 | package de.marcelsauer.profiler.processor; 2 | 3 | public interface StackProcessor { 4 | 5 | void process(RecordingEvent event); 6 | 7 | void start(); 8 | 9 | void stop(); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/de/marcelsauer/profiler/processor/StackProcessorFactory.java: -------------------------------------------------------------------------------- 1 | package de.marcelsauer.profiler.processor; 2 | 3 | import org.apache.log4j.Logger; 4 | 5 | import de.marcelsauer.profiler.config.Config; 6 | import de.marcelsauer.profiler.processor.inmemory.InMemoryStackProcessor; 7 | import de.marcelsauer.profiler.util.Util; 8 | 9 | public class StackProcessorFactory { 10 | private static final Logger logger = Logger.getLogger(StackProcessorFactory.class); 11 | private static StackProcessor stackProcessor; 12 | 13 | public static StackProcessor getStackProcessor() { 14 | if (stackProcessor != null) { 15 | return stackProcessor; 16 | } 17 | String processor = Config.get().processor.fullQualifiedClass; 18 | if (Util.isEmpty(processor)) { 19 | stackProcessor = new InMemoryStackProcessor(); 20 | } else { 21 | try { 22 | Class pr = Class.forName(processor); 23 | stackProcessor = (StackProcessor) pr.newInstance(); 24 | } catch (Exception e) { 25 | throw new IllegalStateException("could not create stack processor " + stackProcessor, e); 26 | } 27 | } 28 | logger.info("using stack processor of type: " + stackProcessor.getClass().getName()); 29 | 30 | stackProcessor.start(); 31 | 32 | addShutdownHook(); 33 | 34 | return stackProcessor; 35 | } 36 | 37 | private static void addShutdownHook() { 38 | Runtime.getRuntime().addShutdownHook(new Thread("jca-shutdown-thread") { 39 | @Override 40 | public void run() { 41 | logger.info("shutting down jca processor " + stackProcessor.getClass().getName()); 42 | stackProcessor.stop(); 43 | } 44 | }); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/de/marcelsauer/profiler/processor/file/AsyncFileWritingStackProcessor.java: -------------------------------------------------------------------------------- 1 | package de.marcelsauer.profiler.processor.file; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.FileAlreadyExistsException; 5 | import java.nio.file.Files; 6 | import java.nio.file.Paths; 7 | import java.nio.file.StandardOpenOption; 8 | import java.text.SimpleDateFormat; 9 | import java.util.Collection; 10 | import java.util.Date; 11 | 12 | import org.apache.log4j.Logger; 13 | 14 | import de.marcelsauer.profiler.config.Config; 15 | import de.marcelsauer.profiler.processor.AbstractAsyncStackProcessor; 16 | import de.marcelsauer.profiler.processor.RecordingEvent; 17 | 18 | /** 19 | * writes data to configured file 20 | */ 21 | public class AsyncFileWritingStackProcessor extends AbstractAsyncStackProcessor { 22 | 23 | private static final Logger logger = Logger.getLogger(AsyncFileWritingStackProcessor.class); 24 | private static final String STACK_DIR = Config.get().processor.stackFolderName; 25 | 26 | @Override 27 | protected void doStart() { 28 | createStackFolderIfNotPresent(); 29 | } 30 | 31 | @Override 32 | protected void doStop() { 33 | // nothing to do here 34 | } 35 | 36 | @Override 37 | protected void doProcess(Collection snapshots) { 38 | String outFilename = getOutFilename(); 39 | try { 40 | for (RecordingEvent event : snapshots) { 41 | writeEventToFile(event, outFilename); 42 | } 43 | } catch (IOException e) { 44 | throw new RuntimeException(e); 45 | } 46 | } 47 | 48 | private void writeEventToFile(RecordingEvent event, String outFilename) throws IOException { 49 | String json = event.asJson(); 50 | Files.write(Paths.get(outFilename), (json + "\n").getBytes("UTF-8"), StandardOpenOption.CREATE, StandardOpenOption.APPEND); 51 | } 52 | 53 | private String getOutFilename() { 54 | String fileDateTimePart = new SimpleDateFormat("yyyy_dd_MM").format(new Date()); 55 | return String.format("%s/jct_%s.log", STACK_DIR, fileDateTimePart); 56 | } 57 | 58 | private void createStackFolderIfNotPresent() { 59 | try { 60 | Files.createDirectory(Paths.get(STACK_DIR)); 61 | } catch (FileAlreadyExistsException e) { 62 | // ignore 63 | } catch (IOException e) { 64 | throw new RuntimeException("problem with output directory " + STACK_DIR, e); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/de/marcelsauer/profiler/processor/inmemory/InMemoryCountingCollector.java: -------------------------------------------------------------------------------- 1 | package de.marcelsauer.profiler.processor.inmemory; 2 | 3 | import java.util.Collections; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | import org.apache.log4j.Logger; 8 | 9 | /** 10 | * @author msauer 11 | */ 12 | public class InMemoryCountingCollector { 13 | 14 | private static final Logger LOGGER = Logger.getLogger(InMemoryCountingCollector.class); 15 | 16 | private static final Map stacks = new HashMap<>(); 17 | 18 | public static synchronized void increment(String trace) { 19 | if (stacks.containsKey(trace)) { 20 | stacks.put(trace, stacks.get(trace) + 1); 21 | } else { 22 | stacks.put(trace, 1); 23 | } 24 | LOGGER.debug("collected " + stacks.size() + " unique stacks so far."); 25 | } 26 | 27 | public static Map getCollectedStacks() { 28 | return Collections.unmodifiableMap(stacks); 29 | } 30 | 31 | public static void purgeCollectedStacks() { 32 | stacks.clear(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/de/marcelsauer/profiler/processor/inmemory/InMemoryStackProcessor.java: -------------------------------------------------------------------------------- 1 | package de.marcelsauer.profiler.processor.inmemory; 2 | 3 | import de.marcelsauer.profiler.processor.RecordingEvent; 4 | import de.marcelsauer.profiler.processor.StackProcessor; 5 | 6 | public class InMemoryStackProcessor implements StackProcessor { 7 | public void process(RecordingEvent event) { 8 | InMemoryCountingCollector.increment(event.stack.toString()); 9 | } 10 | 11 | @Override 12 | public void start() { 13 | // noop 14 | } 15 | 16 | @Override 17 | public void stop() { 18 | // noop 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/de/marcelsauer/profiler/processor/inmemory/server/MapUtil.java: -------------------------------------------------------------------------------- 1 | package de.marcelsauer.profiler.processor.inmemory.server; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.IOException; 5 | import java.io.ObjectOutputStream; 6 | import java.util.Collections; 7 | import java.util.Comparator; 8 | import java.util.LinkedHashMap; 9 | import java.util.LinkedList; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | /** 14 | * taken from http://stackoverflow.com/questions/109383/how-to-sort-a-mapkey-value-on-the-values-in-java 15 | */ 16 | class MapUtil { 17 | static > Map sortByValue(Map map) { 18 | List> list = new LinkedList<>(map.entrySet()); 19 | Collections.sort(list, new Comparator>() { 20 | public int compare(Map.Entry o1, Map.Entry o2) { 21 | return (o2.getValue()).compareTo(o1.getValue()); 22 | } 23 | }); 24 | 25 | Map result = new LinkedHashMap<>(); 26 | for (Map.Entry entry : list) { 27 | result.put(entry.getKey(), entry.getValue()); 28 | } 29 | return result; 30 | } 31 | 32 | static int approximateSizeInBytes(Map map) { 33 | try { 34 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 35 | ObjectOutputStream oos = new ObjectOutputStream(baos); 36 | oos.writeObject(map); 37 | oos.close(); 38 | int size = baos.size(); 39 | return size; 40 | } catch (IOException e) { 41 | throw new RuntimeException(e); 42 | } 43 | } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /src/main/java/de/marcelsauer/profiler/processor/inmemory/server/Server.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: EliteMedianet GmbH, Hamburg 3 | */ 4 | package de.marcelsauer.profiler.processor.inmemory.server; 5 | 6 | import java.io.FileWriter; 7 | import java.io.IOException; 8 | import java.net.InetSocketAddress; 9 | import java.text.SimpleDateFormat; 10 | import java.util.Date; 11 | import java.util.Map; 12 | 13 | import org.apache.log4j.Logger; 14 | 15 | import com.sun.net.httpserver.HttpExchange; 16 | import com.sun.net.httpserver.HttpHandler; 17 | import com.sun.net.httpserver.HttpServer; 18 | 19 | import de.marcelsauer.profiler.processor.inmemory.InMemoryCountingCollector; 20 | import de.marcelsauer.profiler.recorder.Statistics; 21 | 22 | /** 23 | * @author msauer 24 | */ 25 | public class Server { 26 | 27 | private static final Logger LOGGER = Logger.getLogger(Server.class); 28 | private HttpServer httpServer; 29 | private Date startTime; 30 | private static final String LOG_DIR = System.getProperty("jct.logDir"); 31 | 32 | public static void main(String[] args) { 33 | new Server().start(); 34 | } 35 | 36 | public void start() { 37 | startTime = new Date(); 38 | 39 | int port = getPort(); 40 | 41 | try { 42 | this.httpServer = HttpServer.create(new InetSocketAddress(port), 0); 43 | } catch (IOException e) { 44 | throw new RuntimeException(e); 45 | } 46 | 47 | configureRouting(); 48 | 49 | this.httpServer.start(); 50 | 51 | this.addShutdownHook(this); 52 | 53 | LOGGER.info(String.format("started status server on port %d%n", port)); 54 | } 55 | 56 | private void configureRouting() { 57 | this.httpServer.createContext("/status/", createStatusHandler()); 58 | this.httpServer.createContext("/purge/", createPurgeHandler()); 59 | this.httpServer.createContext("/stacks/", createStackHandler()); 60 | this.httpServer.createContext("/dump/", createDumpHandler()); 61 | } 62 | 63 | private int getPort() { 64 | int port = 9001; 65 | String p = System.getProperty("jct.tracer.server.port"); 66 | if (p != null && !"".equals(p.trim())) { 67 | port = Integer.parseInt(p); 68 | } 69 | return port; 70 | } 71 | 72 | private class Stats { 73 | 74 | final int uniqueStacks; 75 | final Date started; 76 | final long size; 77 | final int instrumentedMethodsCount; 78 | final int instrumentedClassesCount; 79 | 80 | private Stats(int uniqueStacks, Date started, long size, int instrumentedMethodsCount, int instrumentedClassesCount) { 81 | this.uniqueStacks = uniqueStacks; 82 | this.started = started; 83 | this.size = size; 84 | this.instrumentedMethodsCount = instrumentedMethodsCount; 85 | this.instrumentedClassesCount = instrumentedClassesCount; 86 | } 87 | 88 | @Override 89 | public String toString() { 90 | return String.format( 91 | "{\n" + 92 | " \"uniqueStacks\":%d,\n" + 93 | " \"started\":\"%s\",\n" + 94 | " \"stackSizeInBytes\":%d,\n" + 95 | " \"stackSizeInMBytes\":%d,\n" + 96 | " \"numberOfMethodsInstrumented\":%d,\n" + 97 | " \"numberOfClassesInstrumented\":%d\n" + 98 | "}", 99 | uniqueStacks, 100 | started, 101 | size, 102 | (size / 1024 / 1024), 103 | instrumentedMethodsCount, 104 | instrumentedClassesCount 105 | ); 106 | } 107 | 108 | } 109 | 110 | private void addNoCache(HttpExchange exchange) { 111 | exchange.getResponseHeaders().add("Cache-Control", "must-revalidate,no-cache,no-store"); 112 | } 113 | 114 | private void addJson(HttpExchange exchange) { 115 | exchange.getResponseHeaders().add("Content-Type", "application/json"); 116 | } 117 | 118 | private void addText(HttpExchange exchange) { 119 | exchange.getResponseHeaders().add("Content-Type", "application/text"); 120 | } 121 | 122 | private HttpHandler createStackHandler() { 123 | return new HttpHandler() { 124 | public void handle(HttpExchange exchange) throws IOException { 125 | addNoCache(exchange); 126 | addText(exchange); 127 | exchange.sendResponseHeaders(200, 0); 128 | Map collectedStacks = InMemoryCountingCollector.getCollectedStacks(); 129 | 130 | StringBuilder sb = new StringBuilder(); 131 | 132 | Map sortedByCount = MapUtil.sortByValue(collectedStacks); 133 | for (String stack : sortedByCount.keySet()) { 134 | sb.append(sortedByCount.get(stack) + "x\n" + stack + "\n"); 135 | sb.append("---------------------------------------------------------------- \n"); 136 | } 137 | exchange.getResponseBody().write(sb.toString().getBytes("UTF-8")); 138 | exchange.close(); 139 | } 140 | }; 141 | } 142 | 143 | private HttpHandler createDumpHandler() { 144 | return new HttpHandler() { 145 | public void handle(HttpExchange exchange) throws IOException { 146 | addNoCache(exchange); 147 | addText(exchange); 148 | exchange.sendResponseHeaders(201, 0); 149 | Map collectedStacks = InMemoryCountingCollector.getCollectedStacks(); 150 | 151 | String file = new SimpleDateFormat("yyyy_dd_MM_HHmmss").format(new Date()); 152 | String outFilename = String.format(LOG_DIR + "/jct_%s.txt", file); 153 | 154 | FileWriter writer = null; 155 | 156 | Map sortedByCount = MapUtil.sortByValue(collectedStacks); 157 | try { 158 | writer = new FileWriter(outFilename); 159 | for (String stack : sortedByCount.keySet()) { 160 | writer.write(sortedByCount.get(stack) + "x\n" + stack + "\n"); 161 | writer.write("---------------------------------------------------------------- \n"); 162 | } 163 | } finally { 164 | writer.flush(); 165 | writer.close(); 166 | } 167 | 168 | exchange.getResponseBody().write((String.format("written to '%s' consider purging now to free memory ...", outFilename)).getBytes("UTF-8")); 169 | exchange.close(); 170 | } 171 | }; 172 | } 173 | 174 | private HttpHandler createStatusHandler() { 175 | return new HttpHandler() { 176 | public void handle(HttpExchange exchange) throws IOException { 177 | addNoCache(exchange); 178 | addJson(exchange); 179 | exchange.sendResponseHeaders(200, 0); 180 | Map collectedStacks = InMemoryCountingCollector.getCollectedStacks(); 181 | 182 | Stats stats = new Stats(collectedStacks.size(), startTime, MapUtil.approximateSizeInBytes(collectedStacks), Statistics.getInstrumentedMethodsCount(), Statistics.getInstrumentedClassesCount()); 183 | 184 | exchange.getResponseBody().write(stats.toString().getBytes("UTF-8")); 185 | exchange.close(); 186 | } 187 | 188 | }; 189 | } 190 | 191 | private HttpHandler createPurgeHandler() { 192 | return new HttpHandler() { 193 | public void handle(HttpExchange exchange) throws IOException { 194 | addNoCache(exchange); 195 | addText(exchange); 196 | exchange.sendResponseHeaders(200, 0); 197 | InMemoryCountingCollector.purgeCollectedStacks(); 198 | exchange.getResponseBody().write("purged".getBytes("UTF-8")); 199 | exchange.close(); 200 | LOGGER.info("successfully purged"); 201 | } 202 | }; 203 | } 204 | 205 | private void stop() { 206 | this.httpServer.stop(0); 207 | } 208 | 209 | private void addShutdownHook(final Server server) { 210 | Runtime.getRuntime().addShutdownHook(new Thread("HealthCheckHttpServerShutdownHook") { 211 | @Override 212 | public void run() { 213 | LOGGER.info("shutdown of server ...."); 214 | server.stop(); 215 | } 216 | }); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/main/java/de/marcelsauer/profiler/processor/noop/AsyncNoopStackProcessor.java: -------------------------------------------------------------------------------- 1 | package de.marcelsauer.profiler.processor.noop; 2 | 3 | import java.util.Collection; 4 | 5 | import de.marcelsauer.profiler.processor.AbstractAsyncStackProcessor; 6 | import de.marcelsauer.profiler.processor.RecordingEvent; 7 | 8 | public class AsyncNoopStackProcessor extends AbstractAsyncStackProcessor { 9 | @Override 10 | protected void doStart() { 11 | 12 | } 13 | 14 | @Override 15 | protected void doStop() { 16 | 17 | } 18 | 19 | @Override 20 | protected void doProcess(Collection snapshots) { 21 | // discard 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/de/marcelsauer/profiler/processor/udp/AsyncUdpStackProcessor.java: -------------------------------------------------------------------------------- 1 | package de.marcelsauer.profiler.processor.udp; 2 | 3 | import java.io.IOException; 4 | import java.net.DatagramPacket; 5 | import java.net.DatagramSocket; 6 | import java.net.InetAddress; 7 | import java.net.SocketException; 8 | import java.net.UnknownHostException; 9 | import java.util.Collection; 10 | 11 | import org.apache.log4j.Logger; 12 | 13 | import de.marcelsauer.profiler.config.Config; 14 | import de.marcelsauer.profiler.processor.AbstractAsyncStackProcessor; 15 | import de.marcelsauer.profiler.processor.RecordingEvent; 16 | 17 | /** 18 | * sends data to configured udp socket 19 | */ 20 | public class AsyncUdpStackProcessor extends AbstractAsyncStackProcessor { 21 | 22 | private static final Logger logger = Logger.getLogger(AsyncUdpStackProcessor.class); 23 | 24 | private DatagramSocket socket; 25 | private InetAddress address; 26 | 27 | @Override 28 | protected void doStart() { 29 | initSocketAndAddress(); 30 | logger.info(String.format("started udp stack processor on %s:%d", Config.get().processor.udpHost, Config.get().processor.udpPort)); 31 | } 32 | 33 | @Override 34 | protected void doStop() { 35 | socket.close(); 36 | } 37 | 38 | @Override 39 | protected void doProcess(Collection snapshots) { 40 | for (RecordingEvent event : snapshots) { 41 | String json = event.asJson(); 42 | byte[] buf = new byte[0]; 43 | try { 44 | buf = json.getBytes("UTF-8"); 45 | DatagramPacket packet = new DatagramPacket(buf, buf.length, address, Config.get().processor.udpPort); 46 | socket.send(packet); 47 | } catch (IOException e) { 48 | // UDP has max length of 64k message ... 49 | throw new RuntimeException("could not send message. discarding. length was: " + buf.length, e); 50 | } 51 | } 52 | } 53 | 54 | 55 | private void initSocketAndAddress() { 56 | try { 57 | socket = new DatagramSocket(); 58 | address = InetAddress.getByName(Config.get().processor.udpHost); 59 | } catch (SocketException | UnknownHostException e) { 60 | throw new RuntimeException(e); 61 | } 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/de/marcelsauer/profiler/recorder/Recorder.java: -------------------------------------------------------------------------------- 1 | package de.marcelsauer.profiler.recorder; 2 | 3 | import java.util.LinkedList; 4 | import java.util.Stack; 5 | 6 | import org.apache.log4j.Logger; 7 | 8 | import de.marcelsauer.profiler.processor.RecordingEvent; 9 | import de.marcelsauer.profiler.processor.StackProcessor; 10 | import de.marcelsauer.profiler.processor.StackProcessorFactory; 11 | 12 | /** 13 | * @author msauer 14 | */ 15 | public class Recorder { 16 | private static final Logger logger = Logger.getLogger(Recorder.class); 17 | 18 | private static final ThreadLocal> stack = new ThreadLocal>() { 19 | @Override 20 | protected Stack initialValue() { 21 | return new Stack<>(); 22 | } 23 | }; 24 | 25 | private static final ThreadLocal> calls = new ThreadLocal>() { 26 | @Override 27 | protected LinkedList initialValue() { 28 | return new LinkedList<>(); 29 | } 30 | }; 31 | 32 | private static final StackProcessor stackProcessor = StackProcessorFactory.getStackProcessor(); 33 | 34 | public static void touch() { 35 | // just to load the class ... 36 | } 37 | 38 | /** 39 | * call to this method is inserted at the beginning of each method via instrumentation. 40 | * do not delete, called via agent instrumentation 41 | */ 42 | public static void start(String methodName) { 43 | de.marcelsauer.profiler.processor.Stack.StackEntry stackEntry = new de.marcelsauer.profiler.processor.Stack.StackEntry(methodName, System.nanoTime(), stack.get().size()); 44 | debug("starting to record " + stackEntry); 45 | stack.get().push(stackEntry); 46 | calls.get().add(stackEntry); 47 | } 48 | 49 | /** 50 | * call to this method is inserted at the beginning of each method via instrumentation. 51 | * do not delete, called via agent instrumentation 52 | */ 53 | public static void stop() { 54 | de.marcelsauer.profiler.processor.Stack.StackEntry stackEntry = stack.get().pop(); 55 | debug("stopping to record " + stackEntry); 56 | stackEntry.end(); 57 | if (stack.get().empty()) { 58 | recordingFinished(); 59 | } 60 | } 61 | 62 | private static void debug(String s) { 63 | logger.debug(s); 64 | } 65 | 66 | private static void info(String s) { 67 | logger.info(s); 68 | } 69 | 70 | private static void recordingFinished() { 71 | debug("recording finished for stack"); 72 | 73 | Statistics.recordingFinished(); 74 | stackProcessor.process(new RecordingEvent(new de.marcelsauer.profiler.processor.Stack(calls.get()))); 75 | 76 | stack.set(new Stack()); 77 | calls.set(new LinkedList()); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/de/marcelsauer/profiler/recorder/Statistics.java: -------------------------------------------------------------------------------- 1 | package de.marcelsauer.profiler.recorder; 2 | 3 | import java.util.concurrent.atomic.AtomicInteger; 4 | 5 | /** 6 | * @author msauer 7 | */ 8 | public class Statistics { 9 | private static int instrumentedMethodsCount = 0; 10 | private static int instrumentedClassesCount = 0; 11 | private static AtomicInteger stacksRecordedCounter = new AtomicInteger(0); 12 | 13 | public static void addInstrumentedClass(String fqClazz) { 14 | instrumentedClassesCount++; 15 | } 16 | 17 | public static void addInstrumentedMethod(String fqMethod) { 18 | instrumentedMethodsCount++; 19 | } 20 | 21 | public static int getInstrumentedMethodsCount() { 22 | return instrumentedMethodsCount; 23 | } 24 | 25 | public static int getInstrumentedClassesCount() { 26 | return instrumentedClassesCount; 27 | } 28 | 29 | public static void recordingFinished() { 30 | stacksRecordedCounter.incrementAndGet(); 31 | } 32 | 33 | public static long getStacksRecordedCounter() { 34 | return stacksRecordedCounter.intValue(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/de/marcelsauer/profiler/transformer/Transformer.java: -------------------------------------------------------------------------------- 1 | package de.marcelsauer.profiler.transformer; 2 | 3 | import java.lang.instrument.ClassFileTransformer; 4 | import java.lang.instrument.IllegalClassFormatException; 5 | import java.security.ProtectionDomain; 6 | 7 | import org.apache.log4j.Logger; 8 | 9 | import de.marcelsauer.profiler.config.Config; 10 | import de.marcelsauer.profiler.instrumenter.DefaultInstrumentationCallback; 11 | import de.marcelsauer.profiler.instrumenter.Instrumenter; 12 | import de.marcelsauer.profiler.transformer.filter.CombinedFilter; 13 | 14 | /** 15 | * @author msauer 16 | */ 17 | public class Transformer implements ClassFileTransformer { 18 | 19 | private static final Logger logger = Logger.getLogger(Transformer.class); 20 | private final CombinedFilter combinedFilter; 21 | 22 | Instrumenter instrumenter; 23 | 24 | public Transformer(Config config) { 25 | this.instrumenter = new Instrumenter(new DefaultInstrumentationCallback(config)); 26 | this.combinedFilter = new CombinedFilter(config); 27 | logger.debug("using transformer: " + Transformer.class.getName()); 28 | } 29 | 30 | public byte[] transform(ClassLoader loader, String className, Class klass, ProtectionDomain domain, byte[] byteCode) throws IllegalClassFormatException { 31 | boolean shouldBeInstrumented = combinedFilter.matches(className); 32 | if (shouldBeInstrumented) { 33 | logger.debug(String.format("[instrumenting] '%s'.", className)); 34 | return this.instrumenter.instrument(className, loader, klass); 35 | } 36 | return byteCode; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/de/marcelsauer/profiler/transformer/filter/CombinedFilter.java: -------------------------------------------------------------------------------- 1 | package de.marcelsauer.profiler.transformer.filter; 2 | 3 | import de.marcelsauer.profiler.config.Config; 4 | 5 | public class CombinedFilter implements Filter { 6 | 7 | private final Config config; 8 | private final RegexFilter inclusionFilter; 9 | private final RegexFilter exclusionFilter; 10 | 11 | public CombinedFilter(Config config) { 12 | this.config = config; 13 | this.inclusionFilter = new RegexFilter(config.classes.included); 14 | this.exclusionFilter = new RegexFilter(config.classes.excluded); 15 | } 16 | 17 | public boolean matches(String fqClassName) { 18 | if (exclusionFilter.matches(fqClassName)) { 19 | return false; 20 | } 21 | return inclusionFilter.matches(fqClassName); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/de/marcelsauer/profiler/transformer/filter/Filter.java: -------------------------------------------------------------------------------- 1 | package de.marcelsauer.profiler.transformer.filter; 2 | 3 | /** 4 | * @author msauer 5 | */ 6 | public interface Filter { 7 | boolean matches(String fqClassName); 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/de/marcelsauer/profiler/transformer/filter/RegexFilter.java: -------------------------------------------------------------------------------- 1 | package de.marcelsauer.profiler.transformer.filter; 2 | 3 | import java.util.Set; 4 | 5 | /** 6 | * @author msauer 7 | */ 8 | public class RegexFilter implements Filter { 9 | 10 | private final Set config; 11 | 12 | public RegexFilter(Set config) { 13 | this.config = config; 14 | } 15 | 16 | public boolean matches(String fqClassName) { 17 | if (fqClassName == null) { 18 | return false; 19 | } 20 | for (String fgClassRegex : config) { 21 | if (fqClassName.matches(fgClassRegex)) { 22 | return true; 23 | } 24 | } 25 | return false; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/de/marcelsauer/profiler/util/Util.java: -------------------------------------------------------------------------------- 1 | package de.marcelsauer.profiler.util; 2 | 3 | import javassist.CtMethod; 4 | import javassist.bytecode.AccessFlag; 5 | 6 | /** 7 | * @author msauer 8 | */ 9 | public class Util { 10 | public static String toJVM(String clazz) { 11 | return clazz.replace(".", "/"); 12 | } 13 | 14 | public static String fromJVM(String clazz) { 15 | return clazz.replace("/", "."); 16 | } 17 | 18 | public static boolean isPublic(CtMethod declaredMethod) { 19 | return AccessFlag.isPublic(declaredMethod.getModifiers()); 20 | } 21 | 22 | public static String packageName(String clazz) { 23 | if (clazz.contains("/")) { 24 | return Util.fromJVM(clazz).substring(0, clazz.lastIndexOf("/")); 25 | } else { 26 | return clazz.substring(0, clazz.lastIndexOf(".")); 27 | } 28 | } 29 | 30 | public static boolean isEmpty(String in) { 31 | return in == null || in.trim().length() == 0; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Premain-Class: de.marcelsauer.profiler.agent.Agent 3 | Agent-Class: de.marcelsauer.profiler.agent.Agent 4 | Can-Redefine-Classes: true 5 | Can-Retransform-Classes: true 6 | Can-Set-Native-Method-Prefix: true 7 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/config.yaml: -------------------------------------------------------------------------------- 1 | classes: 2 | included: [] 3 | excluded: [] 4 | -------------------------------------------------------------------------------- /src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger=${jct.loglevel}, logfile 2 | 3 | log4j.appender.logfile=org.apache.log4j.FileAppender 4 | log4j.appender.logfile.encoding=UTF-8 5 | log4j.appender.logfile.File=${jct.logDir}/jct_agent.log 6 | log4j.appender.logfile.layout=org.apache.log4j.PatternLayout 7 | log4j.appender.logfile.layout.ConversionPattern=[%-5p] %d %c - %m%n 8 | 9 | log4j.appender.stackfile=org.apache.log4j.FileAppender 10 | log4j.appender.stackfile.encoding=UTF-8 11 | log4j.appender.stackfile.layout=org.apache.log4j.PatternLayout 12 | log4j.appender.stackfile.layout.ConversionPattern={"thread":"%t", "stack": {"%m"}}%n 13 | -------------------------------------------------------------------------------- /src/test/java/de/marcelsauer/profiler/config/ConfigCreatorTest.java: -------------------------------------------------------------------------------- 1 | package de.marcelsauer.profiler.config; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertNotNull; 5 | 6 | import org.junit.Test; 7 | 8 | public class ConfigCreatorTest { 9 | 10 | @Test 11 | public void testThatNoIncludesOrExcludesDoesntBreak() { 12 | Config.initDefaultFromYamlFile(); 13 | } 14 | 15 | @Test 16 | public void testThatPackagesAreInConfig() { 17 | //given 18 | 19 | //when 20 | Config configuration = Config.initDefaultFromYamlFile(); 21 | 22 | //then 23 | assertNotNull(configuration); 24 | assertEquals(0, configuration.classes.included.size()); 25 | assertEquals(0, configuration.classes.excluded.size()); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/test/java/de/marcelsauer/profiler/processor/RecordingEventTest.java: -------------------------------------------------------------------------------- 1 | package de.marcelsauer.profiler.processor; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import java.util.Arrays; 6 | import java.util.Collections; 7 | 8 | import org.junit.Test; 9 | 10 | public class RecordingEventTest { 11 | 12 | @Test 13 | public void thatJsonCanBeQueried() { 14 | // given 15 | Stack.StackEntry e1 = new Stack.StackEntry("m1", 123, 1); 16 | Stack.StackEntry e2 = new Stack.StackEntry("m2", 456, 2); 17 | Stack.StackEntry e3 = new Stack.StackEntry("m3", 789, 1); 18 | RecordingEvent event1 = new RecordingEvent(new Stack(Arrays.asList(e1, e2, e3))); 19 | 20 | // when 21 | String result = event1.asJson(); 22 | 23 | // then 24 | assertEquals("{\"timestampMillis\" : \"" + event1.timestampMillis + "\", \"stack\" : [\"m1\",\"m2\",\"m3\"]}", result); 25 | } 26 | 27 | @Test 28 | public void thatJsonCanBeQueriedForOneCall() { 29 | // given 30 | Stack.StackEntry e1 = new Stack.StackEntry("m1", 123, 1); 31 | RecordingEvent event1 = new RecordingEvent(new Stack(Collections.singletonList(e1))); 32 | 33 | // when 34 | String result = event1.asJson(); 35 | 36 | // then 37 | assertEquals("{\"timestampMillis\" : \"" + event1.timestampMillis + "\", \"stack\" : [\"m1\"]}", result); 38 | } 39 | 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/de/marcelsauer/profiler/recorder/RecorderTest.java: -------------------------------------------------------------------------------- 1 | package de.marcelsauer.profiler.recorder; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import org.junit.Test; 6 | 7 | import de.marcelsauer.profiler.processor.inmemory.InMemoryCountingCollector; 8 | 9 | public class RecorderTest { 10 | 11 | @Test 12 | public void thatStacksAreCorrectlyRecordedAndCounted() { 13 | String nestedMethodA = "method_A"; 14 | String nestedMethodA_A = "method_A_A"; 15 | String nestedMethodA_A_A = "method_A_A_A"; 16 | String nestedMethodB = "method_B"; 17 | 18 | Recorder.start("method_1"); 19 | 20 | Recorder.start(nestedMethodA); 21 | Recorder.start(nestedMethodA_A); 22 | Recorder.stop(); 23 | Recorder.start(nestedMethodA_A); 24 | Recorder.stop(); 25 | Recorder.start(nestedMethodA_A); 26 | Recorder.start(nestedMethodA_A_A); 27 | Recorder.stop(); 28 | Recorder.stop(); 29 | Recorder.start(nestedMethodA_A); 30 | Recorder.stop(); 31 | Recorder.stop(); 32 | Recorder.start(nestedMethodA); 33 | Recorder.stop(); 34 | Recorder.start(nestedMethodA); 35 | Recorder.stop(); 36 | Recorder.start(nestedMethodA); 37 | Recorder.stop(); 38 | 39 | Recorder.start(nestedMethodB); 40 | Recorder.stop(); 41 | Recorder.start(nestedMethodB); 42 | Recorder.stop(); 43 | Recorder.start(nestedMethodB); 44 | Recorder.stop(); 45 | Recorder.start(nestedMethodB); 46 | Recorder.stop(); 47 | 48 | Recorder.stop(); 49 | 50 | String next = InMemoryCountingCollector.getCollectedStacks().keySet().iterator().next(); 51 | assertEquals("[StackEntry{methodName='method_1'}, StackEntry{methodName='method_A'}, StackEntry{methodName='method_A_A'}, StackEntry{methodName='method_A_A'}, StackEntry{methodName='method_A_A'}, StackEntry{methodName='method_A_A_A'}, StackEntry{methodName='method_A_A'}, StackEntry{methodName='method_A'}, StackEntry{methodName='method_A'}, StackEntry{methodName='method_A'}, StackEntry{methodName='method_B'}, StackEntry{methodName='method_B'}, StackEntry{methodName='method_B'}, StackEntry{methodName='method_B'}]", next); 52 | 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/de/marcelsauer/profiler/transformer/TransformerTest.java: -------------------------------------------------------------------------------- 1 | package de.marcelsauer.profiler.transformer; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.mockito.Matchers.any; 5 | import static org.mockito.Matchers.eq; 6 | import static org.mockito.Mockito.mock; 7 | import static org.mockito.Mockito.when; 8 | 9 | import java.lang.instrument.IllegalClassFormatException; 10 | 11 | import org.junit.Test; 12 | 13 | import de.marcelsauer.profiler.config.Config; 14 | import de.marcelsauer.profiler.instrumenter.Instrumenter; 15 | 16 | public class TransformerTest { 17 | 18 | private final Config config = new Config(); 19 | 20 | @Test 21 | public void thatPackagesCanBeIgnored() throws IllegalClassFormatException { 22 | // given 23 | config.classes.excluded.add("^java.*"); 24 | 25 | byte[] inBuffer = new byte[10]; 26 | 27 | // when 28 | byte[] outBuffer = new Transformer(config).transform(null, "java/lang/String", null, null, inBuffer); 29 | 30 | // then 31 | assertEquals(outBuffer, inBuffer); 32 | } 33 | 34 | @Test 35 | public void thatNonIncludedPackagesAreNotProcessed() throws IllegalClassFormatException { 36 | // given 37 | config.classes.included.add("not.there"); 38 | 39 | byte[] inBuffer = new byte[10]; 40 | 41 | // when 42 | byte[] outBuffer = new Transformer(config).transform(null, "de/marcelsauer/profiler/ClassA", null, null, inBuffer); 43 | 44 | // then 45 | assertEquals(outBuffer, inBuffer); 46 | } 47 | 48 | @Test 49 | public void thatPackageCanBeIncluded() throws IllegalClassFormatException { 50 | // given 51 | config.classes.included.add("^de.marcelsauer.*"); 52 | Transformer transformer = new Transformer(config); 53 | Instrumenter instrumenter = mock(Instrumenter.class); 54 | transformer.instrumenter = instrumenter; 55 | byte[] out = {}; 56 | when(instrumenter.instrument(eq("de/marcelsauer/profiler/ClassA"), any(ClassLoader.class), any(Class.class))).thenReturn(out); 57 | 58 | // when 59 | byte[] outBuffer = transformer.transform(null, "de/marcelsauer/profiler/ClassA", null, null, new byte[]{}); 60 | 61 | // then 62 | assertEquals(outBuffer, out); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/test/java/de/marcelsauer/profiler/transformer/filter/CombinedFilterTest.java: -------------------------------------------------------------------------------- 1 | package de.marcelsauer.profiler.transformer.filter; 2 | 3 | import static org.junit.Assert.assertFalse; 4 | import static org.junit.Assert.assertTrue; 5 | 6 | import org.junit.Test; 7 | 8 | import de.marcelsauer.profiler.config.Config; 9 | 10 | public class CombinedFilterTest { 11 | 12 | private Config config = new Config(); 13 | private Filter filter = new CombinedFilter(config); 14 | 15 | @Test 16 | public void thatInvalidArgumentsDoNotMatch() { 17 | assertFalse(filter.matches(null)); 18 | assertFalse(filter.matches("")); 19 | } 20 | 21 | @Test 22 | public void thatClassesCanBeIncludedViaAllMatcher() { 23 | // given 24 | config.classes.included.add(".*"); 25 | 26 | // when 27 | assertTrue(filter.matches("a.b.C")); 28 | } 29 | 30 | @Test 31 | public void thatClassesWillBeExcluded() { 32 | // given 33 | config.classes.included.add("a.b.C"); 34 | config.classes.excluded.add("a.b.C"); 35 | 36 | // when 37 | assertFalse(filter.matches("a.b.C")); 38 | } 39 | 40 | @Test 41 | public void thatClassesWillBeExcludedViaAllMatcher() { 42 | // given 43 | config.classes.included.add("a.b.C"); 44 | config.classes.excluded.add(".*"); 45 | 46 | // when 47 | assertFalse(filter.matches("a.b.C")); 48 | } 49 | 50 | @Test 51 | public void thatClassesWillBeExcludedViaAllMatcherGlobally() { 52 | // given 53 | config.classes.included.add(".*"); 54 | config.classes.excluded.add(".*"); 55 | 56 | // when 57 | assertFalse(filter.matches("a")); 58 | assertFalse(filter.matches("a.b.C")); 59 | } 60 | 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/test/java/de/marcelsauer/profiler/transformer/filter/RegexFilterTest.java: -------------------------------------------------------------------------------- 1 | package de.marcelsauer.profiler.transformer.filter; 2 | 3 | import static org.junit.Assert.assertFalse; 4 | import static org.junit.Assert.assertTrue; 5 | 6 | import java.util.HashSet; 7 | import java.util.Set; 8 | 9 | import org.junit.Test; 10 | 11 | public class RegexFilterTest { 12 | private Set config = new HashSet<>(); 13 | private Filter filter = new RegexFilter(config); 14 | 15 | @Test 16 | public void thatInvalidArgumentsNotBeInstrumented() { 17 | assertFalse(filter.matches(null)); 18 | assertFalse(filter.matches("")); 19 | } 20 | 21 | @Test 22 | public void thatClassesCanBeIncluded() { 23 | // given 24 | config.add("a.b.C"); 25 | config.add("d.*"); 26 | 27 | // when 28 | assertTrue(filter.matches("a.b.C")); 29 | assertTrue(filter.matches("d.1.2.D")); 30 | } 31 | 32 | @Test 33 | public void thatClassesCanBeIncludedViaAllMatcher() { 34 | // given 35 | config.add(".*"); 36 | 37 | // when 38 | assertTrue(filter.matches("a")); 39 | assertTrue(filter.matches("a.b")); 40 | assertTrue(filter.matches("a.b.C")); 41 | } 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/integration/AbtractAsyncIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package integration; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import java.util.Random; 6 | import java.util.concurrent.ExecutorService; 7 | import java.util.concurrent.Executors; 8 | 9 | import org.junit.Test; 10 | 11 | import de.marcelsauer.profiler.processor.AbstractAsyncStackProcessor; 12 | import de.marcelsauer.profiler.recorder.Statistics; 13 | import integration.package1.A; 14 | import integration.package1.B; 15 | 16 | /** 17 | * assumes you've build the project via

mvn clean package
so that ./target/ contains the agent jar 18 | */ 19 | abstract class AbtractAsyncIntegrationTest { 20 | 21 | @Test 22 | public void thatAsyncProcessorWorks() throws Exception { 23 | // given 24 | int expectedMethodCallCount = 6; 25 | int callCount = 1000; 26 | final int expectedStackCount = expectedMethodCallCount * callCount; 27 | 28 | // when 29 | ExecutorService executor = Executors.newFixedThreadPool(50); 30 | for (int i = 0; i < callCount; i++) { 31 | executor.submit(new Runnable() { 32 | @Override 33 | public void run() { 34 | A a = new A(); 35 | a.a(); 36 | randomSleep(); 37 | a.a(); 38 | a.a(); 39 | 40 | randomSleep(); 41 | 42 | B b = new B(); 43 | b.b(); 44 | b.b(); 45 | randomSleep(); 46 | b.b(); 47 | } 48 | 49 | private void randomSleep() { 50 | Random r = new Random(); 51 | int result = r.nextInt(30 - 10) + 10; 52 | try { 53 | Thread.sleep(result); 54 | } catch (InterruptedException e) { 55 | throw new RuntimeException(e); 56 | } 57 | } 58 | }); 59 | } 60 | 61 | new WaitOrTimeout(new WaitOrTimeout.Callback() { 62 | @Override 63 | public boolean isTrue() { 64 | return expectedStackCount == AbstractAsyncStackProcessor.getSuccessfullyProcessedStacksCounter(); 65 | } 66 | }, expectedStackCount * 3).go(); 67 | 68 | // give some more time ... 69 | Thread.sleep(2000); 70 | 71 | // then 72 | assertEquals(expectedStackCount, Statistics.getStacksRecordedCounter()); 73 | assertEquals(expectedStackCount, AbstractAsyncStackProcessor.getStacksCapturedCounter()); 74 | assertEquals(expectedStackCount, AbstractAsyncStackProcessor.getSuccessfullyProcessedStacksCounter()); 75 | assertEquals(Statistics.getStacksRecordedCounter(), AbstractAsyncStackProcessor.getStacksCapturedCounter()); 76 | 77 | doCustomAssertions(expectedStackCount); 78 | 79 | } 80 | 81 | protected abstract void doCustomAssertions(int expectedStackCount); 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/test/java/integration/AfterVmStartupAgentLoader.java: -------------------------------------------------------------------------------- 1 | package integration; 2 | 3 | import java.lang.management.ManagementFactory; 4 | 5 | import com.sun.tools.attach.VirtualMachine; 6 | 7 | class AfterVmStartupAgentLoader { 8 | 9 | static void loadAgent(String jarFilePath) { 10 | String nameOfRunningVM = ManagementFactory.getRuntimeMXBean().getName(); 11 | int p = nameOfRunningVM.indexOf('@'); 12 | String pid = nameOfRunningVM.substring(0, p); 13 | 14 | try { 15 | VirtualMachine vm = VirtualMachine.attach(pid); 16 | vm.loadAgent(jarFilePath, ""); 17 | vm.detach(); 18 | } catch (Exception e) { 19 | throw new RuntimeException(e); 20 | } 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/integration/AsyncFileIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package integration; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import java.io.IOException; 6 | import java.nio.charset.Charset; 7 | import java.nio.file.Files; 8 | import java.nio.file.Path; 9 | import java.nio.file.Paths; 10 | import java.text.SimpleDateFormat; 11 | import java.util.Date; 12 | 13 | import org.junit.Before; 14 | 15 | /** 16 | * assumes you've build the project via
mvn clean package
so that ./target/ contains the agent jar 17 | * 18 | * @author msauer 19 | */ 20 | public class AsyncFileIntegrationTest extends AbtractAsyncIntegrationTest { 21 | 22 | private static final String JAR_FILE_PATH = "./target/java-code-tracer-1.0-SNAPSHOT-jar-with-dependencies.jar"; 23 | 24 | static { 25 | System.setProperty("jct.config", "./src/test/resources/integration/test-config-asyncfile.yaml"); 26 | System.setProperty("jct.logDir", "./target"); 27 | AfterVmStartupAgentLoader.loadAgent(JAR_FILE_PATH); 28 | } 29 | 30 | @Before 31 | public void cleanup() throws IOException { 32 | Path path = Paths.get(getFilename()); 33 | try { 34 | Files.delete(path); 35 | } catch (java.nio.file.NoSuchFileException e) { 36 | // ignore 37 | } 38 | } 39 | 40 | private String getFilename() { 41 | String fileDateTimePart = new SimpleDateFormat("yyyy_dd_MM").format(new Date()); 42 | return "/tmp/stacks/jct_" + fileDateTimePart + ".log"; 43 | } 44 | 45 | @Override 46 | protected void doCustomAssertions(int expectedStackCount) { 47 | try { 48 | int size = Files.readAllLines(Paths.get(getFilename()), Charset.defaultCharset()).size(); 49 | assertEquals(expectedStackCount, size); 50 | } catch (IOException e) { 51 | throw new RuntimeException(e); 52 | } 53 | 54 | 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/test/java/integration/AsyncUdpIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package integration; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import java.io.IOException; 6 | import java.net.DatagramPacket; 7 | import java.net.DatagramSocket; 8 | import java.net.SocketException; 9 | 10 | import org.junit.Before; 11 | 12 | /** 13 | * @author msauer 14 | */ 15 | public class AsyncUdpIntegrationTest extends AbtractAsyncIntegrationTest { 16 | 17 | private static final String JAR_FILE_PATH = "./target/java-code-tracer-1.0-SNAPSHOT-jar-with-dependencies.jar"; 18 | 19 | static { 20 | System.setProperty("jct.config", "./src/test/resources/integration/test-config-asyncudp.yaml"); 21 | System.setProperty("jct.logDir", "./target"); 22 | AfterVmStartupAgentLoader.loadAgent(JAR_FILE_PATH); 23 | } 24 | 25 | @Before 26 | public void startServer() { 27 | new UdpServer().start(); 28 | } 29 | 30 | @Override 31 | protected void doCustomAssertions(int expectedStackCount) { 32 | assertEquals(expectedStackCount, UdpServer.counter); 33 | } 34 | 35 | private static class UdpServer { 36 | static int counter = 0; 37 | 38 | void start() { 39 | Thread thread = new Thread(new Runnable() { 40 | @Override 41 | public void run() { 42 | DatagramSocket serverSocket = null; 43 | try { 44 | serverSocket = new DatagramSocket(9999); 45 | } catch (SocketException e) { 46 | e.printStackTrace(); 47 | } 48 | byte[] receiveData = new byte[1024]; 49 | while (true) { 50 | DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length); 51 | try { 52 | serverSocket.receive(receivePacket); 53 | } catch (IOException e) { 54 | throw new RuntimeException(e); 55 | } 56 | counter++; 57 | } 58 | } 59 | }); 60 | thread.start(); 61 | } 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/test/java/integration/CountingInMemoryStackProcessor.java: -------------------------------------------------------------------------------- 1 | package integration; 2 | 3 | import de.marcelsauer.profiler.processor.RecordingEvent; 4 | import de.marcelsauer.profiler.processor.inmemory.InMemoryStackProcessor; 5 | 6 | public class CountingInMemoryStackProcessor extends InMemoryStackProcessor { 7 | public static int count = 0; 8 | 9 | public void process(RecordingEvent event) { 10 | super.process(event); 11 | count++; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/integration/InMemoryCountingIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package integration; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | 6 | import java.io.BufferedReader; 7 | import java.io.InputStreamReader; 8 | import java.net.HttpURLConnection; 9 | import java.net.URL; 10 | import java.util.Collection; 11 | 12 | import org.junit.Test; 13 | 14 | import de.marcelsauer.profiler.processor.inmemory.InMemoryCountingCollector; 15 | import de.marcelsauer.profiler.recorder.Statistics; 16 | import integration.package1.A; 17 | import integration.package1.B; 18 | 19 | /** 20 | * assumes you've build the project via
mvn clean package
so that ./target/ contains the agent jar 21 | * 22 | * @author msauer 23 | */ 24 | public class InMemoryCountingIntegrationTest { 25 | 26 | private static final String JAR_FILE_PATH = "./target/java-code-tracer-1.0-SNAPSHOT-jar-with-dependencies.jar"; 27 | 28 | static { 29 | System.setProperty("jct.config", "./src/test/resources/integration/test-config.yaml"); 30 | AfterVmStartupAgentLoader.loadAgent(JAR_FILE_PATH); 31 | } 32 | 33 | @Test 34 | public void thatStackIsInstrumented() throws Exception { 35 | 36 | // given 37 | A a = new A(); 38 | 39 | // when 40 | a.a(); 41 | a.a(); 42 | a.a(); 43 | 44 | B b = new B(); 45 | b.b(); 46 | b.b(); 47 | b.b(); 48 | 49 | //String response = getRequest("http://localhost:9001/status/"); 50 | 51 | //Thread.sleep(2000); 52 | // meanwhile .... 53 | // curl localhost:9001/status/ 54 | // curl localhost:9001/purge/ 55 | 56 | // then 57 | // assertTrue(response.contains("uniqueStacks")); 58 | assertEquals(11, Statistics.getInstrumentedClassesCount()); 59 | assertEquals(14, Statistics.getInstrumentedMethodsCount()); 60 | assertEquals(2, InMemoryCountingCollector.getCollectedStacks().size()); 61 | 62 | Collection values = InMemoryCountingCollector.getCollectedStacks().values(); 63 | assertTrue(values.contains(3)); 64 | assertEquals(6, CountingInMemoryStackProcessor.count); 65 | } 66 | 67 | private String getRequest(String urlToRead) throws Exception { 68 | StringBuilder result = new StringBuilder(); 69 | URL url = new URL(urlToRead); 70 | HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 71 | conn.setRequestMethod("GET"); 72 | BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream())); 73 | String line; 74 | while ((line = rd.readLine()) != null) { 75 | result.append(line); 76 | } 77 | rd.close(); 78 | return result.toString(); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/test/java/integration/WaitOrTimeout.java: -------------------------------------------------------------------------------- 1 | package integration; 2 | 3 | import java.util.Date; 4 | 5 | public class WaitOrTimeout { 6 | 7 | private final Callback cb; 8 | private final int maxMillisWait; 9 | 10 | public void go() { 11 | final Date start = new Date(); 12 | 13 | while (true) { 14 | if (cb.isTrue()) { 15 | return; 16 | } 17 | long elapsed = new Date().getTime() - start.getTime(); 18 | if (elapsed >= maxMillisWait) { 19 | throw new RuntimeException("time out while waiting for condition"); 20 | } 21 | try { 22 | Thread.sleep(100); 23 | } catch (InterruptedException e) { 24 | throw new RuntimeException(e); 25 | } 26 | } 27 | 28 | } 29 | 30 | public interface Callback { 31 | boolean isTrue(); 32 | } 33 | 34 | public WaitOrTimeout(Callback cb, int maxMillisWait) { 35 | this.cb = cb; 36 | this.maxMillisWait = maxMillisWait; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/integration/package1/A.java: -------------------------------------------------------------------------------- 1 | package integration.package1; 2 | 3 | /** 4 | * Created by msauer on 6/26/15. 5 | */ 6 | public class A implements InterfaceA { 7 | 8 | private final B b = new B(); 9 | 10 | public void a() { 11 | b.b(); 12 | b.b(); 13 | b.b(); 14 | b.b(); 15 | b.b(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/integration/package1/AbstractClass.java: -------------------------------------------------------------------------------- 1 | package integration.package1; 2 | 3 | /** 4 | * Created by msauer on 6/26/15. 5 | */ 6 | abstract class AbstractClass { 7 | 8 | abstract void abstractClassMethod(); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/integration/package1/B.java: -------------------------------------------------------------------------------- 1 | package integration.package1; 2 | 3 | import integration.package2.Collaborator1; 4 | import integration.package2.Collaborator2; 5 | 6 | /** 7 | * Created by msauer on 6/26/15. 8 | */ 9 | public class B { 10 | 11 | private final SubClass subClass = new SubClass(); 12 | private final Collaborator1 collaborator1 = new Collaborator1(); 13 | private final Collaborator2 collaborator2 = new Collaborator2(); 14 | 15 | public void b() { 16 | subClass.subclassMethod(); 17 | collaborator1.collaborator1Method(); 18 | collaborator1.collaborator1Method(); 19 | collaborator1.collaborator1Method(); 20 | collaborator1.collaborator1Method(); 21 | collaborator2.collaborator2Method(); 22 | collaborator2.collaborator2Method(); 23 | collaborator2.collaborator2Method(); 24 | collaborator2.collaborator2Method(); 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/integration/package1/ClassWithInnerClasses.java: -------------------------------------------------------------------------------- 1 | package integration.package1; 2 | 3 | /** 4 | * Created by msauer on 6/26/15. 5 | */ 6 | class ClassWithInnerClasses { 7 | 8 | 9 | void classWithInnerClasses() { 10 | new Runnable() { 11 | public void run() { 12 | 13 | } 14 | }.run(); 15 | 16 | new StaticInnerClass().staticInnerClassMethod(); 17 | new NonStaticInnerClass().nonStaticInnerClassMethod(); 18 | } 19 | 20 | static class StaticInnerClass { 21 | public void staticInnerClassMethod() { 22 | 23 | } 24 | } 25 | 26 | class NonStaticInnerClass { 27 | public void nonStaticInnerClassMethod() { 28 | 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/integration/package1/InterfaceA.java: -------------------------------------------------------------------------------- 1 | package integration.package1; 2 | 3 | /** 4 | * Created by msauer on 6/26/15. 5 | */ 6 | public interface InterfaceA { 7 | } 8 | -------------------------------------------------------------------------------- /src/test/java/integration/package1/SubClass.java: -------------------------------------------------------------------------------- 1 | package integration.package1; 2 | 3 | /** 4 | * Created by msauer on 6/26/15. 5 | */ 6 | class SubClass extends SuperClass implements SubInterface { 7 | 8 | private ClassWithInnerClasses classWithInnerClasses = new ClassWithInnerClasses(); 9 | 10 | void subclassMethod() { 11 | subinterfaceMethod(); 12 | classWithInnerClasses.classWithInnerClasses(); 13 | } 14 | 15 | public void subinterfaceMethod() { 16 | superinterfaceMethod(); 17 | } 18 | 19 | public void superinterfaceMethod() { 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/integration/package1/SubInterface.java: -------------------------------------------------------------------------------- 1 | package integration.package1; 2 | 3 | /** 4 | * Created by msauer on 6/26/15. 5 | */ 6 | interface SubInterface extends Superinterface { 7 | void subinterfaceMethod(); 8 | } 9 | -------------------------------------------------------------------------------- /src/test/java/integration/package1/SuperClass.java: -------------------------------------------------------------------------------- 1 | package integration.package1; 2 | 3 | /** 4 | * Created by msauer on 6/26/15. 5 | */ 6 | class SuperClass extends AbstractClass { 7 | 8 | void superclassMethod() { 9 | 10 | } 11 | 12 | @Override 13 | void abstractClassMethod() { 14 | 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/integration/package1/Superinterface.java: -------------------------------------------------------------------------------- 1 | package integration.package1; 2 | 3 | /** 4 | * Created by msauer on 6/26/15. 5 | */ 6 | interface Superinterface { 7 | void superinterfaceMethod(); 8 | } 9 | -------------------------------------------------------------------------------- /src/test/java/integration/package2/Collaborator1.java: -------------------------------------------------------------------------------- 1 | package integration.package2; 2 | 3 | /** 4 | * Created by msauer on 6/26/15. 5 | */ 6 | public class Collaborator1 { 7 | 8 | public void collaborator1Method() { 9 | 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/test/java/integration/package2/Collaborator2.java: -------------------------------------------------------------------------------- 1 | package integration.package2; 2 | 3 | /** 4 | * Created by msauer on 6/26/15. 5 | */ 6 | public class Collaborator2 { 7 | 8 | public void collaborator2Method() { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/test/resources/integration/test-config-asyncfile.yaml: -------------------------------------------------------------------------------- 1 | # Comment 2 | classes: 3 | included: 4 | - ^integration.package.* 5 | excluded: 6 | - ^something.excluded.* 7 | - .*junit.* 8 | - .*intellij.* 9 | - .*eclipse.* 10 | processor: 11 | fullQualifiedClass: de.marcelsauer.profiler.processor.file.AsyncFileWritingStackProcessor 12 | stackFolderName: /tmp/stacks/ 13 | -------------------------------------------------------------------------------- /src/test/resources/integration/test-config-asyncudp.yaml: -------------------------------------------------------------------------------- 1 | # Comment 2 | classes: 3 | included: 4 | - ^integration.package.* 5 | excluded: 6 | - ^something.excluded.* 7 | - .*junit.* 8 | - .*intellij.* 9 | - .*eclipse.* 10 | processor: 11 | fullQualifiedClass: de.marcelsauer.profiler.processor.udp.AsyncUdpStackProcessor 12 | udpHost: localhost 13 | udpPort: 9999 14 | -------------------------------------------------------------------------------- /src/test/resources/integration/test-config.yaml: -------------------------------------------------------------------------------- 1 | # Comment 2 | classes: 3 | included: 4 | - ^integration.package.* 5 | excluded: 6 | - ^something.excluded.* 7 | - .*junit.* 8 | - .*intellij.* 9 | - .*eclipse.* 10 | processor: 11 | fullQualifiedClass: integration.CountingInMemoryStackProcessor 12 | -------------------------------------------------------------------------------- /src/test/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger=info, console 2 | 3 | log4j.appender.console=org.apache.log4j.ConsoleAppender 4 | log4j.appender.console.layout=org.apache.log4j.PatternLayout 5 | log4j.appender.console.layout.ConversionPattern=[%-5p] %d %c - %m%n 6 | --------------------------------------------------------------------------------