{"id":15069343,"url":"https://github.com/niesfisch/java-code-tracer","last_synced_at":"2025-06-23T12:36:30.414Z","repository":{"id":47111941,"uuid":"45256455","full_name":"niesfisch/java-code-tracer","owner":"niesfisch","description":"JCT is a call graph generator that works via byte code instrumentation. it records the flow through your application to gather call statistics. It helps to analyze which code is still used in production and which code can be removed (e.g. for big monolithic legacy applications)","archived":false,"fork":false,"pushed_at":"2022-11-29T16:20:12.000Z","size":694,"stargazers_count":21,"open_issues_count":0,"forks_count":7,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-10T17:33:35.694Z","etag":null,"topics":["bytecode","callgraph","java","javaagent","jdk","jre","jvm","legacy","profile","profiler","stack","stacktrace","tracer","tracing"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/niesfisch.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-10-30T14:39:24.000Z","updated_at":"2025-03-02T17:34:14.000Z","dependencies_parsed_at":"2023-01-23T09:15:24.835Z","dependency_job_id":null,"html_url":"https://github.com/niesfisch/java-code-tracer","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/niesfisch/java-code-tracer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/niesfisch%2Fjava-code-tracer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/niesfisch%2Fjava-code-tracer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/niesfisch%2Fjava-code-tracer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/niesfisch%2Fjava-code-tracer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/niesfisch","download_url":"https://codeload.github.com/niesfisch/java-code-tracer/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/niesfisch%2Fjava-code-tracer/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261480454,"owners_count":23164919,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bytecode","callgraph","java","javaagent","jdk","jre","jvm","legacy","profile","profiler","stack","stacktrace","tracer","tracing"],"created_at":"2024-09-25T01:41:58.079Z","updated_at":"2025-06-23T12:36:25.398Z","avatar_url":"https://github.com/niesfisch.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"[\u003cimg src=\"https://api.travis-ci.org/niesfisch/java-code-tracer.png\"/\u003e](http://travis-ci.org/niesfisch/java-code-tracer/builds)\n\n# JCT = Java Code Tracer \n\nJCT 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:\n\n\u003e do we still need this code? nobody seems to be calling it ....\n\n\u003e but i am still scared to remove it :-(\n\n\u003e maybe the code is called in production?\n\nJCT helps you answering these questions by collecting information about your running application.\n\n# Installing JCT for your application\n\nfirst clone or download this project\n\n## create config\n\nthe 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.\n\n```bash\nmkdir ${HOME}/.jct/\ncp doc/config-sample-file.yaml ${HOME}/.jct/\n```\n\nthe recorded stacks will be save to the folder specified in the config file.\n\n## built the agent jar that will be used\n\n```bash\nmvn clean package\nls -al ./target/\n```\n\n## start application with agent\n\n```bash\n# (multiline for readability)\njava -jar someApplication.jar\n  -javaagent:\"/path_to/target/java-code-tracer-1.0-SNAPSHOT-jar-with-dependencies.jar\"\n  -Djct.loglevel=INFO\n  -Djct.config=${HOME}/.jct/config-sample-file.yaml (optional, default is under /src/main/resources/META-INF/config.yaml, will be merged)\n  -Djct.logDir=/tmp/jct\n  -noverify (needed for the moment)\n```\n\n## Output Processor(s))\n\nthere are multiple ways to handle the captured data. the following processor are currently available:\n\n- **AsyncFileWritingStackProcessor**, writes stacks to dedicated files (see [config.yaml](src/test/resources/integration/test-config-asyncfile.yaml))\n- **AsyncUdpStackProcessor**, sends each stack to configured UDP port for further processing (see [config.yaml](src/test/resources/integration/test-config-asyncudp.yaml))\n\nsee further down for a fullblown hello world example.\n\n## Message Format\n\n```json\n{\n    \"stack\": [\n        \"de.marcelsauer.helloworld.subA.InSubA.a_1()\",\n        \"de.marcelsauer.helloworld.subA.InSubA_InnerClass.a_inner_1()\",\n        \"de.marcelsauer.helloworld.subA.InSubA_InnerClass.a_inner_2()\",\n        \"de.marcelsauer.helloworld.subA.InSubA_InnerClass.a_inner_2()\"\n    ],\n    \"timestampMillis\": \"1528120883697\"\n}\n```\n\n```\ntimestampMillis: timestamp milliseconds the first stack entry was started\nstack: all captured stack elements during the call, where stack[0] is the entry and stack[n] is the end \n```\n\n## Logging\n\nFiles can be found in `jct.logDir`\n \n## Debugging\n\nif you need more information about what will be instrumented etc. tune the loglevel. \n\n```bash\n-Djct.loglevel=DEBUG\n```\n\nand check the logs ...\n\n# How it works\n\nJCT 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. \n\n## Example Hello World walkthrough\n\nIn 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.\n\nhint: 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.\n\nnow it's time to start it locally ... \n\n```bash\n# produce jar with agent\nmvn clean package\n# start hello world jar with agent attached to it, (multiline for readability)\njava -javaagent:\"${PWD}/target/java-code-tracer-1.0-SNAPSHOT-jar-with-dependencies.jar\"\n\t -Djct.loglevel=INFO \t  \n\t -Djct.config=./doc/config-sample-helloworld.yaml\n\t -Djct.logDir=/tmp/jct -noverify \n\t -jar \"${PWD}/doc/helloworld-loop.jar\"\n```\n\nyou should see \"Hello World ..\" printed to the console multiple times. \n\nopen another tab ... \n\ncheck the logs ...\n\n```bash\ncat /tmp/jct/jct_agent.log\n```\n\nevery x seconds the collected stacks will be dumped to a file. you need to aggregate these files yourself however you see fit.\n\n```bash\ncat /tmp/stacks/jct_xxxx_xx_xx_xxxxxx_xxx.log\n\n{\"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()\"]}\n{\"timestampMillis\" : \"1528120452903\", \"stack\" : [\"de.marcelsauer.helloworld.subA.InSubA.a_2()\"]}\n{\"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()\"]}\n{\"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()\"]}\n```\n\nyou could use some simple aggregation like this\n\n```bash\ncd /tmp/stacks/\nfor file in `find . -name \"jct*\"`; do cat $file \u003e\u003e all.log; done\ncat all.log | perl -pe 's/.*\\[(.*)\\].*/\\1/' | sort | uniq -c\n\n   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()\"\n   5 \"de.marcelsauer.helloworld.subA.InSubA.a_2()\"\n   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()\"\n   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()\"\n\n```\n\n### JCT -\u003e UDP -\u003e Logstash -\u003e Elasticsearch example\n\nhere 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)\n\n```bash\n\n# tab1, start logstash with out UDP input and elasticsearch output\nlogstash -f doc/logstash.conf\n\n#tab2, start elasticsearch\ndocker run -p 9200:9200 -p 9300:9300 -e \"discovery.type=single-node\" docker.elastic.co/elasticsearch/elasticsearch:6.2.4\n\n#tab3 (multiline for readability), start helloworld loop that will send captured stacks to UDP-\u003elogstash-\u003eelastisearch\njava -javaagent:\"${PWD}/target/java-code-tracer-1.0-SNAPSHOT-jar-with-dependencies.jar\" \n\t -Djct.loglevel=INFO \n\t -Djct.config=./doc/config-sample-helloworld-udp.yaml \n\t -Djct.logDir=/tmp/jct \n\t -noverify \n\t -jar \"${PWD}/doc/helloworld-loop.jar\"\n\n# query captured data\ncurl -XGET http://127.0.0.1:9200/jct_stacks/_search\n```\n\n# Similar Projects/Ideas\n\n- [Datadog dd-trace-java](https://github.com/DataDog/dd-trace-java/)\n- [NewRelic](https://newrelic.com/)\n- [Call Graph](https://github.com/gousiosg/java-callgraph)\n\n# ToDos, Ideas\n\n- use proper dependency injection instead of static calls\n\n# License\n\n[MIT](LICENSE.txt)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fniesfisch%2Fjava-code-tracer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fniesfisch%2Fjava-code-tracer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fniesfisch%2Fjava-code-tracer/lists"}