diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index a61d834..5d577a8 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -15,9 +15,9 @@ body: attributes: label: "Checklist" options: - - label: "I am able to reproduce the bug with the [latest version](https://github.com/xdev-software/template-placeholder/releases/latest)" + - label: "I am able to reproduce the bug with the [latest version](https://github.com/xdev-software/thread-origin-agent/releases/latest)" required: true - - label: "I made sure that there are *no existing issues* - [open](https://github.com/xdev-software/template-placeholder/issues) or [closed](https://github.com/xdev-software/template-placeholder/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to." + - label: "I made sure that there are *no existing issues* - [open](https://github.com/xdev-software/thread-origin-agent/issues) or [closed](https://github.com/xdev-software/thread-origin-agent/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to." required: true - label: "I have taken the time to fill in all the required details. I understand that the bug report will be dismissed otherwise." required: true diff --git a/.github/ISSUE_TEMPLATE/enhancement.yml b/.github/ISSUE_TEMPLATE/enhancement.yml index 764cae1..cd1d38c 100644 --- a/.github/ISSUE_TEMPLATE/enhancement.yml +++ b/.github/ISSUE_TEMPLATE/enhancement.yml @@ -13,7 +13,7 @@ body: attributes: label: "Checklist" options: - - label: "I made sure that there are *no existing issues* - [open](https://github.com/xdev-software/template-placeholder/issues) or [closed](https://github.com/xdev-software/template-placeholder/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to." + - label: "I made sure that there are *no existing issues* - [open](https://github.com/xdev-software/thread-origin-agent/issues) or [closed](https://github.com/xdev-software/thread-origin-agent/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to." required: true - label: "I have taken the time to fill in all the required details. I understand that the feature request will be dismissed otherwise." required: true diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml index 6ecd6ad..7c19dfa 100644 --- a/.github/ISSUE_TEMPLATE/question.yml +++ b/.github/ISSUE_TEMPLATE/question.yml @@ -12,7 +12,7 @@ body: attributes: label: "Checklist" options: - - label: "I made sure that there are *no existing issues* - [open](https://github.com/xdev-software/template-placeholder/issues) or [closed](https://github.com/xdev-software/template-placeholder/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to." + - label: "I made sure that there are *no existing issues* - [open](https://github.com/xdev-software/thread-origin-agent/issues) or [closed](https://github.com/xdev-software/thread-origin-agent/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to." required: true - label: "I have taken the time to fill in all the required details. I understand that the question will be dismissed otherwise." required: true diff --git a/.github/workflows/check-build.yml b/.github/workflows/check-build.yml index 0818116..164fe45 100644 --- a/.github/workflows/check-build.yml +++ b/.github/workflows/check-build.yml @@ -21,7 +21,6 @@ on: env: PRIMARY_MAVEN_MODULE: ${{ github.event.repository.name }} - DEMO_MAVEN_MODULE: ${{ github.event.repository.name }}-demo jobs: build: @@ -64,11 +63,11 @@ jobs: exit 1 fi - - name: Upload demo files + - name: Upload files uses: actions/upload-artifact@v4 with: - name: demo-files-java-${{ matrix.java }} - path: ${{ env.DEMO_MAVEN_MODULE }}/target/${{ env.DEMO_MAVEN_MODULE }}.jar + name: files-java-${{ matrix.java }} + path: ${{ env.PRIMARY_MAVEN_MODULE }}/target/${{ env.PRIMARY_MAVEN_MODULE }}-*.jar if-no-files-found: error checkstyle: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 43767e8..750210f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -99,16 +99,9 @@ jobs: See [Changelog#v${{ steps.version.outputs.release }}](https://github.com/${{ github.repository }}/blob/develop/CHANGELOG.md#${{ steps.version.outputs.releasenumber }}) for more information. ## Installation - Add the following lines to your pom: - ```XML - - software.xdev - ${{ env.PRIMARY_MAVEN_MODULE }} - ${{ steps.version.outputs.release }} - - ``` - - publish-maven: + Download the javaagent from the assets section below and [attach it to your java application](https://github.com/xdev-software/thread-origin-agent#usage). + + publish-assets: runs-on: ubuntu-latest needs: [prepare-release] timeout-minutes: 60 @@ -121,25 +114,22 @@ jobs: git config --global user.name "GitHub Actions" git pull - - name: Set up JDK Apache Maven Central + - name: Set up JDK uses: actions/setup-java@v4 - with: # running setup-java again overwrites the settings.xml + with: java-version: '17' distribution: 'temurin' - server-id: ossrh - server-username: MAVEN_CENTRAL_USERNAME - server-password: MAVEN_CENTRAL_TOKEN - gpg-passphrase: MAVEN_GPG_PASSPHRASE - gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} - - - name: Publish to Apache Maven Central - run: ../mvnw -B deploy -Possrh -DskipTests - env: - MAVEN_CENTRAL_USERNAME: ${{ secrets.S01_OSS_SONATYPE_MAVEN_USERNAME }} - MAVEN_CENTRAL_TOKEN: ${{ secrets.S01_OSS_SONATYPE_MAVEN_TOKEN }} - MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} + + - name: Build + run: ../mvnw -B clean package working-directory: ${{ env.PRIMARY_MAVEN_MODULE }} + - name: Upload Assets + uses: shogo82148/actions-upload-release-asset@v1 + with: + upload_url: ${{ needs.prepare_release.outputs.upload_url }} + asset_path: ${{ env.PRIMARY_MAVEN_MODULE }}/target/${{ env.PRIMARY_MAVEN_MODULE }}-*.jar + publish-pages: runs-on: ubuntu-latest needs: [prepare-release] @@ -173,7 +163,7 @@ jobs: after-release: runs-on: ubuntu-latest - needs: [publish-maven] + needs: [publish-assets] timeout-minutes: 10 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml deleted file mode 100644 index df6dbb7..0000000 --- a/.github/workflows/sonar.yml +++ /dev/null @@ -1,79 +0,0 @@ -name: Sonar - -on: - workflow_dispatch: - push: - branches: [ develop ] - paths-ignore: - - '**.md' - - '.config/**' - - '.github/**' - - '.idea/**' - - 'assets/**' - pull_request: - branches: [ develop ] - paths-ignore: - - '**.md' - - '.config/**' - - '.github/**' - - '.idea/**' - - 'assets/**' - -env: - SONARCLOUD_ORG: ${{ github.event.organization.login }} - SONARCLOUD_HOST: https://sonarcloud.io - -jobs: - token-check: - runs-on: ubuntu-latest - if: ${{ !(github.event_name == 'pull_request' && startsWith(github.head_ref, 'renovate/')) }} - timeout-minutes: 5 - outputs: - hasToken: ${{ steps.check-token.outputs.has }} - steps: - - id: check-token - run: | - [ -z $SONAR_TOKEN ] && echo "has=false" || echo "has=true" >> "$GITHUB_OUTPUT" - env: - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - - sonar-scan: - runs-on: ubuntu-latest - needs: token-check - if: ${{ needs.token-check.outputs.hasToken }} - timeout-minutes: 30 - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - - - name: Set up JDK - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: 17 - - - name: Cache SonarCloud packages - uses: actions/cache@v4 - with: - path: ~/.sonar/cache - key: ${{ runner.os }}-sonar - restore-keys: ${{ runner.os }}-sonar - - - name: Cache Maven packages - uses: actions/cache@v4 - with: - path: ~/.m2 - key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} - restore-keys: ${{ runner.os }}-m2 - - - name: Build with Maven - run: | - ./mvnw -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar \ - -DskipTests \ - -Dsonar.projectKey=${{ env.SONARCLOUD_ORG }}_${{ github.event.repository.name }} \ - -Dsonar.organization=${{ env.SONARCLOUD_ORG }} \ - -Dsonar.host.url=${{ env.SONARCLOUD_HOST }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.github/workflows/test-deploy.yml b/.github/workflows/test-deploy.yml deleted file mode 100644 index 03f5339..0000000 --- a/.github/workflows/test-deploy.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Test Deployment - -on: - workflow_dispatch: - -env: - PRIMARY_MAVEN_MODULE: ${{ github.event.repository.name }} - -jobs: - publish-maven: - runs-on: ubuntu-latest - timeout-minutes: 60 - steps: - - uses: actions/checkout@v4 - - - name: Set up JDK OSSRH - uses: actions/setup-java@v4 - with: # running setup-java again overwrites the settings.xml - distribution: 'temurin' - java-version: '17' - server-id: ossrh - server-username: MAVEN_CENTRAL_USERNAME - server-password: MAVEN_CENTRAL_TOKEN - gpg-passphrase: MAVEN_GPG_PASSPHRASE - gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} - - - name: Publish to OSSRH - run: ../mvnw -B deploy -Possrh -DskipTests - working-directory: ${{ env.PRIMARY_MAVEN_MODULE }} - env: - MAVEN_CENTRAL_USERNAME: ${{ secrets.S01_OSS_SONATYPE_MAVEN_USERNAME }} - MAVEN_CENTRAL_TOKEN: ${{ secrets.S01_OSS_SONATYPE_MAVEN_TOKEN }} - MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} diff --git a/.run/Run Demo.run.xml b/.run/Run Demo.run.xml deleted file mode 100644 index 5fb2bc2..0000000 --- a/.run/Run Demo.run.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29..b9f548c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1,13 @@ +# 1.1.0 +* Add option to log caller threads #11 (@3nol) +* Updated dependencies + +# 1.0.1 +* Reword and improve logging +* Can now monitor classes that extend from Thread +* ``Thread#join`` is no longer logged by default. Can be enabled using ``-DTOA_LOG_THREAD_JOINS`` +* Method instrumentation failures can be displayed using ``-DTOA_DISPLAY_METHOD_INSTRUMENTATION_FAILURES`` + +# 1.0.0 +Initial release +* Migrated from old repository diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index be2a186..431e3b7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,10 +34,9 @@ You should have the following things installed: * Ensure that the JDK/Java-Version is correct -## Releasing [![Build](https://img.shields.io/github/actions/workflow/status/xdev-software/template-placeholder/release.yml?branch=master)](https://github.com/xdev-software/template-placeholder/actions/workflows/release.yml) +## Releasing [![Build](https://img.shields.io/github/actions/workflow/status/xdev-software/thread-origin-agent/release.yml?branch=master)](https://github.com/xdev-software/thread-origin-agent/actions/workflows/release.yml) Before releasing: -* Consider doing a [test-deployment](https://github.com/xdev-software/template-placeholder/actions/workflows/test-deploy.yml?query=branch%3Adevelop) before actually releasing. * Check the [changelog](CHANGELOG.md) If the ``develop`` is ready for release, create a pull request to the ``master``-Branch and merge the changes diff --git a/LICENSE b/LICENSE index ccaa2b3..807d2b7 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,8 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2024 XDEV Software + Copyright 2019 XDEV Software + Copyright 2011 kreyssel Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index eccf80b..9fbcaa6 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,57 @@ -[![Latest version](https://img.shields.io/maven-central/v/software.xdev/template-placeholder?logo=apache%20maven)](https://mvnrepository.com/artifact/software.xdev/template-placeholder) -[![Build](https://img.shields.io/github/actions/workflow/status/xdev-software/template-placeholder/check-build.yml?branch=develop)](https://github.com/xdev-software/template-placeholder/actions/workflows/check-build.yml?query=branch%3Adevelop) -[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=xdev-software_template-placeholder&metric=alert_status)](https://sonarcloud.io/dashboard?id=xdev-software_template-placeholder) +[![Build](https://img.shields.io/github/actions/workflow/status/xdev-software/thread-origin-agent/check-build.yml?branch=develop)](https://github.com/xdev-software/thread-origin-agent/actions/workflows/check-build.yml?query=branch%3Adevelop) -# template-placeholder +# Thread Origin Agent +In many situations is it helpful to find out who created a [Thread](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Thread.html). + +To find the origin of a thread, this project provides a [javaagent](https://docs.oracle.com/en/java/javase/21/docs/api/java.instrument/java/lang/instrument/package-summary.html) which logs the stacktrace at Thread creation. ## Installation -[Installation guide for the latest release](https://github.com/xdev-software/template-placeholder/releases/latest#Installation) +[Installation guide for the latest release](https://github.com/xdev-software/thread-origin-agent/releases/latest#Installation) + +## Usage +Insert ``-javaagent:=`` into the JVM-arguments (at the beginning!)
+Examples: +```bash +java -javaagent:thread-origin-agent-1.0.0.jar -jar .jar +java -javaagent:"C:\temp\thread-origin-agent-1.0.0.jar"=sun/awt,sun/java2d -jar .jar +``` + +> [!NOTE] +> Please note that it's not possible to monitor all ``Thread`` starts as the ``Thread`` class is loaded and used extremely early.
+> Some static instantiations that use Threads e.g. ``ForkJoinPool#common`` are therefore not affected by changing the underlying bytecode. + +
Example output for a Spring Boot application + +``` +[TOA] Arg: null +[TOA] Ignoring excluded: +[TOA] Trying to retransform loaded classes +[TOA] Ignoring javassist.CtField +... +[TOA] Retransformed loaded classes; 820x successful, 150x unmodifiable +[TOA] Detected java.lang.Thread.start() id: 46 name: background-preinit +[TOA] org.springframework.boot.autoconfigure.BackgroundPreinitializer.performPreinitialization(BackgroundPreinitializer.java:129) +[TOA] org.springframework.boot.autoconfigure.BackgroundPreinitializer.onApplicationEvent(BackgroundPreinitializer.java:85) +[TOA] org.springframework.boot.autoconfigure.BackgroundPreinitializer.onApplicationEvent(BackgroundPreinitializer.java:55) +... +[TOA] Detected java.lang.Thread.start() id: 47 name: Thread-0 +[TOA] org.springframework.boot.autoconfigure.condition.OnClassCondition$ThreadedOutcomesResolver.(OnClassCondition.java:147) +... +``` + +
+ +### Additional configuration options + +These options can be configured as system properties. Example:
+``-DTOA_LOG_CALLER_THREADS`` + +| Option | Description | +| --- | --- | +| ``TOA_LOG_THREAD_JOINS`` | Also logs ``Thread#join`` calls | +| ``TOA_LOG_CALLER_THREADS`` | Displays additional information about the thread that started the new thread | +| ``TOA_DISPLAY_METHOD_INSTRUMENTATION_FAILURES`` | Displays method instrumentation failures.
This happens when a method is not present or can't be instrumented due to other reasons (e.g. JDK internal method).
Produces a lot of log output so it's recommended to only enable it for debugging | ## Support If you need support as soon as possible and you can't wait for any pull request, feel free to use [our support](https://xdev.software/en/services/support). @@ -15,4 +60,6 @@ If you need support as soon as possible and you can't wait for any pull request, See the [contributing guide](./CONTRIBUTING.md) for detailed instructions on how to get started with our project. ## Dependencies and Licenses -View the [license of the current project](LICENSE) or the [summary including all dependencies](https://xdev-software.github.io/template-placeholder/dependencies) +View the [license of the current project](LICENSE) or the [summary including all dependencies](https://xdev-software.github.io/thread-origin-agent/dependencies) + +This project was inspired by [kreyssel/maven-examples](https://github.com/kreyssel/maven-examples) diff --git a/SECURITY.md b/SECURITY.md index 34b9514..7ab1a71 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,4 +2,4 @@ ## Reporting a Vulnerability -Please report a security vulnerability [on GitHub Security Advisories](https://github.com/xdev-software/template-placeholder/security/advisories/new). +Please report a security vulnerability [on GitHub Security Advisories](https://github.com/xdev-software/thread-origin-agent/security/advisories/new). diff --git a/pom.xml b/pom.xml index 26d6779..43d9dc9 100644 --- a/pom.xml +++ b/pom.xml @@ -5,8 +5,8 @@ 4.0.0 software.xdev - template-placeholder-root - 1.0.0-SNAPSHOT + thread-origin-agent-root + 1.1.1-SNAPSHOT pom @@ -15,8 +15,7 @@ - template-placeholder - template-placeholder-demo + thread-origin-agent diff --git a/renovate.json5 b/renovate.json5 index 2874d45..e626363 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -4,7 +4,7 @@ "packageRules": [ { "description": "Ignore project internal dependencies", - "packagePattern": "^software.xdev:template-placeholder", + "packagePattern": "^software.xdev:thread-origin-agent", "datasources": [ "maven" ], diff --git a/template-placeholder-demo/pom.xml b/template-placeholder-demo/pom.xml deleted file mode 100644 index 1af633c..0000000 --- a/template-placeholder-demo/pom.xml +++ /dev/null @@ -1,85 +0,0 @@ - - - 4.0.0 - - - software.xdev - template-placeholder-root - 1.0.0-SNAPSHOT - - - template-placeholder-demo - 1.0.0-SNAPSHOT - jar - - - XDEV Software - https://xdev.software - - - - 17 - ${javaVersion} - - UTF-8 - UTF-8 - - software.xdev.Application - - - - - software.xdev - template-placeholder - ${project.version} - - - - - ${project.artifactId} - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.13.0 - - ${maven.compiler.release} - - -proc:none - - - - - org.apache.maven.plugins - maven-assembly-plugin - 3.7.1 - - - - ${mainClass} - - - true - - - - jar-with-dependencies - - false - - - - make-assembly - package - - single - - - - - - - diff --git a/template-placeholder/pom.xml b/thread-origin-agent/pom.xml similarity index 64% rename from template-placeholder/pom.xml rename to thread-origin-agent/pom.xml index df945cf..0192558 100644 --- a/template-placeholder/pom.xml +++ b/thread-origin-agent/pom.xml @@ -5,20 +5,20 @@ 4.0.0 software.xdev - template-placeholder - 1.0.0-SNAPSHOT + thread-origin-agent + 1.1.1-SNAPSHOT jar - template-placeholder - template-placeholder - https://github.com/xdev-software/template-placeholder + thread-origin-agent + thread-origin-agent + https://github.com/xdev-software/thread-origin-agent - https://github.com/xdev-software/template-placeholder - scm:git:https://github.com/xdev-software/template-placeholder.git + https://github.com/xdev-software/thread-origin-agent + scm:git:https://github.com/xdev-software/thread-origin-agent.git - 2023 + 2019 XDEV Software @@ -73,16 +73,13 @@ - - - ossrh - https://s01.oss.sonatype.org/content/repositories/snapshots - - - ossrh - https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ - - + + + org.javassist + javassist + 3.30.2-GA + + @@ -140,34 +137,26 @@ + org.apache.maven.plugins - maven-javadoc-plugin - 3.11.2 - - - attach-javadocs - package - - jar - - - + maven-assembly-plugin + 3.7.1 - true - none + + jar-with-dependencies + + false + + src/main/resources/META-INF/MANIFEST.MF + - - - org.apache.maven.plugins - maven-source-plugin - 3.3.1 - attach-sources + make-assembly package - jar-no-fork + single @@ -175,66 +164,6 @@ - - ossrh - - - - org.codehaus.mojo - flatten-maven-plugin - 1.6.0 - - ossrh - - - - flatten - process-resources - - flatten - - - - - - org.apache.maven.plugins - maven-gpg-plugin - 3.2.7 - - - sign-artifacts - verify - - sign - - - - - - --pinentry-mode - loopback - - - - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.7.0 - true - - ossrh - https://s01.oss.sonatype.org/ - - 30 - true - - - - - checkstyle diff --git a/thread-origin-agent/src/main/java/software/xdev/tools/threadoriginagent/ThreadOriginTransformer.java b/thread-origin-agent/src/main/java/software/xdev/tools/threadoriginagent/ThreadOriginTransformer.java new file mode 100644 index 0000000..7a2aa23 --- /dev/null +++ b/thread-origin-agent/src/main/java/software/xdev/tools/threadoriginagent/ThreadOriginTransformer.java @@ -0,0 +1,273 @@ +/* + * Copyright © 2019 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package software.xdev.tools.threadoriginagent; + +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.Instrumentation; +import java.lang.instrument.UnmodifiableClassException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javassist.CannotCompileException; +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtMethod; +import javassist.NotFoundException; +import javassist.expr.ExprEditor; +import javassist.expr.MethodCall; + + +/** + * This javaagent should ALWAYS be the first! + */ +public class ThreadOriginTransformer implements ClassFileTransformer +{ + private static final boolean DISPLAY_METHOD_INSTRUMENTATION_FAILURES = + System.getProperty("TOA_DISPLAY_METHOD_INSTRUMENTATION_FAILURES") != null; + private static final boolean LOG_THREAD_JOINS = + System.getProperty("TOA_LOG_THREAD_JOINS") != null; + private static final boolean LOG_CALLER_THREADS = + System.getProperty("TOA_LOG_CALLER_THREADS") != null; + + private static final String PROCEED = "$proceed($$); "; + + private static final String PRINT_CALLER_THREAD = LOG_CALLER_THREADS + ? "java.lang.Thread currentThread = java.lang.Thread.currentThread(); " + + "System.out.println(\"[TOA] Called from \" + currentThread.getClass().getName() + " + + "\" id: \" + currentThread.getId() + " + + "\" name: \" + currentThread.getName()); " + : ""; + + private static final String PRINT_STACK = + "java.lang.StackTraceElement[] elements = java.lang.Thread.currentThread().getStackTrace(); " + + "java.lang.StringBuilder sb = new java.lang.StringBuilder(); " + + "for(int i = 1; i < elements.length; i++) " + + " sb.append(\"[TOA] \\t\")" + + " .append(elements[i])" + + " .append(i < elements.length - 1 ? java.lang.System.lineSeparator() : \"\"); " + + "java.lang.System.out.println(sb.toString()); "; + + private static CtClass threadClazz; + + /** + * Don't log calls to classnames if they start with the string mentioned here
e.g: + * -javaagent:thread-origin-agent-1.0.0.jar=sun/awt,sun/java2d + */ + private final List excluded = new ArrayList<>(); + + public ThreadOriginTransformer(final String argument) + { + log("Arg: " + argument); + + if(argument != null) + { + this.excluded.addAll(Arrays.asList(argument.split(","))); + } + + log("Ignoring excluded: " + String.join(",", this.excluded)); + } + + /** + * Add agent + *

+ * see also src/main/resources/META-INF/MANIFEST.MF + *

+ */ + public static void premain(final String agentArgument, final Instrumentation instrumentation) + { + try + { + threadClazz = ClassPool.getDefault().get("java.lang.Thread"); + if(threadClazz == null) + { + throw new IllegalStateException("Unable to find Thread.class"); + } + } + catch(final Exception ex) + { + log("Failed to instrument Thread class: " + ex); + throw new IllegalStateException(ex); + } + + instrumentation.addTransformer(new ThreadOriginTransformer(agentArgument)); + + log("Trying to retransform loaded classes"); + long unmodifiable = 0; + long success = 0; + for(final Class loadedClazz : instrumentation.getAllLoadedClasses()) + { + if(loadedClazz.getName().startsWith("software.xdev.tools") + || loadedClazz.getName().startsWith("javassist")) + { + log("Ignoring " + loadedClazz.getName()); + continue; + } + + try + { + instrumentation.retransformClasses(loadedClazz); + success++; + } + catch(final UnmodifiableClassException e) + { + unmodifiable++; + } + } + log("Retransformed loaded classes; " + success + "x successful, " + unmodifiable + "x unmodifiable"); + } + + @SuppressWarnings({"PMD.CognitiveComplexity", "PMD.NPathComplexity"}) + @Override + public byte[] transform( + final ClassLoader loader, + final String className, + final Class clazz, + final java.security.ProtectionDomain domain, + final byte[] bytes) + { + byte[] resultingBytes = bytes; + if(className == null) + { + log("ClassName was null; Class=" + clazz.getCanonicalName()); + return resultingBytes; + } + + if(this.excluded.stream().anyMatch(className::startsWith)) + { + log("Excluded class=" + className); + return resultingBytes; + } + + try + { + final ClassPool classPool = ClassPool.getDefault(); + final CtClass classUnderTransformation = classPool.makeClass(new java.io.ByteArrayInputStream(bytes)); + if(classUnderTransformation == null) + { + return resultingBytes; + } + + classUnderTransformation.instrument(new ExprEditor() + { + @Override + public void edit(final MethodCall m) throws CannotCompileException + { + final CtMethod method; + try + { + method = m.getMethod(); + } + catch(final NotFoundException e) + { + if(DISPLAY_METHOD_INSTRUMENTATION_FAILURES) + { + String enclosingClass = "?"; + try + { + enclosingClass = m.getEnclosingClass().getName(); + } + catch(final Exception ex) + { + // Ignore + } + + String signature = "?"; + try + { + signature = m.getSignature(); + } + catch(final Exception ex) + { + // Ignore + } + + log("Could not find '" + enclosingClass + "->" + signature + "' method; Message: " + + e.getMessage()); + } + return; + } + + final CtClass declaringClass = method.getDeclaringClass(); + + final boolean isThreadDeclaringClass; + try + { + isThreadDeclaringClass = threadClazz != null + ? declaringClass.subclassOf(threadClazz) + : Thread.class.getName().equals(declaringClass.getName()); + } + catch(final Exception ne) + { + log("Failed to determine if declaring class is subtype of Thread: " + ne.getMessage()); + return; + } + if(isThreadDeclaringClass) + { + ThreadOriginTransformer.this.replaceThread(m, method.getName(), declaringClass); + } + } + }); + + resultingBytes = classUnderTransformation.toBytecode(); + } + catch(final Exception e) + { + log("Could not instrument " + + className + + "/" + + clazz.getCanonicalName() + + ", exception: " + + e.getMessage()); + } + + return resultingBytes; + } + + void replaceThread(final MethodCall m, final String methodName, final CtClass declaringClass) + throws CannotCompileException + { + if(methodName.equals("start")) + { + m.replace("{ " + + "System.out.println(\"[TOA] Detected " + + declaringClass.getName() + + ".start() id: \" + ((Thread)$0).getId() + \" name: \" + " + + "((Thread)$0).getName()); " + + PRINT_CALLER_THREAD + + PRINT_STACK + + PROCEED + + "} "); + } + else if(LOG_THREAD_JOINS && methodName.equals("join")) + { + m.replace("{ " + + "System.out.println(\"[TOA] Detected " + + declaringClass.getName() + + ".join() id: \" + ((Thread)$0).getId() + \" name: \" + " + + "((Thread)$0).getName()); " + + PRINT_CALLER_THREAD + + PROCEED + + "} "); + } + } + + @SuppressWarnings("java:S106") + private static void log(final String message) + { + System.out.println("[TOA] " + message); + } +} diff --git a/thread-origin-agent/src/main/resources/META-INF/MANIFEST.MF b/thread-origin-agent/src/main/resources/META-INF/MANIFEST.MF new file mode 100644 index 0000000..7c3dcc8 --- /dev/null +++ b/thread-origin-agent/src/main/resources/META-INF/MANIFEST.MF @@ -0,0 +1,2 @@ +Premain-Class: software.xdev.tools.threadoriginagent.ThreadOriginTransformer +Can-Retransform-Classes: true