diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..cc24c5b93 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: jbachorik +issuehunt: jbachorik +otechie: # Replace with a single Otechie username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 000000000..8b5cbfdfb --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,58 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ develop, master ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ develop ] + schedule: + - cron: '23 5 * * 4' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'java' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://git.io/codeql-language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + - name: Build BTrace + run: | + ./gradlew -x test build + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/continuous.yml b/.github/workflows/continuous.yml new file mode 100644 index 000000000..8b177a1cf --- /dev/null +++ b/.github/workflows/continuous.yml @@ -0,0 +1,127 @@ +# This workflow will build a Java project with Gradle +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle + +name: BTrace CI/CD + +on: + push: + branches: [ develop, master ] + pull_request: + branches: [ develop ] + workflow_dispatch: + +defaults: + run: + shell: bash + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Set up Java + uses: actions/setup-java@v4 + with: + java-version: 11 + distribution: temurin + - name: Checkout + uses: actions/checkout@v4 + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + - name: Build + run: ./gradlew build + - name: Upload build data + if: always() + uses: actions/upload-artifact@v4 + with: + name: build + retention-days: 1 + path: | + btrace-dist/build + - name: Archive test reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-reports + path: | + **/reports/**/* + + test: + needs: build + runs-on: ubuntu-latest + timeout-minutes: 10 + strategy: + matrix: + java: [ 8, 11, 17, 21 ] + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + - name: Set up Java ${{ matrix.java }} + uses: actions/setup-java@v4 + with: + java-version: ${{ matrix.java }} + distribution: temurin + - name: Download build data + uses: actions/download-artifact@v4 + with: + name: build + path: btrace-dist/build + - name: Run tests + run: | + ./gradlew -Pintegration :integration-tests:test + - name: Integration test reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: integration-test-reports-${{ matrix.java }} + path: | + integration-tests/build/reports/**/* + - name: Archive binary artifacts + if: success() && matrix.java == '11' + uses: actions/upload-artifact@v4 + with: + name: btrace-dist + path: | + btrace-dist/build/distributions/**/btrace-*-bin*.tar.gz + + publish: + if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master' + needs: test + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + - name: Set up Java + uses: actions/setup-java@v4 + with: + java-version: 11 + distribution: temurin + - name: Download build data + uses: actions/download-artifact@v4 + with: + name: build + - name: Deploy Maven + run: ./gradlew -x test :btrace-dist:publish + env: + GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }} + GPG_SIGNING_PWD: ${{ secrets.GPG_SIGNING_PWD }} + BTRACE_SONATYPE_USER: ${{ secrets.BTRACE_SONATYPE_USER }} + BTRACE_SONATYPE_PWD: ${{ secrets.BTRACE_SONATYPE_PWD }} + + cleanup: + runs-on: ubuntu-latest + needs: publish + steps: + - name: Cleanup temporary artifacts + uses: geekyeggo/delete-artifact@v5 + with: + name: build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..eed1b2655 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,59 @@ +on: + push: + tags: + - 'v*' + +name: Create Release + +defaults: + run: + shell: bash + +jobs: + build: + name: Create Release + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Branch name + id: branch_name + run: | + echo ::set-output name=SOURCE_NAME::${GITHUB_REF#refs/*/} + echo ::set-output name=SOURCE_BRANCH::${GITHUB_REF#refs/heads/} + echo ::set-output name=SOURCE_TAG::${GITHUB_REF#refs/tags/} + - name: Set up Java + uses: actions/setup-java@v4 + with: + java-version: 11 + distribution: temurin + - name: Build artifacts + run: ./gradlew :btrace-dist:build + - name: Create Release + id: create_release + uses: softprops/action-gh-release@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + draft: true + prerelease: false + files: | + btrace-dist/build/distributions/btrace-${{ steps.branch_name.outputs.SOURCE_TAG }}-bin.tar.gz + btrace-dist/build/distributions/btrace-${{ steps.branch_name.outputs.SOURCE_TAG }}-bin.zip + btrace-dist/build/distributions/btrace-${{ steps.branch_name.outputs.SOURCE_TAG }}-sdkman-bin.zip + - name: Deploy Maven + id: deploy_maven + env: + GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }} + GPG_SIGNING_PWD: ${{ secrets.GPG_SIGNING_PWD }} + BTRACE_SONATYPE_USER: ${{ secrets.BTRACE_SONATYPE_USER }} + BTRACE_SONATYPE_PWD: ${{ secrets.BTRACE_SONATYPE_PWD }} + run: | + ./gradlew :btrace-dist:publishAllPublicationsToMavenRepository + - name: Update SDKMan! + id: update_sdkman + env: + SKDMAN_API_KEY: ${{ secrets.SDKMAN_KEY }} + SDKMAN_API_TOKEN: ${{ secrets.SDKMAN_TOKEN }} + run: | + ./gradlew :btrace-dist:sdkMinorRelease diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 000000000..4a53aae70 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,19 @@ +name: Mark stale issues and pull requests + +on: + schedule: + - cron: "0 0 * * *" + +jobs: + stale: + + runs-on: ubuntu-latest + + steps: + - uses: actions/stale@v9 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: 'Stale issue message' + stale-pr-message: 'Stale pull request message' + stale-issue-label: 'no-issue-activity' + stale-pr-label: 'no-pr-activity' diff --git a/.gitignore b/.gitignore index bdaedf088..27358af05 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,9 @@ *.war *.ear +# Un-ignore specific files +!btrace-instr/src/test/resources/packed/test-pack.jar + # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* @@ -18,8 +21,8 @@ make/netbeans/nbproject/private TEST-* make/junit* make/private.properties -benchmark/target -benchmark/build +btrace-benchmark/target +btrace-benchmark/build benchmark/*.iml junit* /btrace-statsd/target/ @@ -35,3 +38,7 @@ gradle.properties /.nb-gradle/ /.nb-gradle-properties .github_changelog_generator +**/out/* +gradle-wrapper.properties + +/.java.versions diff --git a/.hgignore b/.hgignore deleted file mode 100644 index 1df59c455..000000000 --- a/.hgignore +++ /dev/null @@ -1,22 +0,0 @@ -(^|/)CVS($|/) -(^|/)\.hg($|/) -(^|/)\.hgtags($|/) -(^|/)build($|/) -(^|/)release($|/) -(^|/)javadoc($|/) -(^|/)dist($|/) -(^|/)private($|/) -(^|/)output($|/) -patches -webrev* -website -benchmark/target -^project.log$ -^tailor.state$ -^tailor.state.old$ -^tailor.state.journal$ -^TEST-* -.class$ -.idea -.iml -^make/private\.properties$ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 7f1ae101b..000000000 --- a/.travis.yml +++ /dev/null @@ -1,37 +0,0 @@ -env: - global: - - secure: EIQ8VWbg8LRnX7n6PIP9HjXLg4nIKOsJk0xnMunbMZ475Z+pTd73awLu6VCbjO5DVAd9R7W36snIlGVv92j3dai6e0jOO+WtSTQ7gd1cEq+4swEXg7Qvc1oyqsXVL+FoO+j1InZxxUx93ObLRGvFZiUeBhnPNdqsM794EkUScN4= - - secure: HLMEzx4NuU+oXU5yGViN8pAfFvhVeib5zvfH21nVbOC1Rg57HUKQTRCg5S/acr5C8At4BfHORt7p3MHztGB/GXylzm1kFRnKliJ50S9BkEW+/852f2gbGmnIEe7aMz4nFOheRpLgk7zcZrbYbztoRAFL+HREBiEaAZu8bti4gpU= -sudo: false -cache: - directories: - - "$HOME/.gradle" -language: java -jdk: -- oraclejdk8 - -notifications: - webhooks: - urls: https://webhooks.gitter.im/e/31a671345ecfdce18046 - on_success: change - on_failure: always - on_start: never -before_install: -- pip install --user codecov -- sudo dpkg --purge --force-depends ca-certificates-java -- sudo apt-get install ca-certificates-java -- sudo update-ca-certificates -f -install: "./gradlew assemble -DexcludeBenchmark" -script: "./gradlew check -DexcludeBenchmark" -after_success: -- "./gradlew buildDistributions -DexcludeBenchmark" -- codecov -deploy: - provider: bintray - file: "bintray.json" - user: "jbachorik" - key: - secure: I+Lvki1TM2pQPJEf75U098GSQexgjudOZ2TDCPSQEdGys7PdW7SIy5THiHYdCG+tO8dSKQ3FUXufDD1KzhHxEBSWUX1rpAgE3SGvxYwzAU+bl3FvoQzjb+WD69ya86h0TFM2fVOJsg4+iDimheHcD+7mCuYx3ga1dsi8lVjh38s= - on: - branch: master - condition: ${TRAVIS_JDK_VERSION} == oraclejdk8 diff --git a/README.md b/README.md index e266c203c..530c5b224 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,11 @@ -[![Download](https://api.bintray.com/packages/btraceio/releases/btrace/images/download.svg) ](https://bintray.com/btraceio/releases/btrace/_latestVersion) [![Build Status](https://travis-ci.org/btraceio/btrace.svg?branch=master)](https://travis-ci.org/btraceio/btrace) [![codecov.io](https://codecov.io/github/btraceio/btrace/coverage.svg?branch=master)](https://codecov.io/github/btraceio/btrace?branch=master) [![Join the chat at https://gitter.im/jbachorik/btrace](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/btraceio/btrace?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Project Stats](https://www.openhub.net/p/btrace/widgets/project_thin_badge.gif)](https://www.openhub.net/p/btrace) +[![Dev build](https://github.com/btraceio/btrace/workflows/BTrace%20CI%2FCD/badge.svg?branch=develop)](https://github.com/btraceio/btrace/actions?query=workflow%3A%22BTrace+CI%2FCD%22+branch%3Adevelop) [![Download](https://img.shields.io/github/v/release/btraceio/btrace?sort=semver)](https://github.com/btraceio/btrace/releases/latest) [![codecov.io](https://codecov.io/github/btraceio/btrace/coverage.svg?branch=develop)](https://codecov.io/github/btraceio/btrace?branch=develop) [![huhu](https://img.shields.io/badge/Slack-join%20chat-brightgreen")](http://btrace.slack.com/) [![Join the chat at https://gitter.im/jbachorik/btrace](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/btraceio/btrace?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Project Stats](https://www.openhub.net/p/btrace/widgets/project_thin_badge.gif)](https://www.openhub.net/p/btrace) # btrace A safe, dynamic tracing tool for the Java platform ## Version -1.3.11.2 ([Release Page](https://github.com/btraceio/btrace/releases/latest)) - -_! NOTE: For the latest develop changes head to ['develop' branch](https://github.com/btraceio/btrace/tree/develop)_ +2.2.6 ## Quick Summary BTrace is a safe, dynamic tracing tool for the Java platform. @@ -19,34 +17,26 @@ BTrace can be used to dynamically trace a running Java program (similar to DTrac * Powered by [JCTools](https://github.com/JCTools/JCTools) * Powered by [hppcrt](https://github.com/vsonnier/hppcrt) * Optimized with [JProfiler Java Profiler](http://www.ej-technologies.com/products/jprofiler/overview.html) +* Build env helper using [SDKMAN!](https://sdkman.io/) ## Building BTrace ### Setup You will need the following applications installed -* [JDK](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) (preferrably JDK8) * [Git](http://git-scm.com/downloads) -* [Gradle](http://gradle.org) -* (optionally) [Ant](http://ant.apache.org/bindownload.cgi) -* (optionally) [Maven3](http://maven.apache.org/download.cgi) +* (optionally, the default launcher is the bundled `gradlew` wrapper) [Gradle](http://gradle.org) + ### Build #### Gradle ```sh cd -./gradlew build -./gradlew buildDistributions -``` -The binary dist packages can be found in `/build/distributions` as the *.tar.gz, *.zip, *.rpm and *.deb files. - -#### Ant (legacy build) -```sh -cd /make -ant dist +./gradlew :btrace-dist:build ``` -The binary dist packages can be found in `/dist` as the *.tar.gz and *.zip files +The binary dist packages can be found in `/btrace-dist/build/distributions` as the *.tar.gz, *.zip, *.rpm and *.deb files. +The exploded binary folder which can be used right away is located at `/btrace-dist/build/resources/main` which serves as the __BTRACE_HOME__ location. ## Using BTrace @@ -69,30 +59,22 @@ For the detailed user guide, please, check the [Wiki](https://github.com/btracei ### Maven Integration The [maven plugin](https://github.com/btraceio/btrace-maven) is providing easy compilation of __BTrace__ scripts as a part of the build process. As a bonus you can utilize the _BTrace Project Archetype_ to bootstrap developing __BTrace__ scripts. -## Mailing lists - -These mailing lists are hosted at **http://librelist.com** - -* **btrace.users@librelist.com** -* **btrace.dev@librelist.com** -* **btrace.commits@librelist.com** - ## Contributing - !!! Important !!! -Pull requests can be accepted only from the signers of [Oracle Contributor Agreement](http://www.oracle.com/technetwork/community/oca-486395.html) +Pull requests can be accepted only from the signers of [Oracle Contributor Agreement](https://oca.opensource.oracle.com/) ### Deb Repository Using the command line, add the following to your /etc/apt/sources.list system config file: ``` -echo "deb http://dl.bintray.com/btraceio/deb trusty universe" | sudo tee -a /etc/apt/sources.list +echo "deb http://dl.bintray.com/btraceio/deb xenial universe" | sudo tee -a /etc/apt/sources.list ``` Or, add the repository URLs using the "Software Sources" admin UI: ``` -deb http://dl.bintray.com/btraceio/deb trusty universe +deb http://dl.bintray.com/btraceio/deb xenial universe ``` ### RPM Repository diff --git a/benchmark/build.gradle b/benchmark/build.gradle deleted file mode 100644 index 1572e18b2..000000000 --- a/benchmark/build.gradle +++ /dev/null @@ -1,63 +0,0 @@ -plugins { - id 'java' -} - -description 'A JMH benchmark to assert the overhead imposed by various types of BTrace instrumentation.' - - -sourceCompatibility = 8 -targetCompatibility = 8 - -def env = System.getenv() -def javaHome = env['JAVA_HOME'] - - -ext { - versions = [ - jmh: '1.11.1' - ] - - libs = [ - jmh: ["org.openjdk.jmh:jmh-core:${versions.jmh}", - "org.openjdk.jmh:jmh-generator-annprocess:${versions.jmh}"] - ] -} - - -task btracec(type: JavaExec) { - group 'Build' - inputs.files 'src/main/resources/scripts' - outputs.dir "${buildDir}/classes/main" - - environment('BTRACE_HOME', "$projectDir") - classpath configurations.compile - main 'com.sun.btrace.compiler.Compiler' - args '-d' - args "${buildDir}/classes/main" - args fileTree(dir: "src/main/resources/scripts", include: 'TraceScript.java') -} -compileJava.dependsOn btracec - - -['BTraceBench', 'ProfilerBenchmarks', 'StatsdBenchmarks', 'StringOpBenchmarks'].each { className -> - task(type: JavaExec, className) { - group 'Verification' - description "Run benchmark for class ${className}." - - inputs.files project.classes.outputs - inputs.files project.btracec.outputs - - classpath project.configurations.runtime - main "net.java.btrace.${className}" - } -} - - -dependencies { - compile project(':'), - files( rootProject.tasks.agentJar.outputs ), - files( rootProject.tasks.bootJar.outputs ), - files("${buildDir}/classes/main"), - libs.jmh, - files("${javaHome}/lib/tools.jar") -} diff --git a/benchmark/maven/nb-configuration.xml b/benchmark/maven/nb-configuration.xml deleted file mode 100644 index d991f3c01..000000000 --- a/benchmark/maven/nb-configuration.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - false - all - - diff --git a/benchmark/maven/nbactions.xml b/benchmark/maven/nbactions.xml deleted file mode 100644 index a4f124268..000000000 --- a/benchmark/maven/nbactions.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - run - - jar - - - process-classes - org.codehaus.mojo:exec-maven-plugin:1.2.1:exec - - - -classpath %classpath net.java.btrace.BTraceBench - java - - - - debug - - jar - - - process-classes - org.codehaus.mojo:exec-maven-plugin:1.2.1:exec - - - -Xdebug -Xrunjdwp:transport=dt_socket,server=n,address=${jpda.address} -classpath %classpath net.java.btrace.BTraceBench - java - true - - - - profile - - jar - - - process-classes - org.codehaus.mojo:exec-maven-plugin:1.2.1:exec - - - -classpath %classpath net.java.btrace.BTraceBench - java - - - diff --git a/benchmark/maven/pom.xml b/benchmark/maven/pom.xml deleted file mode 100644 index 4eab659e7..000000000 --- a/benchmark/maven/pom.xml +++ /dev/null @@ -1,223 +0,0 @@ - - - - 4.0.0 - - net.java.btrace - benchmark - 1.0-SNAPSHOT - jar - - BTrace Benchmark - - - 3.0 - - - - - org.openjdk.jmh - jmh-core - ${jmh.version} - - - org.openjdk.jmh - jmh-generator-annprocess - ${jmh.version} - provided - - - com.sun.tools.btrace - btrace-client - 1.3.6 - - - com.sun.tools.btrace - btrace-agent - 1.3.6 - - - com.sun.tools.btrace - btrace-boot - 1.3.6 - - - sun.jdk - tools - - - - - UTF-8 - 1.7.1 - ${java.home}/../Classes/classes.jar - ${java.home}/../lib/tools.jar - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.1 - - 1.6 - 1.8 - 1.8 - - - - org.apache.maven.plugins - maven-shade-plugin - 2.2 - - - package - - shade - - - benchmarks - - - org.openjdk.jmh.Main - - - - - com.sun.tools.btrace:* - - - - - - - - org.codehaus.mojo - exec-maven-plugin - 1.1.1 - - - process-resources - - exec - - - ${java.home}/bin/java - - -cp - - com.sun.btrace.compiler.Compiler - -d - ${basedir}/target/classes/ - ${basedir}/src/main/resources/scripts/TraceScript.java - - - - - - - - - - maven-clean-plugin - 2.5 - - - maven-deploy-plugin - 2.8.1 - - - maven-install-plugin - 2.5.1 - - - maven-jar-plugin - 2.4 - - - maven-javadoc-plugin - 2.9.1 - - - maven-resources-plugin - 2.6 - - - maven-site-plugin - 3.3 - - - maven-source-plugin - 2.2.1 - - - maven-surefire-plugin - 2.17 - - - - - - - mac - - - ${classes.jar} - - - - - - sun.jdk - tools - 1.6.0 - system - ${classes.jar} - - - - - - - true - - - - - sun.jdk - tools - 1.7.0 - system - ${tools.jar} - - - - - - A JMH benchmar to assert the overhead imposed by various types of BTrace instrumentation - diff --git a/benchmark/pom.xml b/benchmark/pom.xml deleted file mode 100644 index c3143a790..000000000 --- a/benchmark/pom.xml +++ /dev/null @@ -1,223 +0,0 @@ - - - - 4.0.0 - - net.java.btrace - benchmark - 1.0-SNAPSHOT - jar - - BTrace Benchmark - - - 3.0 - - - - - org.openjdk.jmh - jmh-core - ${jmh.version} - - - org.openjdk.jmh - jmh-generator-annprocess - ${jmh.version} - provided - - - com.sun.tools.btrace - btrace-client - 1.3.11.3 - - - com.sun.tools.btrace - btrace-agent - 1.3.11.3 - - - com.sun.tools.btrace - btrace-boot - 1.3.11.3 - - - sun.jdk - tools - - - - - UTF-8 - 1.20 - ${java.home}/../Classes/classes.jar - ${java.home}/../lib/tools.jar - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.1 - - 1.6 - 1.8 - 1.8 - - - - org.apache.maven.plugins - maven-shade-plugin - 2.2 - - - package - - shade - - - benchmarks - - - org.openjdk.jmh.Main - - - - - com.sun.tools.btrace:* - - - - - - - - org.codehaus.mojo - exec-maven-plugin - 1.1.1 - - - process-resources - - exec - - - ${java.home}/bin/java - - -cp - - com.sun.btrace.compiler.Compiler - -d - ${basedir}/target/classes/ - ${basedir}/src/main/resources/scripts/TraceScript.java - - - - - - - - - - maven-clean-plugin - 2.5 - - - maven-deploy-plugin - 2.8.1 - - - maven-install-plugin - 2.5.1 - - - maven-jar-plugin - 2.4 - - - maven-javadoc-plugin - 2.9.1 - - - maven-resources-plugin - 2.6 - - - maven-site-plugin - 3.3 - - - maven-source-plugin - 2.2.1 - - - maven-surefire-plugin - 2.17 - - - - - - - mac - - - ${classes.jar} - - - - - - sun.jdk - tools - 1.6.0 - system - ${classes.jar} - - - - - - - true - - - - - sun.jdk - tools - 1.7.0 - system - ${tools.jar} - - - - - - A JMH benchmark to assert the overhead imposed by various types of BTrace instrumentation - diff --git a/benchmark/src/main/java/com/sun/btrace/runtime/OnMethodTemplateBenchmark.java b/benchmark/src/main/java/com/sun/btrace/runtime/OnMethodTemplateBenchmark.java deleted file mode 100644 index 71c2f0775..000000000 --- a/benchmark/src/main/java/com/sun/btrace/runtime/OnMethodTemplateBenchmark.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2018, Jaroslav Bachorik . - * All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Copyright owner designates - * this particular file as subject to the "Classpath" exception as provided - * by the owner in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - */ -package com.sun.btrace.runtime; - -import com.sun.btrace.ArgsMap; -import com.sun.btrace.DebugSupport; -import com.sun.btrace.SharedSettings; -import java.util.concurrent.TimeUnit; -import org.openjdk.jmh.annotations.*; -import org.openjdk.jmh.infra.Blackhole; -import org.openjdk.jmh.runner.Runner; -import org.openjdk.jmh.runner.options.Options; -import org.openjdk.jmh.runner.options.OptionsBuilder; - -@State(Scope.Thread) -@OutputTimeUnit(TimeUnit.MICROSECONDS) -@Fork(1) -@BenchmarkMode(Mode.AverageTime) -public class OnMethodTemplateBenchmark { - private ArgsMap argsMap; - - @Setup - public void setup() { - argsMap = new ArgsMap(new String[]{"arg1=val1"}, new DebugSupport(SharedSettings.GLOBAL)); - } - - @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) - @Measurement(iterations = 5, time = 1200, timeUnit = TimeUnit.MILLISECONDS) - @Benchmark - public void testEmptyTemplate(Blackhole bh) { - bh.consume(argsMap.template("")); - } - - @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) - @Measurement(iterations = 5, time = 1200, timeUnit = TimeUnit.MILLISECONDS) - @Benchmark - public void testMatchTemplate(Blackhole bh) { - bh.consume(argsMap.template("this-is-${arg1}")); - } - - @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) - @Measurement(iterations = 5, time = 1200, timeUnit = TimeUnit.MILLISECONDS) - @Benchmark - public void testNoMatchTemplate(Blackhole bh) { - bh.consume(argsMap.template("this-is-${arg2}")); - } - - public static void main(String[] args) throws Exception { - Options opt = new OptionsBuilder() - .addProfiler("stack") - .include(".*" + OnMethodTemplateBenchmark.class.getSimpleName() + ".*test.*") - .build(); - - new Runner(opt).run(); - } -} diff --git a/benchmark/src/main/java/net/java/btrace/BTraceBench.java b/benchmark/src/main/java/net/java/btrace/BTraceBench.java deleted file mode 100644 index e31907de4..000000000 --- a/benchmark/src/main/java/net/java/btrace/BTraceBench.java +++ /dev/null @@ -1,363 +0,0 @@ -/* - * Copyright (c) 2005, 2014, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package net.java.btrace; - -import com.sun.btrace.BTraceRuntime; -import com.sun.btrace.ArgsMap; -import com.sun.btrace.CommandListener; -import com.sun.btrace.comm.DataCommand; -import com.sun.btrace.comm.OkayCommand; -import com.sun.btrace.instr.MethodTracker; - -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.PrintWriter; -import java.net.URL; -import java.net.URLClassLoader; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; -import java.nio.file.FileVisitResult; -import java.nio.file.FileVisitor; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.Random; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeUnit; - -import org.openjdk.jmh.annotations.Benchmark; -import org.openjdk.jmh.annotations.BenchmarkMode; -import org.openjdk.jmh.annotations.Fork; -import org.openjdk.jmh.annotations.Measurement; -import org.openjdk.jmh.annotations.Mode; -import org.openjdk.jmh.annotations.OutputTimeUnit; -import org.openjdk.jmh.annotations.Scope; -import org.openjdk.jmh.annotations.Setup; -import org.openjdk.jmh.annotations.State; -import org.openjdk.jmh.annotations.Threads; -import org.openjdk.jmh.annotations.Warmup; -import org.openjdk.jmh.runner.Runner; -import org.openjdk.jmh.runner.options.Options; -import org.openjdk.jmh.runner.options.OptionsBuilder; - -@State(Scope.Thread) -@OutputTimeUnit(TimeUnit.MICROSECONDS) -@Fork(1) -@BenchmarkMode(Mode.AverageTime) -public class BTraceBench { - - private static class BTraceConfig { - - private final String agentJar; - private final String scriptPath; - private final Path tmpRoot; - - private static final FileVisitor DEL_TREE = new FileVisitor() { - @Override - public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { - return FileVisitResult.CONTINUE; - } - - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - Files.delete(file); - return FileVisitResult.CONTINUE; - } - - @Override - public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { - return FileVisitResult.TERMINATE; - } - - @Override - public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { - Files.delete(dir); - return FileVisitResult.CONTINUE; - } - }; - - public BTraceConfig(Path tmpRoot, String agentJar, String scriptPath) { - this.agentJar = agentJar; - this.scriptPath = scriptPath; - this.tmpRoot = tmpRoot; - } - - public void cleanup() throws IOException { - Files.walkFileTree(tmpRoot, DEL_TREE); - } - } - - long counter; - long sampleCounter; - long durCounter; - - BTraceRuntime br; - LinkedBlockingQueue l = new LinkedBlockingQueue<>(); - PrintWriter pw; - CommandListener cl; - - @Setup - public void setup() { - MethodTracker.registerCounter(1, 10); - MethodTracker.registerCounter(2, 50); - MethodTracker.registerCounter(3, 100); - - Random r = new Random(System.currentTimeMillis()); - sampleCounter = 0; - durCounter = 0; - counter = r.nextInt(); - try { - FileOutputStream fos = new FileOutputStream("/tmp/test.dump"); - pw = new PrintWriter(fos); - cl = (c) -> { - if (c instanceof DataCommand) { - ((DataCommand) c).print(pw); - } - }; - } catch (Exception e) { - cl = (c) -> { - }; - } - br = new BTraceRuntime("BenchmarkClass", new ArgsMap(), cl, null, null); - } - - @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) - @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) - @Benchmark - public void testInstrumentedMethod() { - counter++; - } - - @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) - @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) - @Benchmark - public void testInstrumentedMethodLevelNoMatch() { - counter++; - } - - @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) - @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) - @Benchmark - public void testInstrumentedMethodSampled() { - counter++; - } - - @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) - @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) - @Benchmark - public void testInstrumentedMethodPrintln1() { - counter++; - } - - @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) - @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) - @Benchmark - public void testInstrumentedMethodPrintln1Sampled() { - counter++; - } - - @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) - @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) - @Benchmark - public void testInstrumentedMethodPrintln2() { - counter++; - } - - @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) - @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) - @Benchmark - public void testInstrumentedMethodPrintln3() { - counter++; - } - - @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) - @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) - @Benchmark - public void testInstrumentedMethodPrintln24() { - counter++; - } - - @Warmup(iterations = 5, time = 100, timeUnit = TimeUnit.MILLISECONDS) - @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) - @Benchmark - public void testMethod() { - counter++; - } - - @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) - @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) - @Benchmark - public void testInstrDuration() { - durCounter++; - } - - public boolean x = true; - @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) - @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) - @Benchmark - public void testInstrDurationSampled() { - sampleCounter++; - } - - @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) - @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) - @Benchmark - public void testInstrDurationSampledAdaptive() { - sampleCounter++; - } - - @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) - @Measurement(iterations = 5, time = 2000, timeUnit = TimeUnit.MILLISECONDS) - @Benchmark - public void testSendCommand() { - br.send(new OkayCommand()); - } - - @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) - @Measurement(iterations = 5, time = 2000, timeUnit = TimeUnit.MILLISECONDS) - @Threads(2) - @Benchmark - public void testSendCommandMulti2() { - br.send(new OkayCommand()); - } - - @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) - @Measurement(iterations = 5, time = 2000, timeUnit = TimeUnit.MILLISECONDS) - @Threads(4) - @Benchmark - public void testSendCommandMulti4() { - br.send(new OkayCommand()); - } - - @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) - @Measurement(iterations = 5, time = 2000, timeUnit = TimeUnit.MILLISECONDS) - @Threads(8) - @Benchmark - public void testSendCommandMulti8() { - br.send(new OkayCommand()); - } - - @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) - @Measurement(iterations = 5, time = 2000, timeUnit = TimeUnit.MILLISECONDS) - @Threads(16) - @Benchmark - public void testSendCommandMulti16() { - br.send(new OkayCommand()); - } - - long sampleHit10Checks = 0; - long sampleHit10Sampled = 0; - - @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) - @Measurement(iterations = 20, time = 100, timeUnit = TimeUnit.MILLISECONDS) - @Benchmark - @Threads(2) - public void testSampleHit10() { - sampleHit10Checks++; - if (MethodTracker.hit(1)) { - sampleHit10Sampled++; - } - } - - @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) - @Measurement(iterations = 20, time = 100, timeUnit = TimeUnit.MILLISECONDS) - @Benchmark - @Threads(2) - public void testSampleHit50() { - sampleHit10Checks++; - if (MethodTracker.hit(2)) { - sampleHit10Sampled++; - } - } - - @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) - @Measurement(iterations = 20, time = 100, timeUnit = TimeUnit.MILLISECONDS) - @Benchmark - @Threads(2) - public void testSampleHit100() { - sampleHit10Checks++; - if (MethodTracker.hit(3)) { - sampleHit10Sampled++; - } - } - - @org.openjdk.jmh.annotations.TearDown - public void teardown() { - System.err.println(); - if (sampleHit10Checks > 0) { - System.err.println("=== testSampleHit10"); - System.err.println("#samples ~ " + sampleHit10Sampled); - if (sampleHit10Sampled > 0) { - System.err.println("#sampling rate ~ " + (sampleHit10Checks / sampleHit10Sampled)); - } - } - } - - public static void main(String[] args) throws Exception { - BTraceConfig bc = getConfig(); - try { - Options opt = new OptionsBuilder() - .addProfiler("stack") - .jvmArgsPrepend("-javaagent:" + bc.agentJar + "=stdout=true,noServer=true," - + "script=" + bc.scriptPath) - .include(".*" + BTraceBench.class.getSimpleName() + ".*test.*") - .build(); - - new Runner(opt).run(); - } finally { - bc.cleanup(); - } - } - - private static BTraceConfig getConfig() throws IOException { - FileSystem fs = FileSystems.getDefault(); - - Path agentPath = null; - Path bootPath = null; - ClassLoader cl = ClassLoader.getSystemClassLoader(); - URL[] urls = ((URLClassLoader)cl).getURLs(); - for (URL url: urls) { - final String path = url.getPath(); - if (path.contains("btrace-agent")) { - agentPath = fs.getPath(path); - } else if (path.contains("btrace-boot")) { - bootPath = fs.getPath(path); - } - } - if (agentPath == null) { throw new IllegalArgumentException("btrace-agent.jar not found"); } - if (bootPath == null) { throw new IllegalArgumentException("btrace-boot.jar not found"); } - - Path tmpDir = Files.createTempDirectory("btrace-bench-"); - - Path targetPath = Files.copy(agentPath, tmpDir.resolve("btrace-agent.jar"), StandardCopyOption.REPLACE_EXISTING); - Files.copy(bootPath, tmpDir.resolve("btrace-boot.jar"), StandardCopyOption.REPLACE_EXISTING); - - URL traceLoc = BTraceBench.class.getResource("/scripts/TraceScript.class"); - String trace = traceLoc.getPath(); - - return new BTraceConfig(tmpDir, targetPath.toString(), trace); - } -} diff --git a/benchmark/src/main/java/net/java/btrace/ClassFilterBenchmark.java b/benchmark/src/main/java/net/java/btrace/ClassFilterBenchmark.java deleted file mode 100644 index 2a7a99f62..000000000 --- a/benchmark/src/main/java/net/java/btrace/ClassFilterBenchmark.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (c) 2018, Jaroslav Bachorik . - * All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Copyright owner designates - * this particular file as subject to the "Classpath" exception as provided - * by the owner in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - */ -package net.java.btrace; - -import com.sun.btrace.runtime.ClassFilter; -import com.sun.btrace.runtime.OnMethod; -import java.util.ArrayList; -import java.util.Collections; -import java.util.concurrent.TimeUnit; -import org.openjdk.jmh.annotations.*; -import org.openjdk.jmh.infra.Blackhole; -import org.openjdk.jmh.runner.Runner; -import org.openjdk.jmh.runner.options.Options; -import org.openjdk.jmh.runner.options.OptionsBuilder; - -@State(Scope.Thread) -@OutputTimeUnit(TimeUnit.MICROSECONDS) -@Fork(1) -@BenchmarkMode(Mode.AverageTime) -public class ClassFilterBenchmark { - private static final String CLASS_A_PKG = "io.btrace.benchmark"; - private static final String CLASS_A_NAME = "ClassA"; - private static final String CLASS_A = CLASS_A_PKG + "." + CLASS_A_NAME; - - private ClassFilter cfSimple; - private ClassFilter cfRegexName; - private ClassFilter cfSubtype; - - @Setup - public void setup() { - OnMethod simpleClassFilter = new OnMethod(); - simpleClassFilter.setClazz(CLASS_A); - - OnMethod regexNameFilter = new OnMethod(); - regexNameFilter.setClazz("/.*\\." + CLASS_A_NAME + "/"); - - OnMethod subtypeFilter = new OnMethod(); - subtypeFilter.setClazz("+java.util.List"); - - cfSimple = new ClassFilter(Collections.singleton(simpleClassFilter)); - cfRegexName = new ClassFilter(Collections.singleton(regexNameFilter)); - cfSubtype = new ClassFilter(Collections.singleton(subtypeFilter)); - } - - @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) - @Measurement(iterations = 5, time = 1200, timeUnit = TimeUnit.MILLISECONDS) - @Benchmark - public void testSimpleClassNameMatch(Blackhole bh) { - bh.consume(cfSimple.isNameMatching(CLASS_A)); - } - - @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) - @Measurement(iterations = 5, time = 1200, timeUnit = TimeUnit.MILLISECONDS) - @Benchmark - public void testRegexNameMatch(Blackhole bh) { - bh.consume(cfRegexName.isNameMatching(CLASS_A)); - } - - @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) - @Measurement(iterations = 5, time = 1200, timeUnit = TimeUnit.MILLISECONDS) - @Benchmark - public void testSubtypeMatch(Blackhole bh) { - bh.consume(cfSubtype.isCandidate(ArrayList.class)); - } - - @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) - @Measurement(iterations = 5, time = 1200, timeUnit = TimeUnit.MILLISECONDS) - @Benchmark - public void testSubtypeNoMatch(Blackhole bh) { - bh.consume(cfSubtype.isCandidate(String.class)); - } - - public static void main(String[] args) throws Exception { - Options opt = new OptionsBuilder() - .addProfiler("stack") - .include(".*" + ClassFilterBenchmark.class.getSimpleName() + ".*test.*") - .build(); - - new Runner(opt).run(); - } -} diff --git a/benchmark/src/main/java/net/java/btrace/ProbeLoadingBenchmark.java b/benchmark/src/main/java/net/java/btrace/ProbeLoadingBenchmark.java deleted file mode 100644 index 159bcd3f0..000000000 --- a/benchmark/src/main/java/net/java/btrace/ProbeLoadingBenchmark.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package net.java.btrace; - -import com.sun.btrace.SharedSettings; -import com.sun.btrace.org.objectweb.asm.ClassReader; -import com.sun.btrace.org.objectweb.asm.Opcodes; -import com.sun.btrace.org.objectweb.asm.tree.ClassNode; -import com.sun.btrace.runtime.BTraceProbe; -import com.sun.btrace.runtime.BTraceProbeFactory; -import com.sun.btrace.runtime.BTraceProbeNode; -import com.sun.btrace.runtime.BTraceProbePersisted; -import java.io.*; -import java.util.concurrent.TimeUnit; -import org.openjdk.jmh.annotations.*; -import org.openjdk.jmh.infra.Blackhole; -import org.openjdk.jmh.runner.Runner; -import org.openjdk.jmh.runner.options.Options; -import org.openjdk.jmh.runner.options.OptionsBuilder; - -@State(Scope.Thread) -@OutputTimeUnit(TimeUnit.MILLISECONDS) -@Fork(1) -@BenchmarkMode(Mode.AverageTime) -public class ProbeLoadingBenchmark { - private InputStream classStream; - private BTraceProbeFactory bpf; - - @Setup(Level.Trial) - public void setup() throws Exception { - bpf = new BTraceProbeFactory(SharedSettings.GLOBAL); - } - - @Setup(Level.Invocation) - public void setupRun() throws Exception { - classStream = ProbeLoadingBenchmark.class.getResourceAsStream("/scripts/TraceScript.class"); - } - - @TearDown(Level.Invocation) - public void tearDownRun() throws Exception { - classStream.close(); - } - - @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) - @Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.MILLISECONDS) - @Benchmark - public void testBTraceProbeNew(Blackhole bh) throws Exception { - BTraceProbe bp = bpf.createProbe(classStream); - if (bp == null) { - throw new NullPointerException(); - } - bh.consume(bp); - } - - public static void main(String[] args) throws Exception { - Options opt = new OptionsBuilder() - .addProfiler("stack") - .include(".*" + ProbeLoadingBenchmark.class.getSimpleName() + ".*test.*") - .build(); - - new Runner(opt).run(); - } -} diff --git a/benchmark/src/main/java/net/java/btrace/ProfilerBenchmarks.java b/benchmark/src/main/java/net/java/btrace/ProfilerBenchmarks.java deleted file mode 100644 index 696f80325..000000000 --- a/benchmark/src/main/java/net/java/btrace/ProfilerBenchmarks.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package net.java.btrace; - -import com.sun.btrace.profiling.MethodInvocationProfiler; -import java.util.concurrent.TimeUnit; -import org.openjdk.jmh.annotations.Benchmark; -import org.openjdk.jmh.annotations.BenchmarkMode; -import org.openjdk.jmh.annotations.Fork; -import org.openjdk.jmh.annotations.Measurement; -import org.openjdk.jmh.annotations.Mode; -import org.openjdk.jmh.annotations.OutputTimeUnit; -import org.openjdk.jmh.annotations.Scope; -import org.openjdk.jmh.annotations.Setup; -import org.openjdk.jmh.annotations.State; -import org.openjdk.jmh.annotations.Threads; -import org.openjdk.jmh.annotations.Warmup; -import org.openjdk.jmh.runner.Runner; -import org.openjdk.jmh.runner.options.Options; -import org.openjdk.jmh.runner.options.OptionsBuilder; -import org.openjdk.jmh.runner.options.VerboseMode; - -/** - * Basic benchmark for the performance of {@linkplain MethodInvocationProfiler} - * @author Jaroslav Bachorik - */ -@State(Scope.Thread) -@OutputTimeUnit(TimeUnit.MICROSECONDS) -@Fork(1) -@BenchmarkMode(Mode.AverageTime) -public class ProfilerBenchmarks { - private MethodInvocationProfiler mip1; - private MethodInvocationProfiler mip2; - - @Setup - public void setup() { - mip1 = new MethodInvocationProfiler(1); - mip2 = new MethodInvocationProfiler(500); - } - - @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) - @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) - @Benchmark - @Threads(1) - public void testOneMethodSingleThread() { - mip1.recordEntry("a"); - mip1.recordExit("a", 1); - } - - @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) - @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) - @Benchmark - @Threads(1) - public void testTwoMethods01Thread() { - mip2.recordEntry("a"); - mip2.recordEntry("b"); - mip2.recordExit("b", 10); - mip2.recordExit("a", 1); - } - - @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) - @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) - @Benchmark - @Threads(2) - public void testTwoMethods02Threads() { - mip2.recordEntry("a"); - mip2.recordEntry("b"); - mip2.recordExit("b", 10); - mip2.recordExit("a", 1); - } - - @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) - @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) - @Benchmark - @Threads(4) - public void testTwoMethods04Threads() { - mip2.recordEntry("a"); - mip2.recordEntry("b"); - mip2.recordExit("b", 10); - mip2.recordExit("a", 1); - } - - @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) - @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) - @Benchmark - @Threads(8) - public void testTwoMethods08Threads() { - mip2.recordEntry("a"); - mip2.recordEntry("b"); - mip2.recordExit("b", 10); - mip2.recordExit("a", 1); - } - @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) - @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) - @Benchmark - @Threads(16) - public void testTwoMethods16Threads() { - mip2.recordEntry("a"); - mip2.recordEntry("b"); - mip2.recordExit("b", 10); - mip2.recordExit("a", 1); - } - - public static void main(String[] args) throws Exception { - Options opt = new OptionsBuilder() - .addProfiler("stack") - .verbosity(VerboseMode.NORMAL) - .include(".*" + ProfilerBenchmarks.class.getSimpleName() + ".*test.*") - .build(); - - new Runner(opt).run(); - } -} diff --git a/benchmark/src/main/java/net/java/btrace/StatsdBenchmarks.java b/benchmark/src/main/java/net/java/btrace/StatsdBenchmarks.java deleted file mode 100644 index 96ceead88..000000000 --- a/benchmark/src/main/java/net/java/btrace/StatsdBenchmarks.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package net.java.btrace; - -import com.sun.btrace.profiling.MethodInvocationProfiler; -import com.sun.btrace.services.impl.Statsd; -import java.util.concurrent.TimeUnit; -import org.openjdk.jmh.annotations.Benchmark; -import org.openjdk.jmh.annotations.BenchmarkMode; -import org.openjdk.jmh.annotations.Fork; -import org.openjdk.jmh.annotations.Measurement; -import org.openjdk.jmh.annotations.Mode; -import org.openjdk.jmh.annotations.OutputTimeUnit; -import org.openjdk.jmh.annotations.Scope; -import org.openjdk.jmh.annotations.Setup; -import org.openjdk.jmh.annotations.State; -import org.openjdk.jmh.annotations.Threads; -import org.openjdk.jmh.annotations.Warmup; -import org.openjdk.jmh.profile.ProfilerFactory; -import org.openjdk.jmh.runner.Runner; -import org.openjdk.jmh.runner.options.Options; -import org.openjdk.jmh.runner.options.OptionsBuilder; - -/** - * Basic benchmark for the performance of {@linkplain MethodInvocationProfiler} - * @author Jaroslav Bachorik - */ -@State(Scope.Thread) -@OutputTimeUnit(TimeUnit.MICROSECONDS) -@Fork(1) -@BenchmarkMode(Mode.AverageTime) -public class StatsdBenchmarks { - private Statsd c; - - @Setup - public void setup() { - c = Statsd.getInstance(); - } - - @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) - @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) - @Benchmark - @Threads(1) - public void testGauge_1() { - c.gauge("g1", 10); - } - - public static void main(String[] args) throws Exception { - Options opt = new OptionsBuilder() - .addProfiler("stack") - .include(".*" + StatsdBenchmarks.class.getSimpleName() + ".*test.*") - .build(); - - new Runner(opt).run(); - } -} diff --git a/benchmark/src/main/java/net/java/btrace/StringOpBenchmarks.java b/benchmark/src/main/java/net/java/btrace/StringOpBenchmarks.java deleted file mode 100644 index 2a1428d08..000000000 --- a/benchmark/src/main/java/net/java/btrace/StringOpBenchmarks.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package net.java.btrace; - -import java.util.concurrent.TimeUnit; -import org.openjdk.jmh.annotations.Benchmark; -import org.openjdk.jmh.annotations.BenchmarkMode; -import org.openjdk.jmh.annotations.Fork; -import org.openjdk.jmh.annotations.Level; -import org.openjdk.jmh.annotations.Measurement; -import org.openjdk.jmh.annotations.Mode; -import org.openjdk.jmh.annotations.OutputTimeUnit; -import org.openjdk.jmh.annotations.Scope; -import org.openjdk.jmh.annotations.Setup; -import org.openjdk.jmh.annotations.State; -import org.openjdk.jmh.annotations.Warmup; -import org.openjdk.jmh.profile.ProfilerFactory; -import org.openjdk.jmh.runner.Runner; -import org.openjdk.jmh.runner.options.Options; -import org.openjdk.jmh.runner.options.OptionsBuilder; - -@State(Scope.Thread) -@OutputTimeUnit(TimeUnit.MICROSECONDS) -@Fork(1) -@BenchmarkMode(Mode.AverageTime) -public class StringOpBenchmarks { - private static final String STRING_PART = "h"; - - StringBuilder sb; - String st; - String res; - - @Setup - public void setup() { - st = ""; - } - - @Setup(Level.Invocation) - public void setupEach() { - sb = new StringBuilder(); - } - - @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) - @Measurement(iterations = 5, time = 1200, timeUnit = TimeUnit.MILLISECONDS) - @Benchmark - public void testStringBuilder() { - sb.append(STRING_PART).append(STRING_PART); - } - - @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) - @Measurement(iterations = 5, time = 1200, timeUnit = TimeUnit.MILLISECONDS) - @Benchmark - public void testStringPlus() { - res = st + STRING_PART + STRING_PART; - } - - @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) - @Measurement(iterations = 5, time = 1200, timeUnit = TimeUnit.MILLISECONDS) - @Benchmark - public void testStrCat() { - res = st.concat(STRING_PART).concat(STRING_PART); - } - - public static void main(String[] args) throws Exception { - Options opt = new OptionsBuilder() - .addProfiler("gc") - .include(".*" + StringOpBenchmarks.class.getSimpleName() + ".*test.*") - .build(); - - new Runner(opt).run(); - } -} diff --git a/benchmark/src/main/resources/scripts/TraceScript.java b/benchmark/src/main/resources/scripts/TraceScript.java deleted file mode 100644 index 3382172c4..000000000 --- a/benchmark/src/main/resources/scripts/TraceScript.java +++ /dev/null @@ -1,103 +0,0 @@ -package scripts; - - -import com.sun.btrace.annotations.BTrace; -import com.sun.btrace.annotations.Duration; -import com.sun.btrace.annotations.Kind; -import com.sun.btrace.annotations.Level; -import com.sun.btrace.annotations.Location; -import com.sun.btrace.annotations.OnMethod; -import com.sun.btrace.annotations.ProbeClassName; -import com.sun.btrace.annotations.ProbeMethodName; - -import static com.sun.btrace.BTraceUtils.*; -import com.sun.btrace.annotations.Sampled; - - -@BTrace -public class TraceScript { - @OnMethod(clazz="net.java.btrace.BTraceBench", method="testInstrumentedMethod") - public static void onMethodEntryEmpty(@ProbeClassName String pcn, @ProbeMethodName String pmn) { - - } - - @OnMethod(clazz="net.java.btrace.BTraceBench", method="testInstrumentedMethodLevelNoMatch", enableAt = @Level("100")) - public static void onMethodEntryEmptyLevelNoMatch(@ProbeClassName String pcn, @ProbeMethodName String pmn) { - - } - - @OnMethod(clazz="net.java.btrace.BTraceBench", method="testInstrumentedMethodSampled") - @Sampled(kind = Sampled.Sampler.Const) - public static void onMethodEntryEmptySampled(@ProbeClassName String pcn, @ProbeMethodName String pmn) { - - } - - @OnMethod(clazz="net.java.btrace.BTraceBench", method="testInstrDuration", location = @Location(Kind.RETURN)) - public static void onMethodRetDuration(@ProbeClassName String pcn, @ProbeMethodName String pmn, @Duration long dur) { - - } - - @OnMethod(clazz="net.java.btrace.BTraceBench", method="testInstrDurationSampled", location = @Location(Kind.RETURN)) - @Sampled(kind = Sampled.Sampler.Const) - public static void onMethodRetDurationSampled(@ProbeClassName String pcn, @ProbeMethodName String pmn, @Duration long dur) { - - } - - @OnMethod(clazz="net.java.btrace.BTraceBench", method="testInstrDurationSampledAdaptive", location = @Location(Kind.RETURN)) - @Sampled - public static void onMethodRetDurationSampledAdaptive(@ProbeClassName String pcn, @ProbeMethodName String pmn, @Duration long dur) { - - } - - @OnMethod(clazz="net.java.btrace.BTraceBench", method="testInstrumentedMethodPrintln1") - public static void onMethodEntryPrintln1(@ProbeClassName String pcn, @ProbeMethodName String pmn) { - println(pcn); - } - - @OnMethod(clazz="net.java.btrace.BTraceBench", method="testInstrumentedMethodPrintln1Sampled") - @Sampled - public static void onMethodEntryPrintln1Sampled(@ProbeClassName String pcn, @ProbeMethodName String pmn) { - println(pcn); - } - - @OnMethod(clazz="net.java.btrace.BTraceBench", method="testInstrumentedMethodPrintln2") - public static void onMethodEntryPrintln2(@ProbeClassName String pcn, @ProbeMethodName String pmn) { - println(pcn); - println(pmn); - } - - @OnMethod(clazz="net.java.btrace.BTraceBench", method="testInstrumentedMethodPrintln3") - public static void onMethodEntryPrintln3(@ProbeClassName String pcn, @ProbeMethodName String pmn) { - println(pcn); - println(pmn); - println(pmn); - } - - @OnMethod(clazz="net.java.btrace.BTraceBench", method="testInstrumentedMethodPrintln24") - public static void onMethodEntryPrintln24(@ProbeClassName String pcn, @ProbeMethodName String pmn) { - println(pcn); - println(pmn); - println(pmn); - println(pcn); - println(pmn); - println(pmn); - println(pcn); - println(pmn); - println(pmn); - println(pcn); - println(pmn); - println(pmn); - println(pcn); - println(pmn); - println(pmn); - println(pcn); - println(pmn); - println(pmn); - println(pcn); - println(pmn); - println(pmn); - println(pcn); - println(pmn); - println(pmn); - } -} diff --git a/benchmarks/agent-benchmark/build.gradle b/benchmarks/agent-benchmark/build.gradle new file mode 100644 index 000000000..72b6ab554 --- /dev/null +++ b/benchmarks/agent-benchmark/build.gradle @@ -0,0 +1,62 @@ +plugins { + id 'java' + alias(libs.plugins.jmh) +} + +description 'A JMH benchmark to assert the overhead imposed by various types of BTrace instrumentation.' + +def env = System.getenv() +def javaHome = env['JAVA_HOME'] + +dependencies { + implementation project(path: ":btrace-dist", configuration: "shadow") + implementation project(":btrace-compiler") + jmh tasks.getByPath(':btrace-dist:bootJar').outputs.getFiles() + + jmh libs.jmh + jmh libs.jmh.annprocess +} + +task btracec(type: JavaExec) { + group 'Build' + inputs.files 'src/main/resources/scripts' + outputs.dir buildDir.toPath().resolve("classes/java/main") + + environment('BTRACE_HOME', "$projectDir") + classpath configurations.runtimeClasspath + mainClass = 'org.openjdk.btrace.compiler.Compiler' + args '-d' + args "${buildDir}/classes/java/main/" + args '-packext' + args 'btclass' + args fileTree(dir: "src/jmh/btrace", include: 'TraceScript.java') +} +compileJmhJava.dependsOn btracec +jmhClasses.dependsOn btracec + +jmhJar { + include 'META-INF/BenchmarkList' + include 'META-INF/CompilerHints' + include 'org/openjdk/jmh/**' + include 'org/openjdk/btrace/bench/**/*.class' + include 'org/openjdk/btrace/generated/**/*' + include "joptsimple/**" + include "org/apache/**" + include 'jmh*' + include 'benchmark/**' + include '*.btclass' +} + +jmh { + warmupIterations = 5 + iterations = 10 + fork = 2 + jvm = "${env['JAVA_HOME']}/bin/java" + duplicateClassesStrategy = DuplicatesStrategy.WARN + def agentJarPath = tasks.getByPath(':btrace-dist:agentJar').outputs.getFiles().getSingleFile() + def scriptPath = buildDir.toPath().resolve('classes/java/main/TraceScript.btclass') + def agent = "-javaagent:${agentJarPath}=stdout=false,noServer=true,debug=false,script=${scriptPath}" + jvmArgsAppend = ["-Djmh.basedir=${buildDir.getParentFile()}", "-Dproject.version=${project.version}", "-Xmx128m", "-agentpath:/tmp/libasyncProfiler.dylib=start,event=cpu,jfr=7,file=/tmp/btrace.jfr", "${agent}"] + includes = ['.*BTraceBench.*'] + profilers = ['stack'] +} \ No newline at end of file diff --git a/benchmarks/agent-benchmark/src/jmh/btrace/TraceScript.java b/benchmarks/agent-benchmark/src/jmh/btrace/TraceScript.java new file mode 100644 index 000000000..e279dd17c --- /dev/null +++ b/benchmarks/agent-benchmark/src/jmh/btrace/TraceScript.java @@ -0,0 +1,109 @@ +import static org.openjdk.btrace.core.BTraceUtils.*; + +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.Duration; +import org.openjdk.btrace.core.annotations.Kind; +import org.openjdk.btrace.core.annotations.Level; +import org.openjdk.btrace.core.annotations.Location; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.ProbeClassName; +import org.openjdk.btrace.core.annotations.ProbeMethodName; +import org.openjdk.btrace.core.annotations.Sampled; + +@BTrace +public class TraceScript { + @OnMethod(clazz = "benchmark.BTraceBench", method = "testInstrumentedMethod") + public static void onMethodEntryEmpty(@ProbeClassName String pcn, @ProbeMethodName String pmn) {} + + @OnMethod( + clazz = "benchmark.BTraceBench", + method = "testInstrumentedMethodLevelNoMatch", + enableAt = @Level("100")) + public static void onMethodEntryEmptyLevelNoMatch( + @ProbeClassName String pcn, @ProbeMethodName String pmn) {} + + @OnMethod(clazz = "benchmark.BTraceBench", method = "testInstrumentedMethodSampled") + @Sampled(kind = Sampled.Sampler.Const) + public static void onMethodEntryEmptySampled( + @ProbeClassName String pcn, @ProbeMethodName String pmn) {} + + @OnMethod( + clazz = "benchmark.BTraceBench", + method = "testInstrDuration", + location = @Location(Kind.RETURN)) + public static void onMethodRetDuration( + @ProbeClassName String pcn, @ProbeMethodName String pmn, @Duration long dur) {} + + @OnMethod( + clazz = "benchmark.BTraceBench", + method = "testInstrDurationSampled", + location = @Location(Kind.RETURN)) + @Sampled(kind = Sampled.Sampler.Const) + public static void onMethodRetDurationSampled( + @ProbeClassName String pcn, @ProbeMethodName String pmn, @Duration long dur) {} + + @OnMethod( + clazz = "benchmark.BTraceBench", + method = "testInstrDurationSampledAdaptive", + location = @Location(Kind.RETURN)) + @Sampled + public static void onMethodRetDurationSampledAdaptive( + @ProbeClassName String pcn, @ProbeMethodName String pmn, @Duration long dur) {} + + @OnMethod(clazz = "benchmark.BTraceBench", method = "testInstrumentedMethodPrintln1") + public static void onMethodEntryPrintln1( + @ProbeClassName String pcn, @ProbeMethodName String pmn) { + println(pcn); + } + + @OnMethod(clazz = "benchmark.BTraceBench", method = "testInstrumentedMethodPrintln1Sampled") + @Sampled + public static void onMethodEntryPrintln1Sampled( + @ProbeClassName String pcn, @ProbeMethodName String pmn) { + println(pcn); + } + + @OnMethod(clazz = "benchmark.BTraceBench", method = "testInstrumentedMethodPrintln2") + public static void onMethodEntryPrintln2( + @ProbeClassName String pcn, @ProbeMethodName String pmn) { + println(pcn); + println(pmn); + } + + @OnMethod(clazz = "benchmark.BTraceBench", method = "testInstrumentedMethodPrintln3") + public static void onMethodEntryPrintln3( + @ProbeClassName String pcn, @ProbeMethodName String pmn) { + println(pcn); + println(pmn); + println(pmn); + } + + @OnMethod(clazz = "benchmark.BTraceBench", method = "testInstrumentedMethodPrintln24") + public static void onMethodEntryPrintln24( + @ProbeClassName String pcn, @ProbeMethodName String pmn) { + println(pcn); + println(pmn); + println(pmn); + println(pcn); + println(pmn); + println(pmn); + println(pcn); + println(pmn); + println(pmn); + println(pcn); + println(pmn); + println(pmn); + println(pcn); + println(pmn); + println(pmn); + println(pcn); + println(pmn); + println(pmn); + println(pcn); + println(pmn); + println(pmn); + println(pcn); + println(pmn); + println(pmn); + } +} diff --git a/benchmarks/agent-benchmark/src/jmh/java/benchmark/BTraceBench.java b/benchmarks/agent-benchmark/src/jmh/java/benchmark/BTraceBench.java new file mode 100644 index 000000000..5e43c8dd2 --- /dev/null +++ b/benchmarks/agent-benchmark/src/jmh/java/benchmark/BTraceBench.java @@ -0,0 +1,344 @@ +/* + * Copyright (c) 2005, 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package benchmark; + +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.FileVisitResult; +import java.nio.file.FileVisitor; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Random; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import org.openjdk.btrace.core.comm.CommandListener; +import org.openjdk.btrace.instr.MethodTracker; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Threads; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +@State(Scope.Thread) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@Fork(1) +@BenchmarkMode(Mode.AverageTime) +public class BTraceBench { + + private static class BTraceConfig { + + private final String agentJar; + private final String scriptPath; + private final Path tmpRoot; + + private static final FileVisitor DEL_TREE = + new FileVisitor() { + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) + throws IOException { + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) + throws IOException { + Files.delete(file); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { + return FileVisitResult.TERMINATE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + Files.delete(dir); + return FileVisitResult.CONTINUE; + } + }; + + public BTraceConfig(Path tmpRoot, String agentJar, String scriptPath) { + this.agentJar = agentJar; + this.scriptPath = scriptPath; + this.tmpRoot = tmpRoot; + } + + public void cleanup() throws IOException { + Files.walkFileTree(tmpRoot, DEL_TREE); + } + } + + long counter; + long sampleCounter; + long durCounter; + + @Setup + public void setup() { + MethodTracker.registerCounter(1, 10); + MethodTracker.registerCounter(2, 50); + MethodTracker.registerCounter(3, 100); + + Random r = new Random(System.currentTimeMillis()); + sampleCounter = 0; + durCounter = 0; + counter = r.nextInt(); + } + + @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) + @Benchmark + public void testInstrumentedMethod() { + counter++; + } + + @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) + @Benchmark + public void testInstrumentedMethodLevelNoMatch() { + counter++; + } + + @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) + @Benchmark + public void testInstrumentedMethodSampled() { + counter++; + } + + @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) + @Benchmark + public void testInstrumentedMethodPrintln1() { + counter++; + } + + @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) + @Benchmark + public void testInstrumentedMethodPrintln1Sampled() { + counter++; + } + + @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) + @Benchmark + public void testInstrumentedMethodPrintln2() { + counter++; + } + + @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) + @Benchmark + public void testInstrumentedMethodPrintln3() { + counter++; + } + + @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) + @Benchmark + public void testInstrumentedMethodPrintln24() { + counter++; + } + + @Warmup(iterations = 5, time = 100, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) + @Benchmark + public void testMethod() { + counter++; + } + + @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) + @Benchmark + public void testInstrDuration() { + durCounter++; + } + + public boolean x = true; + + @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) + @Benchmark + public void testInstrDurationSampled() { + sampleCounter++; + } + + @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) + @Benchmark + public void testInstrDurationSampledAdaptive() { + sampleCounter++; + } + + // @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) + // @Measurement(iterations = 5, time = 2000, timeUnit = TimeUnit.MILLISECONDS) + // @Benchmark + // public void testSendCommand() { + // br.send(new OkayCommand()); + // } + // + // @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) + // @Measurement(iterations = 5, time = 2000, timeUnit = TimeUnit.MILLISECONDS) + // @Threads(2) + // @Benchmark + // public void testSendCommandMulti2() { + // br.send(new OkayCommand()); + // } + // + // @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) + // @Measurement(iterations = 5, time = 2000, timeUnit = TimeUnit.MILLISECONDS) + // @Threads(4) + // @Benchmark + // public void testSendCommandMulti4() { + // br.send(new OkayCommand()); + // } + // + // @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) + // @Measurement(iterations = 5, time = 2000, timeUnit = TimeUnit.MILLISECONDS) + // @Threads(8) + // @Benchmark + // public void testSendCommandMulti8() { + // br.send(new OkayCommand()); + // } + // + // @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) + // @Measurement(iterations = 5, time = 2000, timeUnit = TimeUnit.MILLISECONDS) + // @Threads(16) + // @Benchmark + // public void testSendCommandMulti16() { + // br.send(new OkayCommand()); + // } + + long sampleHit10Checks = 0; + long sampleHit10Sampled = 0; + + @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 20, time = 100, timeUnit = TimeUnit.MILLISECONDS) + @Benchmark + @Threads(2) + public void testSampleHit10() { + sampleHit10Checks++; + if (MethodTracker.hit(1)) { + sampleHit10Sampled++; + } + } + + @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 20, time = 100, timeUnit = TimeUnit.MILLISECONDS) + @Benchmark + @Threads(2) + public void testSampleHit50() { + sampleHit10Checks++; + if (MethodTracker.hit(2)) { + sampleHit10Sampled++; + } + } + + @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 20, time = 100, timeUnit = TimeUnit.MILLISECONDS) + @Benchmark + @Threads(2) + public void testSampleHit100() { + sampleHit10Checks++; + if (MethodTracker.hit(3)) { + sampleHit10Sampled++; + } + } + + @org.openjdk.jmh.annotations.TearDown + public void teardown() { + if (sampleHit10Checks > 0) { + System.err.println("=== testSampleHit10"); + System.err.println("#samples ~ " + sampleHit10Sampled); + if (sampleHit10Sampled > 0) { + System.err.println("#sampling rate ~ " + (sampleHit10Checks / sampleHit10Sampled)); + } + } + } + + public static void main(String[] args) throws Exception { + BTraceConfig bc = getConfig(); + try { + Options opt = + new OptionsBuilder() + .addProfiler("stack") +// .jvmArgsPrepend( +// "-javaagent:" +// + bc.agentJar +// + "=stdout=false,noServer=true," +// + "script=" +// + bc.scriptPath) + .include(".*" + BTraceBench.class.getSimpleName() + ".*test.*") + .build(); + + new Runner(opt).run(); + } finally { + bc.cleanup(); + } + } + + private static BTraceConfig getConfig() throws IOException { + FileSystem fs = FileSystems.getDefault(); + + Path distLibs = null; + String basedir = System.getProperty("jmh.basedir"); + String version = System.getProperty("project.version"); + String scriptPath = System.getProperty("script.path"); + Path root = null; + if (basedir == null) { + root = fs.getPath(".").toAbsolutePath(); + } else { + root = Paths.get(basedir).getParent(); + } + distLibs = root.resolve("btrace-dist/build/resources/main/" + version + "/libs"); + + Path agentPath = distLibs.resolve("btrace-agent.jar"); + Path bootPath = distLibs.resolve("btrace-boot.jar"); + + Path tmpDir = Files.createTempDirectory("btrace-bench-"); + + Path targetPath = + Files.copy( + agentPath, tmpDir.resolve("btrace-agent.jar"), StandardCopyOption.REPLACE_EXISTING); + Files.copy(bootPath, tmpDir.resolve("btrace-boot.jar"), StandardCopyOption.REPLACE_EXISTING); + + return new BTraceConfig(tmpDir, targetPath.toString(), scriptPath + "/TraceScript.btclass"); + } +} diff --git a/benchmarks/runtime-benchmarks/build.gradle b/benchmarks/runtime-benchmarks/build.gradle new file mode 100644 index 000000000..161b96504 --- /dev/null +++ b/benchmarks/runtime-benchmarks/build.gradle @@ -0,0 +1,69 @@ +plugins { + id 'java' + alias(libs.plugins.jmh) +} + +description 'A JMH benchmark to assert the overhead imposed by various types of BTrace instrumentation.' + +def env = System.getenv() +def javaHome = env['JAVA_HOME'] + +configurations { + compilerDeps +} + +dependencies { + implementation project(path: ":btrace-dist", configuration: "shadow") + implementation project(":btrace-compiler") + jmh project(":btrace-instr") + jmh project(":btrace-runtime") + jmh project(":btrace-statsd") + jmh libs.jmh + jmh libs.jmh.annprocess + compilerDeps project(path: ":btrace-dist", configuration: "shadow") + compilerDeps project(":btrace-compiler") +} + +task btracec(type: JavaExec) { + group 'Build' + inputs.files 'src/main/resources/scripts' + outputs.dir "${buildDir}/classes/java/main" + + environment('BTRACE_HOME', "$projectDir") + classpath configurations.compilerDeps + mainClass = 'org.openjdk.btrace.compiler.Compiler' + args '-d' + args "${buildDir}/classes/java/main/" + args '-packext' + args 'btclass' + args fileTree(dir: "src/jmh/btrace", include: 'TraceScript.java') +} +compileJmhJava.dependsOn btracec +jmhClasses.dependsOn btracec + +jmhJar { + include 'META-INF/BenchmarkList' + include 'META-INF/CompilerHints' + include 'org/jctools/**/*' + include 'org/objectweb/asm/**' + include 'org/openjdk/jmh/**' + include 'org/openjdk/btrace/bench/**/*.class' + include "org/openjdk/btrace/core/**" + include "org/openjdk/btrace/instr/**" + include 'org/openjdk/btrace/generated/**/*' + include 'org/openjdk/btrace/runtime/**' + include 'org/openjdk/btrace/services/**' + include "joptsimple/**" + include "org/apache/**" + include '*.btclass' + include 'jmh*' +} + +jmh { + duplicateClassesStrategy = DuplicatesStrategy.WARN + jvmArgsAppend = ["-Djmh.basedir=${project.buildDir.getParent()}", "-Dproject.version=${project.version}"] +// jmhVersion = '1.27' + includes = ['org.openjdk.btrace.bench.ClassFilterBenchmark'] + verbosity = 'EXTRA' +} + diff --git a/benchmarks/runtime-benchmarks/src/jmh/btrace/TraceScript.java b/benchmarks/runtime-benchmarks/src/jmh/btrace/TraceScript.java new file mode 100644 index 000000000..99fb5e21d --- /dev/null +++ b/benchmarks/runtime-benchmarks/src/jmh/btrace/TraceScript.java @@ -0,0 +1,111 @@ +import static org.openjdk.btrace.core.BTraceUtils.*; + +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.Duration; +import org.openjdk.btrace.core.annotations.Kind; +import org.openjdk.btrace.core.annotations.Level; +import org.openjdk.btrace.core.annotations.Location; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.ProbeClassName; +import org.openjdk.btrace.core.annotations.ProbeMethodName; +import org.openjdk.btrace.core.annotations.Sampled; + +@BTrace +public class TraceScript { + @OnMethod(clazz = "org.openjdk.btrace.BTraceBench", method = "testInstrumentedMethod") + public static void onMethodEntryEmpty(@ProbeClassName String pcn, @ProbeMethodName String pmn) {} + + @OnMethod( + clazz = "org.openjdk.btrace.BTraceBench", + method = "testInstrumentedMethodLevelNoMatch", + enableAt = @Level("100")) + public static void onMethodEntryEmptyLevelNoMatch( + @ProbeClassName String pcn, @ProbeMethodName String pmn) {} + + @OnMethod(clazz = "org.openjdk.btrace.BTraceBench", method = "testInstrumentedMethodSampled") + @Sampled(kind = Sampled.Sampler.Const) + public static void onMethodEntryEmptySampled( + @ProbeClassName String pcn, @ProbeMethodName String pmn) {} + + @OnMethod( + clazz = "org.openjdk.btrace.BTraceBench", + method = "testInstrDuration", + location = @Location(Kind.RETURN)) + public static void onMethodRetDuration( + @ProbeClassName String pcn, @ProbeMethodName String pmn, @Duration long dur) {} + + @OnMethod( + clazz = "org.openjdk.btrace.BTraceBench", + method = "testInstrDurationSampled", + location = @Location(Kind.RETURN)) + @Sampled(kind = Sampled.Sampler.Const) + public static void onMethodRetDurationSampled( + @ProbeClassName String pcn, @ProbeMethodName String pmn, @Duration long dur) {} + + @OnMethod( + clazz = "org.openjdk.btrace.BTraceBench", + method = "testInstrDurationSampledAdaptive", + location = @Location(Kind.RETURN)) + @Sampled + public static void onMethodRetDurationSampledAdaptive( + @ProbeClassName String pcn, @ProbeMethodName String pmn, @Duration long dur) {} + + @OnMethod(clazz = "org.openjdk.btrace.BTraceBench", method = "testInstrumentedMethodPrintln1") + public static void onMethodEntryPrintln1( + @ProbeClassName String pcn, @ProbeMethodName String pmn) { + println(pcn); + } + + @OnMethod( + clazz = "org.openjdk.btrace.BTraceBench", + method = "testInstrumentedMethodPrintln1Sampled") + @Sampled + public static void onMethodEntryPrintln1Sampled( + @ProbeClassName String pcn, @ProbeMethodName String pmn) { + println(pcn); + } + + @OnMethod(clazz = "org.openjdk.btrace.BTraceBench", method = "testInstrumentedMethodPrintln2") + public static void onMethodEntryPrintln2( + @ProbeClassName String pcn, @ProbeMethodName String pmn) { + println(pcn); + println(pmn); + } + + @OnMethod(clazz = "org.openjdk.btrace.BTraceBench", method = "testInstrumentedMethodPrintln3") + public static void onMethodEntryPrintln3( + @ProbeClassName String pcn, @ProbeMethodName String pmn) { + println(pcn); + println(pmn); + println(pmn); + } + + @OnMethod(clazz = "org.openjdk.btrace.BTraceBench", method = "testInstrumentedMethodPrintln24") + public static void onMethodEntryPrintln24( + @ProbeClassName String pcn, @ProbeMethodName String pmn) { + println(pcn); + println(pmn); + println(pmn); + println(pcn); + println(pmn); + println(pmn); + println(pcn); + println(pmn); + println(pmn); + println(pcn); + println(pmn); + println(pmn); + println(pcn); + println(pmn); + println(pmn); + println(pcn); + println(pmn); + println(pmn); + println(pcn); + println(pmn); + println(pmn); + println(pcn); + println(pmn); + println(pmn); + } +} diff --git a/benchmarks/runtime-benchmarks/src/jmh/java/org/openjdk/btrace/bench/ClassFilterBenchmark.java b/benchmarks/runtime-benchmarks/src/jmh/java/org/openjdk/btrace/bench/ClassFilterBenchmark.java new file mode 100644 index 000000000..5cc22effc --- /dev/null +++ b/benchmarks/runtime-benchmarks/src/jmh/java/org/openjdk/btrace/bench/ClassFilterBenchmark.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2018, Jaroslav Bachorik . + * All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Copyright owner designates + * this particular file as subject to the "Classpath" exception as provided + * by the owner in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ +package org.openjdk.btrace.bench; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.concurrent.TimeUnit; +import org.openjdk.btrace.instr.ClassFilter; +import org.openjdk.btrace.instr.OnMethod; +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +@State(Scope.Thread) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@Fork(1) +@BenchmarkMode(Mode.AverageTime) +public class ClassFilterBenchmark { + private static final String CLASS_A_PKG = "org.openjdk.btrace.benchmark"; + private static final String CLASS_A_NAME = "ClassA"; + private static final String CLASS_A = CLASS_A_PKG + "." + CLASS_A_NAME; + + private ClassFilter cfSimple; + private ClassFilter cfRegexName; + private ClassFilter cfSubtype; + + @Setup + public void setup() { + OnMethod simpleClassFilter = new OnMethod(); + simpleClassFilter.setClazz(CLASS_A); + + OnMethod regexNameFilter = new OnMethod(); + regexNameFilter.setClazz("/.*\\." + CLASS_A_NAME + "/"); + + OnMethod subtypeFilter = new OnMethod(); + subtypeFilter.setClazz("+java.util.List"); + + cfSimple = new ClassFilter(Collections.singleton(simpleClassFilter)); + cfRegexName = new ClassFilter(Collections.singleton(regexNameFilter)); + cfSubtype = new ClassFilter(Collections.singleton(subtypeFilter)); + } + + @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 5, time = 1200, timeUnit = TimeUnit.MILLISECONDS) + @Benchmark + public void testSimpleClassNameMatch(Blackhole bh) { + bh.consume(cfSimple.isNameMatching(CLASS_A)); + } + + @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 5, time = 1200, timeUnit = TimeUnit.MILLISECONDS) + @Benchmark + public void testRegexNameMatch(Blackhole bh) { + bh.consume(cfRegexName.isNameMatching(CLASS_A)); + } + + @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 5, time = 1200, timeUnit = TimeUnit.MILLISECONDS) + @Benchmark + public void testSubtypeMatch(Blackhole bh) { + bh.consume(cfSubtype.isCandidate(ArrayList.class)); + } + + @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 5, time = 1200, timeUnit = TimeUnit.MILLISECONDS) + @Benchmark + public void testSubtypeNoMatch(Blackhole bh) { + bh.consume(cfSubtype.isCandidate(String.class)); + } + + public static void main(String[] args) throws Exception { + Options opt = + new OptionsBuilder() + .addProfiler("stack") + .include(".*" + ClassFilterBenchmark.class.getSimpleName() + ".*test.*") + .build(); + + new Runner(opt).run(); + } +} diff --git a/benchmarks/runtime-benchmarks/src/jmh/java/org/openjdk/btrace/bench/OnMethodTemplateBenchmark.java b/benchmarks/runtime-benchmarks/src/jmh/java/org/openjdk/btrace/bench/OnMethodTemplateBenchmark.java new file mode 100644 index 000000000..06afc2b8f --- /dev/null +++ b/benchmarks/runtime-benchmarks/src/jmh/java/org/openjdk/btrace/bench/OnMethodTemplateBenchmark.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2018, Jaroslav Bachorik . + * All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Copyright owner designates + * this particular file as subject to the "Classpath" exception as provided + * by the owner in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ +package org.openjdk.btrace.bench; + +import java.util.concurrent.TimeUnit; +import org.openjdk.btrace.core.ArgsMap; +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +@State(Scope.Thread) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@Fork(1) +@BenchmarkMode(Mode.AverageTime) +public class OnMethodTemplateBenchmark { + private ArgsMap argsMap; + + @Setup + public void setup() { + argsMap = new ArgsMap(new String[] {"arg1=val1"}); + } + + @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 5, time = 1200, timeUnit = TimeUnit.MILLISECONDS) + @Benchmark + public void testEmptyTemplate(Blackhole bh) { + bh.consume(argsMap.template("")); + } + + @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 5, time = 1200, timeUnit = TimeUnit.MILLISECONDS) + @Benchmark + public void testMatchTemplate(Blackhole bh) { + bh.consume(argsMap.template("this-is-${arg1}")); + } + + @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 5, time = 1200, timeUnit = TimeUnit.MILLISECONDS) + @Benchmark + public void testNoMatchTemplate(Blackhole bh) { + bh.consume(argsMap.template("this-is-${arg2}")); + } + + public static void main(String[] args) throws Exception { + Options opt = + new OptionsBuilder() + .addProfiler("stack") + .include(".*" + OnMethodTemplateBenchmark.class.getSimpleName() + ".*test.*") + .build(); + + new Runner(opt).run(); + } +} diff --git a/benchmarks/runtime-benchmarks/src/jmh/java/org/openjdk/btrace/bench/ProbeLoadingBenchmark.java b/benchmarks/runtime-benchmarks/src/jmh/java/org/openjdk/btrace/bench/ProbeLoadingBenchmark.java new file mode 100644 index 000000000..65ef41a54 --- /dev/null +++ b/benchmarks/runtime-benchmarks/src/jmh/java/org/openjdk/btrace/bench/ProbeLoadingBenchmark.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.btrace.bench; + +import java.io.*; +import java.util.concurrent.TimeUnit; +import org.openjdk.btrace.core.SharedSettings; +import org.openjdk.btrace.instr.BTraceProbe; +import org.openjdk.btrace.instr.BTraceProbeFactory; +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +@State(Scope.Thread) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@Fork(1) +@BenchmarkMode(Mode.AverageTime) +public class ProbeLoadingBenchmark { + private InputStream classStream; + private BTraceProbeFactory bpf; + + @Setup(Level.Trial) + public void setup() throws Exception { + bpf = new BTraceProbeFactory(SharedSettings.GLOBAL); + } + + @Setup(Level.Invocation) + public void setupRun() throws Exception { + classStream = ProbeLoadingBenchmark.class.getResourceAsStream("/TraceScript.btclass"); + } + + @TearDown(Level.Invocation) + public void tearDownRun() throws Exception { + classStream.close(); + } + + @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS) + @Benchmark + public void testBTraceProbeNew(Blackhole bh) throws Exception { + BTraceProbe bp = bpf.createProbe(classStream); + if (bp == null) { + throw new NullPointerException(); + } + bh.consume(bp); + } + + public static void main(String[] args) throws Exception { + Options opt = + new OptionsBuilder() + .addProfiler("stack") + .include(".*" + ProbeLoadingBenchmark.class.getSimpleName() + ".*test.*") + .build(); + + new Runner(opt).run(); + } +} diff --git a/benchmarks/runtime-benchmarks/src/jmh/java/org/openjdk/btrace/bench/ProfilerBenchmark.java b/benchmarks/runtime-benchmarks/src/jmh/java/org/openjdk/btrace/bench/ProfilerBenchmark.java new file mode 100644 index 000000000..8b07bb7df --- /dev/null +++ b/benchmarks/runtime-benchmarks/src/jmh/java/org/openjdk/btrace/bench/ProfilerBenchmark.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.btrace.bench; + +import java.util.concurrent.TimeUnit; +import org.openjdk.btrace.runtime.profiling.MethodInvocationProfiler; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Threads; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; +import org.openjdk.jmh.runner.options.VerboseMode; + +/** + * Basic benchmark for the performance of {@linkplain MethodInvocationProfiler} + * + * @author Jaroslav Bachorik + */ +@State(Scope.Thread) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@Fork(1) +@BenchmarkMode(Mode.AverageTime) +public class ProfilerBenchmark { + private MethodInvocationProfiler mip1; + private MethodInvocationProfiler mip2; + + @Setup + public void setup() { + mip1 = new MethodInvocationProfiler(1); + mip2 = new MethodInvocationProfiler(500); + } + + @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) + @Benchmark + @Threads(1) + public void testOneMethodSingleThread() { + mip1.recordEntry("a"); + mip1.recordExit("a", 1); + } + + @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) + @Benchmark + @Threads(1) + public void testTwoMethods01Thread() { + mip2.recordEntry("a"); + mip2.recordEntry("b"); + mip2.recordExit("b", 10); + mip2.recordExit("a", 1); + } + + @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) + @Benchmark + @Threads(2) + public void testTwoMethods02Threads() { + mip2.recordEntry("a"); + mip2.recordEntry("b"); + mip2.recordExit("b", 10); + mip2.recordExit("a", 1); + } + + @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) + @Benchmark + @Threads(4) + public void testTwoMethods04Threads() { + mip2.recordEntry("a"); + mip2.recordEntry("b"); + mip2.recordExit("b", 10); + mip2.recordExit("a", 1); + } + + @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) + @Benchmark + @Threads(8) + public void testTwoMethods08Threads() { + mip2.recordEntry("a"); + mip2.recordEntry("b"); + mip2.recordExit("b", 10); + mip2.recordExit("a", 1); + } + + @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) + @Benchmark + @Threads(16) + public void testTwoMethods16Threads() { + mip2.recordEntry("a"); + mip2.recordEntry("b"); + mip2.recordExit("b", 10); + mip2.recordExit("a", 1); + } + + public static void main(String[] args) throws Exception { + Options opt = + new OptionsBuilder() + .addProfiler("stack") + .verbosity(VerboseMode.NORMAL) + .include(".*" + ProfilerBenchmark.class.getSimpleName() + ".*test.*") + .build(); + + new Runner(opt).run(); + } +} diff --git a/benchmarks/runtime-benchmarks/src/jmh/java/org/openjdk/btrace/bench/StatsdBenchmark.java b/benchmarks/runtime-benchmarks/src/jmh/java/org/openjdk/btrace/bench/StatsdBenchmark.java new file mode 100644 index 000000000..6c8b91902 --- /dev/null +++ b/benchmarks/runtime-benchmarks/src/jmh/java/org/openjdk/btrace/bench/StatsdBenchmark.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.btrace.bench; + +import java.util.concurrent.TimeUnit; +import org.openjdk.btrace.statsd.Statsd; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Threads; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +/** + * Basic benchmark for the performance of {@linkplain Statsd} + * + * @author Jaroslav Bachorik + */ +@State(Scope.Thread) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@Fork(1) +@BenchmarkMode(Mode.AverageTime) +public class StatsdBenchmark { + private Statsd c; + + @Setup + public void setup() { + c = Statsd.getInstance(); + } + + @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) + @Benchmark + @Threads(1) + public void testGauge_1() { + c.gauge("g1", 10); + } + + public static void main(String[] args) throws Exception { + Options opt = + new OptionsBuilder() + .addProfiler("stack") + .include(".*" + StatsdBenchmark.class.getSimpleName() + ".*test.*") + .build(); + + new Runner(opt).run(); + } +} diff --git a/benchmarks/runtime-benchmarks/src/jmh/java/org/openjdk/btrace/bench/StringOpBenchmark.java b/benchmarks/runtime-benchmarks/src/jmh/java/org/openjdk/btrace/bench/StringOpBenchmark.java new file mode 100644 index 000000000..a99cf2830 --- /dev/null +++ b/benchmarks/runtime-benchmarks/src/jmh/java/org/openjdk/btrace/bench/StringOpBenchmark.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.btrace.bench; + +import java.util.concurrent.TimeUnit; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +@State(Scope.Thread) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@Fork(1) +@BenchmarkMode(Mode.AverageTime) +public class StringOpBenchmark { + private static final String STRING_PART = "h"; + + StringBuilder sb; + String st; + String res; + + @Setup + public void setup() { + st = ""; + } + + @Setup(Level.Invocation) + public void setupEach() { + sb = new StringBuilder(); + } + + @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 5, time = 1200, timeUnit = TimeUnit.MILLISECONDS) + @Benchmark + public void testStringBuilder() { + sb.append(STRING_PART).append(STRING_PART); + } + + @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 5, time = 1200, timeUnit = TimeUnit.MILLISECONDS) + @Benchmark + public void testStringPlus() { + res = st + STRING_PART + STRING_PART; + } + + @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 5, time = 1200, timeUnit = TimeUnit.MILLISECONDS) + @Benchmark + public void testStrCat() { + res = st.concat(STRING_PART).concat(STRING_PART); + } + + public static void main(String[] args) throws Exception { + Options opt = + new OptionsBuilder() + .addProfiler("gc") + .include(".*" + StringOpBenchmark.class.getSimpleName() + ".*test.*") + .build(); + + new Runner(opt).run(); + } +} diff --git a/bin/btrace b/bin/btrace deleted file mode 100755 index 97ab9dfba..000000000 --- a/bin/btrace +++ /dev/null @@ -1,47 +0,0 @@ -#! /bin/sh - -if [ -z "$BTRACE_HOME" -o ! -d "$BTRACE_HOME" ] ; then - # resolve links - $0 could be a link to btrace's home - PRG="$0" - progname=`basename "$0"` - BTRACE_HOME=`dirname "$PRG"`/.. - BTRACE_HOME=`cd "$BTRACE_HOME" && pwd` -fi - -if [ -f "${BTRACE_HOME}/build/btrace-client.jar" ] ; then - if [ "${JAVA_HOME}" != "" ]; then - TOOLS_JAR="${JAVA_HOME}/lib/tools.jar" - if [ ! -f ${TOOLS_JAR} ] ; then - case "`uname`" in - Darwin*) - # In older JDK versions for Mac OS X, tools.jar is classes.jar - # and is kept in a different location. Check if we can locate - # classes.jar based on ${JAVA_VERSION} - TOOLS_JAR="/System/Library/Frameworks/JavaVM.framework/Versions/${JAVA_VERSION}/Classes/classes.jar" - - # if we can't find, try relative path from ${JAVA_HOME}. Usually, - # /System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home - # is JAVA_HOME. (or whatever version beyond 1.6.0!) - if [ ! -f ${TOOLS_JAR} ] ; then - TOOLS_JAR="${JAVA_HOME}/../Classes/classes.jar" - fi - - # If we still can't find, tell the user to set JAVA_VERSION. - # This way, we can avoid zip file errors from the agent side - # and "connection refused" message from client. - if [ ! -f ${TOOLS_JAR} ] ; then - echo "Please set JAVA_VERSION to the target java version" - exit 1 - fi - ;; - esac - fi - ${JAVA_HOME}/bin/java -cp ${BTRACE_HOME}/build/btrace-client.jar:${TOOLS_JAR}:/usr/share/lib/java/dtrace.jar com.sun.btrace.client.Main $* - else - echo "Please set JAVA_HOME before running this script" - exit 1 - fi -else - echo "Please set BTRACE_HOME before running this script" - exit 1 -fi diff --git a/bin/btrace.bat b/bin/btrace.bat deleted file mode 100644 index cf8249d40..000000000 --- a/bin/btrace.bat +++ /dev/null @@ -1,19 +0,0 @@ -@echo off - -rem %~dp0 is expanded pathname of the current script under NT -set DEFAULT_BTRACE_HOME=%~dp0.. - -if "%BTRACE_HOME%"=="" set BTRACE_HOME=%DEFAULT_BTRACE_HOME% -set DEFAULT_BTRACE_HOME= - -if not exist "%BTRACE_HOME%\build\btrace-client.jar" goto noBTraceHome - -if "%JAVA_HOME%" == "" goto noJavaHome - "%JAVA_HOME%/bin/java" -Dcom.sun.btrace.probeDescPath=. -Dcom.sun.btrace.dumpClasses=false -Dcom.sun.btrace.debug=false -Dcom.sun.btrace.unsafe=false -cp "%BTRACE_HOME%/build/btrace-client.jar;%JAVA_HOME%/lib/tools.jar" com.sun.btrace.client.Main %* - goto end -:noJavaHome - echo Please set JAVA_HOME before running this script - goto end -:noBTraceHome - echo Please set BTRACE_HOME before running this script -:end diff --git a/bin/btracec b/bin/btracec deleted file mode 100755 index 37b18fc37..000000000 --- a/bin/btracec +++ /dev/null @@ -1,39 +0,0 @@ -#! /bin/sh - -if [ -z "$BTRACE_HOME" -o ! -d "$BTRACE_HOME" ] ; then - # resolve links - $0 could be a link to btrace's home - PRG="$0" - progname=`basename "$0"` - BTRACE_HOME=`dirname "$PRG"`/.. - BTRACE_HOME=`cd "$BTRACE_HOME" && pwd` -fi - -if [ -f "${BTRACE_HOME}/build/btrace-client.jar" ] ; then - if [ "${JAVA_HOME}" != "" ]; then - if [ "$1" = "--version" ] - then - ${JAVA_HOME}/bin/java -jar ${BTRACE_HOME}/build/btrace-client.jar com.sun.btrace.Main --version - exit 0 - fi - case "`uname`" in - Darwin*) - if [ -f /System/Library/Frameworks/JavaVM.framework/Versions/${JAVA_VERSION}/Classes/classes.jar ]; then - TOOLS_JAR="/System/Library/Frameworks/JavaVM.framework/Versions/${JAVA_VERSION}/Classes/classes.jar" - else - TOOLS_JAR="${JAVA_HOME}/lib/tools.jar" - fi - ;; - *) - TOOLS_JAR="${JAVA_HOME}/lib/tools.jar" - ;; - esac - - ${JAVA_HOME}/bin/java -cp ${BTRACE_HOME}/build/btrace-client.jar:${TOOLS_JAR} com.sun.btrace.compiler.Compiler $* - else - echo "Please set JAVA_HOME before running this script" - exit 1 - fi -else - echo "Please set BTRACE_HOME before running this script" - exit 1 -fi diff --git a/bin/btracec.bat b/bin/btracec.bat deleted file mode 100644 index 4c7938876..000000000 --- a/bin/btracec.bat +++ /dev/null @@ -1,23 +0,0 @@ -@echo off - -rem %~dp0 is expanded pathname of the current script under NT -set DEFAULT_BTRACE_HOME=%~dp0.. - -if "%BTRACE_HOME%"=="" set BTRACE_HOME=%DEFAULT_BTRACE_HOME% -set DEFAULT_BTRACE_HOME= - -if not exist "%BTRACE_HOME%\build\btrace-client.jar" goto noBTraceHome - -if "%JAVA_HOME%" == "" goto noJavaHome - if "%1" == "--version" ( - %JAVA_HOME%\bin\java -jar %BTRACE_HOME%/build/btrace-client.jar com.sun.btrace.Main --version - goto end - ) - "%JAVA_HOME%/bin/java" -cp "%BTRACE_HOME%/build/btrace-client.jar;%JAVA_HOME%/lib/tools.jar" com.sun.btrace.compiler.Compiler %* - goto end -:noJavaHome - echo Please set JAVA_HOME before running this script - goto end -:noBTraceHome - echo Please set BTRACE_HOME before running this script -:end \ No newline at end of file diff --git a/bin/btracer b/bin/btracer deleted file mode 100755 index 5b14881e4..000000000 --- a/bin/btracer +++ /dev/null @@ -1,102 +0,0 @@ -#! /bin/sh - -if [ -z "$BTRACE_HOME" -o ! -d "$BTRACE_HOME" ] ; then - # resolve links - $0 could be a link to btrace's home - PRG="$0" - progname=`basename "$0"` - BTRACE_HOME=`dirname "$PRG"`/.. - BTRACE_HOME=`cd "$BTRACE_HOME" && pwd` -fi - -usage() { - echo "btracer " - echo "Options:" - echo " --version\t\t\tShow BTrace version" - echo " -v\t\t\t\tRun in verbose mode" - echo " -u\t\t\t\tRun in unsafe mode" - echo " -p \t\t\tBTrace agent server port" - echo " -statsd [:]\tUse this StatsD server" - echo " -o \t\t\tThe path to a file the btrace agent will store its output" - echo " -d \t\t\tDump modified classes to the provided location" - echo " -pd \t\t\tSearch for the probe XML descriptors here" - echo " --noserver\t\t\tDon't start the socket server" - echo " --stdout\t\t\tRedirect the btrace output to stdout instead of writing it to an arbitrary file" - echo " -bcp \t\t\tAppend to bootstrap class path" - echo " -scp \t\t\tAppend to system class path" - echo " -h\t\t\t\tThis message" - exit 0 -} - -if [ $# -eq 0 ] -then - usage -fi - -OPTIONS= - -if [ "${JAVA_HOME}" != "" ]; then - while [ true ] - do - case $1 in - -v) - OPTIONS="debug=true,$OPTIONS" - ;; - -u) - OPTIONS="unsafe=true,$OPTIONS" - ;; - -p) - OPTIONS="port=$2,$OPTIONS" - shift - ;; - -d) - OPTIONS="dumpClasses=true,dumpDir=$2,$OPTIONS" - shift - ;; - -o) - OPTIONS="scriptOutputFile=$2,$OPTIONS" - shift - ;; - -pd) - OPTIONS="probeDescPath=$2,$OPTIONS" - shift - ;; - -bcp) - OPTIONS="bootClassPath=$2,$OPTIONS" - shift - ;; - -scp) - OPTIONS="systemClassPath=$2,$OPTIONS" - shift - ;; - -statsd) - OPTIONS="statsd=$2,$OPTIONS" - ;; - --noserver) - OPTIONS="noServer=true,$OPTIONS" - ;; - --stdout) - OPTIONS="stdout=true,$OPTIONS" - ;; - --version) - $JAVA_HOME/bin/java -jar ${BTRACE_HOME}/build/btrace-client.jar com.sun.btrace.Main --version - exit 0 - ;; - -h) - usage - ;; - *) - break - ;; - esac - shift - done - - if [ -f "${BTRACE_HOME}/build/btrace-agent.jar" ] ; then - ${JAVA_HOME}/bin/java -Xshare:off -javaagent:${BTRACE_HOME}/build/btrace-agent.jar=$OPTIONS,script="$@" - else - echo "Please set BTRACE_HOME before running this script" - fi -else - echo "Please set JAVA_HOME before running this script" - exit 1 -fi diff --git a/bintray.json b/bintray.json deleted file mode 100644 index df05823ce..000000000 --- a/bintray.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "package": { - "name": "btrace-bin", - "repo": "snapshots", - "subject": "btraceio" - }, - - "version": { - "name": "SNAPSHOT" - }, - - "files": - [ - { - "includePattern": "build/distributions/(.*)\.(tgz|zip)", - "uploadPattern": "btrace-bin-SNAPSHOT.$2", - "matrixParams": { - "override": 1 - } - } - ], - "publish": true -} diff --git a/btrace-agent/build.gradle b/btrace-agent/build.gradle new file mode 100644 index 000000000..496d0e2f8 --- /dev/null +++ b/btrace-agent/build.gradle @@ -0,0 +1,13 @@ +dependencies { + implementation libs.slf4j + implementation libs.asm + + def toolsJar = getToolsJar(); + if (toolsJar.getAsFile().exists()) { + implementation files("${toolsJar}") + } + implementation project(':btrace-core') + implementation project(':btrace-services-api') + implementation project(':btrace-runtime') + implementation project(':btrace-instr') +} \ No newline at end of file diff --git a/btrace-agent/src/main/java/org/openjdk/btrace/agent/Client.java b/btrace-agent/src/main/java/org/openjdk/btrace/agent/Client.java new file mode 100644 index 000000000..5db3f1f7a --- /dev/null +++ b/btrace-agent/src/main/java/org/openjdk/btrace/agent/Client.java @@ -0,0 +1,549 @@ +/* + * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.agent; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.lang.annotation.Annotation; +import java.lang.instrument.Instrumentation; +import java.lang.instrument.UnmodifiableClassException; +import java.lang.management.ManagementFactory; +import java.text.DateFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.openjdk.btrace.core.ArgsMap; +import org.openjdk.btrace.core.BTraceRuntime; +import org.openjdk.btrace.core.SharedSettings; +import org.openjdk.btrace.core.comm.Command; +import org.openjdk.btrace.core.comm.CommandListener; +import org.openjdk.btrace.core.comm.ErrorCommand; +import org.openjdk.btrace.core.comm.ExitCommand; +import org.openjdk.btrace.core.comm.InstrumentCommand; +import org.openjdk.btrace.core.comm.MessageCommand; +import org.openjdk.btrace.core.comm.RenameCommand; +import org.openjdk.btrace.core.comm.RetransformationStartNotification; +import org.openjdk.btrace.core.comm.StatusCommand; +import org.openjdk.btrace.instr.BTraceProbe; +import org.openjdk.btrace.instr.BTraceProbeFactory; +import org.openjdk.btrace.instr.BTraceProbePersisted; +import org.openjdk.btrace.instr.BTraceTransformer; +import org.openjdk.btrace.instr.ClassCache; +import org.openjdk.btrace.instr.ClassFilter; +import org.openjdk.btrace.instr.ClassInfo; +import org.openjdk.btrace.instr.HandlerRepositoryImpl; +import org.openjdk.btrace.instr.InstrumentUtils; +import org.openjdk.btrace.instr.Instrumentor; +import org.openjdk.btrace.instr.templates.impl.MethodTrackingExpander; +import org.openjdk.btrace.runtime.BTraceRuntimeAccess; +import org.openjdk.btrace.runtime.BTraceRuntimes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Abstract class that represents a BTrace client at the BTrace agent. + * + * @author A. Sundararajan + * @author J. Bachorik (j.bachorik@btrace.io) + */ +abstract class Client implements CommandListener { + private static final Logger log = LoggerFactory.getLogger(Client.class); + + private static final Map CLIENTS = new ConcurrentHashMap<>(); + private static final Map WRITER_MAP = new HashMap<>(); + private static final Pattern SYSPROP_PTN = Pattern.compile("\\$\\{(.+?)}"); + + static { + ClassFilter.class.getClassLoader(); + InstrumentUtils.class.getClassLoader(); + Instrumentor.class.getClassLoader(); + ClassReader.class.getClassLoader(); + ClassWriter.class.getClassLoader(); + Annotation.class.getClassLoader(); + MethodTrackingExpander.class.getClassLoader(); + ClassCache.class.getClassLoader(); + ClassInfo.class.getClassLoader(); + } + + private final Instrumentation inst; + final SharedSettings settings; + final ArgsMap argsMap; + private final BTraceTransformer transformer; + volatile PrintWriter out; + private volatile BTraceRuntime.Impl runtime; + private volatile String outputName; + private BTraceProbe probe; + private Timer flusher; + private volatile boolean initialized = false; + private volatile boolean shuttingDown = false; + final UUID id = UUID.randomUUID(); + + Client(ClientContext ctx) { + this(ctx.getInstr(), ctx.getArguments(), ctx.getSettings(), ctx.getTransformer()); + } + + private Client(Instrumentation inst, ArgsMap argsMap, SharedSettings s, BTraceTransformer t) { + this.inst = inst; + this.argsMap = argsMap; + settings = s != null ? s : SharedSettings.GLOBAL; + transformer = t; + + setupWriter(); + CLIENTS.put(id, this); + } + + private static String pid() { + String pName = ManagementFactory.getRuntimeMXBean().getName(); + if (pName != null && pName.length() > 0) { + String[] parts = pName.split("@"); + if (parts.length == 2) { + return parts[0]; + } + } + + return "-1"; + } + + protected final void initialize() { + initialized = true; + } + + @SuppressWarnings("DefaultCharset") + private final void setupWriter() { + String outputFile = settings.getOutputFile(); + if (outputFile == null || outputFile.equals("::null") || outputFile.equals("/dev/null")) return; + + if (!outputFile.equals("::stdout")) { + String outputDir = settings.getScriptDir(); + String output = (outputDir != null ? outputDir + File.separator : "") + outputFile; + outputFile = templateOutputFileName(output); + log.info("Redirecting output to {}", outputFile); + } + out = WRITER_MAP.get(outputFile); + if (out == null) { + if (outputFile.equals("::stdout")) { + out = new PrintWriter(System.out); + } else { + if (settings.getFileRollMilliseconds() > 0) { + out = + new PrintWriter( + new BufferedWriter( + TraceOutputWriter.rollingFileWriter(new File(outputFile), settings))); + } else { + out = + new PrintWriter( + new BufferedWriter(TraceOutputWriter.fileWriter(new File(outputFile)))); + } + } + WRITER_MAP.put(outputFile, out); + out.append("### BTrace Log: ") + .append(DateFormat.getInstance().format(new Date())) + .append("\n\n"); + startFlusher(); + } + outputName = outputFile; + } + + private void startFlusher() { + int flushInterval; + String flushIntervalStr = System.getProperty("org.openjdk.btrace.FileClient.flush"); + if (flushIntervalStr == null) { + flushIntervalStr = System.getProperty("com.sun.btrace.FileClient.flush", "5"); + } + try { + flushInterval = Integer.parseInt(flushIntervalStr); + } catch (NumberFormatException e) { + flushInterval = 5; // default + } + + int flushSec = flushInterval; + if (flushSec > -1) { + flusher = new Timer("BTrace FileClient Flusher", true); + flusher.scheduleAtFixedRate( + new TimerTask() { + @Override + public void run() { + try { + if (out != null) { + boolean entered = BTraceRuntime.enter(); + try { + out.flush(); + } finally { + if (entered) { + BTraceRuntime.leave(); + } + } + } + } catch (Throwable t) { + t.printStackTrace(); + } + } + }, + flushSec, + flushSec); + } else { + flusher = null; + } + } + + private String templateOutputFileName(String fName) { + if (fName != null) { + boolean dflt = fName.contains("[default]"); + String agentName = System.getProperty("btrace.agent", "default"); + String clientName = settings.getClientName(); + fName = + fName + .replace("${client}", clientName != null ? clientName : "") + .replace("${ts}", String.valueOf(System.currentTimeMillis())) + .replace("${pid}", pid()) + .replace("${agent}", agentName != null ? "." + agentName : "") + .replace("[default]", ""); + + fName = replaceSysProps(fName); + if (dflt && log.isDebugEnabled()) { + log.debug("scriptOutputFile not specified. defaulting to {}", fName); + } + } + return fName; + } + + private String replaceSysProps(String str) { + int replaced = 0; + do { + StringBuffer sb = new StringBuffer(); + replaced = replaceSysProps(str, sb); + str = sb.toString(); + } while (replaced > 0); + return str; + } + + private int replaceSysProps(String str, StringBuffer sb) { + int cnt = 0; + Matcher m = SYSPROP_PTN.matcher(str); + while (m.find()) { + String key = m.group(1); + String val = System.getProperty(key); + if (val != null) { + cnt++; + m.appendReplacement(sb, val); + } else { + m.appendReplacement(sb, m.group(0)); + } + } + m.appendTail(sb); + return cnt; + } + + static Collection listProbes() { + List probes = new ArrayList<>(CLIENTS.size()); + for (Client client : CLIENTS.values()) { + if (client instanceof RemoteClient) { + if (((RemoteClient) client).isDisconnected()) { + probes.add(client.id + " [" + client.getClassName() + "]"); + } + } + } + return probes; + } + + synchronized void onExit(int exitCode) { + if (!shuttingDown) { + shuttingDown = true; + if (out != null) { + out.flush(); + } + + BTraceRuntime.leave(); + try { + log.debug("onExit:"); + log.debug("cleaning up transformers"); + cleanupTransformers(); + log.debug("removing instrumentation"); + retransformLoaded(); + log.debug("closing all I/O"); + Thread.sleep(300); + try { + closeAll(); + } catch (IOException e) { + // ignore IOException when closing + } + log.debug("done"); + } catch (Throwable th) { + // ExitException is expected here + if (!th.getClass().getName().equals("ExitException")) { + log.debug("Failed to gracefully exit BTrace probe", th); + BTraceRuntime.handleException(th); + } + } finally { + runtime.shutdownCmdLine(); + CLIENTS.remove(id); + HandlerRepositoryImpl.unregisterProbe(probe); + } + } + } + + final Class loadClass(InstrumentCommand instr) throws IOException { + ArgsMap args = instr.getArguments(); + byte[] btraceCode = instr.getCode(); + try { + probe = load(btraceCode, ArgsMap.merge(argsMap, args)); + if (probe == null) { + log.debug("Failed to load BTrace probe code"); + return null; + } + + if (!settings.isTrusted()) { + probe.checkVerified(); + } + } catch (Throwable th) { + log.debug("Filed to load BTrace probe code", th); + errorExit(th); + return null; + } + if (log.isDebugEnabled()) { + log.debug("creating BTraceRuntime instance for {}", probe.getClassName()); + } + runtime = BTraceRuntimes.getRuntime(probe.getClassName(), args, this, inst); + Runtime.getRuntime() + .addShutdownHook( + new Thread( + () -> { + if (runtime != null) { + runtime.handleExit(0); + } + })); + if (probe.isClassRenamed()) { + if (log.isDebugEnabled()) { + log.debug("class renamed to {}", probe.getClassName()); + } + sendCommand(new RenameCommand(probe.getClassName())); + } + if (log.isDebugEnabled()) { + log.debug("created BTraceRuntime instance for {}", probe.getClassName()); + log.debug("sending Okay command"); + } + + sendCommand(new StatusCommand()); + + boolean entered = false; + try { + entered = BTraceRuntimeAccess.enter(runtime); + return probe.register(runtime, transformer); + } catch (Throwable th) { + log.debug("Failed to load BTrace probe", th); + errorExit(th); + return null; + } finally { + if (entered) { + BTraceRuntime.leave(); + } + } + } + + protected void closeAll() throws IOException { + if (flusher != null) { + flusher.cancel(); + } + if (out != null) { + out.close(); + } + WRITER_MAP.remove(outputName); + } + + private void errorExit(Throwable th) throws IOException { + log.debug("sending error command"); + sendCommand(new ErrorCommand(th)); + log.debug("sending exit command"); + sendCommand(new ExitCommand(1)); + closeAll(); + } + + private void cleanupTransformers() { + if (probe != null) { + probe.unregister(); + } + } + + // package privates below this point + final boolean isInitialized() { + return initialized; + } + + final BTraceRuntime.Impl getRuntime() { + return runtime; + } + + final String getClassName() { + return probe != null ? probe.getClassName() : ""; + } + + private final boolean isCandidate(Class c) { + String cname = c.getName().replace('.', '/'); + if (c.isInterface() || c.isPrimitive() || c.isArray()) { + return false; + } + if (ClassFilter.isSensitiveClass(cname)) { + return false; + } else { + return probe.willInstrument(c); + } + } + + private final void startRetransformClasses(int numClasses) { + sendCommand(new RetransformationStartNotification(numClasses)); + if (log.isDebugEnabled()) { + log.debug("calling retransformClasses ({} classes to be retransformed)", numClasses); + } + } + + final void endRetransformClasses() { + sendCommand(new StatusCommand()); + log.debug("finished retransformClasses"); + } + + // Internals only below this point + private BTraceProbe load(byte[] buf, ArgsMap args) { + BTraceProbeFactory f = new BTraceProbeFactory(settings); + log.debug("loading BTrace class"); + BTraceProbe cn = f.createProbe(buf, args); + + if (cn != null) { + if (cn.isVerified()) { + if (log.isDebugEnabled()) { + log.debug("loaded '{}' successfully", cn.getClassName()); + } + } else { + if (log.isDebugEnabled()) { + log.debug("{} failed verification", cn.getClassName()); + } + return null; + } + } + return BTraceProbePersisted.from(cn); + } + + boolean retransformLoaded() throws UnmodifiableClassException { + if (runtime == null) { + return false; + } + if (probe.isTransforming() && settings.isRetransformStartup()) { + ArrayList> list = new ArrayList<>(); + log.debug("retransforming loaded classes"); + log.debug("filtering loaded classes"); + ClassCache cc = ClassCache.getInstance(); + for (Class c : inst.getAllLoadedClasses()) { + if (c != null) { + if (inst.isModifiableClass(c) && isCandidate(c)) { + if (log.isDebugEnabled()) { + log.debug("candidate {} added", c); + } + list.add(c); + } + } + } + list.trimToSize(); + int size = list.size(); + if (size > 0) { + Class[] classes = new Class[size]; + list.toArray(classes); + startRetransformClasses(size); + if (log.isDebugEnabled()) { + for (Class c : classes) { + try { + log.debug("Attempting to retransform class: {}", c.getName()); + inst.retransformClasses(c); + } catch (ClassFormatError | VerifyError e) { + log.debug("Class '{}' verification failed", c.getName(), e); + sendCommand( + new MessageCommand( + "[BTRACE WARN] Class verification failed: " + + c.getName() + + " (" + + e.getMessage() + + ")")); + } + } + } else { + try { + inst.retransformClasses(classes); + } catch (ClassFormatError | VerifyError e) { + /* + * If the en-block retransformation fails because of verification retry classes one-by-one. + * Otherwise all classes are rolled back to the original state and no instrumentation + * is applied. + */ + for (Class c : classes) { + try { + inst.retransformClasses(c); + } catch (ClassFormatError | VerifyError e1) { + log.debug("Class '{}' verification failed", c.getName(), e); + sendCommand( + new MessageCommand( + "[BTRACE WARN] Class verification failed: " + + c.getName() + + " (" + + e.getMessage() + + ")")); + } + } + } + } + } + } + return true; + } + + protected void sendCommand(Command command) { + runtime.send(command); + } + + static Client findClient(String uuid) { + try { + UUID id = UUID.fromString(uuid); + return CLIENTS.get(id); + } catch (IllegalArgumentException e) { + return null; + } + } + + @Override + public String toString() { + return "BTrace Client: " + id + "[" + probe.getClassName() + "]"; + } +} diff --git a/btrace-agent/src/main/java/org/openjdk/btrace/agent/ClientContext.java b/btrace-agent/src/main/java/org/openjdk/btrace/agent/ClientContext.java new file mode 100644 index 000000000..103dc5068 --- /dev/null +++ b/btrace-agent/src/main/java/org/openjdk/btrace/agent/ClientContext.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.btrace.agent; + +import java.lang.instrument.Instrumentation; +import org.openjdk.btrace.core.ArgsMap; +import org.openjdk.btrace.core.SharedSettings; +import org.openjdk.btrace.instr.BTraceTransformer; + +/** + * Client-context data class + * + * @author Jaroslav Bachorik + */ +class ClientContext { + private final Instrumentation instr; + private final BTraceTransformer transformer; + private final ArgsMap args; + private final SharedSettings settings; + + ClientContext( + Instrumentation instr, BTraceTransformer transformer, ArgsMap args, SharedSettings settings) { + this.instr = instr; + this.transformer = transformer; + this.args = args; + this.settings = settings; + } + + Instrumentation getInstr() { + return instr; + } + + BTraceTransformer getTransformer() { + return transformer; + } + + SharedSettings getSettings() { + return settings; + } + + ArgsMap getArguments() { + return args; + } +} diff --git a/btrace-agent/src/main/java/org/openjdk/btrace/agent/FileClient.java b/btrace-agent/src/main/java/org/openjdk/btrace/agent/FileClient.java new file mode 100644 index 000000000..20dd753a3 --- /dev/null +++ b/btrace-agent/src/main/java/org/openjdk/btrace/agent/FileClient.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.btrace.agent; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLDecoder; +import java.security.CodeSigner; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import org.openjdk.btrace.core.comm.Command; +import org.openjdk.btrace.core.comm.ExitCommand; +import org.openjdk.btrace.core.comm.InstrumentCommand; +import org.openjdk.btrace.core.comm.PrintableCommand; +import org.openjdk.btrace.instr.Constants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Represents a local client communicated by trace file. The trace script is specified as a File of + * a .class file or a byte array containing bytecode of the trace script. + * + * @author A. Sundararajan + * @author J.Bachorik + */ +class FileClient extends Client { + private static final Logger log = LoggerFactory.getLogger(FileClient.class); + + private final AtomicBoolean noOutputNotified = new AtomicBoolean(false); + + private boolean canLoadPack = true; + + FileClient(ClientContext ctx, File scriptFile) throws IOException { + super(ctx); + if (!init(readScript(scriptFile))) { + log.warn("Unable to load BTrace script {}", scriptFile); + } + } + + private static byte[] readAll(InputStream is, long size) throws IOException { + if (is == null) throw new NullPointerException(); + + byte[] buf = new byte[size != -1 ? Math.min((int) size, 512 * 1024 * 1024) : 8192]; + int bufsize = buf.length; + int off = 0; + int read; + while ((read = is.read(buf, off, bufsize - off)) > -1) { + off += read; + if (off >= bufsize) { + buf = Arrays.copyOf(buf, bufsize * 2); + bufsize = buf.length; + } + } + return Arrays.copyOf(buf, off); + } + + private boolean init(byte[] code) throws IOException { + InstrumentCommand cmd = new InstrumentCommand(code, argsMap); + boolean ret = loadClass(cmd) != null; + if (ret) { + initialize(); + } + return ret; + } + + @SuppressWarnings("RedundantThrows") + @Override + public void onCommand(Command cmd) throws IOException { + if (log.isDebugEnabled()) { + log.debug("client {}: got {}", getClassName(), cmd); + } + switch (cmd.getType()) { + case Command.EXIT: + onExit(((ExitCommand) cmd).getExitCode()); + break; + default: + if (cmd instanceof PrintableCommand) { + if (out == null) { + if (noOutputNotified.compareAndSet(false, true)) { + log.debug("No output stream. DataCommand output is ignored."); + } + } else { + ((PrintableCommand) cmd).print(out); + } + } + break; + } + } + + private byte[] readScript(File file) throws IOException { + String path = file.getPath(); + if (path.startsWith(Constants.EMBEDDED_BTRACE_SECTION_HEADER)) { + return settings.isTrusted() ? loadQuick(path) : loadWithSecurity(path); + } else { + int size = (int) file.length(); + try (FileInputStream fis = new FileInputStream(file)) { + return readAll(fis, size); + } + } + } + + private byte[] loadQuick(String path) throws IOException { + try (InputStream is = ClassLoader.getSystemResourceAsStream(path)) { + return readAll(is, -1); + } + } + + private byte[] loadWithSecurity(String path) throws IOException { + URL scriptUrl = ClassLoader.getSystemResource(path); + if (scriptUrl.getProtocol().equals("jar")) { + String jarPath = scriptUrl.getPath().substring(5, scriptUrl.getPath().indexOf("!")); + JarFile jar = new JarFile(URLDecoder.decode(jarPath, "UTF-8")); + Enumeration ens = jar.entries(); + + while (ens.hasMoreElements()) { + JarEntry en = ens.nextElement(); + + if (!en.isDirectory()) { + if (en.toString().equals(path)) { + byte[] data = readAll(jar.getInputStream(en), en.getSize()); + CodeSigner[] signers = en.getCodeSigners(); + canLoadPack = signers != null && signers.length != 0; + return data; + } + } + } + } + return null; + } +} diff --git a/btrace-agent/src/main/java/org/openjdk/btrace/agent/Main.java b/btrace-agent/src/main/java/org/openjdk/btrace/agent/Main.java new file mode 100644 index 000000000..322e8c721 --- /dev/null +++ b/btrace-agent/src/main/java/org/openjdk/btrace/agent/Main.java @@ -0,0 +1,902 @@ +/* + * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.btrace.agent; + +import static org.openjdk.btrace.core.Args.ALLOWED_CALLS; +import static org.openjdk.btrace.core.Args.BOOT_CLASS_PATH; +import static org.openjdk.btrace.core.Args.CMD_QUEUE_LIMIT; +import static org.openjdk.btrace.core.Args.CONFIG; +import static org.openjdk.btrace.core.Args.DEBUG; +import static org.openjdk.btrace.core.Args.DUMP_CLASSES; +import static org.openjdk.btrace.core.Args.DUMP_DIR; +import static org.openjdk.btrace.core.Args.FILE_ROLL_MAX_ROLLS; +import static org.openjdk.btrace.core.Args.FILE_ROLL_MILLISECONDS; +import static org.openjdk.btrace.core.Args.HELP; +import static org.openjdk.btrace.core.Args.LIBS; +import static org.openjdk.btrace.core.Args.NO_SERVER; +import static org.openjdk.btrace.core.Args.PORT; +import static org.openjdk.btrace.core.Args.PROBE_DESC_PATH; +import static org.openjdk.btrace.core.Args.SCRIPT; +import static org.openjdk.btrace.core.Args.SCRIPT_DIR; +import static org.openjdk.btrace.core.Args.SCRIPT_OUTPUT_DIR; +import static org.openjdk.btrace.core.Args.SCRIPT_OUTPUT_FILE; +import static org.openjdk.btrace.core.Args.STARTUP_RETRANSFORM; +import static org.openjdk.btrace.core.Args.STATSD; +import static org.openjdk.btrace.core.Args.STDOUT; +import static org.openjdk.btrace.core.Args.SYSTEM_CLASS_PATH; +import static org.openjdk.btrace.core.Args.TRACK_RETRANSFORMS; +import static org.openjdk.btrace.core.Args.TRUSTED; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.instrument.Instrumentation; +import java.lang.instrument.UnmodifiableClassException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.URL; +import java.nio.file.FileVisitResult; +import java.nio.file.FileVisitor; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.StringTokenizer; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; +import java.util.jar.JarFile; +import java.util.regex.Pattern; +import java.util.zip.ZipFile; +import org.openjdk.btrace.core.ArgsMap; +import org.openjdk.btrace.core.BTraceRuntime; +import org.openjdk.btrace.core.DebugSupport; +import org.openjdk.btrace.core.Messages; +import org.openjdk.btrace.core.SharedSettings; +import org.openjdk.btrace.core.comm.ErrorCommand; +import org.openjdk.btrace.core.comm.StatusCommand; +import org.openjdk.btrace.core.comm.WireIO; +import org.openjdk.btrace.instr.BTraceProbeFactory; +import org.openjdk.btrace.instr.BTraceTransformer; +import org.openjdk.btrace.instr.Constants; +import org.openjdk.btrace.runtime.BTraceRuntimes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This is the main class for BTrace java.lang.instrument agent. + * + * @author A. Sundararajan + * @author Joachim Skeie (rolling output) + */ +@SuppressWarnings("RedundantThrows") +public final class Main { + public static final int BTRACE_DEFAULT_PORT = 2020; + private static final Pattern KV_PATTERN = Pattern.compile(","); + private static final SharedSettings settings = SharedSettings.GLOBAL; + private static final BTraceTransformer transformer = + new BTraceTransformer(new DebugSupport(settings)); + // #BTRACE-42: Non-daemon thread prevents traced application from exiting + private static final ThreadFactory qProcessorThreadFactory = + r -> { + Thread result = new Thread(r, "BTrace Command Queue Processor"); + result.setDaemon(true); + return result; + }; + private static final ExecutorService serializedExecutor = + Executors.newSingleThreadExecutor(qProcessorThreadFactory); + private static final long ts = System.nanoTime(); + private static volatile ArgsMap argMap; + private static volatile Instrumentation inst; + private static volatile Long fileRollMilliseconds; + + private static final Logger log = LoggerFactory.getLogger(Main.class); + + public static void premain(String args, Instrumentation inst) { + main(args, inst); + } + + public static void agentmain(String args, Instrumentation inst) { + main(args, inst); + } + + private static synchronized void main(String args, Instrumentation inst) { + if (Main.inst != null) { + return; + } else { + Main.inst = inst; + } + + try { + loadArgs(args); + boolean isDebug = Boolean.parseBoolean(argMap.get(DEBUG)); + // set the debug level based on cmdline config + settings.setDebug(isDebug); + DebugSupport.initLoggers(isDebug, log); + + log.debug("parsed command line arguments"); + parseArgs(); + // settings are all built-up; set the logging system properties accordingly + DebugSupport.initLoggers(settings.isDebug(), log); + + log.debug("Adding class transformer"); + inst.addTransformer(transformer, true); + int startedScripts = startScripts(); + + String tmp = argMap.get(NO_SERVER); + // noServer is defaulting to true if startup scripts are defined + boolean noServer = tmp != null ? Boolean.parseBoolean(tmp) : hasScripts(); + if (noServer) { + log.debug("noServer is true, server not started"); + return; + } + Thread agentThread = + new Thread( + () -> { + BTraceRuntime.enter(); + try { + startServer(); + } finally { + BTraceRuntime.leave(); + } + }); + // set the fall-back instrumentation object to BTraceRuntime + BTraceRuntime.instrumentation = inst; + // force back-registration of BTraceRuntimeImpl in BTraceRuntime + BTraceRuntimes.getDefault(); + // init BTraceRuntime + BTraceRuntime.initUnsafe(); + BTraceRuntime.enter(); + try { + agentThread.setDaemon(true); + log.debug("starting agent thread"); + agentThread.start(); + } finally { + BTraceRuntime.leave(); + } + } finally { + log.debug("Agent init took: {}", (System.nanoTime() - ts) + "ns"); + } + } + + private static boolean hasScripts() { + return argMap.containsKey(SCRIPT) || argMap.containsKey(SCRIPT_DIR); + } + + private static final class LogValue { + final String logLine; + final Throwable throwable; + + public LogValue(String logLine, Throwable throwable) { + this.logLine = logLine; + this.throwable = throwable; + } + } + + private static void loadDefaultArguments(String config) { + try { + String propTarget = Constants.EMBEDDED_BTRACE_SECTION_HEADER + "agent.properties"; + InputStream is = ClassLoader.getSystemResourceAsStream(propTarget); + if (is != null) { + Properties ps = new Properties(); + ps.load(is); + StringBuilder logMsg = new StringBuilder(); + for (Map.Entry entry : ps.entrySet()) { + String keyConfig = ""; + String argKey = (String) entry.getKey(); + int configPos = argKey.lastIndexOf('#'); + if (configPos > -1) { + keyConfig = argKey.substring(0, configPos); + argKey = argKey.substring(configPos + 1); + } + if (config == null || keyConfig.isEmpty() || config.equals(keyConfig)) { + String argVal = (String) entry.getValue(); + switch (argKey) { + case SCRIPT: + { + // special treatment for the 'script' parameter + boolean replace = false; + String scriptVal = argVal; + if (scriptVal.startsWith("!")) { + scriptVal = scriptVal.substring(1); + replace = true; + } else { + String oldVal = argMap.get(argKey); + if (oldVal != null && !oldVal.isEmpty()) { + scriptVal = oldVal + ":" + scriptVal; + } else { + replace = true; + } + } + if (replace) { + logMsg + .append("setting default agent argument '") + .append(argKey) + .append("' to '") + .append(scriptVal) + .append("'\n"); + } else { + logMsg + .append("augmenting default agent argument '") + .append(argKey) + .append("':'") + .append(argMap.get(argKey)) + .append("' with '") + .append(argVal) + .append("'\n"); + } + + argMap.put(argKey, scriptVal); + break; + } + case ALLOWED_CALLS: + { + if (Boolean.parseBoolean(argMap.get(argKey))) { + // merge allowed calls from command line and agent.properties + String oldVal = argMap.get(argKey); + String newVal = oldVal + "|" + argVal; + logMsg + .append("merging default agent argument '") + .append(argKey) + .append("':'") + .append(oldVal) + .append("' with '") + .append(argVal) + .append("'"); + argMap.put(argKey, newVal); + } else { + logMsg + .append("argument '") + .append(argKey) + .append("' is applicable only in sandboxed mode"); + } + break; + } + case SYSTEM_CLASS_PATH: // fall through + case BOOT_CLASS_PATH: // fall through + case CONFIG: + { + logMsg.append("argument '").append(argKey).append("' is not overridable\n"); + break; + } + default: + { + if (!argMap.containsKey(argKey)) { + logMsg + .append("applying default agent argument '") + .append(argKey) + .append("'='") + .append(argVal) + .append("'\n"); + argMap.put(argKey, argVal); + } + } + } + } + } + DebugSupport.initLoggers(Boolean.parseBoolean(argMap.get(DEBUG)), log); + if (log.isDebugEnabled()) { + log.debug(logMsg.toString()); + } + } + } catch (IOException e) { + if (log.isDebugEnabled()) { + log.debug(e.toString(), e); + } + } + } + + private static int startScripts() { + int scriptCount = 0; + + String p = argMap.get(STDOUT); + boolean traceToStdOut = p != null && !"false".equals(p); + if (log.isDebugEnabled()) { + log.debug("stdout is {}", traceToStdOut); + } + + List scripts = locateScripts(argMap); + for (String script : scripts) { + if (loadBTraceScript(script, traceToStdOut)) { + scriptCount++; + } + } + return scriptCount; + } + + static List locateScripts(ArgsMap argsMap) { + String script = argsMap.get(SCRIPT); + String scriptDir = argsMap.get(SCRIPT_DIR); + + List scripts = new ArrayList<>(); + if (script != null) { + StringTokenizer tokenizer = new StringTokenizer(script, ":"); + if (log.isDebugEnabled()) { + log.debug( + ((tokenizer.countTokens() == 1) ? "initial script is {}" : "initial scripts are {}"), + script); + } + while (tokenizer.hasMoreTokens()) { + scripts.add(tokenizer.nextToken()); + } + } + if (scriptDir != null) { + File dir = new File(scriptDir); + if (dir.isDirectory()) { + if (log.isDebugEnabled()) { + log.debug("found scriptdir: {}", dir.getAbsolutePath()); + } + File[] files = dir.listFiles(); + if (files != null) { + for (File file : files) { + scripts.add(file.getAbsolutePath()); + } + } + } + } + return scripts; + } + + private static void usage() { + System.out.println(Messages.get("btrace.agent.usage")); + System.exit(0); + } + + private static void loadArgs(String args) { + if (args == null) { + args = ""; + } + String[] pairs = KV_PATTERN.split(args); + argMap = new ArgsMap(); + for (String s : pairs) { + int i = s.indexOf('='); + String key, value = ""; + if (i != -1) { + key = s.substring(0, i).trim(); + if (i + 1 < s.length()) { + value = s.substring(i + 1).trim(); + } + } else { + key = s; + } + argMap.put(key, value); + } + } + + private static void parseArgs() { + String p = argMap.get(HELP); + if (p != null) { + usage(); + } + + String libs = argMap.get(LIBS); + String config = argMap.get(CONFIG); + processClasspaths(libs); + loadDefaultArguments(config); + + p = argMap.get(DEBUG); + settings.setDebug(p != null && !"false".equals(p)); + DebugSupport.initLoggers(settings.isDebug(), log); + + log.debug("debugMode is {}", settings.isDebug()); + + for (Map.Entry e : argMap) { + String key = e.getKey(); + p = e.getValue(); + switch (key) { + case STARTUP_RETRANSFORM: + { + if (!p.isEmpty()) { + settings.setRetransformStartup(Boolean.parseBoolean(p)); + log.debug(STARTUP_RETRANSFORM + " is {}", settings.isRetransformStartup()); + } + break; + } + case DUMP_DIR: + { + String dumpClassesVal = argMap.get(DUMP_CLASSES); + if (dumpClassesVal != null) { + boolean dumpClasses = Boolean.parseBoolean(dumpClassesVal); + log.debug(DUMP_CLASSES + " is {}", dumpClasses); + if (dumpClasses) { + String dumpDir = argMap.get(DUMP_DIR); + settings.setDumpDir(dumpDir != null ? dumpDir : "."); + if (isDebug()) { + log.debug(DUMP_DIR + " is {}", dumpDir); + } + } + } + break; + } + case CMD_QUEUE_LIMIT: + { + if (!p.isEmpty()) { + System.setProperty(BTraceRuntime.CMD_QUEUE_LIMIT_KEY, p); + log.debug(CMD_QUEUE_LIMIT + " provided: {}", p); + } + + break; + } + case TRACK_RETRANSFORMS: + { + if (!p.isEmpty()) { + settings.setTrackRetransforms(Boolean.parseBoolean(p)); + if (settings.isTrackRetransforms()) { + log.debug(TRACK_RETRANSFORMS + " is on"); + } + } + break; + } + case SCRIPT_OUTPUT_FILE: + { + if (!p.isEmpty()) { + settings.setOutputFile(p); + log.debug(SCRIPT_OUTPUT_FILE + " is {}", p); + } + break; + } + case SCRIPT_OUTPUT_DIR: + { + if (!p.isEmpty()) { + settings.setScriptOutputDir(p); + log.debug(SCRIPT_OUTPUT_DIR + " is {}", p); + } + break; + } + case FILE_ROLL_MILLISECONDS: + { + if (!p.isEmpty()) { + Long msParsed = null; + try { + msParsed = Long.parseLong(p); + fileRollMilliseconds = msParsed; + } catch (NumberFormatException nfe) { + fileRollMilliseconds = null; + } + if (fileRollMilliseconds != null) { + settings.setFileRollMilliseconds(fileRollMilliseconds.intValue()); + log.debug(FILE_ROLL_MILLISECONDS + " is {}", fileRollMilliseconds); + } + } + break; + } + case FILE_ROLL_MAX_ROLLS: + { + if (!p.isEmpty()) { + Integer rolls = null; + try { + rolls = Integer.parseInt(p); + } catch (NumberFormatException ignored) { + // ignore + } + + if (rolls != null) { + settings.setFileRollMaxRolls(rolls); + } + } + break; + } + case TRUSTED: + { + if (!p.isEmpty()) { + settings.setTrusted(Boolean.parseBoolean(p)); + log.debug("trustedMode is {}", settings.isTrusted()); + } + break; + } + case STATSD: + { + if (!p.isEmpty()) { + String[] parts = p.split(":"); + if (parts.length == 2) { + settings.setStatsdHost(parts[0].trim()); + try { + settings.setStatsdPort(Integer.parseInt(parts[1].trim())); + } catch (NumberFormatException ex) { + log.warn("Invalid statsd port number: {}", parts[1]); + // leave the port unconfigured + } + } else if (parts.length == 1) { + settings.setStatsdHost(parts[0].trim()); + } + } + break; + } + case PROBE_DESC_PATH: + { + settings.setProbeDescPath(!p.isEmpty() ? p : "."); + log.debug("probe descriptor path is {}", settings.getProbeDescPath()); + break; + } + case BOOT_CLASS_PATH: + { + settings.setBootClassPath(!p.isEmpty() ? p : ""); + log.debug("probe boot class path is {}", settings.getBootClassPath()); + break; + } + + default: + { + if (key.startsWith("$")) { + String pKey = key.substring(1); + System.setProperty(pKey, p); + log.debug("Setting system property: {}={}", pKey, p); + } + } + } + } + } + + private static void processClasspaths(String libs) { + URL agentJar = Main.class.getResource("Main.class"); + String bootPath = agentJar.toString().replace("jar:file:", ""); + int idx = bootPath.indexOf("btrace-agent.jar"); + if (idx > -1) { + bootPath = bootPath.substring(0, idx) + "btrace-boot.jar"; + } + String bootClassPath = argMap.get(BOOT_CLASS_PATH); + if (bootClassPath == null) { + bootClassPath = bootPath; + } else { + if (".".equals(bootClassPath)) { + bootClassPath = bootPath; + } else { + bootClassPath = bootPath + File.pathSeparator + bootClassPath; + } + } + log.debug("Bootstrap ClassPath: {}", bootClassPath); + + StringTokenizer tokenizer = new StringTokenizer(bootClassPath, File.pathSeparator); + try { + while (tokenizer.hasMoreTokens()) { + String path = tokenizer.nextToken(); + File f = new File(path); + if (!f.exists()) { + log.warn("BTrace bootstrap classpath resource [{}] does not exist", path); + } else { + if (f.isFile() && f.getName().toLowerCase().endsWith(".jar")) { + JarFile jf = asJarFile(f); + log.debug("Adding jar: {}", jf); + inst.appendToBootstrapClassLoaderSearch(jf); + } else { + log.debug("ignoring boot classpath element '{}' - only jar files allowed", path); + } + } + } + } catch (IOException ex) { + log.debug("adding to boot classpath failed!", ex); + return; + } + + String systemClassPath = argMap.get(SYSTEM_CLASS_PATH); + if (systemClassPath != null) { + log.debug("System ClassPath: {}", systemClassPath); + tokenizer = new StringTokenizer(systemClassPath, File.pathSeparator); + try { + while (tokenizer.hasMoreTokens()) { + String path = tokenizer.nextToken(); + File f = new File(path); + if (!f.exists()) { + log.warn("BTrace system classpath resource [{}] does not exist.", path); + } else { + if (f.isFile() && f.getName().toLowerCase().endsWith(".jar")) { + JarFile jf = asJarFile(f); + inst.appendToSystemClassLoaderSearch(jf); + } else { + log.debug("ignoring system classpath element '{}' - only jar files allowed", path); + } + } + } + } catch (IOException ex) { + log.debug("adding to boot classpath failed!", ex); + return; + } + } + + addPreconfLibs(libs); + } + + @SuppressWarnings("JavaReflectionMemberAccess") + private static JarFile asJarFile(File path) throws IOException { + try { + Class.forName("java.lang.Module"); // bail out early if on pre Java 9 version + Class rtClass = Runtime.class; + Method m = rtClass.getMethod("version"); + Object version = m.invoke(null); + // JPMS enabled version of JarFile has different constructor signature + return JarFile.class + .getConstructor(File.class, boolean.class, int.class, version.getClass()) + .newInstance(path, true, ZipFile.OPEN_READ, version); + } catch (ClassNotFoundException + | NoSuchMethodException + | InstantiationException + | IllegalAccessException + | IllegalArgumentException + | InvocationTargetException + | SecurityException ignore) { + } + + return new JarFile(path); + } + + private static void addPreconfLibs(String libs) { + URL u = + Main.class.getClassLoader().getResource(Main.class.getName().replace('.', '/') + ".class"); + if (u != null) { + String path = u.toString(); + int delimiterPos = path.lastIndexOf('!'); + if (delimiterPos > -1) { + String jar = path.substring(9, delimiterPos); + File jarFile = new File(jar); + Path libRoot = new File(jarFile.getParent() + File.separator + "btrace-libs").toPath(); + Path libFolder = libs != null ? libRoot.resolve(libs) : libRoot; + if (Files.exists(libFolder)) { + appendToBootClassPath(libFolder); + appendToSysClassPath(libFolder); + } else { + if (libs != null && !libs.isEmpty()) { + log.warn( + "Invalid 'libs' configuration [{}]. Path '{}' does not exist.", + libs, + libFolder.toAbsolutePath()); + } + } + } + } + } + + private static void appendToBootClassPath(Path libFolder) { + Path bootLibs = libFolder.resolve("boot"); + if (Files.exists(bootLibs)) { + try { + Files.walkFileTree( + bootLibs, + new FileVisitor() { + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) + throws IOException { + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) + throws IOException { + if (file.toString().toLowerCase().endsWith(".jar")) { + if (log.isDebugEnabled()) { + log.debug("Adding {} to bootstrap classpath", file); + } + inst.appendToBootstrapClassLoaderSearch(new JarFile(file.toFile())); + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed(Path file, IOException exc) + throws IOException { + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) + throws IOException { + return FileVisitResult.CONTINUE; + } + }); + } catch (IOException e) { + log.debug("Failed to enhance bootstrap classpath", e); + } + } + } + + private static void appendToSysClassPath(Path libFolder) { + Path sysLibs = libFolder.resolve("system"); + if (Files.exists(sysLibs)) { + try { + Files.walkFileTree( + sysLibs, + new FileVisitor() { + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) + throws IOException { + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) + throws IOException { + if (file.toString().toLowerCase().endsWith(".jar")) { + if (log.isDebugEnabled()) { + log.debug("Adding {} to system classpath", file); + } + inst.appendToSystemClassLoaderSearch(new JarFile(file.toFile())); + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed(Path file, IOException exc) + throws IOException { + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) + throws IOException { + return FileVisitResult.CONTINUE; + } + }); + } catch (IOException e) { + log.debug("Failed to enhance sytem classpath", e); + } + } + } + + private static boolean loadBTraceScript(String filePath, boolean traceToStdOut) { + if (!BTraceProbeFactory.canLoad(filePath)) { + return false; + } + + try { + String scriptName = ""; + String scriptParent = ""; + File traceScript = new File(filePath); + scriptName = traceScript.getName(); + scriptParent = traceScript.getParent(); + + if (!traceScript.exists()) { + traceScript = new File(Constants.EMBEDDED_BTRACE_SECTION_HEADER + filePath); + } + + if (scriptName.endsWith(".java")) { + if (log.isDebugEnabled()) { + log.debug("refusing {} - script should be a pre-compiled class file", filePath); + } + return false; + } + + SharedSettings clientSettings = new SharedSettings(); + clientSettings.from(settings); + clientSettings.setClientName(scriptName); + if (traceToStdOut) { + clientSettings.setOutputFile("::stdout"); + } else { + String traceOutput = clientSettings.getOutputFile(); + String outDir = clientSettings.getScriptOutputDir(); + if (traceOutput == null || traceOutput.length() == 0) { + clientSettings.setOutputFile("${client}-${agent}.${ts}.btrace[default]"); + if (outDir == null || outDir.length() == 0) { + clientSettings.setScriptOutputDir(scriptParent); + } + } + } + ClientContext ctx = new ClientContext(inst, transformer, argMap, clientSettings); + Client client = new FileClient(ctx, traceScript); + if (client.isInitialized()) { + handleNewClient(client).get(); + return true; + } + } catch (NullPointerException e) { + if (log.isDebugEnabled()) { + log.debug("script {} does not exist!", filePath, e); + } + } catch (RuntimeException | IOException | ExecutionException re) { + if (log.isDebugEnabled()) { + log.debug("Failed to load BTrace script {}", filePath, re); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + return false; + } + + // -- Internals only below this point + @SuppressWarnings("InfiniteLoopStatement") + private static void startServer() { + int port = BTRACE_DEFAULT_PORT; + String p = argMap.get(PORT); + if (p != null) { + try { + port = Integer.parseInt(p); + } catch (NumberFormatException exp) { + error("invalid port assuming default.."); + } + } + ServerSocket ss; + try { + if (log.isDebugEnabled()) { + log.debug("starting server at port {}", port); + } + System.setProperty("btrace.wireio", String.valueOf(WireIO.VERSION)); + + String scriptOutputFile = settings.getOutputFile(); + if (scriptOutputFile != null && scriptOutputFile.length() > 0) { + System.setProperty("btrace.output", scriptOutputFile); + } + ss = new ServerSocket(port); + System.setProperty("btrace.port", String.valueOf(ss.getLocalPort())); + } catch (IOException ioexp) { + ioexp.printStackTrace(); + return; + } + + while (true) { + try { + log.debug("waiting for clients"); + Socket sock = ss.accept(); + if (log.isDebugEnabled()) { + log.debug("client accepted {}", sock); + } + ClientContext ctx = new ClientContext(inst, transformer, argMap, settings); + Client client = RemoteClient.getClient(ctx, sock, value -> handleNewClient(value)); + } catch (RuntimeException | IOException re) { + if (log.isDebugEnabled()) { + log.debug("BTrace server failed", re); + } + } + } + } + + private static Future handleNewClient(Client client) { + return serializedExecutor.submit( + () -> { + try { + boolean entered = BTraceRuntime.enter(); + try { + if (log.isDebugEnabled()) { + log.debug("new Client created {}", client); + } + if (client.retransformLoaded()) { + client.getRuntime().send(new StatusCommand((byte) 1)); + } + } catch (UnmodifiableClassException uce) { + log.debug("BTrace class retransformation failed", uce); + client.getRuntime().send(new ErrorCommand(uce)); + client.getRuntime().send(new StatusCommand(-1 * StatusCommand.STATUS_FLAG)); + } finally { + if (entered) { + BTraceRuntime.leave(); + } + } + } catch (Throwable t) { + t.printStackTrace(); + } + }); + } + + private static void error(String msg) { + System.err.println("btrace ERROR: " + msg); + } + + private static boolean isDebug() { + return settings.isDebug(); + } +} diff --git a/btrace-agent/src/main/java/org/openjdk/btrace/agent/PerfReaderImpl.java b/btrace-agent/src/main/java/org/openjdk/btrace/agent/PerfReaderImpl.java new file mode 100644 index 000000000..994e22e9c --- /dev/null +++ b/btrace-agent/src/main/java/org/openjdk/btrace/agent/PerfReaderImpl.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.agent; + +import java.net.URISyntaxException; +import org.openjdk.btrace.runtime.PerfReader; +import sun.jvmstat.monitor.IntegerMonitor; +import sun.jvmstat.monitor.LongMonitor; +import sun.jvmstat.monitor.Monitor; +import sun.jvmstat.monitor.MonitorException; +import sun.jvmstat.monitor.MonitoredHost; +import sun.jvmstat.monitor.MonitoredVm; +import sun.jvmstat.monitor.StringMonitor; +import sun.jvmstat.monitor.VmIdentifier; + +final class PerfReaderImpl implements PerfReader { + private volatile MonitoredVm thisVm; + + private synchronized MonitoredVm getThisVm() { + if (thisVm == null) { + try { + MonitoredHost localHost = MonitoredHost.getMonitoredHost("localhost"); + VmIdentifier vmIdent = new VmIdentifier("0"); + thisVm = localHost.getMonitoredVm(vmIdent); + } catch (MonitorException | URISyntaxException me) { + throw new IllegalArgumentException("jvmstat perf counters not available: " + me); + } + } + return thisVm; + } + + private Monitor findByName(String name) { + try { + return getThisVm().findByName(name); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public int perfInt(String name) { + Monitor mon = findByName(name); + if (mon == null) { + throw new IllegalArgumentException("no such counter: " + name); + } + if (mon instanceof IntegerMonitor) { + return ((IntegerMonitor) mon).intValue(); + } else if (mon instanceof LongMonitor) { + return (int) ((LongMonitor) mon).longValue(); + } else { + throw new IllegalArgumentException(name + " is not an int"); + } + } + + @Override + public long perfLong(String name) { + Monitor mon = findByName(name); + if (mon == null) { + throw new IllegalArgumentException("no such counter: " + name); + } + if (mon instanceof LongMonitor) { + return ((LongMonitor) mon).longValue(); + } else { + throw new IllegalArgumentException(name + " is not a long"); + } + } + + @Override + public String perfString(String name) { + Monitor mon = findByName(name); + if (mon == null) { + throw new IllegalArgumentException("no such counter: " + name); + } + if (mon instanceof StringMonitor) { + return ((StringMonitor) mon).stringValue(); + } else { + throw new IllegalArgumentException(name + " is not a string"); + } + } +} diff --git a/btrace-agent/src/main/java/org/openjdk/btrace/agent/RemoteClient.java b/btrace-agent/src/main/java/org/openjdk/btrace/agent/RemoteClient.java new file mode 100644 index 000000000..6959f3ae7 --- /dev/null +++ b/btrace-agent/src/main/java/org/openjdk/btrace/agent/RemoteClient.java @@ -0,0 +1,357 @@ +/* + * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.agent; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.net.Socket; +import java.net.SocketException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.concurrent.locks.LockSupport; +import org.openjdk.btrace.core.*; +import org.openjdk.btrace.core.comm.Command; +import org.openjdk.btrace.core.comm.DisconnectCommand; +import org.openjdk.btrace.core.comm.EventCommand; +import org.openjdk.btrace.core.comm.ExitCommand; +import org.openjdk.btrace.core.comm.InstrumentCommand; +import org.openjdk.btrace.core.comm.ListProbesCommand; +import org.openjdk.btrace.core.comm.PrintableCommand; +import org.openjdk.btrace.core.comm.ReconnectCommand; +import org.openjdk.btrace.core.comm.SetSettingsCommand; +import org.openjdk.btrace.core.comm.StatusCommand; +import org.openjdk.btrace.core.comm.WireIO; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Represents a remote client communicated by socket. + * + * @author A. Sundararajan + */ +@SuppressWarnings({"SynchronizeOnNonFinalField", "SynchronizationOnLocalVariableOrMethodParameter"}) +class RemoteClient extends Client { + private static final Logger log = LoggerFactory.getLogger(RemoteClient.class); + + private final class DelayedCommandExecutor implements Function { + private final boolean isConnected; + + public DelayedCommandExecutor(boolean isConnected) { + this.isConnected = isConnected; + } + + @Override + public Boolean apply(Command value) { + return dispatchCommand(value, isConnected); + } + } + + private volatile Socket sock; + private volatile ObjectInputStream ois; + private volatile ObjectOutputStream oos; + + private final AtomicReferenceFieldUpdater sockUpdater = + AtomicReferenceFieldUpdater.newUpdater(RemoteClient.class, Socket.class, "sock"); + private final AtomicReferenceFieldUpdater oisUpdater = + AtomicReferenceFieldUpdater.newUpdater(RemoteClient.class, ObjectInputStream.class, "ois"); + private final AtomicReferenceFieldUpdater oosUpdater = + AtomicReferenceFieldUpdater.newUpdater(RemoteClient.class, ObjectOutputStream.class, "oos"); + + private final CircularBuffer delayedCommands = new CircularBuffer<>(5000); + + static Client getClient(ClientContext ctx, Socket sock, Function> initCallback) + throws IOException { + SharedSettings settings = ctx.getSettings(); + ObjectInputStream ois = new ObjectInputStream(sock.getInputStream()); + ObjectOutputStream oos = new ObjectOutputStream(sock.getOutputStream()); + + while (true) { + Command cmd = WireIO.read(ois); + switch (cmd.getType()) { + case Command.SET_PARAMS: + { + settings.from(((SetSettingsCommand) cmd).getParams()); + break; + } + case Command.INSTRUMENT: + { + log.debug("got instrument command"); + try { + Client client = new RemoteClient(ctx, ois, oos, sock, (InstrumentCommand) cmd); + initCallback.apply(client).get(); + client.sendCommand(new StatusCommand(StatusCommand.STATUS_FLAG)); + return client; + } catch (ExecutionException | InterruptedException e) { + WireIO.write(oos, new StatusCommand(-1 * StatusCommand.STATUS_FLAG)); + throw new IOException(e); + } + } + case Command.RECONNECT: + { + String probeId = ((ReconnectCommand) cmd).getProbeId(); + log.debug("Attempting to reconnect client for probe {}", probeId); + Client client = Client.findClient(probeId); + log.debug("Found client {}", client); + if (client instanceof RemoteClient) { + ((RemoteClient) client).reconnect(ois, oos, sock); + client.sendCommand(new StatusCommand(ReconnectCommand.STATUS_FLAG)); + return client; + } + WireIO.write(oos, new StatusCommand(-1 * ReconnectCommand.STATUS_FLAG)); + throw new IOException("Can not reconnect to non-remote session"); + } + case Command.LIST_PROBES: + { + ListProbesCommand listProbesCommand = (ListProbesCommand) cmd; + listProbesCommand.setProbes(Client.listProbes()); + WireIO.write(oos, listProbesCommand); + break; + } + case Command.EXIT: + { + return null; + } + default: + { + throw new IOException( + "expecting instrument, reconnect or settings command! (" + cmd.getClass() + ")"); + } + } + } + } + + private RemoteClient( + ClientContext ctx, + ObjectInputStream ois, + ObjectOutputStream oos, + Socket sock, + InstrumentCommand cmd) + throws IOException { + super(ctx); + this.sock = sock; + this.ois = ois; + this.oos = oos; + this.settings.from(ctx.getSettings()); + Class btraceClazz = loadClass(cmd); + if (btraceClazz == null) { + throw new RuntimeException("can not load BTrace class"); + } + + initClient(); + } + + private void initClient() { + BTraceRuntime.initUnsafe(); + Thread cmdHandler = + new Thread( + () -> { + try { + BTraceRuntime.enter(); + while (true) { + try { + if (ois == null) { + LockSupport.parkNanos(500_000_000L); // sleep 500ms + continue; + } + Command cmd = WireIO.read(ois); + switch (cmd.getType()) { + case Command.EXIT: + { + log.debug("received exit command"); + onCommand(cmd); + + return; + } + case Command.DISCONNECT: + { + log.debug("received disconnect command"); + onCommand(cmd); + break; + } + case Command.LIST_PROBES: + { + onCommand(cmd); + break; + } + case Command.EVENT: + { + getRuntime().handleEvent((EventCommand) cmd); + break; + } + default: + if (log.isDebugEnabled()) { + log.debug("received {}", cmd); + } + // ignore any other command + } + } catch (Exception exp) { + log.debug("Error while processing BTrace command", exp); + break; + } + } + } finally { + BTraceRuntime.leave(); + } + }); + cmdHandler.setDaemon(true); + log.debug("starting client command handler thread"); + cmdHandler.start(); + } + + @SuppressWarnings("RedundantThrows") + @Override + public void onCommand(Command cmd) throws IOException { + ObjectOutputStream output = oos; + if (output == null) { + if (!cmd.isUrgent()) { + delayedCommands.add(cmd); + } + return; + } + if (log.isDebugEnabled()) { + log.debug("client {}: got {}", getClassName(), cmd); + } + try { + boolean isConnected = true; + try { + synchronized (output) { + output.reset(); + } + } catch (SocketException e) { + isConnected = false; + } + + delayedCommands.forEach(new DelayedCommandExecutor(isConnected)); + + if (!dispatchCommand(cmd, isConnected)) { + if (!cmd.isUrgent()) { + delayedCommands.add(cmd); + } + } + + } catch (IOException ignored) { + // client can be in detached state + } + } + + private boolean dispatchCommand(Command cmd, boolean isConnected) { + if (cmd == Command.NULL) { + return true; // do not dispatch the NULL command + } + ObjectOutputStream output = oos; + ObjectInputStream input = ois; + Socket socket = sock; + if (output == null) { + return false; + } + try { + switch (cmd.getType()) { + case Command.EXIT: + if (isConnected) { + WireIO.write(output, cmd); + } + onExit(((ExitCommand) cmd).getExitCode()); + break; + case Command.LIST_PROBES: + { + if (isConnected) { + ((ListProbesCommand) cmd).setProbes(listProbes()); + WireIO.write(output, cmd); + } + break; + } + case Command.DISCONNECT: + { + ((DisconnectCommand) cmd).setProbeId(id.toString()); + synchronized (output) { + WireIO.write(output, cmd); + output.flush(); + output.close(); + } + oosUpdater.compareAndSet(this, output, null); + if (input != null) { + input.close(); + oisUpdater.compareAndSet(this, input, null); + } + if (socket != null) { + socket.close(); + sockUpdater.compareAndSet(this, socket, null); + } + break; + } + default: + if (out != null) { + if (cmd instanceof PrintableCommand) { + ((PrintableCommand) cmd).print(out); + break; + } + } + if (isConnected) { + WireIO.write(oos, cmd); + } + } + return true; + } catch (IOException e) { + return false; + } + } + + public boolean isDisconnected() { + return sock == null; + } + + @Override + protected void closeAll() throws IOException { + super.closeAll(); + + ObjectOutputStream output = oos; + if (output != null) { + synchronized (output) { + output.close(); + } + oosUpdater.compareAndSet(this, output, null); + } + ObjectInputStream input = ois; + if (input != null) { + input.close(); + oisUpdater.compareAndSet(this, input, null); + } + Socket socket = sock; + if (socket != null) { + socket.close(); + sockUpdater.compareAndSet(this, socket, null); + } + } + + void reconnect(ObjectInputStream ois, ObjectOutputStream oos, Socket socket) throws IOException { + this.sock = socket; + this.ois = ois; + this.oos = oos; + onCommand(Command.NULL); + } +} diff --git a/btrace-agent/src/main/java/org/openjdk/btrace/agent/TraceOutputWriter.java b/btrace-agent/src/main/java/org/openjdk/btrace/agent/TraceOutputWriter.java new file mode 100644 index 000000000..62f219bf9 --- /dev/null +++ b/btrace-agent/src/main/java/org/openjdk/btrace/agent/TraceOutputWriter.java @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.agent; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import org.openjdk.btrace.core.SharedSettings; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class represents various strategies available for dumping BTrace output to a file. + * + * @author Jaroslav Bachorik + */ +abstract class TraceOutputWriter extends Writer { + private static final Logger log = LoggerFactory.getLogger(TraceOutputWriter.class); + + protected TraceOutputWriter() {} + + /** + * Plain file writer - all output will go to one specified file + * + * @param output The file to put the output to + * @return Returns an appropriate {@linkplain TraceOutputWriter} instance or NULL + */ + public static TraceOutputWriter fileWriter(File output) { + TraceOutputWriter instance = null; + try { + instance = new SimpleFileWriter(output); + } catch (IOException e) { + // ignore + } + return instance; + } + + /** + * Time based rolling file writer. Defaults to 100 allowed output chunks. + * + * @param output The file to put the output to + * @param settings The shared settings + * @return Returns an appropriate {@linkplain TraceOutputWriter} instance or NULL + */ + public static TraceOutputWriter rollingFileWriter(File output, SharedSettings settings) { + TraceOutputWriter instance = null; + try { + instance = new TimeBasedRollingFileWriter(output, settings); + } catch (IOException e) { + // ignore + } + return instance; + } + + private static void ensurePathExists(File f) { + if (f == null || f.exists()) return; + + ensurePathExists(f.getParentFile()); + + if (!f.getName().endsWith(".btrace")) { // not creating the actual file + try { + f.createNewFile(); + } catch (IOException e) { + e.printStackTrace(); + // ignore and continue + } + } + } + + private static class SimpleFileWriter extends TraceOutputWriter { + private static final Logger log = LoggerFactory.getLogger(SimpleFileWriter.class); + + private final FileWriter delegate; + + @SuppressWarnings("DefaultCharset") + public SimpleFileWriter(File output) throws IOException { + try { + File parent = output.getParentFile(); + if (parent != null) { + output.getParentFile().mkdirs(); + } + delegate = new FileWriter(output); + } catch (IOException e) { + log.debug("Failed to create file output {}", output.getName(), e); + throw e; + } + } + + @Override + public void close() throws IOException { + delegate.close(); + } + + @Override + public void flush() throws IOException { + delegate.flush(); + } + + @Override + public void write(char[] cbuf, int off, int len) throws IOException { + delegate.write(cbuf, off, len); + } + } + + @SuppressWarnings("DefaultCharset") + private abstract static class RollingFileWriter extends TraceOutputWriter { + private static final Logger log = LoggerFactory.getLogger(RollingFileWriter.class); + protected final SharedSettings settings; + private final ReentrantReadWriteLock writerLock = new ReentrantReadWriteLock(); + private final String path, baseName; + // @GuardedBy writerLock + private FileWriter currentFileWriter; + private int counter = 1; + + public RollingFileWriter(File output, SharedSettings settings) throws IOException { + try { + output.getParentFile().mkdirs(); + currentFileWriter = new FileWriter(output); + path = output.getParentFile().getAbsolutePath(); + baseName = output.getName(); + this.settings = settings; + } catch (IOException e) { + log.debug("Failed to create rolling file output {}", output.getName(), e); + throw e; + } + } + + @Override + public final void close() throws IOException { + try { + writerLock.readLock().lock(); + currentFileWriter.close(); + } finally { + writerLock.readLock().unlock(); + } + } + + @Override + public final void flush() throws IOException { + try { + writerLock.readLock().lock(); + currentFileWriter.flush(); + } finally { + writerLock.readLock().unlock(); + } + + if (needsRoll()) { + nextWriter(); + } + } + + @Override + public final void write(char[] cbuf, int off, int len) throws IOException { + try { + writerLock.readLock().lock(); + currentFileWriter.write(cbuf, off, len); + } finally { + writerLock.readLock().unlock(); + } + } + + private void nextWriter() { + try { + writerLock.writeLock().lock(); + currentFileWriter = getNextWriter(); + } catch (IOException e) { + log.debug("Failed to roll over", e); + } finally { + writerLock.writeLock().unlock(); + } + } + + private FileWriter getNextWriter() throws IOException { + currentFileWriter.close(); + File scriptOutputFile_renameFrom = new File(path + File.separator + baseName); + File scriptOutputFile_renameTo = + new File(path + File.separator + baseName + "." + (counter++)); + + if (scriptOutputFile_renameTo.exists()) { + scriptOutputFile_renameTo.delete(); + } + scriptOutputFile_renameFrom.renameTo(scriptOutputFile_renameTo); + scriptOutputFile_renameFrom = new File(path + File.separator + baseName); + if (counter > settings.getFileRollMaxRolls()) { + counter = 1; + } + return new FileWriter(scriptOutputFile_renameFrom); + } + + protected abstract boolean needsRoll(); + } + + private static class TimeBasedRollingFileWriter extends RollingFileWriter { + private final TimeUnit unit = TimeUnit.MILLISECONDS; + private long lastTimeStamp = System.currentTimeMillis(); + + public TimeBasedRollingFileWriter(File output, SharedSettings settings) throws IOException { + super(output, settings); + } + + @Override + protected boolean needsRoll() { + long currTime = System.currentTimeMillis(); + long myInterval = currTime - lastTimeStamp; + if (unit.convert(myInterval, TimeUnit.MILLISECONDS) >= settings.getFileRollMilliseconds()) { + lastTimeStamp = currTime; + return true; + } + return false; + } + } +} diff --git a/btrace-agent/src/main/resources/META-INF/MANIFEST.MF b/btrace-agent/src/main/resources/META-INF/MANIFEST.MF new file mode 100644 index 000000000..771cce96c --- /dev/null +++ b/btrace-agent/src/main/resources/META-INF/MANIFEST.MF @@ -0,0 +1,5 @@ +Premain-Class: org.openjdk.btrace.agent.Main +Agent-Class: org.openjdk.btrace.agent.Main +Can-Redefine-Classes: true +Can-Retransform-Classes: true +Boot-Class-Path: btrace-boot.jar diff --git a/btrace-agent/src/test/java/org/openjdk/btrace/agent/MainTest.java b/btrace-agent/src/test/java/org/openjdk/btrace/agent/MainTest.java new file mode 100644 index 000000000..ec25128e2 --- /dev/null +++ b/btrace-agent/src/test/java/org/openjdk/btrace/agent/MainTest.java @@ -0,0 +1,46 @@ +package org.openjdk.btrace.agent; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.openjdk.btrace.core.ArgsMap; + +class MainTest { + @Test + void locateScriptsEmpty() { + ArgsMap argsMap = new ArgsMap(); + List scripts = Main.locateScripts(argsMap); + + assertTrue(scripts.isEmpty()); + } + + @Test + void locateScriptsSingle() { + ArgsMap argsMap = new ArgsMap(new String[] {"script=script1"}); + List scripts = Main.locateScripts(argsMap); + + assertEquals(1, scripts.size()); + } + + @Test + void locateScriptsMulti() { + ArgsMap argsMap = new ArgsMap(new String[] {"script=script1:script2"}); + List scripts = Main.locateScripts(argsMap); + + assertEquals(2, scripts.size()); + } + + @Test + void locateScriptsDir() throws Exception { + Path dir = Files.createTempDirectory("test-"); + Path script = Files.createTempFile(dir, "script-", ".btrace"); + ArgsMap argsMap = new ArgsMap(new String[] {"scriptdir=" + dir.toString()}); + List scripts = Main.locateScripts(argsMap); + + assertEquals(1, scripts.size()); + } +} diff --git a/btrace-client/build.gradle b/btrace-client/build.gradle new file mode 100644 index 000000000..7457e77ac --- /dev/null +++ b/btrace-client/build.gradle @@ -0,0 +1,37 @@ +buildscript { scriptHandler -> + apply from: rootProject.file('buildSrc/shared.gradle'), to: scriptHandler +} + +plugins { + alias(libs.plugins.versioning) +} + +dependencies { + implementation libs.slf4j + implementation libs.asm + implementation libs.asm.tree + implementation libs.asm.util + + def toolsJar = getToolsJar(); + if (toolsJar.getAsFile().exists()) { + implementation files("${toolsJar}") + } + + implementation project(':btrace-core') + implementation project(':btrace-compiler') + implementation project(':btrace-instr') +} + +jar { + manifest { + attributes( + 'Built-By' : System.properties['user.name'], + 'Build-Timestamp': new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(new Date()), + 'Build-Revision' : versioning.info.commit, + 'Created-By' : "Gradle ${gradle.gradleVersion}", + 'Build-Jdk' : "${System.properties['java.version']} (${System.properties['java.vendor']} ${System.properties['java.vm.version']})", + 'Build-OS' : "${System.properties['os.name']} ${System.properties['os.arch']} ${System.properties['os.version']}", + 'Main-Class' : "org.openjdk.btrace.client.Main" + ) + } +} \ No newline at end of file diff --git a/btrace-client/src/main/java/org/openjdk/btrace/client/Client.java b/btrace-client/src/main/java/org/openjdk/btrace/client/Client.java new file mode 100644 index 000000000..bb088ea7e --- /dev/null +++ b/btrace-client/src/main/java/org/openjdk/btrace/client/Client.java @@ -0,0 +1,821 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.btrace.client; + +import com.sun.tools.attach.VirtualMachine; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.PrintWriter; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.ConnectException; +import java.net.Socket; +import java.net.URI; +import java.net.URL; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.openjdk.btrace.compiler.Compiler; +import org.openjdk.btrace.core.Args; +import org.openjdk.btrace.core.BTraceRuntime; +import org.openjdk.btrace.core.SharedSettings; +import org.openjdk.btrace.core.annotations.DTrace; +import org.openjdk.btrace.core.annotations.DTraceRef; +import org.openjdk.btrace.core.comm.Command; +import org.openjdk.btrace.core.comm.CommandListener; +import org.openjdk.btrace.core.comm.DisconnectCommand; +import org.openjdk.btrace.core.comm.EventCommand; +import org.openjdk.btrace.core.comm.ExitCommand; +import org.openjdk.btrace.core.comm.InstrumentCommand; +import org.openjdk.btrace.core.comm.ListProbesCommand; +import org.openjdk.btrace.core.comm.MessageCommand; +import org.openjdk.btrace.core.comm.ReconnectCommand; +import org.openjdk.btrace.core.comm.SetSettingsCommand; +import org.openjdk.btrace.core.comm.StatusCommand; +import org.openjdk.btrace.core.comm.WireIO; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class represents a BTrace client. This can be used to create command line as well as a GUI + * based BTrace clients. The BTrace compilation, traced JVM attach, submission of compiled program + * and and I/O to the traced JVM are handled by this class. + * + * @author A. Sundararajan + */ +public class Client { + private static final Logger log = LoggerFactory.getLogger(Client.class); + + private static final String DTRACE_DESC; + private static final String DTRACE_REF_DESC; + private static boolean dtraceEnabled; + private static Method submitFile; + private static Method submitString; + + static { + try { + /* + * Check for DTrace Consumer class -- if we don't have that + * either /usr/lib/share/java/dtrace.jar is not in CLASSPATH + * or we are not running on Solaris 11+. + */ + Class dtraceConsumerClass = Class.forName("org.opensolaris.os.dtrace.Consumer"); + /* + * Check for BTrace's DTrace support class -- if that is available + * may be the user didn't build BTrace on Solaris 11. S/he built + * it on Solaris 10 or below or some other OS. + */ + Class dtraceClass = Class.forName("com.sun.btrace.dtrace.DTrace"); + dtraceEnabled = true; + submitFile = + dtraceClass.getMethod("submit", File.class, String[].class, CommandListener.class); + submitString = + dtraceClass.getMethod("submit", String.class, String[].class, CommandListener.class); + } catch (Exception exp) { + dtraceEnabled = false; + } + DTRACE_DESC = Type.getDescriptor(DTrace.class); + DTRACE_REF_DESC = Type.getDescriptor(DTraceRef.class); + } + + // port on which BTrace agent listens + private final int port; + // the output file or null + private final String outputFile; + // are we running debug mode? + private final boolean debug; + // do we need to track retransforming single classes? (will impose additional overhead) + private final boolean trackRetransforms; + // are we running in trusted mode? + private final boolean trusted; + // are we dumping .class files of + // the instrumented classes? + private final boolean dumpClasses; + // which directory we dump the .class files? + private final String dumpDir; + private final String probeDescPath; + private final String statsdDef; + + // connection state to the traced JVM + private volatile Socket sock; + private volatile ObjectInputStream ois; + private volatile ObjectOutputStream oos; + + private boolean disconnected = false; + + public Client(int port) { + this(port, null, ".", false, false, false, false, null, null); + } + + public Client(int port, String probeDescPath) { + this(port, null, probeDescPath, false, false, false, false, null, null); + } + + public Client( + int port, + String outputFile, + String probeDescPath, + boolean debug, + boolean trackRetransforms, + boolean trusted, + boolean dumpClasses, + String dumpDir, + String statsdDef) { + this.port = port; + this.outputFile = outputFile; + this.probeDescPath = probeDescPath; + this.debug = debug; + this.trusted = trusted; + this.dumpClasses = dumpClasses; + this.dumpDir = dumpDir; + this.trackRetransforms = trackRetransforms; + this.statsdDef = statsdDef; + } + + private static boolean isPortAvailable(int port) { + Socket clSocket = null; + try { + clSocket = new Socket("127.0.0.1", port); + } catch (IOException ignored) { + // ignore + } + if (clSocket != null) { + try { + clSocket.close(); + } catch (IOException ignored) { + // ignore + } + return false; + } + return true; + } + + @SuppressWarnings("DefaultCharset") + public byte[] compile(String fileName, String classPath) { + return compile(fileName, classPath, new PrintWriter(System.err), null); + } + + /** Compiles given BTrace program using given classpath. */ + @SuppressWarnings("DefaultCharset") + public byte[] compile(String fileName, String classPath, String includePath) { + return compile(fileName, classPath, new PrintWriter(System.err), includePath); + } + + public byte[] compile(String fileName, String classPath, PrintWriter err) { + return compile(fileName, classPath, err, null); + } + + /** + * Compiles given BTrace program using given classpath. Errors and warning are written to given + * PrintWriter. + */ + public byte[] compile(String fileName, String classPath, PrintWriter err, String includePath) { + byte[] code = null; + File file = new File(fileName); + if (fileName.endsWith(".java")) { + Compiler compiler = new Compiler(includePath); + classPath += File.pathSeparator + System.getProperty("java.class.path"); + if (log.isDebugEnabled()) { + log.debug("compiling {}", fileName); + } + Map classes = compiler.compile(file, err, ".", classPath); + if (classes == null) { + log.error("btrace compilation for script {} failed!", fileName); + return null; + } + + int size = classes.size(); + if (size != 1) { + log.error("no classes or more than one class in script {}", fileName); + return null; + } + String name = classes.keySet().iterator().next(); + code = classes.get(name); + if (log.isDebugEnabled()) { + log.debug("compiled {}", fileName); + } + } else if (fileName.endsWith(".class")) { + int codeLen = (int) file.length(); + code = new byte[codeLen]; + try { + if (log.isDebugEnabled()) { + log.debug("reading {}", fileName); + } + try (FileInputStream fis = new FileInputStream(file)) { + int off = 0; + int len = 0; + do { + len = fis.read(code, off, codeLen - off); + if (len > -1) { + off += len; + } + } while (off < codeLen && len != -1); + } + if (log.isDebugEnabled()) { + log.debug("read {}", fileName); + } + } catch (IOException exp) { + err.println(exp.getMessage()); + return null; + } + } else { + err.println("BTrace script has to be a .java or a .class"); + return null; + } + + return code; + } + + /** + * Attach the BTrace client to the given Java process. Loads BTrace agent on the target process if + * not loaded already. + */ + public void attach(String pid, String sysCp, String bootCp) throws IOException { + try { + String agentPath = "/btrace-agent.jar"; + URL btracePkg = Client.class.getClassLoader().getResource("org/openjdk/btrace/client"); + if (btracePkg != null) { + String tmp = btracePkg.toString(); + tmp = tmp.substring(0, tmp.indexOf('!')); + tmp = tmp.substring("jar:".length(), tmp.lastIndexOf('/')); + agentPath = tmp + agentPath; + agentPath = new File(new URI(agentPath)).getAbsolutePath(); + attach(pid, agentPath, sysCp, bootCp); + } else { + log.warn("Unable to prepare BTrace agent"); + } + } catch (RuntimeException | IOException e) { + throw e; + } catch (Exception exp) { + throw new IOException(exp.getMessage()); + } + } + + /** + * Attach the BTrace client to the given Java process. Loads BTrace agent on the target process if + * not loaded already. Accepts the full path of the btrace agent jar. Also, accepts system + * classpath and boot classpath optionally. + */ + public void attach(String pid, String agentPath, String sysCp, String bootCp) throws IOException { + try { + VirtualMachine vm = null; + if (log.isDebugEnabled()) { + log.debug("attaching to {}", pid); + } + vm = VirtualMachine.attach(pid); + if (log.isDebugEnabled()) { + log.debug("checking port availability: {}", port); + } + Properties serverVmProps = vm.getSystemProperties(); + int serverPort = Integer.parseInt(serverVmProps.getProperty("btrace.port", "-1")); + if (serverPort != -1) { + if (serverPort != port) { + throw new IOException( + "Can not attach to PID " + + pid + + " on port " + + port + + ". There is already a BTrace server active on port " + + serverPort + + "!"); + } + } else { + if (!isPortAvailable(port)) { + throw new IOException("Port " + port + " unavailable."); + } + } + + if (log.isDebugEnabled()) { + log.debug("attached to {}", pid); + log.debug("loading {}", agentPath); + } + String agentArgs = Args.PORT + "=" + port; + if (statsdDef != null) { + agentArgs += "," + Args.STATSD + "=" + statsdDef; + } + if (debug) { + agentArgs += "," + Args.DEBUG + "=true"; + } + if (trusted) { + agentArgs += "," + Args.TRUSTED + "=true"; + } + if (dumpClasses) { + agentArgs += "," + Args.DUMP_CLASSES + "=true"; + agentArgs += "," + Args.DUMP_DIR + "=" + dumpDir; + } + if (trackRetransforms) { + agentArgs += "," + Args.TRACK_RETRANSFORMS + "=true"; + } + if (bootCp != null) { + agentArgs += "," + Args.BOOT_CLASS_PATH + "=" + bootCp; + } + String toolsPath = + getToolsJarPath( + serverVmProps.getProperty("java.class.path"), serverVmProps.getProperty("java.home")); + if (sysCp == null) { + sysCp = toolsPath; + } else { + sysCp = sysCp + File.pathSeparator + toolsPath; + } + agentArgs += "," + Args.SYSTEM_CLASS_PATH + "=" + sysCp; + String cmdQueueLimit = System.getProperty(BTraceRuntime.CMD_QUEUE_LIMIT_KEY, null); + if (cmdQueueLimit != null) { + agentArgs += ",=" + Args.CMD_QUEUE_LIMIT + cmdQueueLimit; + } + agentArgs += "," + Args.PROBE_DESC_PATH + "=" + probeDescPath; + if (log.isDebugEnabled()) { + log.debug("agent args: {}", agentArgs); + } + vm.loadAgent(agentPath, agentArgs); + if (log.isDebugEnabled()) { + log.debug("loaded {}", agentPath); + } + } catch (RuntimeException | IOException re) { + throw re; + } catch (Exception exp) { + throw new IOException(exp.getMessage()); + } + } + + void connectAndListProbes(String host, CommandListener listener) throws IOException { + if (sock != null) { + throw new IllegalStateException(); + } + try { + if (log.isDebugEnabled()) { + log.debug("opening socket to {}", port); + } + long timeout = System.nanoTime() + TimeUnit.NANOSECONDS.convert(5, TimeUnit.SECONDS); + while (sock == null && System.nanoTime() <= timeout) { + try { + sock = new Socket(host, port); + } catch (ConnectException e) { + log.debug("server not yet available; retrying ..."); + Thread.sleep(20); + } + } + + if (sock == null) { + log.debug("server not available. exiting."); + System.exit(1); + } + oos = new ObjectOutputStream(sock.getOutputStream()); + WireIO.write(oos, new ListProbesCommand()); + ois = new ObjectInputStream(sock.getInputStream()); + + log.debug("entering into command loop"); + commandLoop( + cmd -> { + if (cmd.getType() == Command.LIST_PROBES) { + listener.onCommand(cmd); + System.exit(0); + } else { + listener.onCommand(cmd); + } + }); + } catch (UnknownHostException uhe) { + throw new IOException(uhe); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + void reconnect(String host, String resumeProbe, CommandListener listener, String[] command) + throws IOException { + if (sock != null) { + throw new IllegalStateException(); + } + try { + if (log.isDebugEnabled()) { + log.debug("opening socket to {}", port); + } + long timeout = System.nanoTime() + TimeUnit.NANOSECONDS.convert(5, TimeUnit.SECONDS); + while (sock == null && System.nanoTime() <= timeout) { + try { + sock = new Socket(host, port); + } catch (ConnectException e) { + log.debug("server not yet available; retrying ..."); + Thread.sleep(20); + } + } + + if (sock == null) { + log.debug("server not available. exiting."); + System.exit(1); + } + oos = new ObjectOutputStream(sock.getOutputStream()); + ois = new ObjectInputStream(sock.getInputStream()); + + log.debug("reconnecting client {}", resumeProbe); + WireIO.write(oos, new ReconnectCommand(resumeProbe)); + + log.debug("entering into command loop"); + commandLoop( + new CommandListener() { + boolean statusReported = false; + + @Override + public void onCommand(Command cmd) throws IOException { + if (statusReported || cmd.getType() != Command.STATUS) { + listener.onCommand(cmd); + } else { + StatusCommand statusCommand = (StatusCommand) cmd; + if (statusCommand.getFlag() == ReconnectCommand.STATUS_FLAG) { + if (statusCommand.isSuccess()) { + log.info("Reconnected to an active probe: {}", resumeProbe); + String probeCommand = command[0]; + String probeCommandArg = command[1]; + if (probeCommand != null) { + if (log.isDebugEnabled()) { + log.debug( + "Executing remote command '{}'{}", + probeCommand, + (probeCommandArg != null ? "(" + probeCommandArg + ")" : "")); + } + switch (probeCommand) { + case "exit": + { + sendExit(); + break; + } + case "event": + { + if (probeCommandArg == null || probeCommandArg.equals("*")) { + sendEvent(); + } else { + sendEvent(probeCommandArg); + } + sendDisconnect(); + break; + } + default: + { + log.warn("Unrecognized BTrace command {}", probeCommand); + } + } + } + } else { + log.warn("Unable to reconnect to an active probe: {}", resumeProbe); + System.exit(1); + } + } else { + listener.onCommand(cmd); + } + statusReported = true; + } + } + }); + } catch (UnknownHostException uhe) { + throw new IOException(uhe); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + /** + * Submits the compiled BTrace .class to the VM attached and passes given command line arguments. + * Receives commands from the traced JVM and sends those to the command listener provided. + */ + public void submit(String fileName, byte[] code, String[] args, CommandListener listener) + throws IOException { + submit("localhost", fileName, code, args, listener); + } + + /** + * Submits the compiled BTrace .class to the VM attached and passes given command line arguments. + * Receives commands from the traced JVM and sends those to the command listener provided. + */ + public void submit( + String host, String fileName, byte[] code, String[] args, CommandListener listener) + throws IOException { + if (sock != null) { + throw new IllegalStateException(); + } + submitDTrace(fileName, code, args, listener); + try { + if (log.isDebugEnabled()) { + log.debug("opening socket to {}", port); + } + long timeout = System.nanoTime() + TimeUnit.NANOSECONDS.convert(5, TimeUnit.SECONDS); + while (sock == null && System.nanoTime() <= timeout) { + try { + sock = new Socket(host, port); + } catch (ConnectException e) { + log.debug("server not yet available; retrying ..."); + Thread.sleep(20); + } + } + + if (sock == null) { + log.debug("server not available. exiting."); + System.exit(1); + } + oos = new ObjectOutputStream(sock.getOutputStream()); + log.debug("setting up client settings"); + Map settings = new HashMap<>(); + settings.put(SharedSettings.DEBUG_KEY, debug); + settings.put(SharedSettings.DUMP_DIR_KEY, dumpClasses ? dumpDir : ""); + settings.put(SharedSettings.TRACK_RETRANSFORMS_KEY, trackRetransforms); + settings.put(SharedSettings.TRUSTED_KEY, trusted); + settings.put(SharedSettings.PROBE_DESC_PATH_KEY, probeDescPath); + settings.put(SharedSettings.OUTPUT_FILE_KEY, outputFile); + + WireIO.write(oos, new SetSettingsCommand(settings)); + + ois = new ObjectInputStream(sock.getInputStream()); + + if (log.isDebugEnabled()) { + log.debug("sending instrument command: {}", Arrays.deepToString(args)); + } + SharedSettings sSettings = new SharedSettings(); + sSettings.from(settings); + WireIO.write(oos, new InstrumentCommand(code, args)); + + log.debug("entering into command loop"); + commandLoop( + new CommandListener() { + boolean statusReported = false; + + @Override + public void onCommand(Command cmd) throws IOException { + if (statusReported || cmd.getType() != Command.STATUS) { + listener.onCommand(cmd); + } else { + StatusCommand statusCommand = (StatusCommand) cmd; + if (statusCommand.getFlag() == StatusCommand.STATUS_FLAG) { + if (statusCommand.isSuccess()) { + log.info("Successfully started BTrace probe: {}", fileName); + } else { + log.warn("Failed to start BTrace probe: {}", fileName); + System.exit(1); + } + statusReported = true; + } else { + listener.onCommand(cmd); + } + } + } + }); + } catch (UnknownHostException uhe) { + throw new IOException(uhe); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + /** + * Submits the compiled BTrace .class to the VM attached and passes given command line arguments. + * Receives commands from the traced JVM and sends those to the command listener provided. + */ + public void submit(byte[] code, String[] args, CommandListener listener) throws IOException { + submit(null, code, args, listener); + } + + /** Sends ExitCommand to the traced JVM. */ + public void sendExit() throws IOException { + sendExit(0); + } + + /** Sends ExitCommand to the traced JVM. */ + public void sendExit(int code) throws IOException { + send(new ExitCommand(code)); + } + + public void sendDisconnect() throws IOException { + send(new DisconnectCommand()); + } + + /** Sends an EventCommand to the traced JVM. */ + public void sendEvent() throws IOException { + sendEvent(""); + } + + /** Sends an EventCommand to the traced JVM. */ + public void sendEvent(String name) throws IOException { + send(new EventCommand(name)); + } + + /** Closes all connection state to the traced JVM. */ + public synchronized void close() throws IOException { + if (ois != null) { + ois.close(); + } + if (oos != null) { + oos.close(); + } + if (sock != null) { + sock.close(); + } + reset(); + } + + boolean isDisconnected() { + return disconnected; + } + + void disconnect() throws IOException { + disconnected = true; + sendDisconnect(); + } + + void listProbes() throws IOException { + send(new ListProbesCommand()); + } + + /** reset the internal status of the client */ + private void reset() { + sock = null; + ois = null; + oos = null; + } + + // -- Internals only below this point + private String getToolsJarPath(String javaClassPath, String javaHome) { + // try to get absolute path of tools.jar + // first check this application's classpath + String[] components = javaClassPath.split(File.pathSeparator); + for (String c : components) { + if (c.endsWith("tools.jar")) { + return new File(c).getAbsolutePath(); + } else if (c.endsWith("classes.jar")) { // MacOS specific + return new File(c).getAbsolutePath(); + } + } + // we didn't find -- make a guess! If this app is running on a JDK rather + // than a JRE there will be a tools.jar in $JDK_HOME/lib directory. + if (System.getProperty("os.name").startsWith("Mac")) { + int homeIndex = javaHome.indexOf("/Home"); + if (homeIndex > -1) { + String java_mac_home = javaHome.substring(0, javaHome.indexOf("/Home")); + return java_mac_home + "/Home/lib/tools.jar"; + } + } + // tools.jar is not included in JRE + return javaHome + (javaHome.contains("/jre") ? "/.." : "") + "/lib/tools.jar"; + } + + private void send(Command cmd) throws IOException { + if (oos == null) { + throw new IllegalStateException(); + } + oos.reset(); + WireIO.write(oos, cmd); + } + + private void commandLoop(CommandListener listener) throws IOException { + assert ois != null : "null input stream?"; + AtomicBoolean exited = new AtomicBoolean(false); + while (true) { + try { + Command cmd = WireIO.read(ois); + if (log.isDebugEnabled()) { + log.debug("received command {}", cmd); + } + listener.onCommand(cmd); + if (cmd.getType() == Command.EXIT) { + log.debug("received EXIT cmd"); + return; + } + } catch (IOException e) { + if (exited.compareAndSet(false, true)) listener.onCommand(new ExitCommand(-1)); + throw e; + } catch (NullPointerException e) { + e.printStackTrace(); + if (exited.compareAndSet(false, true)) listener.onCommand(new ExitCommand(-1)); + } + } + } + + private void warn(CommandListener listener, String msg) { + try { + msg = "WARNING: " + msg + "\n"; + listener.onCommand(new MessageCommand(msg)); + } catch (IOException exp) { + if (log.isDebugEnabled()) { + log.debug("Failed to send warning messge", exp); + } + } + } + + private void submitDTrace(String fileName, byte[] code, String[] args, CommandListener listener) { + if (fileName == null || code == null) { + return; + } + + Object dtraceSrc = getDTraceSource(fileName, code); + try { + if (dtraceSrc instanceof String) { + if (dtraceEnabled) { + submitString.invoke(null, dtraceSrc, args, listener); + } else { + warn(listener, "@DTrace is supported only on Solaris 11+"); + } + } else if (dtraceSrc instanceof File) { + if (dtraceEnabled) { + submitFile.invoke(null, dtraceSrc, args, listener); + } else { + warn(listener, "@DTraceRef is supported only on Solaris 11+"); + } + } + } catch (IllegalAccessException | IllegalArgumentException iace) { + iace.printStackTrace(); + } catch (InvocationTargetException ite) { + throw new RuntimeException(ite.getTargetException()); + } + } + + private Object getDTraceSource(String fileName, byte[] code) { + if (isPersistedProbe(code)) { + return null; + } + + ClassReader reader = new ClassReader(code); + Object[] result = new Object[1]; + reader.accept( + new ClassVisitor(Opcodes.ASM9) { + + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean vis) { + if (desc.equals(DTRACE_DESC)) { + return new AnnotationVisitor(Opcodes.ASM9) { + + @Override + public void visit(String name, Object value) { + if (name.equals("value")) { + result[0] = value; + } + } + }; + } else if (desc.equals(DTRACE_REF_DESC)) { + return new AnnotationVisitor(Opcodes.ASM9) { + + @Override + public void visit(String name, Object value) { + if (name.equals("value")) { + String tmp = value.toString(); + File file = new File(tmp); + if (file.isAbsolute()) { + result[0] = file; + } else { + int index = fileName.lastIndexOf(File.separatorChar); + String dir; + if (index == -1) { + dir = "."; + } else { + dir = fileName.substring(0, index); + } + result[0] = new File(dir, tmp); + } + } + } + }; + } else { + return super.visitAnnotation(desc, vis); + } + } + }, + ClassReader.SKIP_CODE); + return result[0]; + } + + private static boolean isPersistedProbe(byte[] code) { + return code[0] == (byte) (0xBA & 0xff) + && code[1] == (byte) (0xCE & 0xff) + && code[2] == (byte) (0XCA & 0xff) + && code[3] == (byte) (0XCA & 0xff); + } +} diff --git a/btrace-client/src/main/java/org/openjdk/btrace/client/JpsUtils.java b/btrace-client/src/main/java/org/openjdk/btrace/client/JpsUtils.java new file mode 100644 index 000000000..f28fdca48 --- /dev/null +++ b/btrace-client/src/main/java/org/openjdk/btrace/client/JpsUtils.java @@ -0,0 +1,85 @@ +package org.openjdk.btrace.client; + +import com.sun.tools.attach.VirtualMachine; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sun.jvmstat.monitor.MonitoredHost; +import sun.jvmstat.monitor.MonitoredVm; +import sun.jvmstat.monitor.MonitoredVmUtil; +import sun.jvmstat.monitor.VmIdentifier; + +final class JpsUtils { + private static final Logger log = LoggerFactory.getLogger(JpsUtils.class); + + static Integer findVmByName(String name) { + try { + return Integer.parseInt(name); + } catch (NumberFormatException ignored) { + Integer pid = null; + try { + MonitoredHost vmHost = MonitoredHost.getMonitoredHost((String) null); + for (Integer vmPid : MonitoredHost.getMonitoredHost("localhost").activeVms()) { + VmIdentifier id = new VmIdentifier(vmPid.toString()); + MonitoredVm vm = vmHost.getMonitoredVm(id); + String mainClass = MonitoredVmUtil.mainClass(vm, false); + if (name.equalsIgnoreCase(mainClass)) { + pid = vmPid; + break; + } + } + } catch (Exception e) { + log.warn("Unexpected exception", e); + } + return pid; + } + } + + static Collection listVms() { + Collection vms = new ArrayList<>(); + try { + MonitoredHost vmHost = MonitoredHost.getMonitoredHost((String) null); + for (Integer vmPid : MonitoredHost.getMonitoredHost("localhost").activeVms()) { + VmIdentifier id = new VmIdentifier(vmPid.toString()); + MonitoredVm mvm = vmHost.getMonitoredVm(id); + if (MonitoredVmUtil.isAttachable(mvm)) { + String mainClass = MonitoredVmUtil.mainClass(mvm, false); + + vms.add( + "(" + + (hasBTraceServer(vmPid) ? "+" : "-") + + ") " + + vmPid + + " " + + mainClass + + " [" + + MonitoredVmUtil.commandLine(mvm) + + "]"); + } + } + } catch (Exception e) { + log.warn("Unexpected exception", e); + } + return vms; + } + + static boolean hasBTraceServer(int pid) { + boolean result = false; + VirtualMachine vm = null; + try { + vm = VirtualMachine.attach(String.valueOf(pid)); + result = vm.getSystemProperties().containsKey("btrace.port"); + } catch (Throwable ignored) { + } finally { + if (vm != null) { + try { + vm.detach(); + } catch (IOException ignored) { + } + } + } + return result; + } +} diff --git a/btrace-client/src/main/java/org/openjdk/btrace/client/Main.java b/btrace-client/src/main/java/org/openjdk/btrace/client/Main.java new file mode 100644 index 000000000..380e94e34 --- /dev/null +++ b/btrace-client/src/main/java/org/openjdk/btrace/client/Main.java @@ -0,0 +1,433 @@ +/* + * Copyright (c) 2008-2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.client; + +import java.io.Console; +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Properties; + +import org.openjdk.btrace.core.DebugSupport; +import org.openjdk.btrace.core.Messages; +import org.openjdk.btrace.core.comm.Command; +import org.openjdk.btrace.core.comm.CommandListener; +import org.openjdk.btrace.core.comm.ExitCommand; +import org.openjdk.btrace.core.comm.PrintableCommand; +import org.openjdk.btrace.core.comm.StatusCommand; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sun.misc.Signal; + +/** + * This is the main class for a simple command line BTrace client. It is possible to create a GUI + * client using the Client class. + * + * @author A. Sundararajan + */ +@SuppressWarnings("RedundantThrows") +public final class Main { + private static final Logger log; + + public static final boolean TRACK_RETRANSFORM; + public static final int BTRACE_DEFAULT_PORT = 2020; + public static final String BTRACE_DEFAULT_HOST = "localhost"; + private static final Console con; + private static final PrintWriter out; + public static volatile boolean exiting; + private static boolean DEBUG; + private static boolean TRUSTED; + private static boolean DUMP_CLASSES; + private static String OUTPUT_FILE; + private static String DUMP_DIR; + private static String PROBE_DESC_PATH; + + static { + DebugSupport.initLoggers(Boolean.getBoolean("com.sun.btrace.debug"), null); + // initialize the logger instance + log = LoggerFactory.getLogger(Main.class); + + if (isDebug()) { + log.debug("btrace debug mode is set"); + } + TRACK_RETRANSFORM = Boolean.getBoolean("com.sun.btrace.trackRetransforms"); + if (TRACK_RETRANSFORM) log.debug("trackRetransforms flag is set"); + TRUSTED = Boolean.getBoolean("com.sun.btrace.unsafe"); + TRUSTED |= Boolean.getBoolean("com.sun.btrace.trusted"); + if (TRUSTED) log.debug("btrace trusted mode is set"); + DUMP_CLASSES = Boolean.getBoolean("com.sun.btrace.dumpClasses"); + if (DUMP_CLASSES) log.debug("dumpClasses flag is set"); + DUMP_DIR = System.getProperty("com.sun.btrace.dumpDir", "."); + if (DUMP_CLASSES) { + if (log.isDebugEnabled()) log.debug("dumpDir is {}", DUMP_DIR); + } + PROBE_DESC_PATH = System.getProperty("com.sun.btrace.probeDescPath", "."); + String javaVersion = getJavaVersion(); + // In Java 22 the console will write standard output to stderr :shrug: + con = javaVersion == null || !javaVersion.startsWith("22") ? System.console() : null; + out = getOutWriter(con); + } + + private static String getJavaVersion() { + Properties props = new Properties(); + try { + props.load(Files.newInputStream(Paths.get(System.getenv("JAVA_HOME"), "release"))); + return props.getProperty("JAVA_VERSION").replace("\"", ""); + } catch (IOException ignored) { + return null; + } + } + + @SuppressWarnings("DefaultCharset") + private static PrintWriter getOutWriter(Console con) { + return (con != null) ? con.writer() : new PrintWriter(System.out); + } + + public static void main(String[] args) throws Exception { + int port = BTRACE_DEFAULT_PORT; + String host = BTRACE_DEFAULT_HOST; + String classPath = "."; + String includePath = null; + + int count = 0; + boolean hostDefined = false; + boolean portDefined = false; + boolean classpathDefined = false; + boolean includePathDefined = false; + String statsdDef = ""; + String resumeProbe = null; + String probeCommand = null; + String probeCommandArg = null; + boolean listProbes = false; + boolean unattended = false; + + for (int i = 0; i < args.length; i++) { + String arg = args[i]; + switch (arg) { + case "-v": + DEBUG = true; + DebugSupport.initLoggers(true, log); + break; + case "--version": + System.out.println(Messages.get("btrace.version")); + return; + case "-l": + for (String vm : JpsUtils.listVms()) { + System.out.println(vm); + } + return; + case "-r": + if (i < args.length - 1) { + if (args[i + 1].equalsIgnoreCase("help")) { + System.out.println(Messages.get("remote.commands.help")); + return; + } + } + break; + } + } + + if (args.length < 2) { + usage(); + } + + for (; ; ) { + if (args[count].isEmpty()) { + continue; + } + if (args[count].charAt(0) == '-') { + if (args.length <= count + 1) { + usage(); + } + if (args[count].equals("-p") && !portDefined) { + try { + port = Integer.parseInt(args[++count]); + if (log.isDebugEnabled()) log.debug("accepting port {}", port); + } catch (NumberFormatException nfe) { + usage(); + } + portDefined = true; + } else if (args[count].equals("-u")) { + TRUSTED = true; + log.debug("btrace trusted mode is set"); + } else if (args[count].equals("-o")) { + OUTPUT_FILE = args[++count]; + if (log.isDebugEnabled()) log.debug("outputFile is {}", OUTPUT_FILE); + } else if (args[count].equals("-d")) { + DUMP_CLASSES = true; + DUMP_DIR = args[++count]; + if (log.isDebugEnabled()) log.debug("dumpDir is {}", DUMP_DIR); + } else if (args[count].equals("-pd")) { + PROBE_DESC_PATH = args[++count]; + if (log.isDebugEnabled()) log.debug("probeDescDir is {}", PROBE_DESC_PATH); + } else if ((args[count].equals("-cp") || args[count].equals("-classpath")) + && !classpathDefined) { + classPath = args[++count]; + if (log.isDebugEnabled()) log.debug("accepting classpath {}", classPath); + classpathDefined = true; + } else if (args[count].equals("-I") && !includePathDefined) { + includePath = args[++count]; + if (log.isDebugEnabled()) log.debug("accepting include path {}", includePath); + includePathDefined = true; + } else if (args[count].equals("-statsd")) { + statsdDef = args[++count]; + } else if (args[count].equals("-v")) { + // already processed + } else if (args[count].equals("-host") && !hostDefined) { + host = args[++count]; + hostDefined = true; + } else if (args[count].equals("-r")) { + if (log.isDebugEnabled()) + log.debug("reconnecting to an already active probe {}", resumeProbe); + resumeProbe = args[++count]; + if (count < args.length - 2 && !args[count + 1].startsWith("-")) { + probeCommand = args[++count].toLowerCase(); + if (probeCommand.equalsIgnoreCase("event") && count < args.length - 2) { + probeCommandArg = args[++count]; + } + } + if (probeCommand != null && log.isDebugEnabled()) { + log.debug( + "executing probe command '{}'{}", + probeCommand, + (probeCommandArg != null ? "(" + probeCommandArg + ")" : "")); + } + } else if (args[count].equals("-lp")) { + log.debug("listing active probes"); + listProbes = true; + } else if (args[count].equals("-x")) { + log.debug("submitting probe in unattended mode"); + unattended = true; + } else { + usage(); + } + count++; + if (count >= args.length) { + break; + } + } else { + break; + } + } + + if (!portDefined) { + if (log.isDebugEnabled()) log.debug("assuming default port {}", port); + } + + if (!classpathDefined) { + if (log.isDebugEnabled()) log.debug("assuming default classpath '{}'", classPath); + } + + if (args.length < (count + 1)) { + usage(); + } + + String pidArg = args[count]; + Integer pid = JpsUtils.findVmByName(pidArg); + if (pid == null) { + errorExit("Unable to find JVM with either PID or name: " + pidArg, 1); + } else { + log.info("Attaching BTrace to PID: {}", pid); + } + + try { + Client client = + new Client( + port, + OUTPUT_FILE, + PROBE_DESC_PATH, + DEBUG, + TRACK_RETRANSFORM, + TRUSTED, + DUMP_CLASSES, + DUMP_DIR, + statsdDef); + if (resumeProbe != null) { + registerExitHook(client); + if (con != null) { + registerSignalHandler(client); + } + client.reconnect( + host, + resumeProbe, + createCommandListener(client), + new String[] {probeCommand, probeCommandArg}); + } else if (listProbes) { + registerExitHook(client); + client.attach(pid.toString(), null, classPath); + client.connectAndListProbes(host, createCommandListener(client)); + System.exit(0); + } else { + String fileName = args[count + 1]; + String[] btraceArgs = new String[args.length - count - 2]; + if (btraceArgs.length > 0) { + System.arraycopy(args, count + 2, btraceArgs, 0, btraceArgs.length); + } + if (!new File(fileName).exists()) { + errorExit("File not found: " + fileName, 1); + } + byte[] code = client.compile(fileName, classPath, includePath); + if (code == null) { + errorExit("BTrace compilation failed", 1); + } + if (log.isDebugEnabled()) { + log.debug("Boot classpath: {}", classPath); + } + if (!hostDefined) client.attach(pid.toString(), null, classPath); + registerExitHook(client); + if (con != null) { + registerSignalHandler(client); + } + log.debug("submitting the BTrace program"); + CommandListener listener = createCommandListener(client); + + boolean isUnattended = unattended; + client.submit( + host, + fileName, + code, + btraceArgs, + cmd -> { + if (isUnattended + && cmd.getType() == Command.STATUS + && ((StatusCommand) cmd).getFlag() == StatusCommand.STATUS_FLAG) { + client.sendDisconnect(); + } else { + listener.onCommand(cmd); + } + }); + } + } catch (IOException exp) { + errorExit(exp.getMessage(), 1); + } + } + + private static CommandListener createCommandListener(Client client) { + return cmd -> { + int type = cmd.getType(); + if (cmd instanceof PrintableCommand) { + ((PrintableCommand) cmd).print(out); + out.flush(); + } else if (type == Command.EXIT) { + exiting = true; + out.flush(); + ExitCommand ecmd = (ExitCommand) cmd; + System.exit(ecmd.getExitCode()); + } + if (type == Command.DISCONNECT) { + System.exit(0); + } + }; + } + + private static void registerExitHook(Client client) { + log.debug("registering shutdown hook"); + Runtime.getRuntime() + .addShutdownHook( + new Thread( + () -> { + if (!exiting) { + try { + if (!client.isDisconnected()) { + log.debug("sending exit command"); + client.sendExit(0); + } else { + log.debug("sending disconnect command"); + client.sendDisconnect(); + } + } catch (IOException ioexp) { + log.debug(ioexp.toString(), ioexp); + } + } + })); + } + + private static void registerSignalHandler(Client client) { + log.debug("registering signal handler for SIGINT"); + Signal.handle( + new Signal("INT"), + sig -> { + try { + con.printf("Please enter your option:\n"); + con.printf( + "\t1. exit\n\t2. send an event\n\t3. send a named event\n\t4. flush console output\n\t5. list probes\n\t6. detach client\n"); + con.flush(); + String option = con.readLine(); + if (option == null) { + return; + } + option = option.trim(); + switch (option) { + case "1": + System.exit(0); + case "2": + log.debug("sending event command"); + client.sendEvent(); + break; + case "3": + con.printf("Please enter the event name: "); + String name = con.readLine(); + if (name != null) { + log.debug("sending event command"); + client.sendEvent(name); + } + break; + case "4": + out.flush(); + break; + case "5": + client.listProbes(); + break; + case "6": + client.disconnect(); + break; + default: + con.printf("invalid option!\n"); + break; + } + } catch (IOException ioexp) { + log.debug(ioexp.toString(), ioexp); + } + }); + } + + private static void usage() { + System.err.println(Messages.get("btrace.usage")); + System.exit(1); + } + + private static boolean isDebug() { + return DEBUG; + } + + private static void errorExit(String msg, int code) { + exiting = true; + System.err.println(msg); + System.exit(code); + } +} diff --git a/btrace-client/src/main/java/org/openjdk/btrace/client/ProbePrinter.java b/btrace-client/src/main/java/org/openjdk/btrace/client/ProbePrinter.java new file mode 100644 index 000000000..5a81d9e9b --- /dev/null +++ b/btrace-client/src/main/java/org/openjdk/btrace/client/ProbePrinter.java @@ -0,0 +1,52 @@ +package org.openjdk.btrace.client; + +import java.io.BufferedInputStream; +import java.io.FileInputStream; +import java.io.InputStream; +import java.io.PrintWriter; +import java.nio.file.Path; +import java.nio.file.Paths; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.util.TraceClassVisitor; +import org.openjdk.btrace.core.SharedSettings; +import org.openjdk.btrace.instr.BTraceProbe; +import org.openjdk.btrace.instr.BTraceProbeFactory; +import org.openjdk.btrace.instr.OnMethod; +import org.openjdk.btrace.instr.OnProbe; + +public final class ProbePrinter { + public static void main(String[] args) throws Exception { + if (args.length != 1) { + System.out.println("Usage: btracep "); + System.exit(0); + } + Path probePath = Paths.get(args[0]); + + try (InputStream probeDataStream = + new BufferedInputStream(new FileInputStream(probePath.toFile()))) { + BTraceProbe probe = + new BTraceProbeFactory(SharedSettings.GLOBAL).createProbe(probeDataStream); + + probe.checkVerified(); + System.out.println("Name: " + probe.getClassName(false)); + System.out.println("Verified: " + probe.isVerified()); + System.out.println("Transforming: " + probe.isTransforming()); + + System.out.println("=== Probe handlers"); + for (OnMethod om : probe.onmethods()) { + System.out.println(om); + } + for (OnProbe op : probe.onprobes()) { + System.out.println(op); + } + + System.out.println("=== Dataholder class"); + ClassReader dataholderReader = new ClassReader(probe.getDataHolderBytecode()); + dataholderReader.accept(new TraceClassVisitor(new PrintWriter(System.out)), 0); + + System.out.println("=== Full probe class"); + ClassReader probeReader = new ClassReader(probe.getFullBytecode()); + probeReader.accept(new TraceClassVisitor(new PrintWriter(System.out)), 0); + } + } +} diff --git a/btrace-compiler/build.gradle b/btrace-compiler/build.gradle new file mode 100644 index 000000000..bd443d519 --- /dev/null +++ b/btrace-compiler/build.gradle @@ -0,0 +1,15 @@ +dependencies { + // https://mvnrepository.com/artifact/org.ow2.asm/asm + implementation libs.asm + implementation libs.asm.util + + def toolsJar = getToolsJar(); + if (toolsJar.getAsFile().exists()) { + implementation files("${toolsJar}") + } + implementation project(path: ':btrace-core') + implementation project(path: ':btrace-runtime') + runtimeOnly project(path: ':btrace-instr') + + testImplementation project(path: ':btrace-instr') +} diff --git a/btrace-compiler/src/main/java/org/openjdk/btrace/compiler/AnnotationSerializer.java b/btrace-compiler/src/main/java/org/openjdk/btrace/compiler/AnnotationSerializer.java new file mode 100644 index 000000000..3ef8efc90 --- /dev/null +++ b/btrace-compiler/src/main/java/org/openjdk/btrace/compiler/AnnotationSerializer.java @@ -0,0 +1,9 @@ +package org.openjdk.btrace.compiler; + +import org.objectweb.asm.tree.AnnotationNode; + +public class AnnotationSerializer { + public static void serialize(AnnotationNode an, StringBuilder sb) { + sb.append("{type:").append(an.desc).append(','); + } +} diff --git a/btrace-compiler/src/main/java/org/openjdk/btrace/compiler/Compiler.java b/btrace-compiler/src/main/java/org/openjdk/btrace/compiler/Compiler.java new file mode 100644 index 000000000..6555c4375 --- /dev/null +++ b/btrace-compiler/src/main/java/org/openjdk/btrace/compiler/Compiler.java @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.btrace.compiler; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Writer; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.ToolProvider; +import org.openjdk.btrace.core.Messages; +import org.openjdk.btrace.runtime.BTraceRuntimeAccess; + +/** + * Compiler for a BTrace program. Note that a BTrace program is a Java program that is specially + * annotated and can *not* use many Java constructs (essentially java--). We use JSR 199 API to + * compile BTrace program but validate the program (for BTrace safety rules) using JSR 269 and + * javac's Tree API. + * + * @author A. Sundararajan + */ +public class Compiler { + private final CompilerHelper compilerHelper; + // null means no preprocessing isf done. + public List includeDirs; + private final StandardJavaFileManager stdManager; + private final String packExtension = "class"; + + public Compiler(String includePath, boolean generatePack) { + if (includePath != null) { + includeDirs = new ArrayList<>(); + String[] paths = includePath.split(File.pathSeparator); + includeDirs.addAll(Arrays.asList(paths)); + } + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + stdManager = compiler.getStandardFileManager(null, null, null); + compilerHelper = new CompilerHelper(compiler, generatePack); + } + + public Compiler(String includePath) { + this(includePath, false); + } + + public Compiler(boolean generatePack) { + this(null, generatePack); + } + + public Compiler() { + this(null); + } + + private static void usage(String msg) { + System.err.println(msg); + System.exit(1); + } + + private static void usage() { + usage(Messages.get("btracec.usage")); + } + + // simple test main + @SuppressWarnings({"DefaultCharset", "RedundantThrows"}) + public static void main(String[] args) throws Exception { + if (args.length == 0) { + usage(); + } + + // turn off the unique client name generation as it is undesired during compilation + try { + Field f = BTraceRuntimeAccess.class.getDeclaredField("uniqueClientClassNames"); + f.setAccessible(true); + f.set(null, false); + } catch (Exception ignored) { + } + + String classPath = "."; + String outputDir = "."; + String includePath = null; + boolean trusted = false; + boolean generatePack = true; + String packExtension = null; + int count = 0; + boolean classPathDefined = false; + boolean outputDirDefined = false; + boolean includePathDefined = false; + boolean trustedDefined = false; + + for (; ; ) { + if (args[count].charAt(0) == '-') { + if (args.length <= count + 1) { + usage(); + } + if ((args[count].equals("-cp") || args[count].equals("-classpath")) && !classPathDefined) { + classPath = args[++count]; + classPathDefined = true; + } else if (args[count].equals("-d") && !outputDirDefined) { + outputDir = args[++count]; + outputDirDefined = true; + } else if (args[count].equals("-I") && !includePathDefined) { + includePath = args[++count]; + includePathDefined = true; + } else if ((args[count].equals("-unsafe") || args[count].equals("-trusted")) + && !trustedDefined) { + trusted = true; + trustedDefined = true; + } else if (args[count].equals("-nopack")) { + generatePack = false; + } else if (args[count].equals("-packext")) { + packExtension = args[++count]; + } else { + usage(); + } + count++; + if (count >= args.length) { + break; + } + } else { + break; + } + } + + if (args.length <= count) { + usage(); + } + + if (!generatePack && packExtension != null) { + usage("Can not specify pack extension if not using packs (-nopack)"); + } + + File[] files = new File[args.length - count]; + for (int i = 0; i < files.length; i++) { + files[i] = new File(args[i + count]); + if (!files[i].exists()) { + usage("File not found: " + files[i]); + } + } + + Compiler compiler = new Compiler(includePath, generatePack); + classPath += File.pathSeparator + System.getProperty("java.class.path"); + try { + Map classes = + compiler.compile(files, new PrintWriter(System.err), ".", classPath); + if (classes != null) { + // write .class files. + for (Map.Entry c : classes.entrySet()) { + String name = c.getKey().replace(".", File.separator); + int index = name.lastIndexOf(File.separatorChar); + String dir = outputDir + File.separator; + if (index != -1) { + dir += name.substring(0, index); + } + new File(dir).mkdirs(); + String file; + if (index != -1) { + file = name.substring(index + 1); + } else { + file = name; + } + file += "." + (packExtension != null ? packExtension : "class"); + File out = new File(dir, file); + try (FileOutputStream fos = new FileOutputStream(out)) { + fos.write(c.getValue()); + } + } + } else { + // fail + System.exit(1); + } + } catch (Throwable t) { + t.printStackTrace(); + // fail + System.exit(1); + } + } + + public Map compile( + String fileName, String source, Writer err, String sourcePath, String classPath) { + // create a new memory JavaFileManager + MemoryJavaFileManager manager = new MemoryJavaFileManager(stdManager, includeDirs); + + // prepare the compilation unit + List compUnits = new ArrayList<>(1); + compUnits.add(MemoryJavaFileManager.makeStringSource(fileName, source, includeDirs)); + return compile(manager, compUnits, err, sourcePath, classPath); + } + + public Map compile(File file, Writer err, String sourcePath, String classPath) { + File[] files = new File[1]; + files[0] = file; + return compile(files, err, sourcePath, classPath); + } + + public Map compile( + File[] files, Writer err, String sourcePath, String classPath) { + Iterable compUnits = stdManager.getJavaFileObjects(files); + List preprocessedCompUnits = new ArrayList<>(); + try { + for (JavaFileObject jfo : compUnits) { + preprocessedCompUnits.add(MemoryJavaFileManager.preprocessedFileObject(jfo, includeDirs)); + } + } catch (IOException ioExp) { + throw new RuntimeException(ioExp); + } + return compile(preprocessedCompUnits, err, sourcePath, classPath); + } + + public Map compile( + Iterable compUnits, + Writer err, + String sourcePath, + String classPath) { + // create a new memory JavaFileManager + MemoryJavaFileManager manager = new MemoryJavaFileManager(stdManager, includeDirs); + return compilerHelper.compile(manager, compUnits, err, sourcePath, classPath); + } + + private Map compile( + MemoryJavaFileManager manager, + Iterable compUnits, + Writer err, + String sourcePath, + String classPath) { + // to collect errors, warnings etc. + + // javac options + + // create a compilation task + + // we add BTrace Verifier as a (JSR 269) Processor + + // print dignostics messages in case of failures. + + // collect .class bytes of all compiled classes + return compilerHelper.compile(manager, compUnits, err, sourcePath, classPath); + } +} diff --git a/btrace-compiler/src/main/java/org/openjdk/btrace/compiler/CompilerClassWriter.java b/btrace-compiler/src/main/java/org/openjdk/btrace/compiler/CompilerClassWriter.java new file mode 100644 index 000000000..c5ca746ab --- /dev/null +++ b/btrace-compiler/src/main/java/org/openjdk/btrace/compiler/CompilerClassWriter.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.btrace.compiler; + +import java.io.File; +import java.io.PrintWriter; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.List; +import org.objectweb.asm.ClassWriter; + +/** @author Jaroslav Bachorik */ +class CompilerClassWriter extends ClassWriter { + private final URLClassLoader cl; + + public CompilerClassWriter(String classPath, PrintWriter perr) { + super(ClassWriter.COMPUTE_FRAMES); + List urls = new ArrayList<>(); + if (classPath != null) { + for (String e : classPath.split(File.pathSeparator)) { + File f = new File(e); + try { + urls.add(f.toURI().toURL()); + } catch (MalformedURLException ex) { + perr.printf("%s is not a valid classpath entry\n", e); + } + } + } + cl = new URLClassLoader(urls.toArray(new URL[0]), getClass().getClassLoader()); + } + + @Override + protected String getCommonSuperClass(String type1, String type2) { + Class c, d; + try { + c = cl.loadClass(type1.replace('/', '.')); + d = cl.loadClass(type2.replace('/', '.')); + } catch (Exception e) { + throw new RuntimeException(e.toString()); + } + if (c.isAssignableFrom(d)) { + return type1; + } + if (d.isAssignableFrom(c)) { + return type2; + } + if (c.isInterface() || d.isInterface()) { + return "java/lang/Object"; + } else { + do { + c = c.getSuperclass(); + } while (!c.isAssignableFrom(d)); + return c.getName().replace('.', '/'); + } + } +} diff --git a/btrace-compiler/src/main/java/org/openjdk/btrace/compiler/CompilerHelper.java b/btrace-compiler/src/main/java/org/openjdk/btrace/compiler/CompilerHelper.java new file mode 100644 index 000000000..c191db7a6 --- /dev/null +++ b/btrace-compiler/src/main/java/org/openjdk/btrace/compiler/CompilerHelper.java @@ -0,0 +1,177 @@ +package org.openjdk.btrace.compiler; + +import com.sun.source.util.JavacTask; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.Writer; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; +import javax.annotation.processing.Processor; +import javax.tools.Diagnostic; +import javax.tools.Diagnostic.Kind; +import javax.tools.DiagnosticCollector; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.openjdk.btrace.core.SharedSettings; + +class CompilerHelper { + private final boolean generatePack; + + // JSR 199 compiler + private final JavaCompiler compiler; + + CompilerHelper(JavaCompiler compiler, boolean generatePack) { + this.compiler = compiler; + this.generatePack = generatePack; + } + + Map compile( + MemoryJavaFileManager manager, + Iterable compUnits, + Writer err, + String sourcePath, + String classPath) { + // to collect errors, warnings etc. + DiagnosticCollector diagnostics = new DiagnosticCollector<>(); + + // javac options + List options = new ArrayList<>(); + options.add("-Xlint:all"); + options.add("-g:lines"); + options.add("-deprecation"); + options.add("-source"); + options.add("8"); + options.add("-target"); + options.add("8"); + if (sourcePath != null) { + options.add("-sourcepath"); + options.add(sourcePath); + } + + if (classPath != null) { + options.add("-classpath"); + options.add(classPath); + } + + // create a compilation task + JavacTask task = + (JavacTask) compiler.getTask(err, manager, diagnostics, options, null, compUnits); + Verifier btraceVerifier = new Verifier(); + task.setTaskListener(btraceVerifier); + + // we add BTrace Verifier as a (JSR 269) Processor + List processors = new ArrayList<>(1); + processors.add(btraceVerifier); + task.setProcessors(processors); + + PrintWriter perr = (err instanceof PrintWriter) ? (PrintWriter) err : new PrintWriter(err); + + // print dignostics messages in case of failures. + if (!task.call() || containsErrors(diagnostics)) { + for (Diagnostic diagnostic : diagnostics.getDiagnostics()) { + printDiagnostic(diagnostic, perr); + } + perr.flush(); + return null; + } + + // collect .class bytes of all compiled classes + Map result = new HashMap<>(); + try { + Map classBytes = manager.getClassBytes(); + List classNames = btraceVerifier.getClassNames(); + for (String name : classNames) { + if (classBytes.containsKey(name)) { + dump(name + "_before", classBytes.get(name)); + ClassReader cr = new ClassReader(classBytes.get(name)); + ClassWriter cw = new CompilerClassWriter(classPath, perr); + cr.accept(new Postprocessor(cw), ClassReader.EXPAND_FRAMES + ClassReader.SKIP_DEBUG); + byte[] classData = cw.toByteArray(); + dump(name + "_after", classData); + if (generatePack) { + // temp hack; need turn off verifier + SharedSettings.GLOBAL.setTrusted(true); + + String[] pathElements = classPath.split(File.pathSeparator); + List urlElements = new ArrayList<>(pathElements.length); + + for (String pathElement : pathElements) { + File f = new File(pathElement); + urlElements.add(f.toURI().toURL()); + } + URLClassLoader generatorCL = + new URLClassLoader( + urlElements.toArray(new URL[0]), Compiler.class.getClassLoader()); + ServiceLoader generators = + ServiceLoader.load(PackGenerator.class, generatorCL); + Iterator iter = generators.iterator(); + if (iter.hasNext()) { + PackGenerator generator = iter.next(); + SharedSettings.GLOBAL.setBootClassPath(classPath); + classData = generator.generateProbePack(classData); + } + } + result.put(name, classData); + } + } + } catch (IOException e) { + e.printStackTrace(perr); + } finally { + try { + manager.close(); + } catch (IOException ignored) { + } + } + return result; + } + + private void dump(String name, byte[] code) { + OutputStream os = null; + try { + name = name.replace(".", "_") + ".class"; + File f = new File(System.getProperty("java.io.tmpdir"), name); + if (!f.exists()) { + f.getParentFile().createNewFile(); + } + os = new FileOutputStream(f); + os.write(code); + } catch (IOException ignored) { + + } finally { + if (os != null) { + try { + os.close(); + } catch (IOException ignored) { + } + } + } + } + + private void printDiagnostic(Diagnostic diagnostic, PrintWriter perr) { + perr.println(diagnostic); + } + + /** + * Checks if the list of diagnostic messages contains at least one error. Certain {@link + * JavacTask} implementations may return success error code even though errors were reported. + */ + private boolean containsErrors(DiagnosticCollector diagnostics) { + for (Diagnostic diagnostic : diagnostics.getDiagnostics()) { + if (diagnostic.getKind() == Kind.ERROR) { + return true; + } + } + return false; + } +} diff --git a/btrace-compiler/src/main/java/org/openjdk/btrace/compiler/ConcatenatingReader.java b/btrace-compiler/src/main/java/org/openjdk/btrace/compiler/ConcatenatingReader.java new file mode 100644 index 000000000..3e106ab67 --- /dev/null +++ b/btrace-compiler/src/main/java/org/openjdk/btrace/compiler/ConcatenatingReader.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2008 Sun Microsystems, Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. ALL + * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, + * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN + * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR + * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR + * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR + * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR + * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE + * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, + * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF + * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for use + * in the design, construction, operation or maintenance of any nuclear + * facility. + * + * Sun gratefully acknowledges that this software was originally authored + * and developed by Kenneth Bradley Russell and Christopher John Kline. + */ +package org.openjdk.btrace.compiler; + +import java.io.BufferedReader; +import java.io.FilterReader; +import java.io.IOException; + +/** + * This code is based on PCPP code from the GlueGen project. + * + *

A Reader implementation which finds lines ending in the backslash character ('\') and + * concatenates them with the next line. + * + * @author Kenneth B. Russell (original author) + * @author A. Sundararajan (changes documented below) + *

Changes: + *

* Changed the package name. * Formatted with NetBeans. + */ +public class ConcatenatingReader extends FilterReader { + private static final String NEW_LINE = System.getProperty("line.separator"); + private final BufferedReader inReader; + // Any leftover characters go here + private char[] curBuf; + private int curPos; + + /** + * This class requires that the input reader be a BufferedReader so it can do line-oriented + * operations. + */ + public ConcatenatingReader(BufferedReader in) { + super(in); + inReader = in; + } + + @Override + public int read() throws IOException { + char[] tmp = new char[1]; + int num = read(tmp, 0, 1); + if (num < 0) { + return -1; + } + return tmp[0]; + } + + // It's easier not to support mark/reset since we don't need it + @Override + public boolean markSupported() { + return false; + } + + @Override + public void mark(int readAheadLimit) throws IOException { + throw new IOException("mark/reset not supported"); + } + + @Override + public void reset() throws IOException { + throw new IOException("mark/reset not supported"); + } + + @Override + public boolean ready() throws IOException { + return curBuf != null || inReader.ready(); + } + + @Override + public int read(char[] cbuf, int off, int len) throws IOException { + if (curBuf == null) { + nextLine(); + } + + if (curBuf == null) { + return -1; + } + + int numRead = 0; + + while ((len > 0) && (curBuf != null) && (curPos < curBuf.length)) { + cbuf[off] = curBuf[curPos]; + ++curPos; + ++off; + --len; + ++numRead; + if (curPos == curBuf.length) { + nextLine(); + } + } + + return numRead; + } + + @Override + public long skip(long n) throws IOException { + long numSkipped = 0; + + while (n > 0) { + int intN = (int) n; + char[] tmp = new char[intN]; + int numRead = read(tmp, 0, intN); + n -= numRead; + numSkipped += numRead; + if (numRead < intN) { + break; + } + } + return numSkipped; + } + + private void nextLine() throws IOException { + String cur = inReader.readLine(); + if (cur == null) { + curBuf = null; + return; + } + // The trailing newline was trimmed by the readLine() method. See + // whether we have to put it back or not, depending on whether the + // last character of the line is the concatenation character. + int numChars = cur.length(); + boolean needNewline = true; + if ((numChars > 0) && (cur.charAt(cur.length() - 1) == '\\')) { + --numChars; + needNewline = false; + } + char[] buf = new char[numChars + (needNewline ? NEW_LINE.length() : 0)]; + cur.getChars(0, numChars, buf, 0); + if (needNewline) { + NEW_LINE.getChars(0, NEW_LINE.length(), buf, numChars); + } + curBuf = buf; + curPos = 0; + } + + // Test harness + /* + public static void main(String[] args) throws IOException { + if (args.length != 1) { + System.out.println("Usage: java ConcatenatingReader [file name]"); + System.exit(1); + } + ConcatenatingReader reader = new ConcatenatingReader(new BufferedReader(new FileReader(args[0]))); + OutputStreamWriter writer = new OutputStreamWriter(System.out); + char[] buf = new char[8192]; + boolean done = false; + while (!done && reader.ready()) { + int numRead = reader.read(buf, 0, buf.length); + writer.write(buf, 0, numRead); + if (numRead < buf.length) + done = true; + } + writer.flush(); + } + */ +} diff --git a/btrace-compiler/src/main/java/org/openjdk/btrace/compiler/MemoryJavaFileManager.java b/btrace-compiler/src/main/java/org/openjdk/btrace/compiler/MemoryJavaFileManager.java new file mode 100644 index 000000000..a00b8342d --- /dev/null +++ b/btrace-compiler/src/main/java/org/openjdk/btrace/compiler/MemoryJavaFileManager.java @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * MemoryJavaFileManager.java + * @author A. Sundararajan + */ +package org.openjdk.btrace.compiler; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.net.URI; +import java.nio.CharBuffer; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.tools.FileObject; +import javax.tools.ForwardingJavaFileManager; +import javax.tools.JavaFileManager; +import javax.tools.JavaFileObject; +import javax.tools.JavaFileObject.Kind; +import javax.tools.SimpleJavaFileObject; + +/** + * JavaFileManager that keeps compiled .class bytes in memory. And also can expose input .java + * "files" from Strings. + * + * @author A. Sundararajan + */ +@SuppressWarnings("RedundantThrows") +public final class MemoryJavaFileManager extends ForwardingJavaFileManager { + + private final List includeDirs; + private Map classBytes; + + public MemoryJavaFileManager(JavaFileManager fileManager, List includeDirs) { + super(fileManager); + this.includeDirs = includeDirs; + classBytes = new HashMap<>(); + } + + static JavaFileObject preprocessedFileObject(JavaFileObject fo, List includeDirs) + throws IOException { + if (includeDirs != null) { + StringWriter out = new StringWriter(); + PCPP pcpp = new PCPP(includeDirs, out); + BufferedReader reader = + new BufferedReader(new InputStreamReader(fo.openInputStream(), StandardCharsets.UTF_8)); + pcpp.run(reader, fo.getName()); + return new StringInputBuffer(fo.getName(), out.toString()); + } else { + return fo; + } + } + + static JavaFileObject makeStringSource(String name, String code, List includeDirs) { + if (includeDirs != null) { + StringWriter out = new StringWriter(); + PCPP pcpp = new PCPP(includeDirs, out); + try { + pcpp.run(new StringReader(code), name); + } catch (IOException exp) { + throw new RuntimeException(exp); + } + return new StringInputBuffer(name, out.toString()); + } else { + return new StringInputBuffer(name, code); + } + } + + static URI toURI(String name) { + File file = new File(name); + if (file.exists()) { + return file.toURI(); + } else { + try { + return URI.create("mfm:///" + name); + } catch (Exception exp) { + return URI.create("mfm:///org/openjdk/btrace/script/java/java_source"); + } + } + } + + public Map getClassBytes() { + return classBytes; + } + + @Override + public void close() throws IOException { + classBytes = new HashMap<>(); + } + + @Override + public void flush() throws IOException {} + + @Override + public JavaFileObject getJavaFileForOutput( + JavaFileManager.Location location, String className, Kind kind, FileObject sibling) + throws IOException { + if (kind == Kind.CLASS) { + return new ClassOutputBuffer(className); + } else { + return super.getJavaFileForOutput(location, className, kind, sibling); + } + } + + @Override + public JavaFileObject getJavaFileForInput( + JavaFileManager.Location location, String className, Kind kind) throws IOException { + JavaFileObject result = super.getJavaFileForInput(location, className, kind); + if (kind == Kind.SOURCE) { + return preprocessedFileObject(result, includeDirs); + } else { + return result; + } + } + + /** A file object used to represent Java source coming from a string. */ + private static class StringInputBuffer extends SimpleJavaFileObject { + + final String code; + + StringInputBuffer(String name, String code) { + super(toURI(name), Kind.SOURCE); + this.code = code; + } + + @Override + public CharBuffer getCharContent(boolean ignoreEncodingErrors) { + return CharBuffer.wrap(code); + } + + public Reader openReader() { + return new StringReader(code); + } + } + + /** A file object that stores Java bytecode into the classBytes map. */ + private class ClassOutputBuffer extends SimpleJavaFileObject { + + private final String name; + + ClassOutputBuffer(String name) { + super(toURI(name), Kind.CLASS); + this.name = name; + } + + @Override + public OutputStream openOutputStream() { + return new FilterOutputStream(new ByteArrayOutputStream()) { + + @Override + public void close() throws IOException { + out.close(); + ByteArrayOutputStream bos = (ByteArrayOutputStream) out; + classBytes.put(name, bos.toByteArray()); + } + }; + } + } +} diff --git a/btrace-compiler/src/main/java/org/openjdk/btrace/compiler/PCPP.java b/btrace-compiler/src/main/java/org/openjdk/btrace/compiler/PCPP.java new file mode 100644 index 000000000..3097df475 --- /dev/null +++ b/btrace-compiler/src/main/java/org/openjdk/btrace/compiler/PCPP.java @@ -0,0 +1,859 @@ +/* + * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.btrace.compiler; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StreamTokenizer; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * A minimal pseudo-C-preprocessor derived from PCPP of the GlueGen project. + * + * @author Kenneth B. Russell (original author) + * @author A. Sundararajan (changes documented below) + *

Changes: + *

* Changed the package name. * Formatted with NetBeans. * Removed preservation of #define + * directives. commented #defines emission. * Commented out printing of line directives. * Print + * space char in output only for word tokens. This way multicharacter operators such as ==, != + * etc. are property printed. + */ +public class PCPP { + + private static final boolean disableDebugPrint = true; + private final Printer printer; + /** + * Map containing the results of #define statements. We must evaluate certain very simple + * definitions. Macros and multi-f defines (which typically contain either macro definitions or + * expressions) are currently not handled. + */ + private final Map defineMap = new HashMap<>(); + + private final Set nonConstantDefines = new HashSet<>(); + /** List containing the #include paths as Strings */ + private final List includePaths; + + private ParseState state; + + public PCPP(List includePaths) { + this.includePaths = includePaths; + printer = new Printer(); + } + + public PCPP(List includePaths, Writer out) { + this.includePaths = includePaths; + printer = new Printer(out); + } + + @SuppressWarnings("DefaultCharset") + public static void main(String[] args) { + try { + Reader reader = null; + String filename = null; + + if (args.length == 0) { + usage(); + } + + List includePaths = new ArrayList<>(); + for (int i = 0; i < args.length; i++) { + if (i < args.length - 1) { + String arg = args[i]; + if (arg.startsWith("-I")) { + String[] paths = arg.substring(2).split(System.getProperty("path.separator")); + includePaths.addAll(Arrays.asList(paths)); + } else { + usage(); + } + } else { + String arg = args[i]; + if (arg.equals("-")) { + reader = new InputStreamReader(System.in); + filename = "standard input"; + } else { + if (arg.startsWith("-")) { + usage(); + } + filename = arg; + reader = new BufferedReader(new FileReader(filename)); + } + } + } + + new PCPP(includePaths).run(reader, filename); + } catch (IOException e) { + e.printStackTrace(); + } + } + + // ---------------------------------------------------------------------- + // Internals only below this point + // + private static void usage() { + System.out.println("Usage: java PCPP [filename | -]"); + System.out.println("Minimal pseudo-C-preprocessor."); + System.out.println("Output goes to standard output. Standard input can be used as input"); + System.out.println("by passing '-' as the argument."); + System.exit(1); + } + + public void run(Reader reader, String filename) throws IOException { + StreamTokenizer tok = null; + BufferedReader bufReader = null; + if (reader instanceof BufferedReader) { + bufReader = (BufferedReader) reader; + } else { + bufReader = new BufferedReader(reader); + } + tok = new StreamTokenizer(new ConcatenatingReader(bufReader)); + tok.resetSyntax(); + tok.wordChars('a', 'z'); + tok.wordChars('A', 'Z'); + tok.wordChars('0', '9'); + tok.wordChars('_', '_'); + tok.wordChars('.', '.'); + tok.wordChars(128 + 32, 255); + tok.whitespaceChars(0, ' '); + tok.quoteChar('"'); + tok.quoteChar('\''); + tok.eolIsSignificant(true); + tok.slashSlashComments(true); + tok.slashStarComments(true); + ParseState curState = new ParseState(tok, filename); + ParseState oldState = state; + state = curState; + lineDirective(); + parse(); + state = oldState; + if (state != null) { + lineDirective(); + } + } + + public String findFile(String filename) { + String sep = File.separator; + for (String inclPath : includePaths) { + String fullPath = inclPath + sep + filename; + File file = new File(fullPath); + if (file.exists()) { + return fullPath; + } + } + return null; + } + + // Accessors + private void pushBackToken() { + state.tok().pushBack(); + } + + /** Equivalent to nextToken(false) */ + private int nextToken() throws IOException { + return nextToken(false); + } + + private int nextToken(boolean returnEOLs) throws IOException { + int lineno = lineNumber(); + // Check to see whether the previous call to nextToken() left an + // EOL on the stream + if (curToken() == StreamTokenizer.TT_EOL) { + state.setStartOfLine(true); + } else if (!state.startOfFile()) { + state.setStartOfLine(false); + } + state.setStartOfFile(false); + int val = state.tok().nextToken(); + if (!returnEOLs) { + if (val == StreamTokenizer.TT_EOL) { + do { + // Consume and return next token, setting state appropriately + val = state.tok().nextToken(); + state.setStartOfLine(true); + printer.println(); + } while (val == StreamTokenizer.TT_EOL); + } + } + if (lineNumber() > lineno + 1) { + // This is a little noisier than it needs to be, but does handle + // the case of multi-line comments properly + lineDirective(); + } + return val; + } + + /** Reads the next token and throws an IOException if it is not the specified token character. */ + private void nextRequiredToken(int requiredToken) throws IOException { + int nextTok = nextToken(); + if (nextTok != requiredToken) { + String msg = "Expected token '" + requiredToken + "' but got "; + switch (nextTok) { + case StreamTokenizer.TT_EOF: + msg += ""; + break; + case StreamTokenizer.TT_EOL: + msg += ""; + break; + default: + msg += "'" + curTokenAsString() + "'"; + break; + } + msg += " at file " + filename() + ", line " + lineNumber(); + throw new IOException(msg); + } + } + + private int curToken() { + return state.tok().ttype; + } + + private String curTokenAsString() { + int t = curToken(); + if (t == StreamTokenizer.TT_WORD) { + return curWord(); + } + if (t == StreamTokenizer.TT_EOL) { + throw new RuntimeException("Should not be converting EOL characters to strings"); + } + char c = (char) t; + if (c == '"' || c == '\'') { + return c + state.tok().sval + c; + } + return String.valueOf(c); + } + + private String nextWord() throws IOException { + int val = nextToken(); + if (val != StreamTokenizer.TT_WORD) { + throw new RuntimeException("Expected word at file " + filename() + ", line " + lineNumber()); + } + return curWord(); + } + + private String curWord() { + return state.tok().sval; + } + + private boolean startOfLine() { + return state.startOfLine(); + } + + private String filename() { + return state.filename(); + } + + private int lineNumber() { + return state.lineNumber(); + } + + ///////////// + // Parsing // + ///////////// + private void parse() throws IOException { + int tok = 0; + while ((tok = nextToken()) != StreamTokenizer.TT_EOF) { + // A '#' at the beginning of a line is a preprocessor directive + if (startOfLine() && (tok == '#')) { + preprocessorDirective(); + } else { + // Output white space plus current token, handling #defines + // (though not properly -- only handling #defines to constants and the empty string) + + // !!HACK!! - print space only for word tokens. This way multicharacter + // operators such as ==, != etc. are property printed. + if (tok == StreamTokenizer.TT_WORD) { + printer.print(" "); + } + String s = curTokenAsString(); + String newS = defineMap.get(s); + if (newS == null) { + newS = s; + } + printer.print(newS); + } + } + printer.flush(); + } + + private void preprocessorDirective() throws IOException { + String w = nextWord(); + boolean shouldPrint = true; + switch (w) { + case "define": + handleDefine(); + shouldPrint = false; + break; + case "undef": + handleUndefine(); + shouldPrint = false; + break; + case "if": + case "elif": + handleIf(w.equals("if")); + shouldPrint = false; + break; + case "ifdef": + case "ifndef": + handleIfdef(w.equals("ifdef")); + shouldPrint = false; + break; + case "else": + handleElse(); + shouldPrint = false; + break; + case "endif": + handleEndif(); + shouldPrint = false; + break; + case "include": + handleInclude(); + shouldPrint = false; + break; + // Unknown preprocessor directive (#pragma?) -- ignore + default: + break; + } + if (shouldPrint) { + printer.print("# "); + printToken(); + } + } + + //////////////////////////////////// + // Handling of #define directives // + //////////////////////////////////// + private void handleUndefine() throws IOException { + // Next token is the name of the #undef + String name = nextWord(); + + debugPrint(true, "#undef " + name); + + // there shouldn't be any extra symbols after the name, but just in case... + List values = new ArrayList<>(); + while (nextToken(true) != StreamTokenizer.TT_EOL) { + values.add(curTokenAsString()); + } + + if (printer.enabled()) { + String oldDef = defineMap.remove(name); + if (oldDef == null) { + System.err.println( + "WARNING: ignoring redundant \"#undef " + + name + + "\", at \"" + + filename() + + "\" line " + + lineNumber() + + ": \"" + + name + + "\" was not previously defined"); + } else { + // System.err.println("UNDEFINED: '" + name + "' (line " + lineNumber() + " file " + + // filename() + ")"); + } + nonConstantDefines.remove(name); + } else { + System.err.println( + "FAILED TO UNDEFINE: '" + + name + + "' (line " + + lineNumber() + + " file " + + filename() + + ")"); + } + } + + private void handleDefine() throws IOException { + // Next token is the name of the #define + String name = nextWord(); + // System.err.println("IN HANDLE_DEFINE: '" + name + "' (line " + lineNumber() + " file " + + // filename() + ")"); + // (Note that this is not actually proper handling for multi-line #defines) + List values = new ArrayList<>(); + while (nextToken(true) != StreamTokenizer.TT_EOL) { + values.add(curTokenAsString()); + } + // if we're not within an active block of code (like inside an "#ifdef + // FOO" where FOO isn't defined), then don't actually alter the definition + // map. + debugPrint(true, "#define " + name); + if (printer.enabled()) { + boolean emitDefine = true; + + // Handle #definitions to nothing or to a constant value + int sz = values.size(); + if (sz == 0) { + // definition to nothing, like "#define FOO" + String oldDef = defineMap.put(name, ""); + if (oldDef != null) { + System.err.println("WARNING: \"" + name + "\" redefined from \"" + oldDef + "\" to \"\""); + } + // We don't want to emit the define, because it would serve no purpose + // and cause GlueGen errors (confuse the GnuCParser) + emitDefine = false; + // System.out.println("//---DEFINED: " + name + "to \"\""); + } else if (sz == 1) { + // See whether the value is a constant + String value = values.get(0); + if (isConstant(value)) { + // Value is numeric constant like "#define FOO 5". + // Put it in the #define map + String oldDef = defineMap.put(name, value); + if (oldDef != null) { + System.err.println( + "WARNING: \"" + name + "\" redefined from \"" + oldDef + "\" to \"" + value + "\""); + } + // System.out.println("//---DEFINED: " + name + " to \"" + value + "\""); + } else { + // Value is a symbolic constant like "#define FOO BAR". + // Try to look up the symbol's value + String newValue = resolveDefine(value, true); + if (newValue != null) { + // Set the value to the value of the symbol. + // + // TO DO: Is this correct? Why not output the symbol unchanged? + // I think that it's a good thing to see that some symbols are + // defined in terms of others. -chris + values.set(0, newValue); + } else { + // Still perform textual replacement + defineMap.put(name, value); + nonConstantDefines.add(name); + emitDefine = false; + } + } + } else { + // Non-constant define; try to do reasonable textual substitution anyway + // (FIXME: should identify some of these, like (-1), as constants) + emitDefine = false; + StringBuilder val = new StringBuilder(); + for (int i = 0; i < sz; i++) { + if (i != 0) { + val.append(" "); + } + val.append(resolveDefine(values.get(i), false)); + } + if (defineMap.get(name) != null) { + // This is probably something the user should investigate. + throw new RuntimeException( + "Cannot redefine symbol \"" + + name + + " from \"" + + defineMap.get(name) + + "\" to non-constant " + + " definition \"" + + val + + "\""); + } + defineMap.put(name, val.toString()); + nonConstantDefines.add(name); + } + + if (emitDefine) { + // commenting out the #define in output + printer.print("// "); + // Print name and value + printer.print("# define "); + printer.print(name); + for (String line : values) { + printer.print(" "); + printer.print(line); + } + printer.println(); + } + } // end if (enabled()) + + // System.err.println("OUT HANDLE_DEFINE: " + name); + } + + private boolean isConstant(String s) { + if (s.startsWith("0x") || s.startsWith("0X")) { + return checkHex(s); + } else { + return checkDecimal(s); + } + } + + private boolean checkHex(String s) { + for (int i = 2; i < s.length(); i++) { + char c = s.charAt(i); + if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) { + return false; + } + } + return true; + } + + private boolean checkDecimal(String s) { + try { + Float.valueOf(s); + } catch (NumberFormatException e) { + // not parsable as a number + return false; + } + return true; + } + + private String resolveDefine(String word, boolean returnNullIfNotFound) { + String lastWord = defineMap.get(word); + if (lastWord == null) { + if (returnNullIfNotFound) { + return null; + } + return word; + } + String nextWord = null; + do { + nextWord = defineMap.get(lastWord); + if (nextWord != null) { + lastWord = nextWord; + } + } while (nextWord != null); + return lastWord; + } + + /** @param isIfdef if true, we're processing #ifdef; if false, we're processing #ifndef. */ + private void handleIfdef(boolean isIfdef) throws IOException { + // Next token is the name of the #ifdef + String symbolName = nextWord(); + debugPrint(true, (isIfdef ? "#ifdef " : "#ifndef ") + symbolName); + boolean symbolIsDefined = defineMap.get(symbolName) != null; + // debugPrint(true, "HANDLE_IFDEF: ifdef(" + symbolName + ") = " + symbolIsDefined ); + printer.pushEnableBit(printer.enabled() && symbolIsDefined == isIfdef); + } + + //////////////////////////////////////////////// + // Handling of #if/#ifdef/ifndef/endif directives // + //////////////////////////////////////////////// + + /** Handles #else directives */ + private void handleElse() { + boolean enabledStatusBeforeElse = printer.enabled(); + printer.popEnableBit(); + printer.pushEnableBit(printer.enabled() && !enabledStatusBeforeElse); + debugPrint(true, "#else "); + } + + private void handleEndif() { + boolean enabledBeforePopping = printer.enabled(); + printer.popEnableBit(); + + // print the endif if we were enabled prior to popEnableBit() (sending + // false to debugPrint means "print regardless of current enabled() state). + debugPrint(!enabledBeforePopping, "#endif/end-else"); + } + + /** @param isIf if true, we're processing #if; if false, we're processing #elif. */ + private void handleIf(boolean isIf) throws IOException { + // System.out.println("IN HANDLE_" + (isIf ? "IF" : "ELIF") + " file \"" + filename() + " line " + // + lineNumber()); + debugPrint(true, (isIf ? "#if" : "#elif")); + boolean defineEvaluatedToTrue = handleIfRecursive(true); + if (!isIf) { + printer.popEnableBit(); + } + printer.pushEnableBit(printer.enabled() && defineEvaluatedToTrue == isIf); + // System.out.println("OUT HANDLE_" + (isIf ? "IF" : "ELIF") +" (evaluated to " + + // defineEvaluatedToTrue + ")"); + } + + /** + * This method is called recursively to process nested sub-expressions such as: + * + *

+   *   #if !defined(OPENSTEP) && !(defined(NeXT) || !defined(NeXT_PDO))
+   * 
+ * + * @param greedy if true, continue evaluating sub-expressions until EOL is reached. If false, + * return as soon as the first sub-expression is processed. + * @return the value of the sub-expression or (if greedy==true) series of sub-expressions. + */ + private boolean handleIfRecursive(boolean greedy) throws IOException { + // System.out.println("IN HANDLE_IF_RECURSIVE (" + ++tmp + ", greedy = " + greedy + ")"); + // System.out.flush(); + + // ifValue keeps track of the current value of the potentially nested + // "defined()" expressions as we process them. + boolean ifValue = true; + int openParens = 0; + int tok; + do { + tok = nextToken(true); + // System.out.println("-- READ: [" + (tok == StreamTokenizer.TT_EOL ? "" + // :curTokenAsString()) + "]"); + switch (tok) { + case '(': + ++openParens; + // System.out.println("OPEN PARENS = " + openParens); + ifValue = ifValue && handleIfRecursive(true); + break; + case ')': + --openParens; + // System.out.println("OPEN PARENS = " + openParens); + break; + case '!': + { + // System.out.println("HANDLE_IF_RECURSIVE HANDLING !"); + boolean rhs = handleIfRecursive(false); + ifValue = !rhs; + // System.out.println("HANDLE_IF_RECURSIVE HANDLED OUT !, RHS = " + rhs); + } + break; + case '&': + { + nextRequiredToken('&'); + // System.out.println("HANDLE_IF_RECURSIVE HANDLING &&, LHS = " + ifValue); + boolean rhs = handleIfRecursive(true); + // System.out.println("HANDLE_IF_RECURSIVE HANDLED &&, RHS = " + rhs); + ifValue = ifValue && rhs; + } + break; + case '|': + { + nextRequiredToken('|'); + // System.out.println("HANDLE_IF_RECURSIVE HANDLING ||, LHS = " + ifValue); + boolean rhs = handleIfRecursive(true); + // System.out.println("HANDLE_IF_RECURSIVE HANDLED ||, RHS = " + rhs); + ifValue = ifValue || rhs; + } + break; + case '>': + case '<': + case '=': + { + // NOTE: we don't handle expressions like this properly + boolean rhs = handleIfRecursive(true); + ifValue = false; + } + break; + case StreamTokenizer.TT_WORD: + { + String word = curTokenAsString(); + if (word.equals("defined")) { + // Handle things like #if defined(SOMESYMBOL) + nextRequiredToken('('); + String symbol = nextWord(); + boolean isDefined = defineMap.get(symbol) != null; + // System.out.println("HANDLE_IF_RECURSIVE HANDLING defined(" + symbol + ") = " + + // isDefined); + ifValue = ifValue && isDefined; + nextRequiredToken(')'); + } else { + // Handle things like #if SOME_SYMBOL. + String symbolValue = defineMap.get(word); + + // See if the statement is "true"; i.e., a non-zero expression + if (symbolValue != null) { + // The statement is true if the symbol is defined and is a constant expression + return (!nonConstantDefines.contains(word)); + } else { + // The statement is true if the symbol evaluates to a non-zero value + // + // NOTE: This doesn't yet handle evaluable expressions like "#if + // SOME_SYMBOL > 5" or "#if SOME_SYMBOL == 0", both of which are + // valid syntax. It only handles numeric symbols like "#if 1" + + try { + // see if it's in decimal form + return Double.parseDouble(word) != 0; + } catch (NumberFormatException nfe1) { + try { + // ok, it's not a valid decimal value, try hex/octal value + return Long.parseLong(word) != 0; + } catch (NumberFormatException nfe2) { + try { + // ok, it's not a valid hex/octal value, try boolean + return Boolean.parseBoolean(word); + } catch (NumberFormatException nfe3) { + // give up; the symbol isn't a numeric or boolean value + return false; + } + } + } + } + } + } // end case TT_WORD + break; + case StreamTokenizer.TT_EOL: + // System.out.println("HANDLE_IF_RECURSIVE HIT !"); + pushBackToken(); // so caller hits EOL as well if we're recursing + break; + case StreamTokenizer.TT_EOF: + throw new RuntimeException( + "Unexpected end of file while parsing " + + "#if statement at file " + + filename() + + ", line " + + lineNumber()); + + default: + throw new RuntimeException( + "Unexpected token (" + + curTokenAsString() + + ") while parsing " + + "#if statement at file " + + filename() + + ", line " + + lineNumber()); + } + // System.out.println("END OF WHILE: greedy = " + greedy + " parens = " +openParens + " not + // EOL = " + (tok != StreamTokenizer.TT_EOL) + " --> " + ((greedy && openParens >= 0) && tok + // != StreamTokenizer.TT_EOL)); + } while ((greedy && openParens >= 0) && tok != StreamTokenizer.TT_EOL); + // System.out.println("OUT HANDLE_IF_RECURSIVE (" + tmp-- + ", returning " + ifValue + ")"); + // System.out.flush(); + return ifValue; + } + + // static int tmp = -1; + + ///////////////////////////////////// + // Handling of #include directives // + ///////////////////////////////////// + @SuppressWarnings("DefaultCharset") + private void handleInclude() throws IOException { + // Two kinds of #includes: one with quoted string for argument, + // one with angle brackets surrounding argument + int t = nextToken(); + String filename = null; + if (t == '"') { + filename = curWord(); + } else if (t == '<') { + // Components of path name are coming in as separate tokens; + // concatenate them + StringBuilder buf = new StringBuilder(); + while ((t = nextToken()) != '>' && (t != StreamTokenizer.TT_EOF)) { + buf.append(curTokenAsString()); + } + if (t == StreamTokenizer.TT_EOF) { + System.err.println("WARNING: unexpected EOF while processing #include directive"); + } + filename = buf.toString(); + } + // if we're not within an active block of code (like inside an "#ifdef + // FOO" where FOO isn't defined), then don't actually process the + // #included file. + debugPrint(true, "#include [" + filename + "]"); + if (printer.enabled()) { + // Look up file in known #include path + String fullname = findFile(filename); + // System.out.println("ACTIVE BLOCK, LOADING " + filename); + if (fullname == null) { + System.err.println("WARNING: unable to find #include file \"" + filename + "\""); + return; + } + // Process this file in-line + Reader reader = new BufferedReader(new FileReader(fullname)); + run(reader, fullname); + } else { + // System.out.println("INACTIVE BLOCK, SKIPPING " + filename); + } + } + + private void debugPrint(boolean onlyPrintIfEnabled, String msg) { + if (disableDebugPrint) { + return; + } + + if (!onlyPrintIfEnabled || printer.enabled()) { + for (int i = Printer.debugPrintIndentLevel; --i > 0; ) { + System.out.print(" "); + } + System.out.println(msg + " (line " + lineNumber() + " file " + filename() + ")"); + } + } + + private void printToken() { + printer.print(curTokenAsString()); + } + + private void lineDirective() { + /* + * Originally this code was emitting line directives. We don't need those. + * + * print("# " + lineNumber() + " \"" + filename() + "\""); + * println(); + */ + } + + // State + static class ParseState { + + private final StreamTokenizer tok; + private final String filename; + // We do not generate #line directives + // private int lineNumber; + private boolean startOfLine; + private boolean startOfFile; + + ParseState(StreamTokenizer tok, String filename) { + this.tok = tok; + this.filename = filename; + // We do not generate #line directives + // lineNumber = 1; + startOfLine = true; + startOfFile = true; + } + + StreamTokenizer tok() { + return tok; + } + + String filename() { + return filename; + } + + int lineNumber() { + return tok.lineno(); + } + + boolean startOfLine() { + return startOfLine; + } + + void setStartOfLine(boolean val) { + startOfLine = val; + } + + boolean startOfFile() { + return startOfFile; + } + + void setStartOfFile(boolean val) { + startOfFile = val; + } + } +} diff --git a/btrace-compiler/src/main/java/org/openjdk/btrace/compiler/PackGenerator.java b/btrace-compiler/src/main/java/org/openjdk/btrace/compiler/PackGenerator.java new file mode 100644 index 000000000..d44800839 --- /dev/null +++ b/btrace-compiler/src/main/java/org/openjdk/btrace/compiler/PackGenerator.java @@ -0,0 +1,7 @@ +package org.openjdk.btrace.compiler; + +import java.io.IOException; + +public interface PackGenerator { + byte[] generateProbePack(byte[] data) throws IOException; +} diff --git a/btrace-compiler/src/main/java/org/openjdk/btrace/compiler/Postprocessor.java b/btrace-compiler/src/main/java/org/openjdk/btrace/compiler/Postprocessor.java new file mode 100644 index 000000000..a8979e03c --- /dev/null +++ b/btrace-compiler/src/main/java/org/openjdk/btrace/compiler/Postprocessor.java @@ -0,0 +1,778 @@ +/* + * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.compiler; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Deque; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +/** @author Jaroslav Bachorik */ +public class Postprocessor extends ClassVisitor { + private final List fields = new ArrayList<>(); + private boolean shortSyntax = false; + private String className = ""; + + public Postprocessor(ClassVisitor cv) { + super(Opcodes.ASM9, cv); + } + + @Override + public void visit( + int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + if (((access & Opcodes.ACC_PUBLIC) + | (access & Opcodes.ACC_PROTECTED) + | (access & Opcodes.ACC_PRIVATE)) + == 0) { + shortSyntax = + true; // specifying "class " rather than "public class " means using + // short syntax + access |= Opcodes.ACC_PUBLIC; // force the public modifier on the btrace class + } + className = name; + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String signature, String[] exceptions) { + if (!shortSyntax) return super.visitMethod(access, name, desc, signature, exceptions); + + if ((access & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PRIVATE)) == 0) { + access &= ~Opcodes.ACC_PROTECTED; + access |= Opcodes.ACC_PUBLIC; + } + int localVarOffset = ((access & Opcodes.ACC_STATIC) == 0) ? -1 : 0; + access |= Opcodes.ACC_STATIC; + + boolean isconstructor = false; + if ("".equals(name)) { + name = ""; + isconstructor = true; + } + + if (isconstructor) { + // the script class needs as dummy default constructor + createDefaultConstructor(); + } + + return new MethodConvertor( + localVarOffset, + isconstructor, + super.visitMethod(access, name, desc, signature, exceptions)); + } + + private void createDefaultConstructor() { + MethodVisitor mv = super.visitMethod(Opcodes.ACC_PUBLIC, "", "()V", null, null); + mv.visitCode(); + // load 'this' + mv.visitVarInsn(Opcodes.ALOAD, 0); + // invoke super constructor + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "", "()V", false); + mv.visitMaxs(1, 1); + mv.visitEnd(); + } + + @Override + public FieldVisitor visitField( + int access, String name, String desc, String signature, Object value) { + if (!shortSyntax) return super.visitField(access, name, desc, signature, value); + + List attrs = new ArrayList<>(); + return new FieldVisitor(Opcodes.ASM9) { + private final List annotations = new ArrayList<>(); + + @Override + public AnnotationVisitor visitAnnotation(String type, boolean visible) { + AnnotationDef ad = new AnnotationDef(type); + annotations.add(ad); + return new AnnotationVisitor(Opcodes.ASM9, super.visitAnnotation(type, visible)) { + @Override + public void visit(String name, Object val) { + ad.addValue(name, val); + super.visit(name, val); + } + }; + } + + @Override + public void visitAttribute(Attribute atrbt) { + super.visitAttribute(atrbt); + attrs.add(atrbt); + } + + @Override + public void visitEnd() { + FieldDescriptor fd = + new FieldDescriptor(access, name, desc, signature, value, attrs, annotations); + fields.add(fd); + super.visitEnd(); + } + }; + } + + @Override + public void visitEnd() { + if (shortSyntax) { + addFields(); + } + } + + private void addFields() { + for (FieldDescriptor fd : fields) { + String fieldName = fd.name; + int fieldAccess = fd.access; + String fieldDesc = fd.desc; + String fieldSignature = fd.signature; + Object fieldValue = fd.value; + + fieldAccess &= ~Opcodes.ACC_PRIVATE; + fieldAccess &= ~Opcodes.ACC_PROTECTED; + fieldAccess |= Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC; + + FieldVisitor fv = + super.visitField(fieldAccess, fieldName, fieldDesc, fieldSignature, fieldValue); + + for (AnnotationDef ad : fd.annotations) { + AnnotationVisitor av = fv.visitAnnotation(ad.getType(), true); + for (Map.Entry attr : ad.getValues().entrySet()) { + av.visit(attr.getKey(), attr.getValue()); + } + } + + for (Attribute attr : fd.attributes) { + fv.visitAttribute(attr); + } + fv.visitEnd(); + } + } + + private static final class AnnotationDef { + private final String type; + private final Map values = new HashMap<>(); + + public AnnotationDef(String type) { + this.type = type; + } + + public void addValue(String name, Object val) { + values.put(name, val); + } + + public String getType() { + return type; + } + + public Map getValues() { + return values; + } + } + + private static class FieldDescriptor { + final int access; + final String name, desc, signature; + final Object value; + final List attributes; + final List annotations; + int var = -1; + boolean initialized; + + FieldDescriptor( + int acc, + String n, + String d, + String sig, + Object val, + List attrs, + List annots) { + access = acc; + name = n; + desc = d; + signature = sig; + value = val; + attributes = attrs; + annotations = annots; + } + } + + private class MethodConvertor extends MethodVisitor { + private final Deque simulatedStack = new ArrayDeque<>(); + private final boolean isConstructor; + private int localVarOffset = 0; + private boolean copyEnabled = false; + + public MethodConvertor(int localVarOffset, boolean isConstructor, MethodVisitor mv) { + super(Opcodes.ASM9, mv); + this.localVarOffset = localVarOffset; + this.isConstructor = isConstructor; + copyEnabled = !isConstructor; // copy is enabled by default for all methods except constructor + } + + @Override + public void visitLocalVariable( + String name, String desc, String signature, Label start, Label end, int index) { + if (index + localVarOffset < 0 || !copyEnabled) { + return; + } + super.visitLocalVariable(name, desc, signature, start, end, index + localVarOffset); + } + + @Override + public void visitVarInsn(int opcode, int var) { + boolean delegate = true; + switch (opcode) { + case Opcodes.ALOAD: + { + delegate = (var + localVarOffset) >= 0; + simulatedStack.push(!delegate); + break; + } + case Opcodes.LLOAD: + case Opcodes.DLOAD: + { + simulatedStack.push(Boolean.FALSE); + // long and double occupy 2 stack slots; fall through + } + case Opcodes.ILOAD: + case Opcodes.FLOAD: + { + simulatedStack.push(Boolean.FALSE); + break; + } + case Opcodes.LSTORE: + case Opcodes.DSTORE: + { + simulatedStack.poll(); + // long and double occupy 2 stack slots; fall through + } + case Opcodes.ASTORE: + case Opcodes.ISTORE: + case Opcodes.FSTORE: + { + simulatedStack.poll(); + break; + } + } + + if (delegate && copyEnabled) super.visitVarInsn(opcode, var + localVarOffset); + } + + @Override + public void visitInsn(int opcode) { + switch (opcode) { + case Opcodes.POP: + { + if (simulatedStack.pop()) { + return; + } + break; + } + case Opcodes.POP2: + { + Boolean[] vals = new Boolean[2]; + vals[0] = simulatedStack.poll(); + vals[1] = simulatedStack.poll(); + if (vals[0] && vals[1]) { + return; + } else if (vals[0] || vals[1]) { + opcode = Opcodes.POP; + } + break; + } + case Opcodes.DUP: + { + Boolean val = simulatedStack.peek(); + val = val != null ? val : Boolean.FALSE; + simulatedStack.push(val); + if (val) return; + break; + } + case Opcodes.DUP_X1: + { + if (simulatedStack.size() < 2) return; + Boolean[] vals = new Boolean[2]; + int cntr = vals.length - 1; + while (cntr >= 0) { + vals[cntr--] = simulatedStack.pop(); + } + simulatedStack.push(vals[vals.length - 1]); + simulatedStack.addAll(Arrays.asList(vals)); + if (vals[1]) { + return; + } else if (vals[0]) { + opcode = Opcodes.DUP; + } + break; + } + case Opcodes.DUP_X2: + { + if (simulatedStack.size() < 3) return; + Boolean[] vals = new Boolean[3]; + int cntr = vals.length - 1; + while (cntr >= 0) { + vals[cntr--] = simulatedStack.pop(); + } + simulatedStack.push(vals[vals.length - 1]); + simulatedStack.addAll(Arrays.asList(vals)); + if (vals[2]) { + return; + } else if (vals[0] && vals[1]) { + opcode = Opcodes.DUP; + } else { + opcode = Opcodes.DUP_X1; + } + break; + } + case Opcodes.DUP2: + { + if (simulatedStack.size() < 2) return; + Boolean[] vals = new Boolean[2]; + int cntr = vals.length - 1; + while (cntr >= 0) { + vals[cntr--] = simulatedStack.pop(); + } + simulatedStack.addAll(Arrays.asList(vals)); + if (vals[0] && vals[1]) { + return; + } else if (vals[0] || vals[1]) { + opcode = Opcodes.DUP; + } + break; + } + case Opcodes.DUP2_X1: + { + if (simulatedStack.size() < 3) return; + Boolean[] vals = new Boolean[3]; + int cntr = vals.length - 1; + while (cntr >= 0) { + vals[cntr--] = simulatedStack.pop(); + } + simulatedStack.push(vals[vals.length - 2]); + simulatedStack.push(vals[vals.length - 1]); + simulatedStack.addAll(Arrays.asList(vals)); + if (vals[1] && vals[2]) { + return; + } + if (vals[0]) { + if (vals[1] || vals[2]) { + opcode = Opcodes.DUP; + } else { + opcode = Opcodes.DUP2; + } + } else { + if (vals[1] || vals[2]) { + opcode = Opcodes.DUP_X1; + } + } + break; + } + case Opcodes.DUP2_X2: + { + Boolean[] vals = {Boolean.FALSE, Boolean.FALSE, Boolean.FALSE, Boolean.FALSE}; + Iterator iter = simulatedStack.descendingIterator(); + int cntr = 0; + while (cntr < vals.length && iter.hasNext()) { + vals[cntr++] = iter.next(); + } + simulatedStack.push(vals[vals.length - 2]); + simulatedStack.push(vals[vals.length - 1]); + simulatedStack.addAll(Arrays.asList(vals)); + break; + } + case Opcodes.SWAP: + { + if (simulatedStack.size() < 2) return; + Boolean[] vals = new Boolean[2]; + + int cntr = vals.length - 1; + while (cntr >= 0) { + vals[cntr--] = simulatedStack.pop(); + } + if (vals[0] || vals[1]) { + return; + } + simulatedStack.push(vals[1]); + simulatedStack.push(vals[0]); + break; + } + // zero operand instructions + case Opcodes.LCONST_0: + case Opcodes.LCONST_1: + case Opcodes.DCONST_0: + case Opcodes.DCONST_1: + { + simulatedStack.push(Boolean.FALSE); + // the value occupies 2 slots on stack + // fall through + } + case Opcodes.ICONST_0: + case Opcodes.ICONST_1: + case Opcodes.ICONST_2: + case Opcodes.ICONST_3: + case Opcodes.ICONST_4: + case Opcodes.ICONST_5: + case Opcodes.ICONST_M1: + case Opcodes.FCONST_0: + case Opcodes.FCONST_1: + case Opcodes.FCONST_2: + case Opcodes.ACONST_NULL: + case Opcodes.MONITORENTER: + case Opcodes.MONITOREXIT: + { + simulatedStack.push(Boolean.FALSE); + break; + } + + // one operand instructions + case Opcodes.INEG: + case Opcodes.FNEG: + case Opcodes.DNEG: + case Opcodes.LNEG: + case Opcodes.I2B: + case Opcodes.I2C: + case Opcodes.I2F: + case Opcodes.I2S: + case Opcodes.L2D: + case Opcodes.D2L: + case Opcodes.F2I: + case Opcodes.CHECKCAST: + case Opcodes.ARRAYLENGTH: + { + // nothing changes in regard to the simulated stack + break; + } + case Opcodes.I2L: + case Opcodes.I2D: + { + simulatedStack.push(Boolean.FALSE); // extending the original value by one slot + break; + } + + // two operand instructions + case Opcodes.LADD: + case Opcodes.DADD: + case Opcodes.LSUB: + case Opcodes.DSUB: + case Opcodes.LMUL: + case Opcodes.DMUL: + case Opcodes.LDIV: + case Opcodes.DDIV: + case Opcodes.LREM: + case Opcodes.DREM: + case Opcodes.LSHL: + case Opcodes.LSHR: + case Opcodes.LUSHR: + case Opcodes.LAND: + case Opcodes.LOR: + case Opcodes.LXOR: + case Opcodes.LALOAD: + case Opcodes.DALOAD: + { + simulatedStack.pop(); + simulatedStack.pop(); + // remove 4 slots == 2 long/double operands and add 2 slots == 1 long/double result + break; + } + case Opcodes.LCMP: + case Opcodes.DCMPL: + case Opcodes.DCMPG: + { + simulatedStack.pop(); + simulatedStack.pop(); + simulatedStack.pop(); + // remove 4 slots == 2 long/double operands and add 1 slot == 1 int result + break; + } + case Opcodes.IADD: + case Opcodes.FADD: + case Opcodes.ISUB: + case Opcodes.FSUB: + case Opcodes.IMUL: + case Opcodes.IDIV: + case Opcodes.FDIV: + case Opcodes.IREM: + case Opcodes.FREM: + case Opcodes.ISHL: + case Opcodes.ISHR: + case Opcodes.IUSHR: + case Opcodes.IAND: + case Opcodes.IOR: + case Opcodes.IXOR: + case Opcodes.FCMPL: + case Opcodes.FCMPG: + case Opcodes.BALOAD: + case Opcodes.SALOAD: + case Opcodes.CALOAD: + case Opcodes.IALOAD: + case Opcodes.FALOAD: + { + simulatedStack.poll(); + // remove 2 slots == 2 intoperands and add 1 slot == 1 int result + break; + } + + // three operand instructions + case Opcodes.LASTORE: + case Opcodes.DASTORE: + { + simulatedStack.pop(); + // LASTORE, DSTORE occupy one more slot compared to BASTORE etc.; falling through + // fall through + } + case Opcodes.BASTORE: // fall through + case Opcodes.CASTORE: // fall through + case Opcodes.SASTORE: // fall through + case Opcodes.IASTORE: // fall through + case Opcodes.FASTORE: + { + simulatedStack.pop(); + simulatedStack.pop(); + simulatedStack.pop(); + } + } + if (copyEnabled) { + super.visitInsn(opcode); + } + } + + @Override + public void visitIntInsn(int opcode, int index) { + switch (opcode) { + case Opcodes.BIPUSH: + case Opcodes.SIPUSH: + { + simulatedStack.push(Boolean.FALSE); + break; + } + } + if (copyEnabled) { + super.visitIntInsn(opcode, index); + } + } + + @Override + public void visitJumpInsn(int opcode, Label label) { + switch (opcode) { + case Opcodes.IFEQ: + case Opcodes.IFNE: + case Opcodes.IFLE: + case Opcodes.IFGE: + case Opcodes.IFGT: + case Opcodes.IFLT: + case Opcodes.IFNULL: + case Opcodes.IFNONNULL: + { + simulatedStack.poll(); + break; + } + case Opcodes.IF_ICMPEQ: + case Opcodes.IF_ICMPGE: + case Opcodes.IF_ICMPGT: + case Opcodes.IF_ICMPLE: + case Opcodes.IF_ICMPLT: + case Opcodes.IF_ICMPNE: + { + simulatedStack.poll(); + simulatedStack.poll(); + break; + } + } + if (copyEnabled) { + super.visitJumpInsn(opcode, label); + } + } + + @Override + public void visitTableSwitchInsn(int i, int i1, Label label, Label... labels) { + simulatedStack.poll(); + if (copyEnabled) { + super.visitTableSwitchInsn(i, i1, label, labels); + } + } + + @Override + public void visitLookupSwitchInsn(Label label, int[] ints, Label[] labels) { + simulatedStack.poll(); + if (copyEnabled) { + super.visitLookupSwitchInsn(label, ints, labels); + } + } + + @Override + public void visitLdcInsn(Object o) { + simulatedStack.push(Boolean.FALSE); + if (o instanceof Long || o instanceof Double) { + simulatedStack.push(Boolean.FALSE); + } + if (copyEnabled) { + super.visitLdcInsn(o); + } + } + + @Override + public void visitMaxs(int maxStack, int maxLocals) { + super.visitMaxs(maxStack, (Math.max(maxLocals + localVarOffset, 0))); + } + + @Override + public void visitIincInsn(int var, int increment) { + if (copyEnabled) { + super.visitIincInsn(var + localVarOffset, increment); + } + } + + @Override + public void visitFieldInsn(int i, String clazz, String name, String desc) { + if (i == Opcodes.GETFIELD) { + Boolean opTarget = simulatedStack.poll(); + opTarget = opTarget != null ? opTarget : Boolean.FALSE; + if (opTarget) { + i = Opcodes.GETSTATIC; + } + } else if (i == Opcodes.PUTFIELD) { + simulatedStack.pop(); + simulatedStack.pop(); + if (desc.equals("J") || desc.equals("D")) { + simulatedStack.pop(); + } + if (clazz.equals(className)) { // all local fields are static + i = Opcodes.PUTSTATIC; + } + } + switch (i) { + case Opcodes.GETFIELD: + case Opcodes.GETSTATIC: + { + simulatedStack.push(Boolean.FALSE); + if (desc.equals("J") || desc.equals("D")) { + simulatedStack.push(Boolean.FALSE); + } + break; + } + } + if (copyEnabled) { + super.visitFieldInsn(i, clazz, name, desc); + } + } + + @Override + public void visitMethodInsn( + int opcode, String clazz, String method, String desc, boolean iface) { + int origOpcode = opcode; + Type[] args = Type.getArgumentTypes(desc); + for (Type t : args) { + for (int i = 0; i < t.getSize(); i++) { + simulatedStack.poll(); + } + } + if (opcode != Opcodes.INVOKESTATIC) { + Boolean targetVal = simulatedStack.poll(); + if (targetVal != null + && targetVal) { // "true" on stack means the original reference to "this" + opcode = Opcodes.INVOKESTATIC; + } + } + if (!Type.getReturnType(desc).equals(Type.VOID_TYPE)) { + simulatedStack.push(Boolean.FALSE); + } + if (!copyEnabled) { + if (origOpcode == Opcodes.INVOKESPECIAL && isConstructor) { + copyEnabled = true; + } + } else { + super.visitMethodInsn(opcode, clazz, method, desc, iface); + } + } + + @Override + public AnnotationVisitor visitAnnotation(String string, boolean bln) { + return copyEnabled + ? super.visitAnnotation(string, bln) + : new AnnotationVisitor(Opcodes.ASM9) {}; + } + + @Override + public AnnotationVisitor visitAnnotationDefault() { + return copyEnabled ? super.visitAnnotationDefault() : new AnnotationVisitor(Opcodes.ASM9) {}; + } + + @Override + public void visitAttribute(Attribute atrbt) { + if (copyEnabled) { + super.visitAttribute(atrbt); + } + } + + @Override + public void visitMultiANewArrayInsn(String string, int i) { + for (int ind = 0; ind < i; ind++) { + simulatedStack.pop(); + } + simulatedStack.push(Boolean.FALSE); + if (copyEnabled) { + super.visitMultiANewArrayInsn(string, i); + } + } + + @Override + public AnnotationVisitor visitParameterAnnotation(int i, String string, boolean bln) { + return copyEnabled + ? super.visitParameterAnnotation(i, string, bln) + : new AnnotationVisitor(Opcodes.ASM9) {}; + } + + @Override + public void visitTypeInsn(int opcode, String typeName) { + switch (opcode) { + case Opcodes.NEW: + { + simulatedStack.push(Boolean.FALSE); + break; + } + } + if (copyEnabled) { + super.visitTypeInsn(opcode, typeName); + } + } + } +} diff --git a/btrace-compiler/src/main/java/org/openjdk/btrace/compiler/Printer.java b/btrace-compiler/src/main/java/org/openjdk/btrace/compiler/Printer.java new file mode 100644 index 000000000..f3bfdbd56 --- /dev/null +++ b/btrace-compiler/src/main/java/org/openjdk/btrace/compiler/Printer.java @@ -0,0 +1,66 @@ +package org.openjdk.btrace.compiler; + +import java.io.PrintWriter; +import java.io.Writer; +import java.util.ArrayList; + +class Printer { + static int debugPrintIndentLevel = 0; + //////////// + // Output // + //////////// + private final PrintWriter writer; + private final ArrayList enabledBits = new ArrayList<>(); + + Printer() { + writer = new PrintWriter(System.err); + } + + Printer(Writer out) { + writer = (out instanceof PrintWriter) ? (PrintWriter) out : new PrintWriter(out); + } + + public static int getDebugPrintIndentLevel() { + return debugPrintIndentLevel; + } + + void println() { + if (enabled()) { + writer.println(); + } + } + + boolean enabled() { + return (enabledBits.isEmpty() || enabledBits.get(enabledBits.size() - 1)); + } + + void pushEnableBit(boolean enabled) { + enabledBits.add(enabled); + ++debugPrintIndentLevel; + // debugPrint(false, "PUSH_ENABLED, NOW: " + enabled()); + } + + void print(String s) { + if (enabled()) { + writer.print(s); + // System.out.print(s);//debug + } + } + + void flush() { + if (enabled()) { + writer.flush(); + // System.err.flush(); //debug + } + } + + void popEnableBit() { + if (enabledBits.isEmpty()) { + System.err.println("WARNING: mismatched #ifdef/endif pairs"); + return; + } + enabledBits.remove(enabledBits.size() - 1); + --debugPrintIndentLevel; + // debugPrint(false, "POP_ENABLED, NOW: " + enabled()); + } +} diff --git a/btrace-compiler/src/main/java/org/openjdk/btrace/compiler/Verifier.java b/btrace-compiler/src/main/java/org/openjdk/btrace/compiler/Verifier.java new file mode 100644 index 000000000..0bd2c0510 --- /dev/null +++ b/btrace-compiler/src/main/java/org/openjdk/btrace/compiler/Verifier.java @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.compiler; + +import com.sun.source.tree.AnnotationTree; +import com.sun.source.tree.AssignmentTree; +import com.sun.source.tree.ClassTree; +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.IdentifierTree; +import com.sun.source.tree.Tree; +import com.sun.source.util.JavacTask; +import com.sun.source.util.SourcePositions; +import com.sun.source.util.TaskEvent; +import com.sun.source.util.TaskListener; +import com.sun.source.util.TreePath; +import com.sun.source.util.Trees; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.Messager; +import javax.annotation.processing.ProcessingEnvironment; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.TypeElement; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; +import javax.tools.Diagnostic.Kind; +import org.openjdk.btrace.core.Messages; +import org.openjdk.btrace.core.annotations.BTrace; + +/** + * An annotation processor that validates a BTrace program. Safety rules (such as no loops, no + * new/throw etc.) are enforced. This uses javac's Tree API in addition to JSR 269. + * + * @author A. Sundararajan + */ +@SupportedAnnotationTypes("*") +@SupportedSourceVersion(SourceVersion.RELEASE_8) +public class Verifier extends AbstractProcessor implements TaskListener { + private final List classNames = new ArrayList<>(); + private final List compUnits = new ArrayList<>(); + private final AttributionTaskListener listener = new AttributionTaskListener(); + private Trees treeUtils; + private ClassTree currentClass; + + @Override + public synchronized void init(ProcessingEnvironment pe) { + super.init(pe); + treeUtils = Trees.instance(pe); + JavacTask task = JavacTask.instance(processingEnv); + task.addTaskListener(listener); + } + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + return true; + } + + @Override + public void started(TaskEvent e) { + if (e.getKind() == TaskEvent.Kind.ENTER) { + CompilationUnitTree ct = e.getCompilationUnit(); + if (ct != null) { + compUnits.add(ct); + } + } + } + + @Override + public void finished(TaskEvent e) { + if (e.getKind() != TaskEvent.Kind.ANALYZE) return; + if (processingEnv == null) { + return; + } + TypeElement elem = e.getTypeElement(); + for (Tree t : e.getCompilationUnit().getTypeDecls()) { + TreePath topLevel = new TreePath(e.getCompilationUnit()); + if (t.getKind() == Tree.Kind.CLASS) { + if (elem.equals(getTreeUtils().getElement(new TreePath(topLevel, t)))) { + currentClass = (ClassTree) t; + break; + } + } + } + if (currentClass != null) { + verify(currentClass, elem); + } + } + + List getClassNames() { + return classNames; + } + + CompilationUnitTree getCompilationUnit() { + for (CompilationUnitTree ct : compUnits) { + for (Tree clazz : ct.getTypeDecls()) { + if (clazz.equals(currentClass)) { + return ct; + } + } + } + return null; + } + + Trees getTreeUtils() { + return treeUtils; + } + + SourcePositions getSourcePositions() { + return treeUtils.getSourcePositions(); + } + + ProcessingEnvironment getProcessingEnvironment() { + return processingEnv; + } + + Messager getMessager() { + return processingEnv.getMessager(); + } + + Elements getElementUtils() { + return processingEnv.getElementUtils(); + } + + Types getTypeUtils() { + return processingEnv.getTypeUtils(); + } + + Locale getLocale() { + return processingEnv.getLocale(); + } + + String annotationName(AnnotationTree at) { + TreePath tp = getTreeUtils().getPath(getCompilationUnit(), at.getAnnotationType()); + Element el = getTreeUtils().getElement(tp); + if (el == null || el.getKind() != ElementKind.ANNOTATION_TYPE) { + return null; + } + return ((TypeElement) el).getQualifiedName().toString(); + } + + // verify each BTrace class + private void verify(ClassTree ct, Element topElement) { + currentClass = ct; + CompilationUnitTree cut = getCompilationUnit(); + String className = ct.getSimpleName().toString(); + ExpressionTree pkgName = cut.getPackageName(); + if (pkgName != null) { + className = pkgName + "." + className; + } + classNames.add(className); + if (hasTrustedAnnotation(ct, topElement)) { + return; + } + ct.accept(new VerifierVisitor(this, topElement), null); + } + + /** Detects if the class is annotated as @BTrace(trusted=true). */ + private boolean hasTrustedAnnotation(ClassTree ct, Element topElement) { + for (AnnotationTree at : ct.getModifiers().getAnnotations()) { + String annFqn = annotationName(at); + if (!BTrace.class.getName().equals(annFqn)) { + continue; + } + // now we have @BTrace, look for unsafe = xxx or trusted = xxx + for (ExpressionTree ext : at.getArguments()) { + if (ext.getKind() != Tree.Kind.ASSIGNMENT) { + continue; + } + AssignmentTree assign = (AssignmentTree) ext; + String name = ((IdentifierTree) assign.getVariable()).getName().toString(); + if (!"unsafe".equals(name) && !"trusted".equals(name)) { + continue; + } + // now rhs is the value of @BTrace.unsafe. + // The value can be complex (!!true, 1 == 2, etc.) - we support only booleans + String val = assign.getExpression().toString(); + if ("true".equals(val)) { + return true; // bingo! + } else if (!"false".equals(val)) { + processingEnv + .getMessager() + .printMessage(Kind.WARNING, Messages.get("no.complex.unsafe.value"), topElement); + } + } + } + return false; + } + + /** A task listener that invokes the processor whenever a class is fully analyzed. */ + private final class AttributionTaskListener implements TaskListener { + + @Override + public void finished(TaskEvent e) { + if (e.getKind() != TaskEvent.Kind.ANALYZE) return; + TypeElement elem = e.getTypeElement(); + TreePath topLevel = new TreePath(e.getCompilationUnit()); + for (Tree t : e.getCompilationUnit().getTypeDecls()) { + if (t.getKind() == Tree.Kind.CLASS) { + if (elem.equals(getTreeUtils().getElement(new TreePath(topLevel, t)))) { + currentClass = (ClassTree) t; + break; + } + } + } + if (currentClass != null) { + verify(currentClass, elem); + } + } + + @Override + public void started(TaskEvent e) {} + } +} diff --git a/btrace-compiler/src/main/java/org/openjdk/btrace/compiler/VerifierVisitor.java b/btrace-compiler/src/main/java/org/openjdk/btrace/compiler/VerifierVisitor.java new file mode 100644 index 000000000..d28b9236a --- /dev/null +++ b/btrace-compiler/src/main/java/org/openjdk/btrace/compiler/VerifierVisitor.java @@ -0,0 +1,726 @@ +/* + * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.compiler; + +import com.sun.source.tree.AnnotationTree; +import com.sun.source.tree.AssertTree; +import com.sun.source.tree.AssignmentTree; +import com.sun.source.tree.CatchTree; +import com.sun.source.tree.ClassTree; +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.CompoundAssignmentTree; +import com.sun.source.tree.DoWhileLoopTree; +import com.sun.source.tree.EnhancedForLoopTree; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.ForLoopTree; +import com.sun.source.tree.MemberSelectTree; +import com.sun.source.tree.MethodInvocationTree; +import com.sun.source.tree.MethodTree; +import com.sun.source.tree.ModifiersTree; +import com.sun.source.tree.NewArrayTree; +import com.sun.source.tree.NewClassTree; +import com.sun.source.tree.ReturnTree; +import com.sun.source.tree.SynchronizedTree; +import com.sun.source.tree.ThrowTree; +import com.sun.source.tree.Tree; +import com.sun.source.tree.TryTree; +import com.sun.source.tree.VariableTree; +import com.sun.source.tree.WhileLoopTree; +import com.sun.source.util.SourcePositions; +import com.sun.source.util.TreePath; +import com.sun.source.util.TreeScanner; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; +import javax.tools.Diagnostic; +import org.openjdk.btrace.core.Messages; +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.Injected; +import org.openjdk.btrace.core.annotations.Kind; +import org.openjdk.btrace.core.annotations.OnError; +import org.openjdk.btrace.core.annotations.OnExit; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.Sampled; + +/** + * This class tree visitor validates a BTrace program's ClassTree. + * + * @author A. Sundararajan + */ +public class VerifierVisitor extends TreeScanner { + private static final String ON_ERROR_TYPE = OnError.class.getName(); + private static final String ON_EXIT_TYPE = OnExit.class.getName(); + private static final String THROWABLE_TYPE = Throwable.class.getName(); + + private final Verifier verifier; + private String className; + private String fqn; + private boolean insideMethod; + + private boolean shortSyntax = false; + private TypeMirror btraceServiceTm = null; + private TypeMirror runtimeServiceTm = null; + private TypeMirror simpleServiceTm = null; + private TypeMirror serviceInjectorTm = null; + + private boolean isInAnnotation = false; + + private final Set eventFieldNames = new HashSet<>(); + + private final TreeScanner jfrFieldNameCollector = + new TreeScanner() { + @Override + public Void visitAnnotation(AnnotationTree node, Void o) { + String annType = node.getAnnotationType().toString(); + if (annType.endsWith("Event")) { + for (ExpressionTree et : node.getArguments()) { + AssignmentTree t = (AssignmentTree) et; + String name = t.getVariable().toString(); + if (name.equals("fields")) { + processEventFields(t); + } + } + } + return super.visitAnnotation(node, o); + } + }; + + public VerifierVisitor(Verifier verifier, Element clzElement) { + this.verifier = verifier; + btraceServiceTm = + verifier + .getElementUtils() + .getTypeElement("org.openjdk.btrace.services.spi.BTraceService") + .asType(); + runtimeServiceTm = + verifier + .getElementUtils() + .getTypeElement("org.openjdk.btrace.services.spi.RuntimeService") + .asType(); + simpleServiceTm = + verifier + .getElementUtils() + .getTypeElement("org.openjdk.btrace.services.spi.SimpleService") + .asType(); + serviceInjectorTm = + verifier + .getElementUtils() + .getTypeElement("org.openjdk.btrace.services.api.Service") + .asType(); + } + + @Override + public Void visitMethodInvocation(MethodInvocationTree node, Void v) { + Element e = getElement(node); + if (e != null + && (e.getKind() == ElementKind.METHOD || e.getKind() == ElementKind.CONSTRUCTOR)) { + String name = e.getSimpleName().toString(); + + // allow constructor calls + if (name.equals("")) { + return super.visitMethodInvocation(node, v); + } + + TypeElement parent = null; + do { + parent = (TypeElement) e.getEnclosingElement(); + } while (parent != null + && (parent.getKind() != ElementKind.CLASS && parent.getKind() != ElementKind.INTERFACE)); + + if (parent != null) { + TypeMirror tm = parent.asType(); + String typeName = tm.toString(); + + if (isSameClass(typeName)) { + return super.visitMethodInvocation(node, v); + } + if (isBTraceClass(typeName)) { + if (typeName.contains("BTraceUtils")) { + if (e.getSimpleName().contentEquals("setEventField")) { + String nameValue = node.getArguments().get(1).toString(); + if (!eventFieldNames.contains(nameValue)) { + reportError("jfr.event.invalid.field", node.getArguments().get(1)); + } + } + } + return super.visitMethodInvocation(node, v); + } + // check service injection + if (verifier.getTypeUtils().isSubtype(tm, btraceServiceTm)) { + return super.visitMethodInvocation(node, v); + } + if (verifier.getTypeUtils().isSubtype(tm, serviceInjectorTm)) { + if (!validateInjectionParams(node)) { + reportError("service.injector.literals", node); + } + + return super.visitMethodInvocation(node, v); + } + } + } + reportError("no.method.calls", node); + return super.visitMethodInvocation(node, v); + } + + private boolean isSameClass(String typeName) { + return fqn.equals(typeName); + } + + private boolean isBTraceClass(String typeName) { + return typeName.equals("org.openjdk.btrace.core.BTraceUtils") + || typeName.startsWith("org.openjdk.btrace.core.BTraceUtils."); + } + + @Override + public Void visitAssert(AssertTree node, Void v) { + reportError("no.asserts", node); + return super.visitAssert(node, v); + } + + @Override + public Void visitAssignment(AssignmentTree node, Void v) { + checkLValue(node.getVariable()); + return super.visitAssignment(node, v); + } + + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree node, Void v) { + checkLValue(node.getVariable()); + return super.visitCompoundAssignment(node, v); + } + + @Override + public Void visitCatch(CatchTree node, Void v) { + reportError("no.catch", node); + return super.visitCatch(node, v); + } + + @Override + public Void visitClass(ClassTree node, Void v) { + // check for local class + if (insideMethod) { + reportError("no.local.class", node); + } + + // check for short BTrace syntax (inferring redundant access qualifiers) + Set mods = node.getModifiers().getFlags(); + if (!mods.contains(Modifier.PRIVATE) + && !mods.contains(Modifier.PROTECTED) + && !mods.contains(Modifier.PUBLIC)) { + shortSyntax = true; + } + // check for inner and nested class + List members = node.getMembers(); + for (Tree m : members) { + if (m.getKind() == Tree.Kind.CLASS) { + reportError("no.nested.class", m); + } + + if (m.getKind() == Tree.Kind.VARIABLE) { + VariableTree vt = (VariableTree) m; + boolean isStatic = isStatic(vt.getModifiers().getFlags()); + if (shortSyntax) { + if (isStatic) { + reportError("no.static.variables", m); + } + } else { + if (!isStatic) { + reportError("no.instance.variables", m); + } + } + } + } + + // should extend java.lang.Object + Tree superClass = node.getExtendsClause(); + if (superClass != null) { + String name = superClass.toString(); + if (!name.equals("Object") && !name.equals("java.lang.Object")) { + reportError("object.superclass.required", superClass); + } + } + + // should not implement interfaces + List interfaces = node.getImplementsClause(); + if (interfaces != null && interfaces.size() > 0) { + reportError("no.interface.implementation", interfaces.get(0)); + } + + ModifiersTree mt = node.getModifiers(); + if (!shortSyntax && !isPublic(mt.getFlags())) { + reportError("class.should.be.public", node); + } + List anno = mt.getAnnotations(); + if (anno != null && !anno.isEmpty()) { + String btrace = BTrace.class.getName(); + for (AnnotationTree at : anno) { + String name = at.getAnnotationType().toString(); + if (name.equals(btrace) || name.equals("BTrace")) { + String oldClassName = className; + try { + className = node.getSimpleName().toString(); + fqn = getElement(node).asType().toString(); + return super.visitClass(node, v); + } finally { + className = oldClassName; + } + } + } + } + reportError("not.a.btrace.program", node); + return null; + } + + @Override + public Void visitDoWhileLoop(DoWhileLoopTree node, Void v) { + reportError("no.do.while", node); + return super.visitDoWhileLoop(node, v); + } + + @Override + public Void visitEnhancedForLoop(EnhancedForLoopTree node, Void v) { + reportError("no.enhanced.for", node); + return super.visitEnhancedForLoop(node, v); + } + + @Override + public Void visitForLoop(ForLoopTree node, Void v) { + reportError("no.for.loop", node); + return super.visitForLoop(node, v); + } + + @Override + public Void visitMethod(MethodTree node, Void v) { + boolean oldInsideMethod = insideMethod; + insideMethod = true; + try { + Name name = node.getName(); + if (name.contentEquals("")) { + return super.visitMethod(node, v); + } else { + checkSampling(node); + + if (isExitHandler(node)) { + if (node.getParameters().size() != 1 + || !"int".equals(node.getParameters().get(0).getType().toString())) { + reportError("onexit.invalid", node); + return super.visitMethod(node, v); + } + } + if (isErrorHandler(node)) { + Element thrElement = getElement(node.getParameters().get(0).getType()); + if (node.getParameters().size() != 1 || !THROWABLE_TYPE.equals(thrElement.toString())) { + reportError("onerror.invalid", node); + } + } + + for (VariableTree vt : node.getParameters()) { + vt.accept( + new TreeScanner() { + @Override + public Void visitAnnotation(AnnotationTree at, Void p) { + isInAnnotation = true; + try { + return super.visitAnnotation(at, p); + } finally { + isInAnnotation = false; + } + } + }, + null); + } + + Set flags = node.getModifiers().getFlags(); + if (shortSyntax) { + if (isStatic(flags)) { + reportError("no.static.method", node); + } + if (isSynchronized(flags)) { + reportError("no.synchronized.methods", node); + } + } else { + boolean isStatic = isStatic(flags); + if (isStatic) { + boolean isPublic = isPublic(node.getModifiers().getFlags()); + if (isPublic) { + if (isSynchronized(flags)) { + reportError("no.synchronized.methods", node); + } + } else { + // force the "public" modifier only on the annotated methods + if (isAnnotated(node)) { + reportError("method.should.be.public", node); + } + } + } else { + reportError("no.instance.method", node); + } + } + + node.accept(jfrFieldNameCollector, null); + + return super.visitMethod(node, v); + } + } finally { + insideMethod = oldInsideMethod; + } + } + + private void addEventFieldNames(AnnotationTree at) { + for (ExpressionTree et1 : at.getArguments()) { + addEventFieldName((AssignmentTree) et1); + } + } + + private void addEventFieldName(AssignmentTree assignmentTree) { + String varName = assignmentTree.getVariable().toString(); + if (varName.equals("name")) { + eventFieldNames.add(assignmentTree.getExpression().toString()); + } + } + + @Override + public Void visitNewArray(NewArrayTree node, Void v) { + if (!isInAnnotation) { + reportError("no.array.creation", node); + } + return super.visitNewArray(node, v); + } + + @Override + public Void visitNewClass(NewClassTree node, Void v) { + Element e = getElement(node); + TypeElement te = (TypeElement) e.getEnclosingElement(); + reportError("no.new.object", node); + return super.visitNewClass(node, v); + } + + @Override + public Void visitReturn(ReturnTree node, Void v) { + if (node.getExpression() != null) { + TreePath tp = verifier.getTreeUtils().getPath(verifier.getCompilationUnit(), node); + while (tp != null) { + tp = tp.getParentPath(); + Tree leaf = tp.getLeaf(); + if (leaf.getKind() == Tree.Kind.METHOD) { + if (isAnnotated((MethodTree) leaf)) { + reportError("return.type.should.be.void", node); + } else { + return super.visitReturn(node, v); + } + } + } + } + return super.visitReturn(node, v); + } + + @Override + public Void visitMemberSelect(MemberSelectTree node, Void v) { + if (!isInAnnotation) { + if (node.getIdentifier().contentEquals("class")) { + TypeMirror tm = getType(node.getExpression()); + if (!verifier.getTypeUtils().isSubtype(tm, btraceServiceTm)) { + reportError("no.class.literals", node); + } + } + } + return super.visitMemberSelect(node, v); + } + + @Override + public Void visitAnnotation(AnnotationTree node, Void unused) { + try { + isInAnnotation = true; + return super.visitAnnotation(node, unused); + } finally { + isInAnnotation = false; + } + } + + @Override + public Void visitSynchronized(SynchronizedTree node, Void v) { + reportError("no.synchronized.blocks", node); + return super.visitSynchronized(node, v); + } + + @Override + public Void visitThrow(ThrowTree node, Void v) { + reportError("no.throw", node); + return super.visitThrow(node, v); + } + + @Override + public Void visitTry(TryTree node, Void v) { + reportError("no.try", node); + return super.visitTry(node, v); + } + + @Override + public Void visitVariable(VariableTree vt, Void p) { + VariableElement ve = (VariableElement) getElement(vt); + + if (ve.getEnclosingElement().getKind() == ElementKind.CLASS) { + // only applying to fields + if (verifier.getTypeUtils().isSubtype(ve.asType(), btraceServiceTm)) { + Injected i = ve.getAnnotation(Injected.class); + if (i == null) { + reportError("missing.injected", vt); + } else { + switch (i.value()) { + case RUNTIME: + { + if (!verifier.getTypeUtils().isSubtype(ve.asType(), runtimeServiceTm)) { + reportError("injected.no.runtime", vt); + } + break; + } + case SIMPLE: + { + if (!verifier.getTypeUtils().isSubtype(ve.asType(), simpleServiceTm)) { + reportError("injected.no.simple", vt); + } + break; + } + } + } + if (vt.getInitializer() != null) { + reportError("injected.no.initializer", vt.getInitializer()); + } + } else { + vt.accept(jfrFieldNameCollector, null); + } + } + + return super.visitVariable(vt, p); + } + + private void processEventFields(AssignmentTree t) { + if (t.getExpression() instanceof AnnotationTree) { + AnnotationTree at = (AnnotationTree) t.getExpression(); + addEventFieldNames(at); + } else if (t.getExpression() instanceof NewArrayTree) { + for (ExpressionTree et2 : ((NewArrayTree) t.getExpression()).getInitializers()) { + AnnotationTree at = (AnnotationTree) et2; + addEventFieldNames(at); + } + } + } + + @Override + public Void visitWhileLoop(WhileLoopTree node, Void v) { + reportError("no.while.loop", node); + return super.visitWhileLoop(node, v); + } + + @Override + public Void visitOther(Tree node, Void v) { + reportError("no.other", node); + return super.visitOther(node, v); + } + + private boolean isStatic(Set modifiers) { + for (Modifier m : modifiers) { + if (m == Modifier.STATIC) { + return true; + } + } + return false; + } + + private boolean isSynchronized(Set modifiers) { + for (Modifier m : modifiers) { + if (m == Modifier.SYNCHRONIZED) { + return true; + } + } + return false; + } + + private boolean isPublic(Set modifiers) { + for (Modifier m : modifiers) { + if (m == Modifier.PUBLIC) { + return true; + } + } + return false; + } + + private boolean isErrorHandler(MethodTree node) { + ModifiersTree mt = node.getModifiers(); + List annos = mt.getAnnotations(); + for (AnnotationTree at : annos) { + String annFqn = verifier.annotationName(at); + if (ON_ERROR_TYPE.equals(annFqn)) { + return true; + } + } + return false; + } + + private boolean isExitHandler(MethodTree node) { + ModifiersTree mt = node.getModifiers(); + List annos = mt.getAnnotations(); + for (AnnotationTree at : annos) { + String annFqn = verifier.annotationName(at); + if (ON_EXIT_TYPE.equals(annFqn)) { + return true; + } + } + return false; + } + + private boolean isAnnotated(MethodTree node) { + ModifiersTree mt = node.getModifiers(); + List annos = mt.getAnnotations(); + for (AnnotationTree at : annos) { + String annFqn = verifier.annotationName(at); + if (annFqn != null && annFqn.startsWith("org.openjdk.btrace.core.annotations")) { + return true; + } + } + return false; + } + + private void checkSampling(MethodTree node) { + ExecutableElement ee = (ExecutableElement) getElement(node); + + Sampled s = ee.getAnnotation(Sampled.class); + OnMethod om = ee.getAnnotation(OnMethod.class); + + if (s != null && om != null) { + Kind k = om.location().value(); + switch (k) { + case ENTRY: + case RETURN: + case ERROR: + case CALL: + { + return; + } + default: + { + // noop + } + } + reportError("sampler.invalid.location", node); + } + } + + private void checkLValue(Tree variable) { + if (variable.getKind() == Tree.Kind.ARRAY_ACCESS) { + reportError("no.assignment", variable); + return; + } + + if (variable.getKind() != Tree.Kind.IDENTIFIER) { + if (className != null) { + String name = variable.toString(); + name = name.substring(0, name.lastIndexOf('.')); + if (!className.equals(name)) { + reportError("no.assignment", variable); + } + } else { + reportError("no.assignment", variable); + } + } + } + + private void reportError(String msg, Tree node) { + SourcePositions srcPos = verifier.getSourcePositions(); + CompilationUnitTree compUnit = verifier.getCompilationUnit(); + if (compUnit != null) { + long pos = srcPos.getStartPosition(compUnit, node); + long line = compUnit.getLineMap().getLineNumber(pos); + String name = compUnit.getSourceFile().getName(); + Element e = getElement(node); + msg = String.format("%s:%d:%s [%s]", name, line, Messages.get(msg), e); + verifier.getMessager().printMessage(Diagnostic.Kind.ERROR, msg, e); + } else { + verifier.getMessager().printMessage(Diagnostic.Kind.ERROR, msg); + } + } + + private Element getElement(Tree t) { + TreePath tp = verifier.getTreeUtils().getPath(verifier.getCompilationUnit(), t); + Element e = verifier.getTreeUtils().getElement(tp); + if (e == null) { + if (t.getKind() == Tree.Kind.NEW_CLASS) { + e = + verifier + .getTreeUtils() + .getElement(new TreePath(tp, ((NewClassTree) t).getIdentifier())); + } + if (t.getKind() == Tree.Kind.THROW) { + e = verifier.getTreeUtils().getElement(new TreePath(tp, ((ThrowTree) t).getExpression())); + } + if (e == null) { + verifier.getMessager().printMessage(Diagnostic.Kind.ERROR, t.toString()); + } + } + return e; + } + + private TypeMirror getType(Tree t) { + TreePath tp = verifier.getTreeUtils().getPath(verifier.getCompilationUnit(), t); + return verifier.getTreeUtils().getTypeMirror(tp); + } + + private boolean validateInjectionParams(MethodInvocationTree node) { + boolean allLiterals = true; + outer: + for (ExpressionTree arg : node.getArguments()) { + switch (arg.getKind()) { + case MEMBER_SELECT: + { + if (!arg.toString().endsWith(".class")) { + allLiterals = false; + break outer; + } + break; + } + case STRING_LITERAL: + { + // allowed parameters + break; + } + default: + { + allLiterals = false; + break outer; + } + } + } + return allLiterals; + } +} diff --git a/btrace-compiler/src/test/java/org/openjdk/btrace/compiler/JfrEventsTest.java b/btrace-compiler/src/test/java/org/openjdk/btrace/compiler/JfrEventsTest.java new file mode 100644 index 000000000..123e2277c --- /dev/null +++ b/btrace-compiler/src/test/java/org/openjdk/btrace/compiler/JfrEventsTest.java @@ -0,0 +1,46 @@ +package org.openjdk.btrace.compiler; + +import java.io.File; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.net.URL; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.util.CheckClassAdapter; +import org.openjdk.btrace.core.SharedSettings; +import org.openjdk.btrace.instr.BTraceProbe; +import org.openjdk.btrace.instr.BTraceProbeFactory; + +import static org.junit.jupiter.api.Assertions.fail; + +public class JfrEventsTest { + @Test + public void testCompile() throws Exception { + URL input = JfrEventsTest.class.getResource("/JfrEventsProbe.java"); + File inputFile = new File(input.toURI()); + Map data = + new Compiler(true) + .compile( + inputFile, + new PrintWriter(System.err), + null, + System.getProperty("java.class.path")); + BTraceProbeFactory factory = new BTraceProbeFactory(SharedSettings.GLOBAL); + for (byte[] bytes : data.values()) { + BTraceProbe probe = factory.createProbe(bytes); + verifyCode(probe.getFullBytecode()); + verifyCode(probe.getDataHolderBytecode()); + } + } + + private void verifyCode(byte[] code) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + CheckClassAdapter.verify(new ClassReader(code), true, pw); + if (sw.toString().contains("AnalyzerException")) { + System.err.println(sw); + fail(); + } + } +} diff --git a/btrace-compiler/src/test/java/org/openjdk/btrace/compiler/TypeErasureTest.java b/btrace-compiler/src/test/java/org/openjdk/btrace/compiler/TypeErasureTest.java new file mode 100644 index 000000000..3951345bc --- /dev/null +++ b/btrace-compiler/src/test/java/org/openjdk/btrace/compiler/TypeErasureTest.java @@ -0,0 +1,47 @@ +package org.openjdk.btrace.compiler; + +import org.junit.jupiter.api.Test; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.util.CheckClassAdapter; +import org.openjdk.btrace.core.SharedSettings; +import org.openjdk.btrace.instr.BTraceProbe; +import org.openjdk.btrace.instr.BTraceProbeFactory; + +import java.io.File; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.net.URL; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.fail; + +public class TypeErasureTest { + @Test + void testTypeErasure() throws Exception { + URL input = TypeErasureTest.class.getResource("/HistoProbe.java"); + File inputFile = new File(input.toURI()); + Map data = + new Compiler(true) + .compile( + inputFile, + new PrintWriter(System.err), + null, + System.getProperty("java.class.path")); + BTraceProbeFactory factory = new BTraceProbeFactory(SharedSettings.GLOBAL); + for (byte[] bytes : data.values()) { + BTraceProbe probe = factory.createProbe(bytes); + verifyCode(probe.getFullBytecode()); + verifyCode(probe.getDataHolderBytecode()); + } + } + + private void verifyCode(byte[] code) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + CheckClassAdapter.verify(new ClassReader(code), true, pw); + if (sw.toString().contains("AnalyzerException")) { + System.err.println(sw); + fail(); + } + } +} diff --git a/btrace-compiler/src/test/resources/HistoProbe.java b/btrace-compiler/src/test/resources/HistoProbe.java new file mode 100644 index 000000000..8d5df886e --- /dev/null +++ b/btrace-compiler/src/test/resources/HistoProbe.java @@ -0,0 +1,27 @@ +package test; + +import static org.openjdk.btrace.core.BTraceUtils.*; +import org.openjdk.btrace.core.annotations.*; + +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +@BTrace public class HistoProbe { + private static Map histo = newHashMap(); + @OnMethod(clazz = "javax.swing.JComponent", method = "") + public static void onMethod(@Self Object obj) { + String cn = name(classOf(obj)); + AtomicInteger ai = get((Map)histo, cn); + if (ai == null) { + ai = newAtomicInteger(1); + put(histo, cn, ai); + } else { + incrementAndGet(ai); // WORKS if commented out + } + } + + @OnTimer(1000) + public static void print() { + printNumberMap("Component Histogram", histo); + } +} \ No newline at end of file diff --git a/btrace-compiler/src/test/resources/JfrEventsProbe.java b/btrace-compiler/src/test/resources/JfrEventsProbe.java new file mode 100644 index 000000000..d02764ffb --- /dev/null +++ b/btrace-compiler/src/test/resources/JfrEventsProbe.java @@ -0,0 +1,36 @@ +package test; + +import org.openjdk.btrace.core.BTraceUtils; +import org.openjdk.btrace.core.annotations.*; +import org.openjdk.btrace.core.jfr.JfrEvent; +import static org.openjdk.btrace.core.BTraceUtils.*; +import static org.openjdk.btrace.core.BTraceUtils.Jfr.*; + +@BTrace public class JfrEventsProbe { + @Event( + name = "CustomEvent", + label = "Custom Event", + fields = { + @Event.Field(type = Event.FieldType.INT, name = "a"), + @Event.Field(type = Event.FieldType.STRING, name = "b") + } + ) + private static JfrEvent.Factory customEventFactory; + + @OnMethod(clazz = "/.*/", method = "/.*/") + public static void onMethod() { + JfrEvent event = prepareEvent(customEventFactory); + setEventField(event, "a", 10); + setEventField(event, "b", "hello"); + commit(event); + } + + @PeriodicEvent(name = "PeriodicEvent", fields = @Event.Field(type = Event.FieldType.INT, name = "cnt", kind = @Event.Field.Kind(name = Event.FieldKind.TIMESTAMP)), period = "1 s") + public static void onPeriod(JfrEvent event) { + if (shouldCommit(event)) { + setEventField(event, "cnt", 1); + commit(event); + } + } + +} \ No newline at end of file diff --git a/btrace-core/build.gradle b/btrace-core/build.gradle new file mode 100644 index 000000000..08aa6c467 --- /dev/null +++ b/btrace-core/build.gradle @@ -0,0 +1,24 @@ +import org.apache.tools.ant.filters.* + +buildscript { scriptHandler -> + apply from: rootProject.file('buildSrc/shared.gradle'), to: scriptHandler +} + +plugins { + alias(libs.plugins.versioning) +} + +dependencies { + implementation libs.slf4j + implementation libs.slf4j.simple + implementation libs.jctools + implementation libs.asm + implementation libs.asm.util +} + +processResources { + filter ReplaceTokens, tokens: [ + "btrace.version": project.version, + "hash" : versioning.info.commit + ] +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/Args.java b/btrace-core/src/main/java/org/openjdk/btrace/core/Args.java new file mode 100644 index 000000000..b20b0ccab --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/Args.java @@ -0,0 +1,28 @@ +package org.openjdk.btrace.core; + +public final class Args { + public static final String ALLOWED_CALLS = "allowedCalls"; + public static final String SYSTEM_CLASS_PATH = "systemClassPath"; + public static final String BOOT_CLASS_PATH = "bootClassPath"; + public static final String CONFIG = "config"; + public static final String SCRIPT = "script"; + public static final String SCRIPT_DIR = "scriptdir"; + public static final String STARTUP_RETRANSFORM = "startupRetransform"; + public static final String DUMP_DIR = "dumpDir"; + public static final String DUMP_CLASSES = "dumpClasses"; + public static final String CMD_QUEUE_LIMIT = "cmdQueueLimit"; + public static final String TRACK_RETRANSFORMS = "trackRetransforms"; + public static final String SCRIPT_OUTPUT_FILE = "scriptOutputFile"; + public static final String SCRIPT_OUTPUT_DIR = "scriptOutputDir"; + public static final String FILE_ROLL_MILLISECONDS = "fileRollMilliseconds"; + public static final String FILE_ROLL_MAX_ROLLS = "fileRollMaxRolls"; + public static final String TRUSTED = "trusted"; + public static final String STATSD = "statsd"; + public static final String PROBE_DESC_PATH = "probeDescPath"; + public static final String DEBUG = "debug"; + public static final String PORT = "port"; + public static final String STDOUT = "stdout"; + public static final String NO_SERVER = "noServer"; + public static final String HELP = "help"; + public static final String LIBS = "libs"; +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/ArgsMap.java b/btrace-core/src/main/java/org/openjdk/btrace/core/ArgsMap.java new file mode 100644 index 000000000..a8d3e4534 --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/ArgsMap.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2018, Jaroslav Bachorik . + * All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Copyright owner designates + * this particular file as subject to the "Classpath" exception as provided + * by the owner in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ +package org.openjdk.btrace.core; + +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** A simple argument map wrapper allowing indexed access */ +public final class ArgsMap implements Iterable> { + private final LinkedHashMap map; + + public ArgsMap(Map args) { + map = args != null ? new LinkedHashMap<>(args) : new LinkedHashMap<>(); + } + + public ArgsMap(String[] argLine) { + map = new LinkedHashMap<>(); + if (argLine != null) { + for (String arg : argLine) { + String[] kv = arg.split("="); + if (kv.length != 2) { + map.put(arg, ""); + } else { + map.put(kv[0], kv[1]); + } + } + } + } + + public ArgsMap() { + this((Map) null); + } + + public ArgsMap(int initialCapacity) { + map = new LinkedHashMap<>(initialCapacity); + } + + public static ArgsMap merge(ArgsMap... maps) { + Map propMap = new LinkedHashMap<>(); + for (ArgsMap map : maps) { + propMap.putAll(map.map); + } + return new ArgsMap(propMap); + } + + public String get(String key) { + return map.get(key); + } + + public String get(int idx) { + if (idx >= 0 && idx < map.size()) { + Iterator> argsIterator = map.entrySet().iterator(); + for (int i = 0; i < idx; i++) { + argsIterator.next(); + } + Map.Entry e = argsIterator.next(); + return e.getValue() != null ? e.getKey() + "=" + e.getValue() : e.getKey(); + } else { + return null; + } + } + + public void clear() { + map.clear(); + } + + public int size() { + return map.size(); + } + + public boolean isEmpty() { + return map.isEmpty(); + } + + public String put(String key, String value) { + return map.put(key, value); + } + + @Override + public Iterator> iterator() { + return map.entrySet().iterator(); + } + + public boolean containsKey(String key) { + return map.containsKey(key); + } + + @Override + public boolean equals(Object o) { + return map.equals(o); + } + + @Override + public int hashCode() { + return map.hashCode(); + } + + @Override + public String toString() { + return "ArgsMap{" + "map=" + map + '}'; + } + + public String template(String value) { + if (value == null) { + return null; + } + if (value.isEmpty()) { + return value; + } + + Matcher matcher = PatternSingleton.INSTANCE.matcher(value); + StringBuffer buffer = new StringBuffer(value.length()); + + while (matcher.find()) { + String val = get(matcher.group(1)); + matcher.appendReplacement(buffer, val != null ? val : "$0"); + } + matcher.appendTail(buffer); + + return buffer.toString(); + } + + private static final class PatternSingleton { + // lazy initialization trick + // do not compile the pattern until it is actually requested + private static final Pattern INSTANCE = Pattern.compile("\\$\\{(.*?)}"); + } +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/BTraceRuntime.java b/btrace-core/src/main/java/org/openjdk/btrace/core/BTraceRuntime.java new file mode 100644 index 000000000..d7894d7cb --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/BTraceRuntime.java @@ -0,0 +1,1311 @@ +/* + * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.core; + +import com.sun.management.HotSpotDiagnosticMXBean; +import java.io.BufferedOutputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.lang.instrument.Instrumentation; +import java.lang.management.GarbageCollectorMXBean; +import java.lang.management.LockInfo; +import java.lang.management.MemoryMXBean; +import java.lang.management.MemoryPoolMXBean; +import java.lang.management.MemoryUsage; +import java.lang.management.MonitorInfo; +import java.lang.management.OperatingSystemMXBean; +import java.lang.management.RuntimeMXBean; +import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Deque; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import org.openjdk.btrace.core.aggregation.Aggregation; +import org.openjdk.btrace.core.aggregation.AggregationFunction; +import org.openjdk.btrace.core.aggregation.AggregationKey; +import org.openjdk.btrace.core.comm.Command; +import org.openjdk.btrace.core.comm.EventCommand; +import org.openjdk.btrace.core.comm.GridDataCommand; +import org.openjdk.btrace.core.comm.NumberDataCommand; +import org.openjdk.btrace.core.comm.NumberMapDataCommand; +import org.openjdk.btrace.core.comm.StringMapDataCommand; +import org.openjdk.btrace.core.jfr.JfrEvent; +import org.openjdk.btrace.core.types.AnyType; +import org.openjdk.btrace.core.types.BTraceCollection; +import org.openjdk.btrace.core.types.BTraceDeque; +import org.openjdk.btrace.core.types.BTraceMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sun.misc.Unsafe; +import sun.security.action.GetPropertyAction; + +@SuppressWarnings("deprecation") +public final class BTraceRuntime { + private static final Logger log = LoggerFactory.getLogger(BTraceRuntime.class); + + public static final String CMD_QUEUE_LIMIT_KEY = "org.openjdk.btrace.core.cmdQueueLimit"; + private static final boolean messageTimestamp = false; + private static final String LINE_SEPARATOR; + // perf counter variability - we always variable variability + private static final int V_Variable = 3; + // perf counter units + private static final int V_None = 1; + private static final int V_String = 5; + private static final int PERF_STRING_LIMIT = 256; + // the number of stack frames taking a thread dump adds + private static final int THRD_DUMP_FRAMES = 1; + + // we need Unsafe to load BTrace class bytes as + // bootstrap class + private static volatile Unsafe unsafe = null; + private static Properties dotWriterProps; + // this field is 'back-set' by the agent at startup thus needs to remain non-final + private static volatile BTraceRuntimeAccessor rtAccessor = () -> null; + private static final String INDENT = " "; + + public static volatile Instrumentation instrumentation = null; + + static { + LINE_SEPARATOR = System.getProperty("line.separator"); + } + + private BTraceRuntime() {} + + private static Impl getRt() { + Impl rt = rtAccessor.getRt(); + return rt; + } + + public static long parseLong(String value, long deflt) { + if (value == null) { + return deflt; + } + try { + return Long.parseLong(value); + } catch (NumberFormatException e) { + return deflt; + } + } + + public static int parseInt(String value, int deflt) { + if (value == null) { + return deflt; + } + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + return deflt; + } + } + + // The following constants are copied from VM code + // for jvmstat. + + public static Unsafe initUnsafe() { + try { + if (unsafe == null) { + unsafe = Unsafe.getUnsafe(); + } + } catch (SecurityException e) { + log.warn("Unable to initialize Unsafe. BTrace will not function properly"); + } + return unsafe; + } + + static int getInstrumentationLevel() { + return getRt().getInstrumentationLevel(); + } + + static void setInstrumentationLevel(int level) { + getRt().setInstrumentationLevel(level); + } + + public static boolean enter() { + BTraceRuntime.Impl rt = getRt(); + return rt == null || rt.enter(); + } + + /** + * Leave method is called by every probed method just before the probe actions end (and actual + * probed method continues). + */ + public static void leave() { + BTraceRuntime.Impl rt = getRt(); + if (rt != null) { + rt.leave(); + } + } + + /** Handles exception from BTrace probe actions. */ + public static void handleException(Throwable th) { + getRt().handleException(th); + } + + // package-private interface to BTraceUtils class. + + private static String identityStr(Object obj) { + int hashCode = java.lang.System.identityHashCode(obj); + return obj.getClass().getName() + "@" + Integer.toHexString(hashCode); + } + + static int speculation() { + return getRt().speculation(); + } + + static void speculate(int id) { + getRt().speculate(id); + } + + static void discard(int id) { + getRt().discard(id); + } + + static void commit(int id) { + getRt().commit(id); + } + + /** + * Indicates whether two given objects are "equal to" one another. For bootstrap classes, returns + * the result of calling Object.equals() override. For non-bootstrap classes, the reference + * identity comparison is done. + * + * @param obj1 first object to compare equality + * @param obj2 second object to compare equality + * @return true if the given objects are equal; false otherwise. + */ + static boolean compare(Object obj1, Object obj2) { + if (obj1 instanceof String) { + return obj1.equals(obj2); + } else if (obj1.getClass().getClassLoader() == null) { + if (obj2 == null || obj2.getClass().getClassLoader() == null) { + return obj1.equals(obj2); + } // else fall through.. + } + return obj1 == obj2; + } + + // BTrace map functions + static Map newHashMap() { + return new BTraceMap<>(new HashMap<>()); + } + + static Map newWeakMap() { + return new BTraceMap<>(new WeakHashMap<>()); + } + + static Deque newDeque() { + return new BTraceDeque<>(new ArrayDeque<>()); + } + + static Appendable newStringBuilder(boolean threadSafe) { + return threadSafe ? new StringBuffer() : new StringBuilder(); + } + + static Appendable newStringBuilder() { + return newStringBuilder(false); + } + + static int size(Collection coll) { + if (coll instanceof BTraceCollection || coll.getClass().getClassLoader() == null) { + return coll.size(); + } else { + throw new IllegalArgumentException(); + } + } + + public static boolean isEmpty(Collection coll) { + if (coll instanceof BTraceCollection || coll.getClass().getClassLoader() == null) { + return coll.isEmpty(); + } else { + throw new IllegalArgumentException(); + } + } + + static boolean contains(Collection coll, Object obj) { + if (coll instanceof BTraceCollection || coll.getClass().getClassLoader() == null) { + for (E e : coll) { + if (compare(e, obj)) { + return true; + } + } + return false; + } else { + throw new IllegalArgumentException(); + } + } + + static Object[] toArray(Collection collection) { + if (collection == null) { + return new Object[0]; + } else { + return collection.toArray(); + } + } + + static V get(Map map, K key) { + if (map instanceof BTraceMap || map.getClass().getClassLoader() == null) { + return map.get(key); + } else { + throw new IllegalArgumentException(); + } + } + + static boolean containsKey(Map map, K key) { + if (map instanceof BTraceMap || map.getClass().getClassLoader() == null) { + return map.containsKey(key); + } else { + throw new IllegalArgumentException(); + } + } + + static boolean containsValue(Map map, V value) { + if (map instanceof BTraceMap || map.getClass().getClassLoader() == null) { + return map.containsValue(value); + } else { + throw new IllegalArgumentException(); + } + } + + static V put(Map map, K key, V value) { + if (map instanceof BTraceMap) { + return map.put(key, value); + } else { + throw new IllegalArgumentException("not a btrace map"); + } + } + + static V remove(Map map, K key) { + if (map instanceof BTraceMap) { + return map.remove(key); + } else { + throw new IllegalArgumentException("not a btrace map"); + } + } + + static void clear(Map map) { + if (map instanceof BTraceMap) { + map.clear(); + } else { + throw new IllegalArgumentException("not a btrace map"); + } + } + + static int size(Map map) { + if (map instanceof BTraceMap || map.getClass().getClassLoader() == null) { + return map.size(); + } else { + throw new IllegalArgumentException(); + } + } + + static boolean isEmpty(Map map) { + if (map instanceof BTraceMap || map.getClass().getClassLoader() == null) { + return map.isEmpty(); + } else { + throw new IllegalArgumentException(); + } + } + + static void putAll(Map src, Map dst) { + dst.putAll(src); + } + + static void copy(Map src, Map dst) { + dst.clear(); + dst.putAll(src); + } + + @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") + static void printMap(Map map) { + if (map instanceof BTraceMap || map.getClass().getClassLoader() == null) { + synchronized (map) { + Map m = new HashMap<>(); + @SuppressWarnings("unchecked") + Set> entries = map.entrySet(); + for (Map.Entry e : entries) { + m.put(BTraceUtils.Strings.str(e.getKey()), BTraceUtils.Strings.str(e.getValue())); + } + printStringMap(null, m); + } + } else { + print(BTraceUtils.Strings.str(map)); + } + } + + public static void push(Deque queue, V value) { + if (queue instanceof BTraceDeque || queue.getClass().getClassLoader() == null) { + queue.push(value); + } else { + throw new IllegalArgumentException(); + } + } + + public static void addLast(Deque queue, V value) { + if (queue instanceof BTraceDeque || queue.getClass().getClassLoader() == null) { + queue.addLast(value); + } else { + throw new IllegalArgumentException(); + } + } + + public static V peekFirst(Deque queue) { + if (queue instanceof BTraceDeque || queue.getClass().getClassLoader() == null) { + return queue.peekFirst(); + } else { + throw new IllegalArgumentException(); + } + } + + public static V peekLast(Deque queue) { + if (queue instanceof BTraceDeque || queue.getClass().getClassLoader() == null) { + return queue.peekLast(); + } else { + throw new IllegalArgumentException(); + } + } + + public static V removeLast(Deque queue) { + if (queue instanceof BTraceDeque || queue.getClass().getClassLoader() == null) { + return queue.removeLast(); + } else { + throw new IllegalArgumentException(); + } + } + + public static V removeFirst(Deque queue) { + if (queue instanceof BTraceDeque || queue.getClass().getClassLoader() == null) { + return queue.removeFirst(); + } else { + throw new IllegalArgumentException(); + } + } + + public static V poll(Deque queue) { + if (queue instanceof BTraceDeque || queue.getClass().getClassLoader() == null) { + return queue.poll(); + } else { + throw new IllegalArgumentException(); + } + } + + public static V peek(Deque queue) { + if (queue instanceof BTraceDeque || queue.getClass().getClassLoader() == null) { + return queue.peek(); + } else { + throw new IllegalArgumentException(); + } + } + + public static void clear(Deque queue) { + if (queue instanceof BTraceDeque || queue.getClass().getClassLoader() == null) { + queue.clear(); + } else { + throw new IllegalArgumentException(); + } + } + + public static Appendable append(Appendable buffer, String strToAppend) { + try { + if (buffer != null && strToAppend != null) { + return buffer.append(strToAppend); + } else { + throw new IllegalArgumentException(); + } + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } + + public static int length(Appendable buffer) { + if (buffer instanceof CharSequence) { + return ((CharSequence) buffer).length(); + } else { + throw new IllegalArgumentException(); + } + } + + static void printNumber(String name, Number value) { + getRt().send(new NumberDataCommand(name, value)); + } + + static void printNumberMap(String name, Map data) { + getRt().send(new NumberMapDataCommand(name, data)); + } + + static void printStringMap(String name, Map data) { + getRt().send(new StringMapDataCommand(name, data)); + } + + // BTrace exit built-in function + static void exit(int exitCode) { + getRt().exit(exitCode); + } + + static long sizeof(Object obj) { + return getRt().sizeof(obj); + } + + // BTrace command line argument functions + static int $length() { + return getRt().$length(); + } + + static String $(int n) { + return getRt().$(n); + } + + static String $(String key) { + return getRt().$(key); + } + + /** @see BTraceUtils#instanceOf(java.lang.Object, java.lang.String) */ + static boolean instanceOf(Object obj, String className) { + if (obj instanceof AnyType) { + // the only time we can have AnyType on stack + // is if it was passed in as a placeholder + // for void @Return parameter value + if (className.equalsIgnoreCase("void")) { + return obj.equals(AnyType.VOID); + } + return false; + } + Class objClass = obj.getClass(); + ClassLoader cl = objClass.getClassLoader(); + cl = cl != null ? cl : ClassLoader.getSystemClassLoader(); + try { + Class target = cl.loadClass(className); + return target.isAssignableFrom(objClass); + } catch (ClassNotFoundException e) { + // non-existing class + log.debug("Class {} can not be found by classloader {}", className, cl, e); + return false; + } + } + + static AtomicInteger newAtomicInteger(int initVal) { + return new BTraceAtomicInteger(initVal); + } + + static int get(AtomicInteger ai) { + if (ai instanceof BTraceAtomicInteger || ai.getClass().getClassLoader() == null) { + return ai.get(); + } else { + throw new IllegalArgumentException(); + } + } + + static void set(AtomicInteger ai, int i) { + if (ai instanceof BTraceAtomicInteger) { + ai.set(i); + } else { + throw new IllegalArgumentException(); + } + } + + static void lazySet(AtomicInteger ai, int i) { + if (ai instanceof BTraceAtomicInteger) { + ai.lazySet(i); + } else { + throw new IllegalArgumentException(); + } + } + + static boolean compareAndSet(AtomicInteger ai, int i, int j) { + if (ai instanceof BTraceAtomicInteger) { + return ai.compareAndSet(i, j); + } else { + throw new IllegalArgumentException(); + } + } + + static boolean weakCompareAndSet(AtomicInteger ai, int i, int j) { + if (ai instanceof BTraceAtomicInteger) { + return ai.weakCompareAndSet(i, j); + } else { + throw new IllegalArgumentException(); + } + } + + static int getAndIncrement(AtomicInteger ai) { + if (ai instanceof BTraceAtomicInteger) { + return ai.getAndIncrement(); + } else { + throw new IllegalArgumentException(); + } + } + + static int getAndDecrement(AtomicInteger ai) { + if (ai instanceof BTraceAtomicInteger) { + return ai.getAndDecrement(); + } else { + throw new IllegalArgumentException(); + } + } + + static int incrementAndGet(AtomicInteger ai) { + if (ai instanceof BTraceAtomicInteger) { + return ai.incrementAndGet(); + } else { + throw new IllegalArgumentException(); + } + } + + static int decrementAndGet(AtomicInteger ai) { + if (ai instanceof BTraceAtomicInteger) { + return ai.decrementAndGet(); + } else { + throw new IllegalArgumentException(); + } + } + + static int getAndAdd(AtomicInteger ai, int i) { + if (ai instanceof BTraceAtomicInteger) { + return ai.getAndAdd(i); + } else { + throw new IllegalArgumentException(); + } + } + + static int addAndGet(AtomicInteger ai, int i) { + if (ai instanceof BTraceAtomicInteger) { + return ai.addAndGet(i); + } else { + throw new IllegalArgumentException(); + } + } + + static int getAndSet(AtomicInteger ai, int i) { + if (ai instanceof BTraceAtomicInteger) { + return ai.getAndSet(i); + } else { + throw new IllegalArgumentException(); + } + } + + static AtomicLong newAtomicLong(long initVal) { + return new BTraceAtomicLong(initVal); + } + + static long get(AtomicLong al) { + if (al instanceof BTraceAtomicLong || al.getClass().getClassLoader() == null) { + return al.get(); + } else { + throw new IllegalArgumentException(); + } + } + + static void set(AtomicLong al, long i) { + if (al instanceof BTraceAtomicLong) { + al.set(i); + } else { + throw new IllegalArgumentException(); + } + } + + static void lazySet(AtomicLong al, long i) { + if (al instanceof BTraceAtomicLong) { + al.lazySet(i); + } else { + throw new IllegalArgumentException(); + } + } + + static boolean compareAndSet(AtomicLong al, long i, long j) { + if (al instanceof BTraceAtomicLong) { + return al.compareAndSet(i, j); + } else { + throw new IllegalArgumentException(); + } + } + + static boolean weakCompareAndSet(AtomicLong al, long i, long j) { + if (al instanceof BTraceAtomicLong) { + return al.weakCompareAndSet(i, j); + } else { + throw new IllegalArgumentException(); + } + } + + static long getAndIncrement(AtomicLong al) { + if (al instanceof BTraceAtomicLong) { + return al.getAndIncrement(); + } else { + throw new IllegalArgumentException(); + } + } + + static long getAndDecrement(AtomicLong al) { + if (al instanceof BTraceAtomicLong) { + return al.getAndDecrement(); + } else { + throw new IllegalArgumentException(); + } + } + + static long incrementAndGet(AtomicLong al) { + if (al instanceof BTraceAtomicLong) { + return al.incrementAndGet(); + } else { + throw new IllegalArgumentException(); + } + } + + static long decrementAndGet(AtomicLong al) { + if (al instanceof BTraceAtomicLong) { + return al.decrementAndGet(); + } else { + throw new IllegalArgumentException(); + } + } + + static long getAndAdd(AtomicLong al, long i) { + if (al instanceof BTraceAtomicLong) { + return al.getAndAdd(i); + } else { + throw new IllegalArgumentException(); + } + } + + static long addAndGet(AtomicLong al, long i) { + if (al instanceof BTraceAtomicLong) { + return al.addAndGet(i); + } else { + throw new IllegalArgumentException(); + } + } + + static long getAndSet(AtomicLong al, long i) { + if (al instanceof BTraceAtomicLong) { + return al.getAndSet(i); + } else { + throw new IllegalArgumentException(); + } + } + + // BTrace perf counter reading functions + static int perfInt(String name) { + return getRt().perfInt(name); + } + + static long perfLong(String name) { + return getRt().perfLong(name); + } + + static String perfString(String name) { + return getRt().perfString(name); + } + + // stack trace functions + private static String stackTraceAllStr(int numFrames, boolean printWarning) { + Set> traces = Thread.getAllStackTraces().entrySet(); + StringBuilder buf = new StringBuilder(); + for (Map.Entry t : traces) { + buf.append(t.getKey()); + buf.append(LINE_SEPARATOR); + buf.append(LINE_SEPARATOR); + StackTraceElement[] st = t.getValue(); + buf.append(stackTraceStr("\t", st, 0, numFrames, printWarning)); + buf.append(LINE_SEPARATOR); + } + return buf.toString(); + } + + static String stackTraceAllStr(int numFrames) { + return stackTraceAllStr(numFrames, false); + } + + static void stackTraceAll(int numFrames) { + getRt().send(stackTraceAllStr(numFrames, true)); + } + + static String stackTraceStr(StackTraceElement[] st, int strip, int numFrames) { + return stackTraceStr(null, st, strip, numFrames, false); + } + + static String stackTraceStr(String prefix, StackTraceElement[] st, int strip, int numFrames) { + return stackTraceStr(prefix, st, strip, numFrames, false); + } + + private static String stackTraceStr( + String prefix, StackTraceElement[] st, int strip, int numFrames, boolean printWarning) { + strip = strip > 0 ? strip + THRD_DUMP_FRAMES : 0; + numFrames = numFrames > 0 ? numFrames : st.length - strip; + + int limit = strip + numFrames; + limit = Math.min(limit, st.length); + + if (prefix == null) { + prefix = ""; + } + + StringBuilder buf = new StringBuilder(); + for (int i = strip; i < limit; i++) { + buf.append(prefix); + buf.append(st[i]); + buf.append(LINE_SEPARATOR); + } + if (printWarning && limit < st.length) { + buf.append(prefix); + buf.append(st.length - limit); + buf.append(" more frame(s) ..."); + buf.append(LINE_SEPARATOR); + } + return buf.toString(); + } + + static void stackTrace(StackTraceElement[] st, int strip, int numFrames) { + stackTrace(null, st, strip, numFrames); + } + + static void stackTrace(String prefix, StackTraceElement[] st, int strip, int numFrames) { + getRt().send(stackTraceStr(prefix, st, strip, numFrames, true)); + } + + // print/println functions + static void print(String str) { + getRt().send(str); + } + + static void println(String str) { + getRt().send(str + LINE_SEPARATOR); + } + + static void println() { + getRt().send(LINE_SEPARATOR); + } + + static String property(String name) { + return AccessController.doPrivileged(new GetPropertyAction(name)); + } + + static Properties properties() { + return AccessController.doPrivileged( + (PrivilegedAction) () -> System.getProperties()); + } + + static String getenv(String name) { + return AccessController.doPrivileged((PrivilegedAction) () -> System.getenv(name)); + } + + static Map getenv() { + return AccessController.doPrivileged( + (PrivilegedAction>) () -> System.getenv()); + } + + static MemoryUsage heapUsage() { + return getRt().getMemoryMXBean().getHeapMemoryUsage(); + } + + static MemoryUsage nonHeapUsage() { + return getRt().getMemoryMXBean().getNonHeapMemoryUsage(); + } + + static long finalizationCount() { + return getRt().getMemoryMXBean().getObjectPendingFinalizationCount(); + } + + static long vmStartTime() { + return getRt().getRuntimeMXBean().getStartTime(); + } + + static long vmUptime() { + return getRt().getRuntimeMXBean().getUptime(); + } + + static List getInputArguments() { + return getRt().getRuntimeMXBean().getInputArguments(); + } + + static String getVmVersion() { + return getRt().getRuntimeMXBean().getVmVersion(); + } + + static boolean isBootClassPathSupported() { + return getRt().getRuntimeMXBean().isBootClassPathSupported(); + } + + static String getBootClassPath() { + return getRt().getRuntimeMXBean().getBootClassPath(); + } + + static long getThreadCount() { + return getRt().getThreadMXBean().getThreadCount(); + } + + static long getPeakThreadCount() { + return getRt().getThreadMXBean().getPeakThreadCount(); + } + + static long getTotalStartedThreadCount() { + return getRt().getThreadMXBean().getTotalStartedThreadCount(); + } + + static long getDaemonThreadCount() { + return getRt().getThreadMXBean().getDaemonThreadCount(); + } + + static long getCurrentThreadCpuTime() { + ThreadMXBean threadMXBean = getRt().getThreadMXBean(); + threadMXBean.setThreadCpuTimeEnabled(true); + return threadMXBean.getCurrentThreadCpuTime(); + } + + static long getCurrentThreadUserTime() { + ThreadMXBean threadMXBean = getRt().getThreadMXBean(); + threadMXBean.setThreadCpuTimeEnabled(true); + return threadMXBean.getCurrentThreadUserTime(); + } + + static void dumpHeap(String fileName, boolean live) { + try { + String name = getRt().resolveFileName(fileName); + getRt().getHotspotMBean().dumpHeap(name, live); + } catch (RuntimeException re) { + throw re; + } catch (Exception exp) { + throw new RuntimeException(exp); + } + } + + static long getTotalGcTime() { + long totalGcTime = 0; + for (GarbageCollectorMXBean gcBean : getRt().getGCMBeans()) { + totalGcTime += gcBean.getCollectionTime(); + } + return totalGcTime; + } + + static String getMemoryPoolUsage(String poolFormat) { + if (poolFormat == null) { + poolFormat = "%1$s;%2$d;%3$d;%4$d;%5$d"; + } + List memPoolList = getRt().getMemoryPoolMXBeans(); + Object[][] poolOutput = new Object[memPoolList.size()][5]; + + StringBuilder membuffer = new StringBuilder(); + + for (int i = 0; i < memPoolList.size(); i++) { + MemoryPoolMXBean memPool = memPoolList.get(i); + poolOutput[i][0] = memPool.getName(); + poolOutput[i][1] = memPool.getUsage().getMax(); + poolOutput[i][2] = memPool.getUsage().getUsed(); + poolOutput[i][3] = memPool.getUsage().getCommitted(); + poolOutput[i][4] = memPool.getUsage().getInit(); + } + for (Object[] memPoolOutput : poolOutput) { + membuffer.append(String.format(poolFormat, memPoolOutput)).append("\n"); + } + + return membuffer.toString(); + } + + static double getSystemLoadAverage() { + return getRt().getOperatingSystemMXBean().getSystemLoadAverage(); + } + + static long getProcessCPUTime() { + OperatingSystemMXBean mbean = getRt().getOperatingSystemMXBean(); + + if (mbean instanceof com.sun.management.OperatingSystemMXBean) { + return ((com.sun.management.OperatingSystemMXBean) mbean).getProcessCpuTime(); + } + + return -1; + } + + static void serialize(Object obj, String fileName) { + try { + BufferedOutputStream bos = + new BufferedOutputStream(new FileOutputStream(getRt().resolveFileName(fileName))); + try (ObjectOutputStream oos = new ObjectOutputStream(bos)) { + oos.writeObject(obj); + } + } catch (RuntimeException re) { + throw re; + } catch (Exception exp) { + throw new RuntimeException(exp); + } + } + + static String toXML(Object obj) { + return getRt().toXML(obj); + } + + static void writeXML(Object obj, String fileName) { + getRt().writeXML(obj, fileName); + } + + static void writeDOT(Object obj, String fileName) { + getRt().writeDOT(obj, fileName); + } + + static void deadlocks(boolean stackTrace) { + ThreadMXBean mbean = getRt().getThreadMXBean(); + if (mbean.isSynchronizerUsageSupported()) { + long[] tids = mbean.findDeadlockedThreads(); + if (tids != null && tids.length > 0) { + ThreadInfo[] infos = mbean.getThreadInfo(tids, true, true); + StringBuilder sb = new StringBuilder(); + for (ThreadInfo ti : infos) { + sb.append("\"") + .append(ti.getThreadName()) + .append("\"" + " Id=") + .append(ti.getThreadId()) + .append(" in ") + .append(ti.getThreadState()); + if (ti.getLockName() != null) { + sb.append(" on lock=").append(ti.getLockName()); + } + if (ti.isSuspended()) { + sb.append(" (suspended)"); + } + if (ti.isInNative()) { + sb.append(" (running in native)"); + } + if (ti.getLockOwnerName() != null) { + sb.append(INDENT) + .append(" owned by ") + .append(ti.getLockOwnerName()) + .append(" Id=") + .append(ti.getLockOwnerId()); + sb.append(LINE_SEPARATOR); + } + + if (stackTrace) { + // print stack trace with locks + StackTraceElement[] stacktrace = ti.getStackTrace(); + MonitorInfo[] monitors = ti.getLockedMonitors(); + for (int i = 0; i < stacktrace.length; i++) { + StackTraceElement ste = stacktrace[i]; + sb.append(INDENT).append("at ").append(ste); + sb.append(LINE_SEPARATOR); + for (MonitorInfo mi : monitors) { + if (mi.getLockedStackDepth() == i) { + sb.append(INDENT).append(" - locked ").append(mi); + sb.append(LINE_SEPARATOR); + } + } + } + sb.append(LINE_SEPARATOR); + } + + LockInfo[] locks = ti.getLockedSynchronizers(); + sb.append(INDENT).append("Locked synchronizers: count = ").append(locks.length); + sb.append(LINE_SEPARATOR); + for (LockInfo li : locks) { + sb.append(INDENT).append(" - ").append(li); + sb.append(LINE_SEPARATOR); + } + sb.append(LINE_SEPARATOR); + } + getRt().send(sb.toString()); + } + } + } + + static int dtraceProbe(String s1, String s2, int i1, int i2) { + if (getRt().isDTraceEnabled()) { + return dtraceProbe0(s1, s2, i1, i2); + } else { + return 0; + } + } + + // called from instrumentation initialization code + public static JfrEvent.Factory createEventFactory(JfrEvent.Template template) { + return getRt().createEventFactory(template); + } + + public static boolean isBootstrapClass(String className) { + return getRt().isBootstrapClass(className); + } + + // BTrace aggregation support + static Aggregation newAggregation(AggregationFunction type) { + return new Aggregation(type); + } + + static AggregationKey newAggregationKey(Object... elements) { + return new AggregationKey(elements); + } + + static void addToAggregation(Aggregation aggregation, long value) { + aggregation.add(value); + } + + static void addToAggregation(Aggregation aggregation, AggregationKey key, long value) { + aggregation.add(key, value); + } + + static void clearAggregation(Aggregation aggregation) { + aggregation.clear(); + } + + static void truncateAggregation(Aggregation aggregation, int count) { + aggregation.truncate(count); + } + + static void printAggregation(String name, Aggregation aggregation) { + getRt().send(new GridDataCommand(name, aggregation.getData())); + } + + static void printSnapshot(String name, Profiler.Snapshot snapshot) { + getRt().send(new GridDataCommand(name, snapshot.getGridData())); + } + + /** + * Prints profiling snapshot using the provided format + * + * @param name The name of the aggregation to be used in the textual output + * @param snapshot The snapshot to print + * @param format The format to use. It mimics {@linkplain String#format(java.lang.String, + * java.lang.Object[]) } behaviour with the addition of the ability to address the key title + * as a 0-indexed item + * @see String#format(java.lang.String, java.lang.Object[]) + */ + static void printSnapshot(String name, Profiler.Snapshot snapshot, String format) { + getRt().send(new GridDataCommand(name, snapshot.getGridData(), format)); + } + + /** + * Precondition: Only values from the first Aggregation are printed. If the subsequent + * aggregations have values for keys which the first aggregation does not have, these rows are + * ignored. + * + * @param name + * @param format + * @param aggregationArray + */ + static void printAggregation(String name, String format, Aggregation[] aggregationArray) { + if (aggregationArray.length > 1 && aggregationArray[0].getKeyData().size() > 1) { + int aggregationDataSize = + aggregationArray[0].getKeyData().get(0).getElements().length + aggregationArray.length; + + List aggregationData = new ArrayList<>(); + + // Iterate through all keys in the first Aggregation and build up an array of aggregationData + for (AggregationKey aggKey : aggregationArray[0].getKeyData()) { + int aggDataIndex = 0; + Object[] currAggregationData = new Object[aggregationDataSize]; + + // Add the key to the from of the current aggregation Data + for (Object obj : aggKey.getElements()) { + currAggregationData[aggDataIndex] = obj; + aggDataIndex++; + } + + for (Aggregation agg : aggregationArray) { + currAggregationData[aggDataIndex] = agg.getValueForKey(aggKey); + aggDataIndex++; + } + + aggregationData.add(currAggregationData); + } + + getRt().send(new GridDataCommand(name, aggregationData, format)); + } + } + + /** + * Prints aggregation using the provided format + * + * @param name The name of the aggregation to be used in the textual output + * @param aggregation The aggregation to print + * @param format The format to use. It mimics {@linkplain String#format(java.lang.String, + * java.lang.Object[]) } behaviour with the addition of the ability to address the key title + * as a 0-indexed item + * @see String#format(java.lang.String, java.lang.Object[]) + */ + static void printAggregation(String name, Aggregation aggregation, String format) { + getRt().send(new GridDataCommand(name, aggregation.getData(), format)); + } + + /** @see BTraceUtils.Profiling#newProfiler() */ + static Profiler newProfiler() { + return getRt().newProfiler(); + } + + /** @see BTraceUtils.Profiling#newProfiler(int) */ + static Profiler newProfiler(int expectedMethodCnt) { + return getRt().newProfiler(expectedMethodCnt); + } + + /** @see BTraceUtils.Profiling#recordEntry(Profiler, java.lang.String) */ + static void recordEntry(Profiler profiler, String methodName) { + profiler.recordEntry(methodName); + } + + // profiling related methods + + /** @see BTraceUtils.Profiling#recordExit(Profiler, java.lang.String, long) */ + static void recordExit(Profiler profiler, String methodName, long duration) { + profiler.recordExit(methodName, duration); + } + + /** @see BTraceUtils.Profiling#snapshot(Profiler) */ + static Profiler.Snapshot snapshot(Profiler profiler) { + return profiler.snapshot(); + } + + /** @see BTraceUtils.Profiling#snapshotAndReset(Profiler) */ + static Profiler.Snapshot snapshotAndReset(Profiler profiler) { + return profiler.snapshot(true); + } + + static void resetProfiler(Profiler profiler) { + profiler.reset(); + } + + static ClassLoader getCallerClassloader(int stackDec) { + return getRt().getCallerClassLoader(stackDec + 1); + } + + public static Class getCallerClass(int stackDec) { + return getRt().getCallerClass(stackDec + 1); + } + + // private methods below this point + // raise DTrace USDT probe + private static native int dtraceProbe0(String s1, String s2, int i1, int i2); + + /** + * Common BTrace runtime interface allowing access from various subsystems (core, runtime, + * instrumentation, clients) + */ + public interface Impl { + void send(String msg); + + void send(Command cmd); + + boolean enter(); + + void leave(); + + int getInstrumentationLevel(); + + void setInstrumentationLevel(int level); + + void handleException(Throwable th); + + int speculation(); + + void speculate(int id); + + void commit(int id); + + void discard(int id); + + void exit(int exitCode); + + long sizeof(Object obj); + + int $length(); + + String $(int n); + + String $(String key); + + String toXML(Object obj); + + void writeXML(Object obj, String fileName); + + void writeDOT(Object obj, String fileName); + + Profiler newProfiler(); + + Profiler newProfiler(int expectedMethodCnt); + + int perfInt(String name); + + long perfLong(String name); + + String perfString(String name); + + String resolveFileName(String name); + + boolean isDTraceEnabled(); + + void handleEvent(EventCommand cmd); + + void handleExit(int i); + + void shutdownCmdLine(); + + List getMemoryPoolMXBeans(); + + HotSpotDiagnosticMXBean getHotspotMBean(); + + MemoryMXBean getMemoryMXBean(); + + RuntimeMXBean getRuntimeMXBean(); + + ThreadMXBean getThreadMXBean(); + + OperatingSystemMXBean getOperatingSystemMXBean(); + + List getGCMBeans(); + + Class defineClass(byte[] code, boolean mustBeBootstrap); + + ClassLoader getCallerClassLoader(int stackDec); + + Class getCallerClass(int stackDec); + + JfrEvent.Factory createEventFactory(JfrEvent.Template template); + + int version(); + + boolean isBootstrapClass(String className); + } + + public interface BTraceRuntimeAccessor { + Impl getRt(); + } + + private static final class BTraceAtomicInteger extends AtomicInteger { + BTraceAtomicInteger(int initVal) { + super(initVal); + } + } + + private static final class BTraceAtomicLong extends AtomicLong { + BTraceAtomicLong(long initVal) { + super(initVal); + } + } +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/BTraceUtils.java b/btrace-core/src/main/java/org/openjdk/btrace/core/BTraceUtils.java new file mode 100644 index 000000000..cb130ab5c --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/BTraceUtils.java @@ -0,0 +1,6567 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.core; + +import java.io.Serializable; +import java.lang.management.MemoryUsage; +import java.lang.ref.Reference; +import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Collection; +import java.util.Deque; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Properties; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; +import jdk.jfr.Event; +import org.openjdk.btrace.core.aggregation.Aggregation; +import org.openjdk.btrace.core.aggregation.AggregationFunction; +import org.openjdk.btrace.core.aggregation.AggregationKey; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.ProbeClassName; +import org.openjdk.btrace.core.annotations.ProbeMethodName; +import org.openjdk.btrace.core.annotations.Self; +import org.openjdk.btrace.core.jfr.JfrEvent; +import org.openjdk.btrace.core.types.AnyType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class is an all-in-one wrapper for BTrace DSL methods + * + * @author A. Sundararajan + * @author Jaroslav Bachorik + */ +public class BTraceUtils { + private static final Logger log = LoggerFactory.getLogger(BTraceUtils.class); + + // standard stack depth decrement for figuring out the caller class calls + private static final int STACK_DEC = 2; + + static { + BTraceRuntime.initUnsafe(); + } + + // do not allow object creation! + private BTraceUtils() {} + + // Thread and stack access + + /** + * Tests whether this thread has been interrupted. The interrupted status of the thread is + * unaffected by this method. + * + *

A thread interruption ignored because a thread was not alive at the time of the interrupt + * will be reflected by this method returning false. + * + * @return true if this thread has been interrupted; false otherwise. + */ + public static boolean isInteruppted() { + return Threads.isInteruppted(); + } + + /** Prints the java stack trace of the current thread. */ + public static void jstack() { + Threads.jstack(2, -1); + } + + /** + * Prints the java stack trace of the current thread. But, at most given number of frames. + * + * @param numFrames number of frames to be printed. When this is negative all frames are printed. + */ + public static void jstack(int numFrames) { + Threads.jstack(2, numFrames); + } + + /** Prints Java stack traces of all the Java threads. */ + public static void jstackAll() { + Threads.jstackAll(2, -1); + } + + /** + * Prints Java stack traces of all the Java threads. But, at most given number of frames. + * + * @param numFrames number of frames to be printed. When this is negative all frames are printed. + */ + public static void jstackAll(int numFrames) { + Threads.jstackAll(2, numFrames); + } + + /** + * Returns the stack trace of current thread as a String. + * + * @return the stack trace as a String. + */ + public static String jstackStr() { + return Threads.jstackStr(2, -1); + } + + /** + * Returns the stack trace of the current thread as a String but includes at most the given number + * of frames. + * + * @param numFrames number of frames to be included. When this is negative all frames are + * included. + * @return the stack trace as a String. + */ + public static String jstackStr(int numFrames) { + return Threads.jstackStr(2, numFrames); + } + + /** + * Returns the stack traces of all Java threads as a String. + * + * @return the stack traces as a String. + */ + public static String jstackAllStr() { + return Threads.jstackAllStr(); + } + + /** + * Returns at most given number of frames in stack traces of all threads as a String. + * + * @param numFrames number of frames to be included. When this is negative all frames are + * included. + * @return the stack traces as a String. + */ + public static String jstackAllStr(int numFrames) { + return Threads.jstackAllStr(numFrames); + } + + /** + * Prints the stack trace of the given exception object. + * + * @param exception throwable for which stack trace is printed. + */ + public static void jstack(Throwable exception) { + Threads.jstack(exception); + } + + /** + * Prints the stack trace of the given exception object. But, prints atmost given number of + * frames. + * + * @param exception throwable for which stack trace is printed. + * @param numFrames maximum number of frames to be printed. + */ + public static void jstack(Throwable exception, int numFrames) { + Threads.jstack(exception, numFrames); + } + + /** + * Returns the stack trace of given exception object as a String. + * + * @param exception the throwable for which stack trace is returned. + */ + public static String jstackStr(Throwable exception) { + return Threads.jstackStr(exception); + } + + /** + * Returns stack trace of given exception object as a String. + * + * @param exception throwable for which stack trace is returned. + * @param numFrames maximum number of frames to be returned. + */ + public static String jstackStr(Throwable exception, int numFrames) { + return Threads.jstackStr(exception, numFrames); + } + + /** + * Returns a reference to the currently executing thread object. + * + * @return the currently executing thread. + */ + public static Thread currentThread() { + return Threads.currentThread(); + } + + /** + * Returns the identifier of the given Thread. The thread ID is a positive long number + * generated when the given thread was created. The thread ID is unique and remains unchanged + * during its lifetime. When a thread is terminated, the thread ID may be reused. + */ + public static long threadId(Thread thread) { + return Threads.threadId(thread); + } + + /** + * Returns the state of the given thread. This method is designed for use in monitoring of the + * system state, not for synchronization control. + */ + public static Thread.State threadState(Thread thread) { + return Threads.threadState(thread); + } + + /** + * Returns true if and only if the current thread holds the monitor lock on the specified + * object. + * + *

This method is designed to allow a program to assert that the current thread already holds a + * specified lock: + * + *

+   *     assert Thread.holdsLock(obj);
+   * 
+ * + * @param obj the object on which to test lock ownership + * @return true if the current thread holds the monitor lock on the specified object. + * @throws NullPointerException if obj is null + */ + public static boolean holdsLock(Object obj) { + return Threads.holdsLock(obj); + } + + /** Prints the Java level deadlocks detected (if any). */ + public static void deadlocks() { + Threads.deadlocks(); + } + + /** + * Prints deadlocks detected (if any). Optionally prints stack trace of the deadlocked threads. + * + * @param stackTrace boolean flag to specify whether to print stack traces of deadlocked threads + * or not. + */ + public static void deadlocks(boolean stackTrace) { + Threads.deadlocks(stackTrace); + } + + /** + * Returns the name of the given thread. + * + * @param thread thread whose name is returned + */ + public static String name(Thread thread) { + return Threads.name(thread); + } + + // class loader access + + /** + * Returns the class loader for the given class. Some implementations may use null to represent + * the bootstrap class loader. This method will return null in such implementations if this class + * was loaded by the bootstrap class loader. + * + * @param clazz the Class for which the class loader is returned + */ + public static ClassLoader loader(Class clazz) { + return clazz.getClassLoader(); + } + + /** + * Returns the parent class loader of the given loader. Some implementations may use null + * to represent the bootstrap class loader. This method will return null in such + * implementations if this class loader's parent is the bootstrap class loader. + * + * @param loader the loader for which the parent loader is returned + * @return The parent ClassLoader + */ + public static ClassLoader parentLoader(ClassLoader loader) { + return loader.getParent(); + } + + // java.lang.Object methods + + /** + * Returns a string representation of the object. In general, the toString method + * returns a string that "textually represents" this object. The result should be a concise but + * informative representation that is easy for a person to read. For bootstrap classes, returns + * the result of calling Object.toString() override. For non-bootstrap classes, default toString() + * value [className@hashCode] is returned. + * + * @param obj the object whose string representation is returned + * @return a string representation of the given object. + */ + public static String str(Object obj) { + return Strings.str(obj); + } + + /** + * Returns identity string of the form class-name@identity-hash + * + * @param obj object for which identity string is returned + * @return identity string + */ + public static String identityStr(Object obj) { + int hashCode = java.lang.System.identityHashCode(obj); + return obj.getClass().getName() + "@" + Integer.toHexString(hashCode); + } + + /** + * Returns a hash code value for the object. This method is supported for the benefit of + * hashtables such as those provided by java.util.Hashtable. For bootstrap classes, + * returns the result of calling Object.hashCode() override. For non-bootstrap classes, the + * identity hash code is returned. + * + * @param obj the Object whose hash code is returned. + * @return a hash code value for the given object. + */ + public static int hash(Object obj) { + if (obj.getClass().getClassLoader() == null) { + return obj.hashCode(); + } else { + return java.lang.System.identityHashCode(obj); + } + } + + /** + * Returns the same hash code for the given object as would be returned by the default method + * hashCode(), whether or not the given object's class overrides hashCode(). The hash code for the + * null reference is zero. + * + * @param obj object for which the hashCode is to be calculated + * @return the hashCode + */ + public static int identityHashCode(Object obj) { + return java.lang.System.identityHashCode(obj); + } + + /** + * Indicates whether two given objects are "equal to" one another. For bootstrap classes, returns + * the result of calling Object.equals() override. For non-bootstrap classes, the reference + * identity comparison is done. + * + * @param obj1 first object to compare equality + * @param obj2 second object to compare equality + * @return true if the given objects are equal; false otherwise. + */ + public static boolean compare(Object obj1, Object obj2) { + return BTraceRuntime.compare(obj1, obj2); + } + + // reflection + + /** + * Returns the runtime class of the given Object. + * + * @param obj the Object whose Class is returned + * @return the Class object of given object + */ + public static Class classOf(Object obj) { + return Reflective.classOf(obj); + } + + /** + * Checks whether the provided object is an instance of the named class. Note: this method + * can be rather CPU intensive, use with caution + * + * @param obj the object to check + * @param className the class name; as a special case {@code void} can be provided to check + * whether the instance is a void value wrapper - {@linkplain AnyType#VOID} + * @return {@code true} if the object can be assigned to an instance of 'className' type + * @since 1.3.5 + */ + public static boolean instanceOf(Object obj, String className) { + return BTraceRuntime.instanceOf(obj, className); + } + + /** + * Returns the Class object representing the class or interface that declares the field + * represented by the given Field object. + * + * @param field whose declaring Class is returned + */ + public static Class declaringClass(Field field) { + return Reflective.declaringClass(field); + } + + /** Returns the name of the given Class object. */ + public static String name(Class clazz) { + return Reflective.name(clazz); + } + + /** + * Returns the name of the Field object. + * + * @param field Field for which name is returned + * @return name of the given field + */ + public static String name(Field field) { + return Reflective.name(field); + } + + /** + * Returns the type of the Field object. + * + * @param field Field for which type is returned + * @return type of the given field + */ + public static Class type(Field field) { + return Reflective.type(field); + } + + /** Returns the access flags of the given Class. */ + public static int accessFlags(Class clazz) { + return Reflective.accessFlags(clazz); + } + + /** Returns the access flags of the given Field. */ + public static int accessFlags(Field field) { + return Reflective.accessFlags(field); + } + + /** Returns the current context class loader */ + public static ClassLoader contextClassLoader() { + return Reflective.contextClassLoader(); + } + + // get Class of the given name + + /** Returns Class object for given class name. */ + public static Class classForName(String name) { + return Reflective.classForName(name); + } + + /** Returns the Class for the given class name using the given class loader. */ + public static Class classForName(String name, ClassLoader cl) { + return Reflective.classForName(name, cl); + } + + /** + * Determines if the class or interface represented by the first Class object is + * either the same as, or is a superclass or superinterface of, the class or interface represented + * by the second Class parameter. It returns true if so; otherwise it + * returns false. + */ + public static boolean isAssignableFrom(Class a, Class b) { + return Reflective.isAssignableFrom(a, b); + } + + /** + * Determines if the specified Object is assignment-compatible with the object + * represented by the specified Class. This method is the dynamic equivalent of the + * Java language instanceof operator. The method returns true if the + * specified Object argument is non-null and can be cast to the reference type + * represented by this Class object without raising a ClassCastException. + * It returns false otherwise. + * + * @param clazz the class that is checked. + * @param obj the object to check. + * @return true if obj is an instance of the given class. + */ + public static boolean isInstance(Class clazz, Object obj) { + return Reflective.isInstance(clazz, obj); + } + + /** + * Returns the Class representing the superclass of the entity (class, interface, + * primitive type or void) represented by the given Class. If the given Class + * represents either the Object class, an interface, a primitive type, or + * void, then null is returned. If the given object represents an array class then the Class + * object representing the Object class is returned. + * + * @param clazz the Class whose super class is returned. + * @return the superclass of the class represented by the given object. + */ + public static Class getSuperclass(Class clazz) { + return Reflective.getSuperclass(clazz); + } + + /** + * Determines if the specified Class object represents an interface type. + * + * @param clazz the Class object to check. + * @return true if the Class represents an interface; false otherwise. + */ + public static boolean isInterface(Class clazz) { + return Reflective.isInterface(clazz); + } + + /** + * Determines if the given Class object represents an array class. + * + * @param clazz Class object to check. + * @return true if the given object represents an array class; false + * otherwise. + */ + public static boolean isArray(Class clazz) { + return Reflective.isArray(clazz); + } + + /** Returns whether the given Class represent primitive type or not. */ + public static boolean isPrimitive(Class clazz) { + return Reflective.isPrimitive(clazz); + } + + /** returns component type of an array Class. */ + public static Class getComponentType(Class clazz) { + return Reflective.getComponentType(clazz); + } + + // Accessing fields by reflection + + /** + * Returns a Field object that reflects the specified declared field of the class or + * interface represented by the given Class object. The name parameter + * is a String that specifies the simple name of the desired field. Returns + * null on not finding field if throwException parameter is false. Else throws + * a RuntimeException when field is not found. + * + * @param clazz Class whose field is returned + * @param name the name of the field + * @param throwException whether to throw exception on failing to find field or not + * @return the Field object for the specified field in this class + */ + public static Field field(Class clazz, String name, boolean throwException) { + return Reflective.field(clazz, name, throwException); + } + + /** + * Returns a Field object that reflects the specified declared field of the class or + * interface represented by the given Class object. The name parameter + * is a String that specifies the simple name of the desired field. Throws a + * RuntimeException when field is not found. + * + * @param clazz Class whose field is returned + * @param name the name of the field + * @return the Field object for the specified field in this class + */ + public static Field field(Class clazz, String name) { + return Reflective.field(clazz, name); + } + + /** + * Returns a Field object that reflects the specified declared field of the class or + * interface represented by the given Class object. The name parameter + * is a String that specifies the simple name of the desired field. Returns + * null on not finding field if throwException parameter is false. Else throws + * a RuntimeException when field is not found. + * + * @param clazz Class whose field is returned + * @param name the name of the field + * @param throwException whether to throw exception on failing to find field or not + * @return the Field object for the specified field in this class + */ + public static Field field(String clazz, String name, boolean throwException) { + ClassLoader callerLoader = BTraceRuntime.getCallerClassloader(STACK_DEC); + return Reflective.field(classForName(clazz, callerLoader), name, throwException); + } + + /** + * Returns a Field object that reflects the specified declared field of the class or + * interface represented by the given Class object. The name parameter + * is a String that specifies the simple name of the desired field. Throws a + * RuntimeException when field is not found. + * + * @param clazz Class whose field is returned + * @param name the name of the field + * @return the Field object for the specified field in this class + */ + public static Field field(String clazz, String name) { + ClassLoader callerLoader = BTraceRuntime.getCallerClassloader(STACK_DEC); + return Reflective.field(classForName(clazz, callerLoader), name); + } + + // field value get methods + + /** + * Gets the value of a static byte field. + * + * @param field Field object whose value is returned. + * @return the value of the byte field + */ + public static byte getByte(Field field) { + return Reflective.getByte(field); + } + + /** + * Gets the value of an instance byte field. + * + * @param field Field object whose value is returned. + * @param obj the object to extract the byte value from + * @return the value of the byte field + */ + public static byte getByte(Field field, Object obj) { + return Reflective.getByte(field, obj); + } + + /** + * Gets the value of a static short field. + * + * @param field Field object whose value is returned. + * @return the value of the short field + */ + public static short getShort(Field field) { + return Reflective.getShort(field); + } + + /** + * Gets the value of an instance short field. + * + * @param field Field object whose value is returned. + * @param obj the object to extract the short value from + * @return the value of the short field + */ + public static short getShort(Field field, Object obj) { + return Reflective.getShort(field, obj); + } + + /** + * Gets the value of a static int field. + * + * @param field Field object whose value is returned. + * @return the value of the int field + */ + public static int getInt(Field field) { + return Reflective.getInt(field); + } + + /** + * Gets the value of an instance int field. + * + * @param field Field object whose value is returned. + * @param obj the object to extract the int value from + * @return the value of the int field + */ + public static int getInt(Field field, Object obj) { + return Reflective.getInt(field, obj); + } + + /** + * Gets the value of a static long field. + * + * @param field Field object whose value is returned. + * @return the value of the long field + */ + public static long getLong(Field field) { + return Reflective.getLong(field); + } + + /** + * Gets the value of an instance long field. + * + * @param field Field object whose value is returned. + * @param obj the object to extract the long value from + * @return the value of the long field + */ + public static long getLong(Field field, Object obj) { + return Reflective.getLong(field, obj); + } + + /** + * Gets the value of a static float field. + * + * @param field Field object whose value is returned. + * @return the value of the float field + */ + public static float getFloat(Field field) { + return Reflective.getFloat(field); + } + + /** + * Gets the value of an instance float field. + * + * @param field Field object whose value is returned. + * @param obj the object to extract the float value from + * @return the value of the float field + */ + public static float getFloat(Field field, Object obj) { + return Reflective.getFloat(field, obj); + } + + /** + * Gets the value of a static double field. + * + * @param field Field object whose value is returned. + * @return the value of the double field + */ + public static double getDouble(Field field) { + return Reflective.getDouble(field); + } + + /** + * Gets the value of an instance double field. + * + * @param field Field object whose value is returned. + * @param obj the object to extract the double value from + * @return the value of the double field + */ + public static double getDouble(Field field, Object obj) { + return Reflective.getDouble(field, obj); + } + + /** + * Gets the value of a static boolean field. + * + * @param field Field object whose value is returned. + * @return the value of the boolean field + */ + public static boolean getBoolean(Field field) { + return Reflective.getBoolean(field); + } + + /** + * Gets the value of an instance boolean field. + * + * @param field Field object whose value is returned. + * @param obj the object to extract the boolean value from + * @return the value of the boolean field + */ + public static boolean getBoolean(Field field, Object obj) { + return Reflective.getBoolean(field, obj); + } + + /** + * Gets the value of a static char field. + * + * @param field Field object whose value is returned. + * @return the value of the char field + */ + public static char getChar(Field field) { + return Reflective.getChar(field); + } + + /** + * Gets the value of an instance char field. + * + * @param field Field object whose value is returned. + * @param obj the object to extract the char value from + * @return the value of the char field + */ + public static char getChar(Field field, Object obj) { + return Reflective.getChar(field, obj); + } + + /** + * Gets the value of a static reference field. + * + * @param field Field object whose value is returned. + * @return the value of the reference field + */ + public static Object get(Field field) { + return Reflective.get(field); + } + + /** + * Gets the value of an instance reference field. + * + * @param field Field object whose value is returned. + * @param obj the object to extract the reference value from + * @return the value of the reference field + */ + public static Object get(Field field, Object obj) { + return Reflective.get(field, obj); + } + + // weak, soft references + + /** + * Creates and returns a weak reference to the given object. + * + * @param obj object for which a weak reference is created. + * @return a weak reference to the given object. + */ + public static WeakReference weakRef(Object obj) { + return References.weakRef(obj); + } + + /** + * Creates and returns a soft reference to the given object. + * + * @param obj object for which a soft reference is created. + * @return a soft reference to the given object. + */ + public static SoftReference softRef(Object obj) { + return References.softRef(obj); + } + + /** + * Returns the given reference object's referent. If the reference object has been cleared, either + * by the program or by the garbage collector, then this method returns null. + * + * @param ref reference object whose referent is returned. + * @return The object to which the reference refers, or null if the reference object + * has been cleared. + */ + public static Object deref(Reference ref) { + return References.deref(ref); + } + + // probe point access + + /** + * Returns the Class object of the currently probed (or traced) class. + * + * @deprecated Since 1.1. Use {@linkplain ProbeClassName} and {@linkplain Self} annotations + * instead + */ + @Deprecated + public static Class probeClass() { + return BTraceRuntime.getCallerClass(STACK_DEC); + } + + /** + * Returns the currently probed method's name. + * + * @deprecated Since 1.1. Use {@linkplain ProbeMethodName} annotation instead + */ + @Deprecated + public static String probeMethod() { + StackTraceElement[] stack = Thread.currentThread().getStackTrace(); + if (stack.length >= 4) { + return stack[3].getMethodName(); + } else { + return null; + } + } + + /** Returns the currently probed source line number (if available). */ + public static int probeLine() { + StackTraceElement[] stack = Thread.currentThread().getStackTrace(); + if (stack.length >= 4) { + return stack[3].getLineNumber(); + } else { + return -1; + } + } + + // printing values + + /** + * Prints the given Map. + * + * @param map Map that is printed. + */ + public static void printMap(Map map) { + BTraceRuntime.printMap(map); + } + + /** + * Prints the given Map. + * + * @param name - the name of the map + * @param data - the map data + */ + public static void printStringMap(String name, Map data) { + BTraceRuntime.printStringMap(name, data); + } + + /** + * Prints the given Map. + * + * @param name - the name of the map + * @param data - the map data + */ + public static void printNumberMap(String name, Map data) { + BTraceRuntime.printNumberMap(name, data); + } + + /** + * Prints a number. + * + * @param name - name of the number data + * @param value - value of the numerical data + */ + public static void printNumber(String name, Number value) { + BTraceRuntime.printNumber(name, value); + } + + /** Prints the elements of the given array as comma separated line bounded by '[' and ']'. */ + public static void printArray(Object[] array) { + StringBuilder buf = new StringBuilder(); + buf.append('['); + for (Object obj : array) { + buf.append(Strings.str(obj)); + buf.append(", "); + } + buf.append(']'); + println(buf.toString()); + } + + /** + * Print all instance fields of an object as name-value pairs. Includes the inherited fields as + * well. + * + * @param obj Object whose fields are printed. + */ + public static void printFields(Object obj) { + Reflective.printFields(obj, false); + } + + /** + * Print all instance fields of an object as name-value pairs. Includes the inherited fields as + * well. Optionally, prints name of the declaring class before each field - so that if same named + * field in super class chain may be disambiguated. + * + * @param obj Object whose fields are printed. + * @param classNamePrefix flag to tell whether to prefix field names names by class name or not. + */ + public static void printFields(Object obj, boolean classNamePrefix) { + Reflective.printFields(obj, classNamePrefix); + } + + /** + * Print all static fields of the class as name-value pairs. Includes the inherited fields as + * well. + * + * @param clazz Class whose static fields are printed. + */ + public static void printStaticFields(Class clazz) { + Reflective.printStaticFields(clazz, false); + } + + /** + * Print all static fields of the class as name-value pairs. Includes the inherited fields as + * well. Optionally, prints name of the declaring class before each field - so that if same named + * field in super class chain may be disambiguated. + * + * @param clazz Class whose static fields are printed. + * @param classNamePrefix flag to tell whether to prefix field names names by class name or not. + */ + public static void printStaticFields(Class clazz, boolean classNamePrefix) { + Reflective.printStaticFields(clazz, classNamePrefix); + } + + // various print methods + public static void print(Object obj) { + BTraceRuntime.print(Strings.str(obj)); + } + + /** + * Prints a boolean value. The string produced by {@link + * java.lang.String#valueOf(boolean)} is sent to BTrace client for "printing". + * + * @param b The boolean to be printed + */ + public static void print(boolean b) { + print(Boolean.valueOf(b)); + } + + /** + * Prints a character. The string produced by {@link + * java.lang.Character#valueOf(char)} is sent to BTrace client for "printing". + * + * @param c The char to be printed + */ + public static void print(char c) { + print(Character.valueOf(c)); + } + + /** + * Prints an integer. The string produced by {@link + * java.lang.String#valueOf(int)} is sent to BTrace client for "printing". + * + * @param i The int to be printed + * @see java.lang.Integer#toString(int) + */ + public static void print(int i) { + print(Integer.valueOf(i)); + } + + /** + * Prints a long integer. The string produced by {@link + * java.lang.String#valueOf(long)} is sent to BTrace client for "printing". + * + * @param l The long to be printed + * @see java.lang.Long#toString(long) + */ + public static void print(long l) { + print(Long.valueOf(l)); + } + + /** + * Prints a floating-point number. The string produced by {@link + * java.lang.String#valueOf(float)} is sent to BTrace client for "printing". + * + * @param f The float to be printed + * @see java.lang.Float#toString(float) + */ + public static void print(float f) { + print(Float.valueOf(f)); + } + + /** + * Prints a double-precision floating-point number. The string produced by + * {@link java.lang.String#valueOf(double)} is sent to BTrace client for "printing". + * + * @param d The double to be printed + * @see java.lang.Double#toString(double) + */ + public static void print(double d) { + print(Double.valueOf(d)); + } + + /** Prints the given object and then prints a newline */ + public static void println(Object obj) { + BTraceRuntime.println(Strings.str(obj)); + } + + /** + * Prints a boolean and then terminate the line. This method behaves as though it invokes + * {@link #print(boolean)} and then {@link #println()}. + * + * @param b The boolean to be printed + */ + public static void println(boolean b) { + println(Boolean.valueOf(b)); + } + + /** + * Prints a character and then terminate the line. This method behaves as though it invokes + * {@link #print(char)} and then {@link #println()}. + * + * @param c The char to be printed. + */ + public static void println(char c) { + println(Character.valueOf(c)); + } + + /** + * Prints an integer and then terminate the line. This method behaves as though it invokes + * {@link #print(int)} and then {@link #println()}. + * + * @param i The int to be printed. + */ + public static void println(int i) { + println(Integer.valueOf(i)); + } + + /** + * Prints a long and then terminate the line. This method behaves as though it invokes + * {@link #print(long)} and then {@link #println()}. + * + * @param l a The long to be printed. + */ + public static void println(long l) { + println(Long.valueOf(l)); + } + + /** + * Prints a float and then terminate the line. This method behaves as though it invokes + * {@link #print(float)} and then {@link #println()}. + * + * @param f The float to be printed. + */ + public static void println(float f) { + println(Float.valueOf(f)); + } + + /** + * Prints a double and then terminate the line. This method behaves as though it invokes + * {@link #print(double)} and then {@link #println()}. + * + * @param d The double to be printed. + */ + public static void println(double d) { + println(Double.valueOf(d)); + } + + /** + * Terminates the current line by writing the line separator string. The line separator string is + * defined by the system property line.separator, and is not necessarily a single + * newline character ('\n'). + */ + public static void println() { + BTraceRuntime.println(); + } + + /** + * Returns the start time of the Java virtual machine in milliseconds. This method returns the + * approximate time when the Java virtual machine started. + * + * @return start time of the Java virtual machine in milliseconds. + */ + public static long vmStartTime() { + return Sys.VM.vmStartTime(); + } + + /** + * Returns the uptime of the Java virtual machine in milliseconds. + * + * @return uptime of the Java virtual machine in milliseconds. + */ + public static long vmUptime() { + return Sys.VM.vmUptime(); + } + + /** + * Returns the current time in milliseconds. Note that while the unit of time of the return value + * is a millisecond, the granularity of the value depends on the underlying operating system and + * may be larger. For example, many operating systems measure time in units of tens of + * milliseconds. + * + * @return the difference, measured in milliseconds, between the current time and midnight, + * January 1, 1970 UTC. + */ + public static long timeMillis() { + return Time.millis(); + } + + /** + * Returns the current value of the most precise available system timer, in nanoseconds. + * + *

This method can only be used to measure elapsed time and is not related to any other notion + * of system or wall-clock time. The value returned represents nanoseconds since some fixed but + * arbitrary time (perhaps in the future, so values may be negative). This method provides + * nanosecond precision, but not necessarily nanosecond accuracy. No guarantees are made about how + * frequently values change. Differences in successive calls that span greater than approximately + * 292 years (263 nanoseconds) will not accurately compute elapsed time due to + * numerical overflow. + * + * @return The current value of the system timer, in nanoseconds. + */ + public static long timeNanos() { + return Time.nanos(); + } + + /** + * Generates a string getTimestamp (current date&time) + * + * @param format The format to be used - see {@linkplain SimpleDateFormat} + * @return Returns a string representing current date&time + * @since 1.1 + */ + public static String timestamp(String format) { + return Time.timestamp(format); + } + + /** + * Generates a string getTimestamp (current date&time) in the default system format + * + * @return Returns a string representing current date&time + * @since 1.1 + */ + public static String timestamp() { + return Time.timestamp(); + } + + // String utilities + public static boolean startsWith(String s, String start) { + return Strings.startsWith(s, start); + } + + public static boolean endsWith(String s, String end) { + return Strings.endsWith(s, end); + } + + /** + * This is synonym to "concat". + * + * @see #concat(String, String) + */ + public static String strcat(String str1, String str2) { + return Strings.strcat(str1, str2); + } + + /** Concatenates the specified strings together. */ + public static String concat(String str1, String str2) { + return Strings.concat(str1, str2); + } + + /** + * Compares two strings lexicographically. The comparison is based on the Unicode value of each + * character in the strings. The character sequence represented by the first String + * object is compared lexicographically to the character sequence represented by the second + * string. The result is a negative integer if the first String object + * lexicographically precedes the second string. The result is a positive integer if the first + * String object lexicographically follows the second string. The result is zero if + * the strings are equal; compareTo returns 0 exactly when the {@link + * String#equals(Object)} method would return true. + */ + public static int compareTo(String str1, String str2) { + return Strings.compareTo(str1, str2); + } + + /** + * This is synonym to "compareTo" method. + * + * @see #compareTo + */ + public static int strcmp(String str1, String str2) { + return Strings.strcmp(str1, str2); + } + + /** + * Compares two strings lexicographically, ignoring case differences. This method returns an + * integer whose sign is that of calling compareTo with normalized versions of the + * strings where case differences have been eliminated by calling + * Character.toLowerCase(Character.toUpperCase(character)) on each character. + */ + public static int compareToIgnoreCase(String str1, String str2) { + return Strings.compareToIgnoreCase(str1, str2); + } + + /** + * This is synonym to "compareToIgnoreCase". + * + * @see #compareToIgnoreCase + */ + public static int stricmp(String str1, String str2) { + return Strings.stricmp(str1, str2); + } + + /** Find String within String */ + public static int strstr(String str1, String str2) { + return Strings.strstr(str1, str2); + } + + public static int indexOf(String str1, String str2) { + return Strings.indexOf(str1, str2); + } + + public static int lastIndexOf(String str1, String str2) { + return Strings.lastIndexOf(str1, str2); + } + + /** Substring */ + public static String substr(String str, int start, int length) { + return Strings.substr(str, start, length); + } + + public static String substr(String str, int start) { + return Strings.substr(str, start); + } + + /** + * Returns the length of the given string. The length is equal to the number of Unicode code units in the string. + * + * @param str String whose length is calculated. + * @return the length of the sequence of characters represented by this object. + */ + public static int length(String str) { + return Strings.length(str); + } + + /** + * This is synonym for "length". + * + * @see #length(String) + */ + public static int strlen(String str) { + return Strings.strlen(str); + } + + // regular expression matching + + /** + * Compiles the given regular expression into a pattern. + * + * @param regex The expression to be compiled + * @throws PatternSyntaxException If the expression's syntax is invalid + */ + public static Pattern regexp(String regex) { + return Strings.regexp(regex); + } + + /** + * This is synonym for "regexp". + * + * @see #regexp(String) + */ + public static Pattern pattern(String regex) { + return Strings.pattern(regex); + } + + /** + * Compiles the given regular expression into a pattern with the given flags. + * + * @param regex The expression to be compiled + * @param flags Match flags, a bit mask that may include {@link Pattern#CASE_INSENSITIVE}, {@link + * Pattern#MULTILINE}, {@link Pattern#DOTALL}, {@link Pattern#UNICODE_CASE}, {@link + * Pattern#CANON_EQ}, {@link Pattern#UNIX_LINES}, {@link Pattern#LITERAL} and {@link + * Pattern#COMMENTS} + * @throws IllegalArgumentException If bit values other than those corresponding to the defined + * match flags are set in flags + * @throws PatternSyntaxException If the expression's syntax is invalid + */ + public static Pattern regexp(String regex, int flags) { + return Strings.regexp(regex, flags); + } + + /** + * This is synonym for "regexp". + * + * @see #regexp(String, int) + */ + public static Pattern pattern(String regex, int flags) { + return Strings.pattern(regex, flags); + } + + /** + * Matches the given (precompiled) regular expression and attempts to match the given input + * against it. + */ + public static boolean matches(Pattern regex, String input) { + return Strings.matches(regex, input); + } + + /** + * Compiles the given regular expression and attempts to match the given input against it. + * + *

An invocation of this convenience method of the form + * + *

+ * + *
+   * Pattern.matches(regex, input);
+ * + *
+ * + *

behaves in exactly the same way as the expression + * + *

+ * + *
+   * Pattern.compile(regex).matcher(input).matches()
+ * + *
+ * + *

If a pattern is to be used multiple times, compiling it once and reusing it will be more + * efficient than invoking this method each time. + * + * @param regex The expression to be compiled + * @param input The character sequence to be matched + * @throws PatternSyntaxException If the expression's syntax is invalid + */ + public static boolean matches(String regex, String input) { + return Strings.matches(regex, input); + } + + // Numbers utilities + + /** + * Returns a double value with a positive sign, greater than or equal to 0.0 + * and less than 1.0. Returned values are chosen pseudorandomly with + * (approximately) uniform distribution from that range. + */ + public static double random() { + return Numbers.random(); + } + + /** + * Returns the natural logarithm (base e) of a double value. Special cases: + * + *

    + *
  • If the argument is NaN or less than zero, then the result is NaN. + *
  • If the argument is positive infinity, then the result is positive infinity. + *
  • If the argument is positive zero or negative zero, then the result is negative infinity. + *
+ * + *

The computed result must be within 1 ulp of the exact result. Results must be + * semi-monotonic. + * + * @param a a value + * @return the value ln a, the natural logarithm of a. + */ + public static strictfp double log(double a) { + return Numbers.log(a); + } + + /** + * Returns the base 10 logarithm of a double value. Special cases: + * + *

    + *
  • If the argument is NaN or less than zero, then the result is NaN. + *
  • If the argument is positive infinity, then the result is positive infinity. + *
  • If the argument is positive zero or negative zero, then the result is negative infinity. + *
  • If the argument is equal to 10n for integer n, then the result + * is n. + *
+ * + *

The computed result must be within 1 ulp of the exact result. Results must be + * semi-monotonic. + * + * @param a a value + * @return the base 10 logarithm of a. + */ + public static strictfp double log10(double a) { + return Numbers.log10(a); + } + + /** + * Returns Euler's number e raised to the power of a double value. Special + * cases: + * + *

    + *
  • If the argument is NaN, the result is NaN. + *
  • If the argument is positive infinity, then the result is positive infinity. + *
  • If the argument is negative infinity, then the result is positive zero. + *
+ * + *

The computed result must be within 1 ulp of the exact result. Results must be + * semi-monotonic. + * + * @param a the exponent to raise e to. + * @return the value ea, where e is the base of the natural + * logarithms. + */ + public static strictfp double exp(double a) { + return Numbers.exp(a); + } + + /** + * Returns true if the specified number is a Not-a-Number (NaN) value, false + * otherwise. + * + * @param d the value to be tested. + * @return true if the value of the argument is NaN; false otherwise. + */ + public static boolean isNaN(double d) { + return Numbers.isNaN(d); + } + + /** + * Returns true if the specified number is a Not-a-Number (NaN) value, false + * otherwise. + * + * @param f the value to be tested. + * @return true if the value of the argument is NaN; false otherwise. + */ + public static boolean isNaN(float f) { + return Numbers.isNaN(f); + } + + /** + * Returns true if the specified number is infinitely large in magnitude, false + * otherwise. + * + * @param d the value to be tested. + * @return true if the value of the argument is positive infinity or negative + * infinity; false otherwise. + */ + public static boolean isInfinite(double d) { + return Numbers.isInfinite(d); + } + + /** + * Returns true if the specified number is infinitely large in magnitude, false + * otherwise. + * + * @param f the value to be tested. + * @return true if the value of the argument is positive infinity or negative + * infinity; false otherwise. + */ + public static boolean isInfinite(float f) { + return Numbers.isInfinite(f); + } + + // string parsing methods + + /** + * Parses the string argument as a boolean. The boolean returned represents the value + * true if the string argument is not null and is equal, ignoring case, + * to the string {@code "true"}. + * + *

Example: {@code Boolean.parseBoolean("True")} returns true.
+ * Example: {@code Boolean.parseBoolean("yes")} returns false. + * + * @param s the String containing the boolean representation to be parsed + * @return the boolean represented by the string argument + */ + public static boolean parseBoolean(String s) { + return Numbers.parseBoolean(s); + } + + /** + * Parses the string argument as a signed decimal byte. The characters in the string + * must all be decimal digits, except that the first character may be an ASCII minus sign + * '-' ('\u002D') to indicate a negative value. The resulting byte + * value is returned. + * + * @param s a String containing the byte representation to be parsed + * @return the byte value represented by the argument in decimal + */ + public static byte parseByte(String s) { + return Numbers.parseByte(s); + } + + /** + * Parses the string argument as a signed decimal short. The characters in the string + * must all be decimal digits, except that the first character may be an ASCII minus sign + * '-' ('\u002D') to indicate a negative value. The resulting short + * value is returned. + * + * @param s a String containing the short representation to be parsed + * @return the short value represented by the argument in decimal. + */ + public static short parseShort(String s) { + return Numbers.parseShort(s); + } + + /** + * Parses the string argument as a signed decimal integer. The characters in the string must all + * be decimal digits, except that the first character may be an ASCII minus sign '-' + * ('\u002D') to indicate a negative value. The resulting integer value is + * returned. + * + * @param s a String containing the int representation to be parsed + * @return the integer value represented by the argument in decimal. + */ + public static int parseInt(String s) { + return Numbers.parseInt(s); + } + + /** + * Parses the string argument as a signed decimal long. The characters in the string + * must all be decimal digits, except that the first character may be an ASCII minus sign + * '-' (\u002D') to indicate a negative value. The resulting long + * value is returned. + * + *

Note that neither the character L ('\u004C') nor l + * ('\u006C') is permitted to appear at the end of the string as a type + * indicator, as would be permitted in Java programming language source code. + * + * @param s a String containing the long representation to be parsed + * @return the long represented by the argument in decimal. + */ + public static long parseLong(String s) { + return Numbers.parseLong(s); + } + + /** + * Returns a new float initialized to the value represented by the specified + * String, as performed by the valueOf method of class Float. + * + * @param s the string to be parsed. + * @return the float value represented by the string argument. + */ + public static float parseFloat(String s) { + return Numbers.parseFloat(s); + } + + /** + * Returns a new double initialized to the value represented by the specified + * String, as performed by the valueOf methcod of class Double. + * + * @param s the string to be parsed. + * @return the double value represented by the string argument. + */ + public static double parseDouble(String s) { + return Numbers.parseDouble(s); + } + + // boxing methods + + /** + * Returns a Boolean instance representing the specified boolean value. If the + * specified boolean value is true, this method returns Boolean.TRUE; + * if it is false, this method returns Boolean.FALSE. + * + * @param b a boolean value. + * @return a Boolean instance representing b. + */ + public static Boolean box(boolean b) { + return Numbers.box(b); + } + + /** + * Returns a Character instance representing the specified char value. + * + * @param c a char value. + * @return a Character instance representing c. + */ + public static Character box(char c) { + return Numbers.box(c); + } + + /** + * Returns a Byte instance representing the specified byte value. + * + * @param b a byte value. + * @return a Byte instance representing b. + */ + public static Byte box(byte b) { + return Numbers.box(b); + } + + /** + * Returns a Short instance representing the specified short value. + * + * @param s a short value. + * @return a Short instance representing s. + */ + public static Short box(short s) { + return Numbers.box(s); + } + + /** + * Returns a Integer instance representing the specified int value. + * + * @param i an int value. + * @return a Integer instance representing i. + */ + public static Integer box(int i) { + return Numbers.box(i); + } + + /** + * Returns a Long instance representing the specified long value. + * + * @param l a long value. + * @return a Long instance representing l. + */ + public static Long box(long l) { + return Numbers.box(l); + } + + /** + * Returns a Float instance representing the specified float value. + * + * @param f a float value. + * @return a Float instance representing f. + */ + public static Float box(float f) { + return Numbers.box(f); + } + + /** + * Returns a Double instance representing the specified double value. + * + * @param d a double value. + * @return a Double instance representing d. + */ + public static Double box(double d) { + return Numbers.box(d); + } + + // unboxing methods + + /** + * Returns the value of the given Boolean object as a boolean primitive. + * + * @param b the Boolean object whose value is returned. + * @return the primitive boolean value of the object. + */ + public static boolean unbox(Boolean b) { + return Numbers.unbox(b); + } + + /** + * Returns the value of the given Character object as a char primitive. + * + * @param ch the Character object whose value is returned. + * @return the primitive char value of the object. + */ + public static char unbox(Character ch) { + return Numbers.unbox(ch); + } + + /** + * Returns the value of the specified Byte as a byte. + * + * @param b Byte that is unboxed + * @return the byte value represented by the Byte. + */ + public static byte unbox(Byte b) { + return Numbers.unbox(b); + } + + /** + * Returns the short value represented by Short. + * + * @param s Short that is unboxed. + * @return the short value represented by the Short. + */ + public static short unbox(Short s) { + return Numbers.unbox(s); + } + + /** + * Returns the value of represented by Integer. + * + * @param i Integer that is unboxed. + * @return the int value represented by the Integer. + */ + public static int unbox(Integer i) { + return Numbers.unbox(i); + } + + /** + * Returns the long value represented by the specified Long. + * + * @param l Long to be unboxed. + * @return the long value represented by the Long. + */ + public static long unbox(Long l) { + return Numbers.unbox(l); + } + + /** + * Returns the float value represented by the specified Float. + * + * @param f Float to be unboxed. + * @return the float value represented by the Float. + */ + public static float unbox(Float f) { + return Numbers.unbox(f); + } + + /** + * Returns the double value represented by the specified Double. + * + * @param d Double to be unboxed. + */ + public static double unbox(Double d) { + return Numbers.unbox(d); + } + + // primitive types to String conversion + + /** + * Returns a String object representing the specified boolean. If the specified boolean + * is true, then the string {@code "true"} will be returned, otherwise the string + * {@code "false"} will be returned. + * + * @param b the boolean to be converted + * @return the string representation of the specified boolean + */ + public static String str(boolean b) { + return Strings.str(b); + } + + /** + * Returns a String object representing the specified char. The result + * is a string of length 1 consisting solely of the specified char. + * + * @param c the char to be converted + * @return the string representation of the specified char + */ + public static String str(char c) { + return Strings.str(c); + } + + /** + * Returns a String object representing the specified integer. The argument is + * converted to signed decimal representation and returned as a string. + * + * @param i an integer to be converted. + * @return a string representation of the argument in base 10. + */ + public static String str(int i) { + return Strings.str(i); + } + + /** + * Returns a string representation of the integer argument as an unsigned integer in base 16. + * + *

The unsigned integer value is the argument plus 232 if the argument is negative; + * otherwise, it is equal to the argument. This value is converted to a string of ASCII digits in + * hexadecimal (base 16) with no extra leading 0s. If the unsigned magnitude is + * zero, it is represented by a single zero character '0' ('\u0030' + * ); otherwise, the first character of the representation of the unsigned magnitude will not be + * the zero character. The following characters are used as hexadecimal digits: + * + *

+ * + *
+   * 0123456789abcdef
+   * 
+ * + *
+ * + * These are the characters '\u0030' through '\u0039' and + * '\u0061' through '\u0066'. + * + * @param i an integer to be converted to a string. + * @return the string representation of the unsigned integer value represented by the argument in + * hexadecimal (base 16). + */ + public static String toHexString(int i) { + return Strings.toHexString(i); + } + + /** + * Returns a String object representing the specified long. The argument + * is converted to signed decimal representation and returned as a string. + * + * @param l a long to be converted. + * @return a string representation of the argument in base 10. + */ + public static String str(long l) { + return Strings.str(l); + } + + /** + * Returns a string representation of the long argument as an unsigned integer in + * base 16. + * + *

The unsigned long value is the argument plus 264 if the argument is + * negative; otherwise, it is equal to the argument. This value is converted to a string of ASCII + * digits in hexadecimal (base 16) with no extra leading 0s. If the unsigned + * magnitude is zero, it is represented by a single zero character '0' ( + * '\u0030'); otherwise, the first character of the representation of the unsigned + * magnitude will not be the zero character. The following characters are used as hexadecimal + * digits: + * + *

+ * + *
+   * 0123456789abcdef
+   * 
+ * + *
+ * + * These are the characters '\u0030' through '\u0039' and + * '\u0061' through '\u0066'. + * + * @param l a long to be converted to a string. + * @return the string representation of the unsigned long value represented by the + * argument in hexadecimal (base 16). + */ + public static String toHexString(long l) { + return Strings.toHexString(l); + } + + /** + * Returns a string representation of the float argument. All characters mentioned + * below are ASCII characters. + * + *
    + *
  • If the argument is NaN, the result is the string "NaN". + *
  • Otherwise, the result is a string that represents the sign and magnitude (absolute value) + * of the argument. If the sign is negative, the first character of the result is '- + * ' ('\u002D'); if the sign is positive, no sign character appears + * in the result. As for the magnitude m: + *
      + *
    • If m is infinity, it is represented by the characters "Infinity" + * ; thus, positive infinity produces the result "Infinity" and + * negative infinity produces the result "-Infinity". + *
    • If m is zero, it is represented by the characters "0.0"; thus, + * negative zero produces the result "-0.0" and positive zero produces + * the result "0.0". + *
    • If m is greater than or equal to 10-3 but less than + * 107, then it is represented as the integer part of m, in decimal + * form with no leading zeroes, followed by '.' ('\u002E' + * ), followed by one or more decimal digits representing the fractional part + * of m. + *
    • If m is less than 10-3 or greater than or equal to + * 107, then it is represented in so-called "computerized scientific + * notation." Let n be the unique integer such that 10n <= + * m < 10n+1; then let a be the mathematically + * exact quotient of m and 10n so that 1 <= a < + * 10. The magnitude is then represented as the integer part of a, as a single + * decimal digit, followed by '.' ('\u002E'), followed + * by decimal digits representing the fractional part of a, followed by the + * letter 'E' ('\u0045'), followed by a representation + * of n as a decimal integer, as produced by the method {@link + * java.lang.Integer#toString(int)}. + *
    + *
+ * + * How many digits must be printed for the fractional part of m or a? There must be + * at least one digit to represent the fractional part, and beyond that as many, but only as many, + * more digits as are needed to uniquely distinguish the argument value from adjacent values of + * type float. That is, suppose that x is the exact mathematical value + * represented by the decimal representation produced by this method for a finite nonzero argument + * f. Then f must be the float value nearest to x; or, if two + * float values are equally close to x, then f must be one of them and + * the least significant bit of the significand of f must be 0. + * + *

+ * + * @param f the float to be converted. + * @return a string representation of the argument. + */ + public static String str(float f) { + return Strings.str(f); + } + + /** + * Returns a string representation of the double argument. All characters mentioned + * below are ASCII characters. + * + *

    + *
  • If the argument is NaN, the result is the string "NaN". + *
  • Otherwise, the result is a string that represents the sign and magnitude (absolute value) + * of the argument. If the sign is negative, the first character of the result is '- + * ' ('\u002D'); if the sign is positive, no sign character appears + * in the result. As for the magnitude m: + *
      + *
    • If m is infinity, it is represented by the characters "Infinity" + * ; thus, positive infinity produces the result "Infinity" and + * negative infinity produces the result "-Infinity". + *
    • If m is zero, it is represented by the characters "0.0"; thus, + * negative zero produces the result "-0.0" and positive zero produces + * the result "0.0". + *
    • If m is greater than or equal to 10-3 but less than + * 107, then it is represented as the integer part of m, in decimal + * form with no leading zeroes, followed by '.' ('\u002E' + * ), followed by one or more decimal digits representing the fractional part + * of m. + *
    • If m is less than 10-3 or greater than or equal to + * 107, then it is represented in so-called "computerized scientific + * notation." Let n be the unique integer such that 10n <= + * m < 10n+1; then let a be the mathematically + * exact quotient of m and 10n so that 1 <= a < + * 10. The magnitude is then represented as the integer part of a, as a single + * decimal digit, followed by '.' ('\u002E'), followed + * by decimal digits representing the fractional part of a, followed by the + * letter 'E' ('\u0045'), followed by a representation + * of n as a decimal integer, as produced by the method {@link + * Integer#toString(int)}. + *
    + *
+ * + * How many digits must be printed for the fractional part of m or a? There must be + * at least one digit to represent the fractional part, and beyond that as many, but only as many, + * more digits as are needed to uniquely distinguish the argument value from adjacent values of + * type double. That is, suppose that x is the exact mathematical value + * represented by the decimal representation produced by this method for a finite nonzero argument + * d. Then d must be the double value nearest to x; or if two + * double values are equally close to x, then d must be one of them and + * the least significant bit of the significand of d must be 0. + * + *

+ * + * @param d the double to be converted. + * @return a string representation of the argument. + */ + public static String str(double d) { + return Strings.str(d); + } + + /** + * Exits the BTrace session -- note that the particular client's tracing session exits and not the + * observed/traced program! After exit call, the trace action method terminates immediately and no + * other probe action method (of that client) will be called after that. + * + * @param exitCode exit value sent to the client + */ + public static void exit(int exitCode) { + Sys.exit(exitCode); + } + + /** + * This is same as exit(int) except that the exit code is zero. + * + * @see #exit(int) + */ + public static void exit() { + Sys.exit(); + } + + /** accessing jvmstat (perf) int counter */ + public static long perfInt(String name) { + return Counters.perfInt(name); + } + + /** accessing jvmstat (perf) long counter */ + public static long perfLong(String name) { + return Counters.perfLong(name); + } + + /** accessing jvmstat (perf) String counter */ + public static String perfString(String name) { + return Counters.perfString(name); + } + + /** Operating on maps */ + // Create a new map + public static Map newHashMap() { + return Collections.newHashMap(); + } + + public static Map newWeakMap() { + return Collections.newWeakMap(); + } + + public static Deque newDeque() { + return Collections.newDeque(); + } + + // get a particular item from a Map + public static V get(Map map, K key) { + return Collections.get(map, key); + } + + // check whether an item exists + public static boolean containsKey(Map map, K key) { + return Collections.containsKey(map, key); + } + + public static boolean containsValue(Map map, V value) { + return Collections.containsValue(map, value); + } + + // put a particular item into a Map + public static V put(Map map, K key, V value) { + return Collections.put(map, key, value); + } + + // remove a particular item from a Map + public static V remove(Map map, K key) { + return Collections.remove(map, key); + } + + // clear all items from a Map + public static void clear(Map map) { + Collections.clear(map); + } + + // return the size of a Map + public static int size(Map map) { + return Collections.size(map); + } + + public static boolean isEmpty(Map map) { + return Collections.isEmpty(map); + } + + // operations on collections + public static int size(Collection coll) { + return Collections.size(coll); + } + + public static boolean isEmpty(Collection coll) { + return Collections.isEmpty(coll); + } + + public static boolean contains(Collection coll, Object obj) { + return Collections.contains(coll, obj); + } + + public static boolean contains(Object[] array, Object value) { + return Collections.contains(array, value); + } + + // operations on Deque + public static void push(Deque queue, V value) { + Collections.push(queue, value); + } + + public static V poll(Deque queue) { + return Collections.poll(queue); + } + + public static V peek(Deque queue) { + return Collections.peek(queue); + } + + public static void addLast(Deque queue, V value) { + Collections.addLast(queue, value); + } + + public static V peekFirst(Deque queue) { + return Collections.peekFirst(queue); + } + + public static V peekLast(Deque queue) { + return Collections.peekLast(queue); + } + + public static V removeLast(Deque queue) { + return Collections.removeLast(queue); + } + + public static V removeFirst(Deque queue) { + return Collections.removeFirst(queue); + } + + /** + * Returns the current instrumentation level. + * + *

Instrumentation level is used in evaluating {@linkplain OnMethod#enableAt()} expressions to + * enable/disable the probe handler. + * + * @return the instrumentation level (non negative integer) + * @since 1.3.4 + */ + public static int getInstrumentationLevel() { + return BTraceRuntime.getInstrumentationLevel(); + } + + /** + * Sets the current instrumentation level. + * + *

Instrumentation level is used in evaluating {@linkplain OnMethod#enableAt()} expressions to + * enable/disable the probe handler. + * + * @param level an arbitrary non negative integer number + * @since 1.3.4 + */ + public static void setInstrumentationLevel(int level) { + if (level >= 0) { + BTraceRuntime.setInstrumentationLevel(level); + } + } + + /** + * Returns n'th command line argument. null if not available. + * + * @param n command line argument index + * @return n'th command line argument + */ + public static String $(int n) { + return Sys.$(n); + } + + /** Returns the process id of the currently BTrace'd process. */ + public static int getpid() { + return Sys.getpid(); + } + + /** Returns the number of command line arguments. */ + public static int $length() { + return Sys.$length(); + } + + // atomic stuff + + /** + * Creates a new AtomicInteger with the given initial value. + * + * @param initialValue the initial value + */ + public static AtomicInteger newAtomicInteger(int initialValue) { + return Atomic.newAtomicInteger(initialValue); + } + + /** + * Gets the current value of the given AtomicInteger. + * + * @param ai AtomicInteger whose value is returned. + * @return the current value + */ + public static int get(AtomicInteger ai) { + return Atomic.get(ai); + } + + /** + * Sets to the given value to the given AtomicInteger. + * + * @param ai AtomicInteger whose value is set. + * @param newValue the new value + */ + public static void set(AtomicInteger ai, int newValue) { + Atomic.set(ai, newValue); + } + + /** + * Eventually sets to the given value to the given AtomicInteger. + * + * @param ai AtomicInteger whose value is lazily set. + * @param newValue the new value + */ + public static void lazySet(AtomicInteger ai, int newValue) { + Atomic.lazySet(ai, newValue); + } + + /** + * Atomically sets the value of given AtomitInteger to the given updated value if the current + * value {@code ==} the expected value. + * + * @param ai AtomicInteger whose value is compared and set. + * @param expect the expected value + * @param update the new value + * @return true if successful. False return indicates that the actual value was not equal to the + * expected value. + */ + public static boolean compareAndSet(AtomicInteger ai, int expect, int update) { + return Atomic.compareAndSet(ai, expect, update); + } + + /** + * Atomically sets the value to the given updated value if the current value {@code ==} the + * expected value. + * + *

May fail spuriously and does not provide + * ordering guarantees, so is only rarely an appropriate alternative to {@code compareAndSet}. + * + * @param ai AtomicInteger whose value is weakly compared and set. + * @param expect the expected value + * @param update the new value + * @return true if successful. + */ + public static boolean weakCompareAndSet(AtomicInteger ai, int expect, int update) { + return Atomic.weakCompareAndSet(ai, expect, update); + } + + /** + * Atomically increments by one the current value of given AtomicInteger. + * + * @param ai AtomicInteger that is incremented. + * @return the previous value + */ + public static int getAndIncrement(AtomicInteger ai) { + return Atomic.getAndIncrement(ai); + } + + /** + * Atomically decrements by one the current value of given AtomicInteger. + * + * @param ai AtomicInteger that is decremented. + * @return the previous value + */ + public static int getAndDecrement(AtomicInteger ai) { + return Atomic.getAndDecrement(ai); + } + + /** + * Atomically increments by one the current value of given AtomicInteger. + * + * @param ai AtomicInteger that is incremented. + * @return the updated value + */ + public static int incrementAndGet(AtomicInteger ai) { + return Atomic.incrementAndGet(ai); + } + + /** + * Atomically decrements by one the current value of given AtomicInteger. + * + * @param ai AtomicInteger whose value is decremented. + * @return the updated value + */ + public static int decrementAndGet(AtomicInteger ai) { + return Atomic.decrementAndGet(ai); + } + + /** + * Atomically adds the given value to the current value. + * + * @param ai AtomicInteger whose value is added to. + * @param delta the value to add + * @return the previous value + */ + public static int getAndAdd(AtomicInteger ai, int delta) { + return Atomic.getAndAdd(ai, delta); + } + + /** + * Atomically adds the given value to the current value. + * + * @param ai AtomicInteger whose value is added to. + * @param delta the value to add + * @return the updated value + */ + public static int addAndGet(AtomicInteger ai, int delta) { + return Atomic.addAndGet(ai, delta); + } + + /** + * Atomically sets to the given value and returns the old value. + * + * @param ai AtomicInteger whose value is set. + * @param newValue the new value + * @return the previous value + */ + public static int getAndSet(AtomicInteger ai, int newValue) { + return Atomic.getAndSet(ai, newValue); + } + + /** + * Creates a new AtomicLong with the given initial value. + * + * @param initialValue the initial value + */ + public static AtomicLong newAtomicLong(long initialValue) { + return Atomic.newAtomicLong(initialValue); + } + + /** + * Gets the current value the given AtomicLong. + * + * @param al AtomicLong whose value is returned. + * @return the current value + */ + public static long get(AtomicLong al) { + return Atomic.get(al); + } + + /** + * Sets to the given value. + * + * @param al AtomicLong whose value is set. + * @param newValue the new value + */ + public static void set(AtomicLong al, long newValue) { + Atomic.set(al, newValue); + } + + /** + * Eventually sets to the given value to the given AtomicLong. + * + * @param al AtomicLong whose value is set. + * @param newValue the new value + */ + public static void lazySet(AtomicLong al, long newValue) { + Atomic.lazySet(al, newValue); + } + + /** + * Atomically sets the value to the given updated value if the current value {@code ==} the + * expected value. + * + * @param al AtomicLong whose value is compared and set. + * @param expect the expected value + * @param update the new value + * @return true if successful. False return indicates that the actual value was not equal to the + * expected value. + */ + public static boolean compareAndSet(AtomicLong al, long expect, long update) { + return Atomic.compareAndSet(al, expect, update); + } + + /** + * Atomically sets the value to the given updated value if the current value {@code ==} the + * expected value. + * + *

May fail spuriously and does not provide ordering guarantees, so is only rarely an + * appropriate alternative to {@code compareAndSet}. + * + * @param al AtomicLong whose value is compared and set. + * @param expect the expected value + * @param update the new value + * @return true if successful. + */ + public static boolean weakCompareAndSet(AtomicLong al, long expect, long update) { + return Atomic.weakCompareAndSet(al, expect, update); + } + + /** + * Atomically increments by one the current value. + * + * @param al AtomicLong whose value is incremented. + * @return the previous value + */ + public static long getAndIncrement(AtomicLong al) { + return Atomic.getAndIncrement(al); + } + + /** + * Atomically decrements by one the current value. + * + * @param al AtomicLong whose value is decremented. + * @return the previous value + */ + public static long getAndDecrement(AtomicLong al) { + return Atomic.getAndDecrement(al); + } + + /** + * Atomically increments by one the current value. + * + * @param al AtomicLong whose value is incremented. + * @return the updated value + */ + public static long incrementAndGet(AtomicLong al) { + return Atomic.incrementAndGet(al); + } + + /** + * Atomically decrements by one the current value. + * + * @param al AtomicLong whose value is decremented. + * @return the updated value + */ + public static long decrementAndGet(AtomicLong al) { + return Atomic.decrementAndGet(al); + } + + /** + * Atomically adds the given value to the current value. + * + * @param al AtomicLong whose value is added to. + * @param delta the value to add + * @return the previous value + */ + public static long getAndAdd(AtomicLong al, long delta) { + return Atomic.getAndAdd(al, delta); + } + + /** + * Atomically adds the given value to the current value. + * + * @param al AtomicLong whose value is added to + * @param delta the value to add + * @return the updated value + */ + public static long addAndGet(AtomicLong al, long delta) { + return Atomic.addAndGet(al, delta); + } + + /** + * Atomically sets to the given value and returns the old value. + * + * @param al AtomicLong that is set. + * @param newValue the new value + * @return the previous value + */ + public static long getAndSet(AtomicLong al, long newValue) { + return Atomic.getAndSet(al, newValue); + } + + /** + * BTrace to DTrace communication chennal. Raise DTrace USDT probe from BTrace. + * + * @see #dtraceProbe(String, String, int, int) + */ + public static int dtraceProbe(String str1, String str2) { + return D.probe(str1, str2); + } + + /** + * BTrace to DTrace communication chennal. Raise DTrace USDT probe from BTrace. + * + * @see #dtraceProbe(String, String, int, int) + */ + public static int dtraceProbe(String str1, String str2, int i1) { + return D.probe(str1, str2, i1); + } + + /** + * BTrace to DTrace communication channel. Raise DTrace USDT probe from BTrace. + * + * @param str1 first String param to DTrace probe + * @param str2 second String param to DTrace probe + * @param i1 first int param to DTrace probe + * @param i2 second int param to DTrace probe + */ + public static int dtraceProbe(String str1, String str2, int i1, int i2) { + return D.probe(str1, str2, i1, i2); + } + + /** + * Gets the system property indicated by the specified key. + * + * @param key the name of the system property. + * @return the string value of the system property, or null if there is no property + * with that key. + * @throws NullPointerException if key is null. + * @throws IllegalArgumentException if key is empty. + */ + public static String property(String key) { + return Sys.Env.property(key); + } + + /** + * Returns all Sys properties. + * + * @return the system properties + */ + public static Properties properties() { + return Sys.Env.properties(); + } + + /** Prints all Sys properties. */ + public static void printProperties() { + Sys.Env.printProperties(); + } + + /** + * Gets the value of the specified environment variable. An environment variable is a + * system-dependent external named value. + * + * @param name the name of the environment variable + * @return the string value of the variable, or null if the variable is not defined + * in the system environment + * @throws NullPointerException if name is null + */ + public static String getenv(String name) { + return Sys.Env.getenv(name); + } + + /** + * Returns an unmodifiable string map view of the current system environment. The environment is a + * system-dependent mapping from names to values which is passed from parent to child processes. + * + * @return the environment as a map of variable names to values + */ + public static Map getenv() { + return Sys.Env.getenv(); + } + + /** Prints all system environment values. */ + public static void printEnv() { + Sys.Env.printEnv(); + } + + /** + * Returns the number of processors available to the Java virtual machine. + * + *

This value may change during a particular invocation of the virtual machine. Applications + * that are sensitive to the number of available processors should therefore occasionally poll + * this property and adjust their resource usage appropriately. + * + * @return the maximum number of processors available to the virtual machine; never smaller than + * one + */ + public static long availableProcessors() { + return Sys.Env.availableProcessors(); + } + + // memory usage + + /** + * Returns the amount of free memory in the Java Virtual Machine. Calling the gc + * method may result in increasing the value returned by freeMemory. + * + * @return an approximation to the total amount of memory currently available for future allocated + * objects, measured in bytes. + */ + public static long freeMemory() { + return Sys.Memory.freeMemory(); + } + + /** + * Returns the total amount of memory in the Java virtual machine. The value returned by this + * method may vary over time, depending on the host environment. + * + *

Note that the amount of memory required to hold an object of any given type may be + * implementation-dependent. + * + * @return the total amount of memory currently available for current and future objects, measured + * in bytes. + */ + public static long totalMemory() { + return Sys.Memory.totalMemory(); + } + + /** + * Returns the maximum amount of memory that the Java virtual machine will attempt to use. If + * there is no inherent limit then the value {@link java.lang.Long#MAX_VALUE} will be returned. + * + * @return the maximum amount of memory that the virtual machine will attempt to use, measured in + * bytes + */ + public static long maxMemory() { + return Sys.Memory.maxMemory(); + } + + /** Returns heap memory usage */ + public static MemoryUsage heapUsage() { + return Sys.Memory.heapUsage(); + } + + /** Returns non-heap memory usage */ + public static MemoryUsage nonHeapUsage() { + return Sys.Memory.nonHeapUsage(); + } + + /** + * Returns the amount of memory in bytes that the Java virtual machine initially requests from the + * operating system for memory management. + */ + public static long init(MemoryUsage mu) { + return Sys.Memory.init(mu); + } + + /** + * Returns the amount of memory in bytes that is committed for the Java virtual machine to use. + * This amount of memory is guaranteed for the Java virtual machine to use. + */ + public static long committed(MemoryUsage mu) { + return Sys.Memory.committed(mu); + } + + /** + * Returns the maximum amount of memory in bytes that can be used for memory management. This + * method returns -1 if the maximum memory size is undefined. + */ + public static long max(MemoryUsage mu) { + return Sys.Memory.max(mu); + } + + /** Returns the amount of used memory in bytes. */ + public static long used(MemoryUsage mu) { + return Sys.Memory.used(mu); + } + + /** Returns the approximate number of objects for which finalization is pending. */ + public static long finalizationCount() { + return Sys.Memory.finalizationCount(); + } + + /** + * Returns the input arguments passed to the Java virtual machine which does not include the + * arguments to the main method. This method returns an empty list if there is no input + * argument to the Java virtual machine. + * + *

Some Java virtual machine implementations may take input arguments from multiple different + * sources: for examples, arguments passed from the application that launches the Java virtual + * machine such as the 'java' command, environment variables, configuration files, etc. + * + *

Typically, not all command-line options to the 'java' command are passed to the Java virtual + * machine. Thus, the returned input arguments may not include all command-line options. + * + * @return a list of String objects; each element is an argument passed to the Java + * virtual machine. + */ + public static List vmArguments() { + return Sys.VM.vmArguments(); + } + + /** + * Prints VM input arguments list. + * + * @see #vmArguments + */ + public static void printVmArguments() { + Sys.VM.printVmArguments(); + } + + /** + * Returns the Java virtual machine implementation version. This method is equivalent to + * Sys.getProperty("java.vm.version"). + * + * @return the Java virtual machine implementation version. + */ + public static String vmVersion() { + return Sys.VM.vmVersion(); + } + + /** + * Tests if the Java virtual machine supports the boot class path mechanism used by the bootstrap + * class loader to search for class files. + * + * @return true if the Java virtual machine supports the class path mechanism; + * false otherwise. + */ + public static boolean isBootClassPathSupported() { + return Sys.VM.isBootClassPathSupported(); + } + + /** + * Returns the boot class path that is used by the bootstrap class loader to search for class + * files. + * + *

Multiple paths in the boot class path are separated by the path separator character of the + * platform on which the Java virtual machine is running. + * + *

A Java virtual machine implementation may not support the boot class path mechanism for the + * bootstrap class loader to search for class files. The {@link #isBootClassPathSupported} method + * can be used to determine if the Java virtual machine supports this method. + * + * @return the boot class path. + * @throws java.lang.UnsupportedOperationException if the Java virtual machine does not support + * this operation. + */ + public static String bootClassPath() { + return Sys.VM.bootClassPath(); + } + + /** + * Returns the Java class path that is used by the system class loader to search for class files. + * This method is equivalent to Sys.getProperty("java.class.path"). + * + * @return the Java class path. + */ + public static String classPath() { + return Sys.VM.classPath(); + } + + /** + * Returns the Java library path. This method is equivalent to + * Sys.getProperty("java.library.path"). + * + *

Multiple paths in the Java library path are separated by the path separator character of the + * platform of the Java virtual machine being monitored. + * + * @return the Java library path. + */ + public static String libraryPath() { + return Sys.VM.libraryPath(); + } + + /** + * Returns the current number of live threads including both daemon and non-daemon threads. + * + * @return the current number of live threads. + */ + public static long threadCount() { + return Sys.VM.threadCount(); + } + + /** + * Returns the peak live thread count since the Java virtual machine started or peak was reset. + * + * @return the peak live thread count. + */ + public static long peakThreadCount() { + return Sys.VM.peakThreadCount(); + } + + /** + * Returns the total number of threads created and also started since the Java virtual machine + * started. + * + * @return the total number of threads started. + */ + public static long totalStartedThreadCount() { + return Sys.VM.totalStartedThreadCount(); + } + + /** + * Returns the current number of live daemon threads. + * + * @return the current number of live daemon threads. + */ + public static long daemonThreadCount() { + return Sys.VM.daemonThreadCount(); + } + + /** + * Returns the total CPU time for the current thread in nanoseconds. The returned value is of + * nanoseconds precision but not necessarily nanoseconds accuracy. If the implementation + * distinguishes between user mode time and system mode time, the returned CPU time is the amount + * of time that the current thread has executed in user mode or system mode. + */ + public static long currentThreadCpuTime() { + return Sys.VM.currentThreadCpuTime(); + } + + /** + * Returns the CPU time that the current thread has executed in user mode in nanoseconds. The + * returned value is of nanoseconds precision but not necessarily nanoseconds accuracy. + */ + public static long currentThreadUserTime() { + return Sys.VM.currentThreadUserTime(); + } + + /** + * Returns the total amount of time spent in GarbageCollection up to this point since the + * application was started. + * + * @return Returns the amount of overall time spent in GC + */ + public static long getTotalGcTime() { + return Sys.Memory.getTotalGcTime(); + } + + /** + * Returns an implementation-specific approximation of the amount of storage consumed by the + * specified object. The result may include some or all of the object's overhead, and thus is + * useful for comparison within an implementation but not between implementations. + * + *

The estimate may change during a single invocation of the JVM. + * + * @param objectToSize the object to size + * @return an implementation-specific approximation of the amount of storage consumed by the + * specified object + * @throws java.lang.NullPointerException if the supplied Object is null. + */ + public static long sizeof(Object objectToSize) { + return BTraceRuntime.sizeof(objectToSize); + } + + /** + * Dump the snapshot of the Java heap to a file in hprof binary format. Only the live objects are + * dumped. Under the current dir of traced app, ./btrace<pid>/btrace-class/ directory is + * created. Under that directory, a file of given fileName is created. + * + * @param fileName name of the file to which heap is dumped + */ + public static void dumpHeap(String fileName) { + Sys.Memory.dumpHeap(fileName); + } + + /** + * Dump the snapshot of the Java heap to a file in hprof binary format. Under the current dir of + * traced app, ./btrace<pid>/btrace-class/ directory is created. Under that directory, a + * file of given fileName is created. + * + * @param fileName name of the file to which heap is dumped + * @param live flag that tells whether only live objects are to be dumped or all objects are to be + * dumped. + */ + public static void dumpHeap(String fileName, boolean live) { + Sys.Memory.dumpHeap(fileName, live); + } + + /** + * Runs the garbage collector. + * + *

Calling the gc method suggests that the Java Virtual Machine expend effort + * toward recycling unused objects in order to make the memory they currently occupy available for + * quick reuse. When control returns from the method call, the Java Virtual Machine has made a + * best effort to reclaim space from all discarded objects. This method calls Sys.gc() to perform + * GC. + */ + public static void gc() { + Sys.Memory.gc(); + } + + /** + * Runs the finalization methods of any objects pending finalization. + * + *

Calling this method suggests that the Java Virtual Machine expend effort toward running the + * finalize methods of objects that have been found to be discarded but whose + * finalize methods have not yet been run. When control returns from the method call, the + * Java Virtual Machine has made a best effort to complete all outstanding finalizations. This + * method calls Sys.runFinalization() to run finalization. + */ + public static void runFinalization() { + Sys.Memory.runFinalization(); + } + + /** + * Serialize a given object into the given file. Under the current dir of traced app, + * ./btrace<pid>/btrace-class/ directory is created. Under that directory, a file of given + * fileName is created. + * + * @param obj object that has to be serialized. + * @param fileName name of the file to which the object is serialized. + */ + public static void serialize(Serializable obj, String fileName) { + Export.serialize(obj, fileName); + } + + /** + * Creates an XML document to persist the tree of the all transitively reachable objects from + * given "root" object. + */ + public static String toXML(Object obj) { + return Export.toXML(obj); + } + + /** + * Writes an XML document to persist the tree of the all the transitively reachable objects from + * the given "root" object. Under the current dir of traced app, ./btrace<pid>/btrace-class/ + * directory is created. Under that directory, a file of the given fileName is created. + */ + public static void writeXML(Object obj, String fileName) { + Export.writeXML(obj, fileName); + } + + /** + * Writes a .dot document to persist the tree of the all the transitively reachable objects from + * the given "root" object. .dot documents can be viewed by Graphviz application + * (www.graphviz.org) Under the current dir of traced app, ./btrace<pid>/btrace-class/ + * directory is created. Under that directory, a file of the given fileName is created. + * + * @since 1.1 + */ + public static void writeDOT(Object obj, String fileName) { + Export.writeDOT(obj, fileName); + } + + // speculative buffer management + + /** + * Returns an identifier for a new speculative buffer. + * + * @return new speculative buffer id + */ + public static int speculation() { + return Speculation.speculation(); + } + + /** + * Sets current speculative buffer id. + * + * @param id the speculative buffer id + */ + public static void speculate(int id) { + Speculation.speculate(id); + } + + /** + * Commits the speculative buffer associated with id. + * + * @param id the speculative buffer id + */ + public static void commit(int id) { + Speculation.commit(id); + } + + /** + * Discards the speculative buffer associated with id. + * + * @param id the speculative buffer id + */ + public static void discard(int id) { + Speculation.discard(id); + } + + // aggregation support + + /** + * Creates a new aggregation based on the given aggregation function type. + * + * @param type the aggregating function to be performed on the data being added to the + * aggregation. + */ + public static Aggregation newAggregation(AggregationFunction type) { + return Aggregations.newAggregation(type); + } + + /** + * Creates a grouping aggregation key with the provided value. The value must be a String or + * Number type. + * + * @param element1 the value of the aggregation key + */ + public static AggregationKey newAggregationKey(Object element1) { + return Aggregations.newAggregationKey(element1); + } + + /** + * Creates a composite grouping aggregation key with the provided values. The values must be + * String or Number types. + * + * @param element1 the first element of the composite aggregation key + * @param element2 the second element of the composite aggregation key + */ + public static AggregationKey newAggregationKey(Object element1, Object element2) { + return Aggregations.newAggregationKey(element1, element2); + } + + /** + * Creates a composite grouping aggregation key with the provided values. The values must be + * String or Number types. + * + * @param element1 the first element of the composite aggregation key + * @param element2 the second element of the composite aggregation key + * @param element3 the third element of the composite aggregation key + */ + public static AggregationKey newAggregationKey( + Object element1, Object element2, Object element3) { + return Aggregations.newAggregationKey(element1, element2, element3); + } + + /** + * Creates a composite grouping aggregation key with the provided values. The values must be + * String or Number types. + * + * @param element1 the first element of the composite aggregation key + * @param element2 the second element of the composite aggregation key + * @param element3 the third element of the composite aggregation key + * @param element4 the fourth element of the composite aggregation key + */ + public static AggregationKey newAggregationKey( + Object element1, Object element2, Object element3, Object element4) { + return Aggregations.newAggregationKey(element1, element2, element3, element4); + } + + /** + * Adds a value to the aggregation with no grouping key. This method should be used when the + * aggregation is to calculate only a single aggregated value. + * + * @param aggregation the aggregation to which the value should be added + */ + public static void addToAggregation(Aggregation aggregation, long value) { + Aggregations.addToAggregation(aggregation, value); + } + + /** + * Adds a value to the aggregation with a grouping key. This method should be used when the + * aggregation should effectively perform a "group by" on the key value. The aggregation will + * calculate a separate aggregated value for each unique aggregation key. + * + * @param aggregation the aggregation to which the value should be added + * @param key the grouping aggregation key + */ + public static void addToAggregation(Aggregation aggregation, AggregationKey key, long value) { + Aggregations.addToAggregation(aggregation, key, value); + } + + /** + * Resets values within the aggregation to the default. This will affect all values within the + * aggregation when multiple aggregation keys have been used. + * + * @param aggregation the aggregation to be cleared + */ + public static void clearAggregation(Aggregation aggregation) { + Aggregations.clearAggregation(aggregation); + } + + /** + * Removes all aggregated values from the aggregation except for the largest or smallest + * abs(count) elements. + * + *

If count is positive, the largest aggregated values in the aggregation will be + * preserved. If count is negative the smallest values will be preserved. If + * count is zero then all elements will be removed. + * + *

Behavior is intended to be similar to the dtrace trunc() function. + * + * @param aggregation the aggregation to be truncated + * @param count the number of elements to preserve. If negative, the smallest abs(count) + * elements are preserved. + */ + public static void truncateAggregation(Aggregation aggregation, int count) { + Aggregations.truncateAggregation(aggregation, count); + } + + /** Prints the aggregation. */ + public static void printAggregation(String name, Aggregation aggregation) { + Aggregations.printAggregation(name, aggregation); + } + + /** + * Prints aggregation using the provided format + * + * @param name The name of the aggregation to be used in the textual output + * @param aggregation The aggregation to print + * @param format The format to use. It mimics {@linkplain String#format(java.lang.String, + * java.lang.Object[]) } behaviour with the addition of the ability to address the key title + * as a 0-indexed item + * @see String#format(java.lang.String, java.lang.Object[]) + * @since 1.1 + */ + public static void printAggregation(String name, Aggregation aggregation, String format) { + Aggregations.printAggregation(name, aggregation, format); + } + + // Internals only below this point + private static void checkStatic(Field field) { + if (!Modifier.isStatic(field.getModifiers())) { + throw new IllegalArgumentException(field.getName() + " is not a static field"); + } + } + + private static Field getField(Class clazz, String name, boolean throwError) { + return AccessController.doPrivileged( + (PrivilegedAction) + () -> { + Field field = null; + Class cClass = clazz; + try { + while (Objects.isNull(field) && Objects.nonNull(cClass)) { + try { + field = cClass.getDeclaredField(name); + } catch (NoSuchFieldException exp) { + // Ignore the exception and continue looking for the parent class + cClass = cClass.getSuperclass(); + } + } + + if (Objects.isNull(cClass)) { + throw new NoSuchFieldException(name); + } + + field.setAccessible(true); + + return field; + } catch (Exception exp) { + if (throwError) { + throw translate(exp); + } else { + return null; + } + } + }); + } + + private static Field[] getAllFields(Class clazz) { + return AccessController.doPrivileged( + (PrivilegedAction) + () -> { + try { + Field[] fields = clazz.getDeclaredFields(); + for (Field f : fields) { + f.setAccessible(true); + } + return fields; + } catch (Exception exp) { + throw translate(exp); + } + }); + } + + private static void addFieldValues( + StringBuilder buf, Object obj, Class clazz, boolean classNamePrefix) { + Field[] fields = getAllFields(clazz); + for (Field f : fields) { + int modifiers = f.getModifiers(); + if (!Modifier.isStatic(modifiers)) { + if (classNamePrefix) { + buf.append(f.getDeclaringClass().getName()); + buf.append('.'); + } + buf.append(f.getName()); + buf.append('='); + try { + buf.append(Strings.str(f.get(obj))); + } catch (Exception exp) { + throw translate(exp); + } + buf.append(", "); + } + } + Class sc = clazz.getSuperclass(); + if (sc != null) { + addFieldValues(buf, obj, sc, classNamePrefix); + } + } + + private static void addStaticFieldValues( + StringBuilder buf, Class clazz, boolean classNamePrefix) { + Field[] fields = getAllFields(clazz); + for (Field f : fields) { + int modifiers = f.getModifiers(); + if (Modifier.isStatic(modifiers)) { + if (classNamePrefix) { + buf.append(f.getDeclaringClass().getName()); + buf.append('.'); + } + buf.append(f.getName()); + buf.append('='); + try { + buf.append(Strings.str(f.get(null))); + } catch (Exception exp) { + throw translate(exp); + } + buf.append(", "); + } + } + Class sc = clazz.getSuperclass(); + if (sc != null) { + addStaticFieldValues(buf, sc, classNamePrefix); + } + } + + private static RuntimeException translate(Exception exp) { + if (exp instanceof RuntimeException) { + return (RuntimeException) exp; + } else { + return new RuntimeException(exp); + } + } + + /** ******** Namespaced methods ***************** */ + + /* + * Wraps the threads related BTrace utility methods + * @since 1.2 + */ + public static class Threads { + // Thread and stack access + + /** + * Tests whether this thread has been interrupted. The interrupted status of the thread + * is unaffected by this method. + * + *

A thread interruption ignored because a thread was not alive at the time of the interrupt + * will be reflected by this method returning false. + * + * @return true if this thread has been interrupted; false otherwise. + */ + public static boolean isInteruppted() { + return Thread.currentThread().isInterrupted(); + } + + /** Prints the java stack trace of the current thread. */ + public static void jstack() { + jstack(1, -1); + } + + /** + * Prints the java stack trace of the current thread. But, atmost given number of frames. + * + * @param numFrames number of frames to be printed. When this is negative all frames are + * printed. + */ + public static void jstack(int numFrames) { + // passing '5' to skip our own frames to generate stack trace + jstack(1, numFrames); + } + + private static void jstack(int strip, int numFrames) { + if (numFrames == 0) return; + StackTraceElement[] st = Thread.currentThread().getStackTrace(); + BTraceRuntime.stackTrace(st, strip + 2, numFrames); + } + + /** Prints Java stack traces of all the Java threads. */ + public static void jstackAll() { + jstackAll(1, -1); + } + + /** + * Prints Java stack traces of all the Java threads. But, at most given number of frames. + * + * @param numFrames number of frames to be printed. When this is negative all frames are + * printed. + */ + public static void jstackAll(int numFrames) { + jstackAll(1, numFrames); + } + + private static void jstackAll(int strip, int numFrames) { + BTraceRuntime.stackTraceAll(numFrames); + } + + /** + * Returns the stack trace of current thread as a String. + * + * @return the stack trace as a String. + */ + public static String jstackStr() { + return jstackStr(1, -1); + } + + /** + * Returns the stack trace of the current thread as a String but includes atmost the given + * number of frames. + * + * @param numFrames number of frames to be included. When this is negative all frames are + * included. + * @return the stack trace as a String. + */ + public static String jstackStr(int numFrames) { + if (numFrames == 0) { + return ""; + } + return jstackStr(1, numFrames); + } + + private static String jstackStr(int strip, int numFrames) { + if (numFrames == 0) { + return ""; + } + StackTraceElement[] st = Thread.currentThread().getStackTrace(); + return BTraceRuntime.stackTraceStr(st, strip + 2, numFrames); + } + + /** + * Returns the stack traces of all Java threads as a String. + * + * @return the stack traces as a String. + */ + public static String jstackAllStr() { + return jstackAllStr(-1); + } + + /** + * Returns atmost given number of frames in stack traces of all threads as a String. + * + * @param numFrames number of frames to be included. When this is negative all frames are + * included. + * @return the stack traces as a String. + */ + public static String jstackAllStr(int numFrames) { + if (numFrames == 0) { + return ""; + } + return BTraceRuntime.stackTraceAllStr(numFrames); + } + + /** + * Prints the stack trace of the given exception object. + * + * @param exception throwable for which stack trace is printed. + */ + public static void jstack(Throwable exception) { + jstack(exception, -1); + } + + /** + * Prints the stack trace of the given exception object. But, prints atmost given number of + * frames. + * + * @param exception throwable for which stack trace is printed. + * @param numFrames maximum number of frames to be printed. + */ + public static void jstack(Throwable exception, int numFrames) { + if (numFrames == 0) { + return; + } + StackTraceElement[] st = exception.getStackTrace(); + println(exception.toString()); + BTraceRuntime.stackTrace("\t", st, 0, numFrames); + Throwable cause = exception.getCause(); + while (cause != null) { + print("Caused by: "); + println(cause.toString()); + st = cause.getStackTrace(); + BTraceRuntime.stackTrace("\t", st, 0, numFrames); + cause = cause.getCause(); + } + } + + /** + * Returns the stack trace of given exception object as a String. + * + * @param exception the throwable for which stack trace is returned. + */ + public static String jstackStr(Throwable exception) { + return jstackStr(exception, -1); + } + + /** + * Returns stack trace of given exception object as a String. + * + * @param exception throwable for which stack trace is returned. + * @param numFrames maximum number of frames to be returned. + */ + public static String jstackStr(Throwable exception, int numFrames) { + if (numFrames == 0) { + return ""; + } + StackTraceElement[] st = exception.getStackTrace(); + StringBuilder buf = new StringBuilder(); + buf.append(Strings.str(exception)); + buf.append(BTraceRuntime.stackTraceStr("\t", st, 0, numFrames)); + Throwable cause = exception.getCause(); + while (cause != null) { + buf.append("Caused by:"); + st = cause.getStackTrace(); + buf.append(BTraceRuntime.stackTraceStr("\t", st, 0, numFrames)); + cause = cause.getCause(); + } + return buf.toString(); + } + + /** + * Returns a reference to the currently executing thread object. + * + * @return the currently executing thread. + */ + public static Thread currentThread() { + return Thread.currentThread(); + } + + /** + * Returns the identifier of the given Thread. The thread ID is a positive long number + * generated when the given thread was created. The thread ID is unique and remains unchanged + * during its lifetime. When a thread is terminated, the thread ID may be reused. + */ + public static long threadId(Thread thread) { + return thread.getId(); + } + + /** + * Returns the state of the given thread. This method is designed for use in monitoring of the + * system state, not for synchronization control. + */ + public static Thread.State threadState(Thread thread) { + return thread.getState(); + } + + /** + * Returns true if and only if the current thread holds the monitor lock on the + * specified object. + * + *

This method is designed to allow a program to assert that the current thread already holds + * a specified lock: + * + *

+     *     assert Thread.holdsLock(obj);
+     * 
+ * + * @param obj the object on which to test lock ownership + * @return true if the current thread holds the monitor lock on the specified object. + * @throws NullPointerException if obj is null + */ + public static boolean holdsLock(Object obj) { + return Thread.holdsLock(obj); + } + + /** Prints the Java level deadlocks detected (if any). */ + public static void deadlocks() { + deadlocks(true); + } + + /** + * Prints deadlocks detected (if any). Optionally prints stack trace of the deadlocked threads. + * + * @param stackTrace boolean flag to specify whether to print stack traces of deadlocked threads + * or not. + */ + public static void deadlocks(boolean stackTrace) { + BTraceRuntime.deadlocks(stackTrace); + } + + /** + * Returns the name of the given thread. + * + * @param thread thread whose name is returned + */ + public static String name(Thread thread) { + return thread.getName(); + } + } + + /* + * Wraps the strings related BTrace utility methods + * @since 1.2 + */ + public static class Strings { + public static boolean startsWith(String s, String start) { + return s.startsWith(start); + } + + public static boolean endsWith(String s, String end) { + return s.endsWith(end); + } + + /** + * This is synonym to "concat". + * + * @see #concat(String, String) + */ + public static String strcat(String str1, String str2) { + return concat(str1, str2); + } + + /** Concatenates the specified strings together. */ + public static String concat(String str1, String str2) { + return str1.concat(str2); + } + + /** + * Compares two strings lexicographically. The comparison is based on the Unicode value of each + * character in the strings. The character sequence represented by the first String + * object is compared lexicographically to the character sequence represented by the second + * string. The result is a negative integer if the first String object + * lexicographically precedes the second string. The result is a positive integer if the first + * String object lexicographically follows the second string. The result is zero if + * the strings are equal; compareTo returns 0 exactly when the {@link + * String#equals(Object)} method would return true. + */ + public static int compareTo(String str1, String str2) { + return str1.compareTo(str2); + } + + /** + * This is synonym to "compareTo" method. + * + * @see #compareTo + */ + public static int strcmp(String str1, String str2) { + return str1.compareTo(str2); + } + + /** + * Compares two strings lexicographically, ignoring case differences. This method returns an + * integer whose sign is that of calling compareTo with normalized versions of the + * strings where case differences have been eliminated by calling + * Character.toLowerCase(Character.toUpperCase(character)) on each character. + */ + public static int compareToIgnoreCase(String str1, String str2) { + return str1.compareToIgnoreCase(str2); + } + + /** + * This is synonym to "compareToIgnoreCase". + * + * @see #compareToIgnoreCase + */ + public static int stricmp(String str1, String str2) { + return str1.compareToIgnoreCase(str2); + } + + /** Find String within String */ + public static int strstr(String str1, String str2) { + return str1.indexOf(str2); + } + + public static int indexOf(String str1, String str2) { + return str1.indexOf(str2); + } + + public static int lastIndexOf(String str1, String str2) { + return str1.lastIndexOf(str2); + } + + /** Substring */ + public static String substr(String str, int start, int length) { + return str.substring(start, length); + } + + public static String substr(String str, int start) { + return str.substring(start); + } + + /** + * Returns the length of the given string. The length is equal to the number of Unicode code units in the string. + * + * @param str String whose length is calculated. + * @return the length of the sequence of characters represented by this object. + */ + public static int length(String str) { + return str.length(); + } + + /** + * This is synonym for "length". + * + * @see #length(String) + */ + public static int strlen(String str) { + return str.length(); + } + + // regular expression matching + + /** + * Compiles the given regular expression into a pattern. + * + * @param regex The expression to be compiled + * @throws PatternSyntaxException If the expression's syntax is invalid + */ + public static Pattern regexp(String regex) { + return Pattern.compile(regex); + } + + /** + * This is synonym for "regexp". + * + * @see #regexp(String) + */ + public static Pattern pattern(String regex) { + return regexp(regex); + } + + /** + * Compiles the given regular expression into a pattern with the given flags. + * + * @param regex The expression to be compiled + * @param flags Match flags, a bit mask that may include {@link Pattern#CASE_INSENSITIVE}, + * {@link Pattern#MULTILINE}, {@link Pattern#DOTALL}, {@link Pattern#UNICODE_CASE}, {@link + * Pattern#CANON_EQ}, {@link Pattern#UNIX_LINES}, {@link Pattern#LITERAL} and {@link + * Pattern#COMMENTS} + * @throws IllegalArgumentException If bit values other than those corresponding to the defined + * match flags are set in flags + * @throws PatternSyntaxException If the expression's syntax is invalid + */ + public static Pattern regexp(String regex, int flags) { + return Pattern.compile(regex, flags); + } + + /** + * This is synonym for "regexp". + * + * @see #regexp(String, int) + */ + public static Pattern pattern(String regex, int flags) { + return regexp(regex, flags); + } + + /** + * Matches the given (precompiled) regular expression and attempts to match the given input + * against it. + */ + public static boolean matches(Pattern regex, String input) { + return regex.matcher(input).matches(); + } + + /** + * Compiles the given regular expression and attempts to match the given input against it. + * + *

An invocation of this convenience method of the form + * + *

+ * + *
+     * Pattern.matches(regex, input);
+ * + *
+ * + *

behaves in exactly the same way as the expression + * + *

+ * + *
+     * Pattern.compile(regex).matcher(input).matches()
+ * + *
+ * + *

If a pattern is to be used multiple times, compiling it once and reusing it will be more + * efficient than invoking this method each time. + * + * @param regex The expression to be compiled + * @param input The character sequence to be matched + * @throws PatternSyntaxException If the expression's syntax is invalid + */ + public static boolean matches(String regex, String input) { + return Pattern.matches(regex, input); + } + + /** + * Returns a String object representing the specified boolean. If the specified boolean + * is true, then the string {@code "true"} will be returned, otherwise the string + * {@code "false"} will be returned. + * + * @param b the boolean to be converted + * @return the string representation of the specified boolean + */ + public static String str(boolean b) { + return Boolean.toString(b); + } + + /** + * Returns a String object representing the specified char. The result + * is a string of length 1 consisting solely of the specified char. + * + * @param c the char to be converted + * @return the string representation of the specified char + */ + public static String str(char c) { + return Character.toString(c); + } + + /** + * Returns a String object representing the specified integer. The argument is + * converted to signed decimal representation and returned as a string. + * + * @param i an integer to be converted. + * @return a string representation of the argument in base 10. + */ + public static String str(int i) { + return Integer.toString(i); + } + + /** + * Returns a string representation of the integer argument as an unsigned integer in + * base 16. + * + *

The unsigned integer value is the argument plus 232 if the argument is + * negative; otherwise, it is equal to the argument. This value is converted to a string of + * ASCII digits in hexadecimal (base 16) with no extra leading 0s. If the + * unsigned magnitude is zero, it is represented by a single zero character '0' ( + * '\u0030'); otherwise, the first character of the representation of the + * unsigned magnitude will not be the zero character. The following characters are used as + * hexadecimal digits: + * + *

+ * + *
+     * 0123456789abcdef
+     * 
+ * + *
+ * + * These are the characters '\u0030' through '\u0039' and + * '\u0061' through '\u0066'. + * + * @param i an integer to be converted to a string. + * @return the string representation of the unsigned integer value represented by the argument + * in hexadecimal (base 16). + */ + public static String toHexString(int i) { + return Integer.toHexString(i); + } + + /** + * Returns a String object representing the specified long. The + * argument is converted to signed decimal representation and returned as a string. + * + * @param l a long to be converted. + * @return a string representation of the argument in base 10. + */ + public static String str(long l) { + return Long.toString(l); + } + + /** + * Returns a string representation of the object. In general, the toString method + * returns a string that "textually represents" this object. The result should be a concise but + * informative representation that is easy for a person to read. For bootstrap classes, returns + * the result of calling Object.toString() override. For non-bootstrap classes, default + * toString() value [className@hashCode] is returned. + * + * @param obj the object whose string representation is returned + * @return a string representation of the given object. + */ + public static String str(Object obj) { + if (obj == null) { + return "null"; + } else if (obj instanceof String) { + return (String) obj; + } else if (obj.getClass().getClassLoader() == null) { + try { + return obj.toString(); + } catch (NullPointerException e) { + // NPE can be thrown from inside the toString() method we have no control over + return "null"; + } + } else { + return identityStr(obj); + } + } + + /** + * Returns a string representation of the long argument as an unsigned integer in + * base 16. + * + *

The unsigned long value is the argument plus 264 if the argument + * is negative; otherwise, it is equal to the argument. This value is converted to a string of + * ASCII digits in hexadecimal (base 16) with no extra leading 0s. If the + * unsigned magnitude is zero, it is represented by a single zero character '0' ( + * '\u0030'); otherwise, the first character of the representation of the + * unsigned magnitude will not be the zero character. The following characters are used as + * hexadecimal digits: + * + *

+ * + *
+     * 0123456789abcdef
+     * 
+ * + *
+ * + * These are the characters '\u0030' through '\u0039' and + * '\u0061' through '\u0066'. + * + * @param l a long to be converted to a string. + * @return the string representation of the unsigned long value represented by the + * argument in hexadecimal (base 16). + */ + public static String toHexString(long l) { + return Long.toHexString(l); + } + + /** + * Returns a string representation of the float argument. All characters mentioned + * below are ASCII characters. + * + *
    + *
  • If the argument is NaN, the result is the string "NaN". + *
  • Otherwise, the result is a string that represents the sign and magnitude (absolute + * value) of the argument. If the sign is negative, the first character of the result is ' + * -' ('\u002D'); if the sign is positive, no sign character + * appears in the result. As for the magnitude m: + *
      + *
    • If m is infinity, it is represented by the characters "Infinity" + * ; thus, positive infinity produces the result "Infinity" and + * negative infinity produces the result "-Infinity". + *
    • If m is zero, it is represented by the characters "0.0"; + * thus, negative zero produces the result "-0.0" and positive zero + * produces the result "0.0". + *
    • If m is greater than or equal to 10-3 but less than + * 107, then it is represented as the integer part of m, in + * decimal form with no leading zeroes, followed by '.' ( + * '\u002E'), followed by one or more decimal digits representing the + * fractional part of m. + *
    • If m is less than 10-3 or greater than or equal to + * 107, then it is represented in so-called "computerized scientific + * notation." Let n be the unique integer such that 10n + * <= m < 10n+1; then let a be the + * mathematically exact quotient of m and 10n so that 1 + * <= a < 10. The magnitude is then represented as the integer part of + * a, as a single decimal digit, followed by '.' ( + * '\u002E'), followed by decimal digits representing the fractional part + * of a, followed by the letter 'E' ('\u0045'), + * followed by a representation of n as a decimal integer, as produced by the + * method {@link + * java.lang.Integer#toString(int)}. + *
    + *
+ * + * How many digits must be printed for the fractional part of m or a? There must + * be at least one digit to represent the fractional part, and beyond that as many, but only as + * many, more digits as are needed to uniquely distinguish the argument value from adjacent + * values of type float. That is, suppose that x is the exact mathematical + * value represented by the decimal representation produced by this method for a finite nonzero + * argument f. Then f must be the float value nearest to x; + * or, if two float values are equally close to x, then f must be one + * of them and the least significant bit of the significand of f must be 0. + * + *

+ * + * @param f the float to be converted. + * @return a string representation of the argument. + */ + public static String str(float f) { + return Float.toString(f); + } + + /** + * Returns a string representation of the double argument. All characters mentioned + * below are ASCII characters. + * + *

    + *
  • If the argument is NaN, the result is the string "NaN". + *
  • Otherwise, the result is a string that represents the sign and magnitude (absolute + * value) of the argument. If the sign is negative, the first character of the result is ' + * -' ('\u002D'); if the sign is positive, no sign character + * appears in the result. As for the magnitude m: + *
      + *
    • If m is infinity, it is represented by the characters "Infinity" + * ; thus, positive infinity produces the result "Infinity" and + * negative infinity produces the result "-Infinity". + *
    • If m is zero, it is represented by the characters "0.0"; + * thus, negative zero produces the result "-0.0" and positive zero + * produces the result "0.0". + *
    • If m is greater than or equal to 10-3 but less than + * 107, then it is represented as the integer part of m, in + * decimal form with no leading zeroes, followed by '.' ( + * '\u002E'), followed by one or more decimal digits representing the + * fractional part of m. + *
    • If m is less than 10-3 or greater than or equal to + * 107, then it is represented in so-called "computerized scientific + * notation." Let n be the unique integer such that 10n + * <= m < 10n+1; then let a be the + * mathematically exact quotient of m and 10n so that 1 + * <= a < 10. The magnitude is then represented as the integer part of + * a, as a single decimal digit, followed by '.' ( + * '\u002E'), followed by decimal digits representing the fractional part + * of a, followed by the letter 'E' ('\u0045'), + * followed by a representation of n as a decimal integer, as produced by the + * method {@link Integer#toString(int)}. + *
    + *
+ * + * How many digits must be printed for the fractional part of m or a? There must + * be at least one digit to represent the fractional part, and beyond that as many, but only as + * many, more digits as are needed to uniquely distinguish the argument value from adjacent + * values of type double. That is, suppose that x is the exact mathematical + * value represented by the decimal representation produced by this method for a finite nonzero + * argument d. Then d must be the double value nearest to x; + * or if two double values are equally close to x, then d must be one + * of them and the least significant bit of the significand of d must be 0. + * + *

+ * + * @param d the double to be converted. + * @return a string representation of the argument. + */ + public static String str(double d) { + return Double.toString(d); + } + + /** + * Safely creates a new instance of an appendable string buffer
+ * + * @param threadSafe Specifies whether the buffer should be thread safe + * @return Returns either {@linkplain StringBuilder} or {@linkplain StringBuffer} instance + * depending on whether the instance is required to be thread safe or not, respectively. + * @since 1.2 + */ + public static Appendable newStringBuilder(boolean threadSafe) { + return BTraceRuntime.newStringBuilder(threadSafe); + } + + /** + * Safely creates a new instance of an appendable string buffer
+ * The buffer will not be thread safe. + * + * @return Returns a new instance of {@linkplain StringBuilder} class + * @since 1.2 + */ + public static Appendable newStringBuilder() { + return BTraceRuntime.newStringBuilder(); + } + + /** + * Appends a string to an appendable buffer created by {@linkplain + * BTraceUtils.Strings#newStringBuilder()} + * + * @param buffer The appendable buffer to append to + * @param strToAppend The string to append + * @return Returns the same appendable buffer instance + * @since 1.2 + */ + public static Appendable append(Appendable buffer, String strToAppend) { + return BTraceRuntime.append(buffer, strToAppend); + } + + /** + * Checks the length of an appendable buffer created by {@linkplain + * BTraceUtils.Strings#newStringBuilder()} + * + * @param buffer The appendable buffer instance + * @return Returns the length of the text contained by the buffer + * @since 1.2 + */ + public static int length(Appendable buffer) { + return BTraceRuntime.length(buffer); + } + } + + /* + * Wraps the numbers related BTrace utility methods + * @since 1.2 + */ + public static class Numbers { + /** + * Returns a double value with a positive sign, greater than or equal to 0.0 + * and less than 1.0. Returned values are chosen pseudorandomly with + * (approximately) uniform distribution from that range. + */ + public static double random() { + return Math.random(); + } + + /** + * Returns the natural logarithm (base e) of a double value. Special cases: + * + *

    + *
  • If the argument is NaN or less than zero, then the result is NaN. + *
  • If the argument is positive infinity, then the result is positive infinity. + *
  • If the argument is positive zero or negative zero, then the result is negative + * infinity. + *
+ * + *

The computed result must be within 1 ulp of the exact result. Results must be + * semi-monotonic. + * + * @param a a value + * @return the value ln a, the natural logarithm of a. + */ + public static strictfp double log(double a) { + return Math.log(a); + } + + /** + * Returns the base 10 logarithm of a double value. Special cases: + * + *

    + *
  • If the argument is NaN or less than zero, then the result is NaN. + *
  • If the argument is positive infinity, then the result is positive infinity. + *
  • If the argument is positive zero or negative zero, then the result is negative + * infinity. + *
  • If the argument is equal to 10n for integer n, then the result + * is n. + *
+ * + *

The computed result must be within 1 ulp of the exact result. Results must be + * semi-monotonic. + * + * @param a a value + * @return the base 10 logarithm of a. + */ + public static strictfp double log10(double a) { + return Math.log10(a); + } + + /** + * Returns Euler's number e raised to the power of a double value. Special + * cases: + * + *

    + *
  • If the argument is NaN, the result is NaN. + *
  • If the argument is positive infinity, then the result is positive infinity. + *
  • If the argument is negative infinity, then the result is positive zero. + *
+ * + *

The computed result must be within 1 ulp of the exact result. Results must be + * semi-monotonic. + * + * @param a the exponent to raise e to. + * @return the value ea, where e is the base of the + * natural logarithms. + */ + public static strictfp double exp(double a) { + return Math.exp(a); + } + + /** + * Returns true if the specified number is a Not-a-Number (NaN) value, false + * otherwise. + * + * @param d the value to be tested. + * @return true if the value of the argument is NaN; false otherwise. + */ + public static boolean isNaN(double d) { + return Double.isNaN(d); + } + + /** + * Returns true if the specified number is a Not-a-Number (NaN) value, false + * otherwise. + * + * @param f the value to be tested. + * @return true if the value of the argument is NaN; false otherwise. + */ + public static boolean isNaN(float f) { + return Float.isNaN(f); + } + + /** + * Returns true if the specified number is infinitely large in magnitude, + * false otherwise. + * + * @param d the value to be tested. + * @return true if the value of the argument is positive infinity or negative + * infinity; false otherwise. + */ + public static boolean isInfinite(double d) { + return Double.isInfinite(d); + } + + /** + * Returns true if the specified number is infinitely large in magnitude, + * false otherwise. + * + * @param f the value to be tested. + * @return true if the value of the argument is positive infinity or negative + * infinity; false otherwise. + */ + public static boolean isInfinite(float f) { + return Float.isInfinite(f); + } + + // string parsing methods + + /** + * Parses the string argument as a boolean. The boolean returned represents the + * value true if the string argument is not null and is equal, + * ignoring case, to the string {@code "true"}. + * + *

Example: {@code Boolean.parseBoolean("True")} returns true.
+ * Example: {@code Boolean.parseBoolean("yes")} returns false. + * + * @param s the String containing the boolean representation to be parsed + * @return the boolean represented by the string argument + */ + public static boolean parseBoolean(String s) { + return Boolean.parseBoolean(s); + } + + /** + * Parses the string argument as a signed decimal byte. The characters in the + * string must all be decimal digits, except that the first character may be an ASCII minus sign + * '-' ('\u002D') to indicate a negative value. The resulting + * byte value is returned. + * + * @param s a String containing the byte representation to be parsed + * @return the byte value represented by the argument in decimal + */ + public static byte parseByte(String s) { + return Byte.parseByte(s); + } + + /** + * Parses the string argument as a signed decimal short. The characters in the + * string must all be decimal digits, except that the first character may be an ASCII minus sign + * '-' ('\u002D') to indicate a negative value. The resulting + * short value is returned. + * + * @param s a String containing the short representation to be parsed + * @return the short value represented by the argument in decimal. + */ + public static short parseShort(String s) { + return Short.parseShort(s); + } + + /** + * Parses the string argument as a signed decimal integer. The characters in the string must all + * be decimal digits, except that the first character may be an ASCII minus sign '-' + * ('\u002D') to indicate a negative value. The resulting integer value + * is returned. + * + * @param s a String containing the int representation to be parsed + * @return the integer value represented by the argument in decimal. + */ + public static int parseInt(String s) { + return Integer.parseInt(s); + } + + /** + * Parses the string argument as a signed decimal long. The characters in the + * string must all be decimal digits, except that the first character may be an ASCII minus sign + * '-' (\u002D') to indicate a negative value. The resulting + * long value is returned. + * + *

Note that neither the character L ('\u004C') nor l + * ('\u006C') is permitted to appear at the end of the string as a type + * indicator, as would be permitted in Java programming language source code. + * + * @param s a String containing the long representation to be parsed + * @return the long represented by the argument in decimal. + */ + public static long parseLong(String s) { + return Long.parseLong(s); + } + + /** + * Returns a new float initialized to the value represented by the specified + * String, as performed by the valueOf method of class Float. + * + * @param s the string to be parsed. + * @return the float value represented by the string argument. + */ + public static float parseFloat(String s) { + return Float.parseFloat(s); + } + + /** + * Returns a new double initialized to the value represented by the specified + * String, as performed by the valueOf methcod of class Double + * . + * + * @param s the string to be parsed. + * @return the double value represented by the string argument. + */ + public static double parseDouble(String s) { + return Double.parseDouble(s); + } + + // boxing methods + + /** + * Returns a Boolean instance representing the specified boolean value. If the + * specified boolean value is true, this method returns Boolean.TRUE; + * if it is false, this method returns Boolean.FALSE. + * + * @param b a boolean value. + * @return a Boolean instance representing b. + */ + public static Boolean box(boolean b) { + return b; + } + + /** + * Returns a Character instance representing the specified char value. + * + * @param c a char value. + * @return a Character instance representing c. + */ + public static Character box(char c) { + return c; + } + + /** + * Returns a Byte instance representing the specified byte value. + * + * @param b a byte value. + * @return a Byte instance representing b. + */ + public static Byte box(byte b) { + return b; + } + + /** + * Returns a Short instance representing the specified short value. + * + * @param s a short value. + * @return a Short instance representing s. + */ + public static Short box(short s) { + return s; + } + + /** + * Returns a Integer instance representing the specified int value. + * + * @param i an int value. + * @return a Integer instance representing i. + */ + public static Integer box(int i) { + return i; + } + + /** + * Returns a Long instance representing the specified long value. + * + * @param l a long value. + * @return a Long instance representing l. + */ + public static Long box(long l) { + return l; + } + + /** + * Returns a Float instance representing the specified float value. + * + * @param f a float value. + * @return a Float instance representing f. + */ + public static Float box(float f) { + return f; + } + + /** + * Returns a Double instance representing the specified double value. + * + * @param d a double value. + * @return a Double instance representing d. + */ + public static Double box(double d) { + return d; + } + + // unboxing methods + + /** + * Returns the value of the given Boolean object as a boolean primitive. + * + * @param b the Boolean object whose value is returned. + * @return the primitive boolean value of the object. + */ + public static boolean unbox(Boolean b) { + return b; + } + + /** + * Returns the value of the given Character object as a char primitive. + * + * @param ch the Character object whose value is returned. + * @return the primitive char value of the object. + */ + public static char unbox(Character ch) { + return ch; + } + + /** + * Returns the value of the specified Byte as a byte. + * + * @param b Byte that is unboxed + * @return the byte value represented by the Byte. + */ + public static byte unbox(Byte b) { + return b; + } + + /** + * Returns the short value represented by Short. + * + * @param s Short that is unboxed. + * @return the short value represented by the Short. + */ + public static short unbox(Short s) { + return s; + } + + /** + * Returns the value of represented by Integer. + * + * @param i Integer that is unboxed. + * @return the int value represented by the Integer. + */ + public static int unbox(Integer i) { + return i; + } + + /** + * Returns the long value represented by the specified Long. + * + * @param l Long to be unboxed. + * @return the long value represented by the Long. + */ + public static long unbox(Long l) { + return l; + } + + /** + * Returns the float value represented by the specified Float. + * + * @param f Float to be unboxed. + * @return the float value represented by the Float. + */ + public static float unbox(Float f) { + return f; + } + + /** + * Returns the double value represented by the specified Double. + * + * @param d Double to be unboxed. + */ + public static double unbox(Double d) { + return d; + } + } + + /* + * Wraps the time related BTrace utility methods + * @since 1.2 + */ + public static class Time { + /** + * Returns the current time in milliseconds. Note that while the unit of time of the return + * value is a millisecond, the granularity of the value depends on the underlying operating + * system and may be larger. For example, many operating systems measure time in units of tens + * of milliseconds. + * + * @return the difference, measured in milliseconds, between the current time and midnight, + * January 1, 1970 UTC. + */ + public static long millis() { + return java.lang.System.currentTimeMillis(); + } + + /** + * Returns the current value of the most precise available system timer, in nanoseconds. + * + *

This method can only be used to measure elapsed time and is not related to any other + * notion of system or wall-clock time. The value returned represents nanoseconds since some + * fixed but arbitrary time (perhaps in the future, so values may be negative). This method + * provides nanosecond precision, but not necessarily nanosecond accuracy. No guarantees are + * made about how frequently values change. Differences in successive calls that span greater + * than approximately 292 years (263 nanoseconds) will not accurately compute elapsed + * time due to numerical overflow. + * + * @return The current value of the system timer, in nanoseconds. + */ + public static long nanos() { + return java.lang.System.nanoTime(); + } + + /** + * Generates a string timestamp (current date&time) + * + * @param format The format to be used - see {@linkplain SimpleDateFormat} + * @return Returns a string representing current date&time + * @since 1.1 + */ + public static String timestamp(String format) { + return new SimpleDateFormat(format).format(Calendar.getInstance().getTime()); + } + + /** + * Generates a string timestamp (current date&time) in the default system format + * + * @return Returns a string representing current date&time + * @since 1.1 + */ + public static String timestamp() { + return new SimpleDateFormat().format(Calendar.getInstance().getTime()); + } + } + + /* + * Wraps the collections related BTrace utility methods + * @since 1.2 + */ + @SuppressWarnings("EmptyMethod") + public static class Collections { + // Create a new map + public static Map newHashMap() { + return BTraceRuntime.newHashMap(); + } + + public static Map newWeakMap() { + return BTraceRuntime.newWeakMap(); + } + + public static Deque newDeque() { + return BTraceRuntime.newDeque(); + } + + public static void putAll(Map src, Map dst) { + BTraceRuntime.putAll(src, dst); + } + + public static void copy(Map src, Map dst) { + BTraceRuntime.copy(src, dst); + } + + public static void copy(Collection src, Collection dst) {} + + // get a particular item from a Map + public static V get(Map map, K key) { + return BTraceRuntime.get(map, key); + } + + // check whether an item exists + public static boolean containsKey(Map map, K key) { + return BTraceRuntime.containsKey(map, key); + } + + public static boolean containsValue(Map map, V value) { + return BTraceRuntime.containsValue(map, value); + } + + // put a particular item into a Map + public static V put(Map map, K key, V value) { + return BTraceRuntime.put(map, key, value); + } + + // remove a particular item from a Map + public static V remove(Map map, K key) { + return BTraceRuntime.remove(map, key); + } + + // clear all items from a Map + public static void clear(Map map) { + BTraceRuntime.clear(map); + } + + // return the size of a Map + public static int size(Map map) { + return BTraceRuntime.size(map); + } + + public static boolean isEmpty(Map map) { + return BTraceRuntime.isEmpty(map); + } + + // operations on collections + public static int size(Collection coll) { + return BTraceRuntime.size(coll); + } + + public static boolean isEmpty(Collection coll) { + return BTraceRuntime.isEmpty(coll); + } + + public static boolean contains(Collection coll, Object obj) { + return BTraceRuntime.contains(coll, obj); + } + + public static boolean contains(Object[] array, Object value) { + for (Object each : array) { + if (compare(each, value)) { + return true; + } + } + return false; + } + + public static Object[] toArray(Collection collection) { + return BTraceRuntime.toArray(collection); + } + + // operations on Deque + public static void push(Deque queue, V value) { + BTraceRuntime.push(queue, value); + } + + public static V poll(Deque queue) { + return BTraceRuntime.poll(queue); + } + + public static V peek(Deque queue) { + return BTraceRuntime.peek(queue); + } + + public static void addLast(Deque queue, V value) { + BTraceRuntime.addLast(queue, value); + } + + public static V peekFirst(Deque queue) { + return BTraceRuntime.peekFirst(queue); + } + + public static V peekLast(Deque queue) { + return BTraceRuntime.peekLast(queue); + } + + public static V removeLast(Deque queue) { + return BTraceRuntime.removeLast(queue); + } + + public static V removeFirst(Deque queue) { + return BTraceRuntime.removeFirst(queue); + } + } + + /* + * Wraps the atomicity related BTrace utility methods + * @since 1.2 + */ + public static class Atomic { + /** + * Creates a new AtomicInteger with the given initial value. + * + * @param initialValue the initial value + */ + public static AtomicInteger newAtomicInteger(int initialValue) { + return BTraceRuntime.newAtomicInteger(initialValue); + } + + /** + * Gets the current value of the given AtomicInteger. + * + * @param ai AtomicInteger whose value is returned. + * @return the current value + */ + public static int get(AtomicInteger ai) { + return BTraceRuntime.get(ai); + } + + /** + * Sets to the given value to the given AtomicInteger. + * + * @param ai AtomicInteger whose value is set. + * @param newValue the new value + */ + public static void set(AtomicInteger ai, int newValue) { + BTraceRuntime.set(ai, newValue); + } + + /** + * Eventually sets to the given value to the given AtomicInteger. + * + * @param ai AtomicInteger whose value is lazily set. + * @param newValue the new value + */ + public static void lazySet(AtomicInteger ai, int newValue) { + BTraceRuntime.lazySet(ai, newValue); + } + + /** + * Atomically sets the value of given AtomitInteger to the given updated value if the current + * value {@code ==} the expected value. + * + * @param ai AtomicInteger whose value is compared and set. + * @param expect the expected value + * @param update the new value + * @return true if successful. False return indicates that the actual value was not equal to the + * expected value. + */ + public static boolean compareAndSet(AtomicInteger ai, int expect, int update) { + return BTraceRuntime.compareAndSet(ai, expect, update); + } + + /** + * Atomically sets the value to the given updated value if the current value {@code ==} the + * expected value. + * + *

May fail spuriously and does not provide + * ordering guarantees, so is only rarely an appropriate alternative to {@code compareAndSet}. + * + * @param ai AtomicInteger whose value is weakly compared and set. + * @param expect the expected value + * @param update the new value + * @return true if successful. + */ + public static boolean weakCompareAndSet(AtomicInteger ai, int expect, int update) { + return BTraceRuntime.weakCompareAndSet(ai, expect, update); + } + + /** + * Atomically increments by one the current value of given AtomicInteger. + * + * @param ai AtomicInteger that is incremented. + * @return the previous value + */ + public static int getAndIncrement(AtomicInteger ai) { + return BTraceRuntime.getAndIncrement(ai); + } + + /** + * Atomically decrements by one the current value of given AtomicInteger. + * + * @param ai AtomicInteger that is decremented. + * @return the previous value + */ + public static int getAndDecrement(AtomicInteger ai) { + return BTraceRuntime.getAndDecrement(ai); + } + + /** + * Atomically increments by one the current value of given AtomicInteger. + * + * @param ai AtomicInteger that is incremented. + * @return the updated value + */ + public static int incrementAndGet(AtomicInteger ai) { + return BTraceRuntime.incrementAndGet(ai); + } + + /** + * Atomically decrements by one the current value of given AtomicInteger. + * + * @param ai AtomicInteger whose value is decremented. + * @return the updated value + */ + public static int decrementAndGet(AtomicInteger ai) { + return BTraceRuntime.decrementAndGet(ai); + } + + /** + * Atomically adds the given value to the current value. + * + * @param ai AtomicInteger whose value is added to. + * @param delta the value to add + * @return the previous value + */ + public static int getAndAdd(AtomicInteger ai, int delta) { + return BTraceRuntime.getAndAdd(ai, delta); + } + + /** + * Atomically adds the given value to the current value. + * + * @param ai AtomicInteger whose value is added to. + * @param delta the value to add + * @return the updated value + */ + public static int addAndGet(AtomicInteger ai, int delta) { + return BTraceRuntime.addAndGet(ai, delta); + } + + /** + * Atomically sets to the given value and returns the old value. + * + * @param ai AtomicInteger whose value is set. + * @param newValue the new value + * @return the previous value + */ + public static int getAndSet(AtomicInteger ai, int newValue) { + return BTraceRuntime.getAndSet(ai, newValue); + } + + /** + * Creates a new AtomicLong with the given initial value. + * + * @param initialValue the initial value + */ + public static AtomicLong newAtomicLong(long initialValue) { + return BTraceRuntime.newAtomicLong(initialValue); + } + + /** + * Gets the current value the given AtomicLong. + * + * @param al AtomicLong whose value is returned. + * @return the current value + */ + public static long get(AtomicLong al) { + return BTraceRuntime.get(al); + } + + /** + * Sets to the given value. + * + * @param al AtomicLong whose value is set. + * @param newValue the new value + */ + public static void set(AtomicLong al, long newValue) { + BTraceRuntime.set(al, newValue); + } + + /** + * Eventually sets to the given value to the given AtomicLong. + * + * @param al AtomicLong whose value is set. + * @param newValue the new value + */ + public static void lazySet(AtomicLong al, long newValue) { + BTraceRuntime.lazySet(al, newValue); + } + + /** + * Atomically sets the value to the given updated value if the current value {@code ==} the + * expected value. + * + * @param al AtomicLong whose value is compared and set. + * @param expect the expected value + * @param update the new value + * @return true if successful. False return indicates that the actual value was not equal to the + * expected value. + */ + public static boolean compareAndSet(AtomicLong al, long expect, long update) { + return BTraceRuntime.compareAndSet(al, expect, update); + } + + /** + * Atomically sets the value to the given updated value if the current value {@code ==} the + * expected value. + * + *

May fail spuriously and does not provide ordering guarantees, so is only rarely an + * appropriate alternative to {@code compareAndSet}. + * + * @param al AtomicLong whose value is compared and set. + * @param expect the expected value + * @param update the new value + * @return true if successful. + */ + public static boolean weakCompareAndSet(AtomicLong al, long expect, long update) { + return BTraceRuntime.weakCompareAndSet(al, expect, update); + } + + /** + * Atomically increments by one the current value. + * + * @param al AtomicLong whose value is incremented. + * @return the previous value + */ + public static long getAndIncrement(AtomicLong al) { + return BTraceRuntime.getAndIncrement(al); + } + + /** + * Atomically decrements by one the current value. + * + * @param al AtomicLong whose value is decremented. + * @return the previous value + */ + public static long getAndDecrement(AtomicLong al) { + return BTraceRuntime.getAndDecrement(al); + } + + /** + * Atomically increments by one the current value. + * + * @param al AtomicLong whose value is incremented. + * @return the updated value + */ + public static long incrementAndGet(AtomicLong al) { + return BTraceRuntime.incrementAndGet(al); + } + + /** + * Atomically decrements by one the current value. + * + * @param al AtomicLong whose value is decremented. + * @return the updated value + */ + public static long decrementAndGet(AtomicLong al) { + return BTraceRuntime.decrementAndGet(al); + } + + /** + * Atomically adds the given value to the current value. + * + * @param al AtomicLong whose value is added to. + * @param delta the value to add + * @return the previous value + */ + public static long getAndAdd(AtomicLong al, long delta) { + return BTraceRuntime.getAndAdd(al, delta); + } + + /** + * Atomically adds the given value to the current value. + * + * @param al AtomicLong whose value is added to + * @param delta the value to add + * @return the updated value + */ + public static long addAndGet(AtomicLong al, long delta) { + return BTraceRuntime.addAndGet(al, delta); + } + + /** + * Atomically sets to the given value and returns the old value. + * + * @param al AtomicLong that is set. + * @param newValue the new value + * @return the previous value + */ + public static long getAndSet(AtomicLong al, long newValue) { + return BTraceRuntime.getAndSet(al, newValue); + } + } + + /* + * Wraps the aggregations related BTrace utility methods + * @since 1.2 + */ + public static class Aggregations { + /** + * Creates a new aggregation based on the given aggregation function type. + * + * @param type the aggregating function to be performed on the data being added to the + * aggregation. + */ + public static Aggregation newAggregation(AggregationFunction type) { + return BTraceRuntime.newAggregation(type); + } + + /** + * Creates a grouping aggregation key with the provided value. The value must be a String or + * Number type. + * + * @param element1 the value of the aggregation key + */ + public static AggregationKey newAggregationKey(Object element1) { + return BTraceRuntime.newAggregationKey(element1); + } + + /** + * Creates a composite grouping aggregation key with the provided values. The values must be + * String or Number types. + * + * @param element1 the first element of the composite aggregation key + * @param element2 the second element of the composite aggregation key + */ + public static AggregationKey newAggregationKey(Object element1, Object element2) { + return BTraceRuntime.newAggregationKey(element1, element2); + } + + /** + * Creates a composite grouping aggregation key with the provided values. The values must be + * String or Number types. + * + * @param element1 the first element of the composite aggregation key + * @param element2 the second element of the composite aggregation key + * @param element3 the third element of the composite aggregation key + */ + public static AggregationKey newAggregationKey( + Object element1, Object element2, Object element3) { + return BTraceRuntime.newAggregationKey(element1, element2, element3); + } + + /** + * Creates a composite grouping aggregation key with the provided values. The values must be + * String or Number types. + * + * @param element1 the first element of the composite aggregation key + * @param element2 the second element of the composite aggregation key + * @param element3 the third element of the composite aggregation key + * @param element4 the fourth element of the composite aggregation key + */ + public static AggregationKey newAggregationKey( + Object element1, Object element2, Object element3, Object element4) { + return BTraceRuntime.newAggregationKey(element1, element2, element3, element4); + } + + /** + * Adds a value to the aggregation with no grouping key. This method should be used when the + * aggregation is to calculate only a single aggregated value. + * + * @param aggregation the aggregation to which the value should be added + */ + public static void addToAggregation(Aggregation aggregation, long value) { + BTraceRuntime.addToAggregation(aggregation, value); + } + + /** + * Adds a value to the aggregation with a grouping key. This method should be used when the + * aggregation should effectively perform a "group by" on the key value. The aggregation will + * calculate a separate aggregated value for each unique aggregation key. + * + * @param aggregation the aggregation to which the value should be added + * @param key the grouping aggregation key + */ + public static void addToAggregation(Aggregation aggregation, AggregationKey key, long value) { + BTraceRuntime.addToAggregation(aggregation, key, value); + } + + /** + * Resets values within the aggregation to the default. This will affect all values within the + * aggregation when multiple aggregation keys have been used. + * + * @param aggregation the aggregation to be cleared + */ + public static void clearAggregation(Aggregation aggregation) { + BTraceRuntime.clearAggregation(aggregation); + } + + /** + * Removes all aggregated values from the aggregation except for the largest or smallest + * abs(count) elements. + * + *

If count is positive, the largest aggregated values in the aggregation will + * be preserved. If count is negative the smallest values will be preserved. If + * count is zero then all elements will be removed. + * + *

Behavior is intended to be similar to the dtrace trunc() function. + * + * @param aggregation the aggregation to be truncated + * @param count the number of elements to preserve. If negative, the smallest abs(count) + * elements are preserved. + */ + public static void truncateAggregation(Aggregation aggregation, int count) { + BTraceRuntime.truncateAggregation(aggregation, count); + } + + public static void printAggregation(String name, Aggregation aggregation) { + BTraceRuntime.printAggregation(name, aggregation); + } + + public static void printAggregation(String name, Aggregation aggregation, String format) { + BTraceRuntime.printAggregation(name, aggregation, format); + } + + public static void printAggregation( + String name, String format, Collection aggregationList) { + Aggregation[] aggregationArray = new Aggregation[aggregationList.size()]; + int index = 0; + for (Aggregation a : aggregationList) { + aggregationArray[index] = a; + index++; + } + BTraceRuntime.printAggregation(name, format, aggregationArray); + } + } + + /** + * Profiling support. It is a highly specialized aggregation (therefore not included in the + * generic aggregations support) which is able to calculate clean self time spent in + * hierarchically called methods (or bigger parts of code) + */ + public static class Profiling { + /** + * Creates a new {@linkplain Profiler} instance + * + * @return A new {@linkplain Profiler} instance + */ + public static Profiler newProfiler() { + return BTraceRuntime.newProfiler(); + } + + /** + * Creates a new {@linkplain Profiler} instance with the specified expected count of the + * distinct methods to be recorded. + * + * @param expectedBlockCnt The expected count of the distinct blocks to be recorded. + * @return Returns a new {@linkplain Profiler} instance + */ + public static Profiler newProfiler(int expectedBlockCnt) { + return BTraceRuntime.newProfiler(expectedBlockCnt); + } + + /** + * Records the entry to a particular code block + * + * @param profiler The {@linkplain Profiler} instance to use + * @param blockName The block identifier + */ + public static void recordEntry(Profiler profiler, String blockName) { + BTraceRuntime.recordEntry(profiler, blockName); + } + + /** + * Records the exit out of a particular code block + * + * @param profiler The {@linkplain Profiler} instance to use + * @param blockName The block identifier + * @param duration The time spent in the mentioned block + */ + public static void recordExit(Profiler profiler, String blockName, long duration) { + BTraceRuntime.recordExit(profiler, blockName, duration); + } + + /** + * Creates a new snapshot of the profiling metrics collected sofar + * + * @param profiler The {@linkplain Profiler} instance to use + * @return Returns an immutable snapshot of the profiling metrics in the form of a map where the + * key is the block name and the value is a map of metrics names and the appropriate values + *
+ * The supported metrics names are: "selfTime", "wallTime" and "invocations" + */ + public static Profiler.Snapshot snapshot(Profiler profiler) { + return BTraceRuntime.snapshot(profiler); + } + + public static Profiler.Snapshot snapshotAndReset(Profiler profiler) { + return BTraceRuntime.snapshotAndReset(profiler); + } + + public static void reset(Profiler profiler) { + BTraceRuntime.resetProfiler(profiler); + } + + public static void printSnapshot(String name, Profiler profiler) { + BTraceRuntime.printSnapshot(name, profiler.snapshot()); + } + + public static void printSnapshot(String name, Profiler profiler, String format) { + BTraceRuntime.printSnapshot(name, profiler.snapshot(), format); + } + } + + /* + * Wraps the speculation related BTrace utility methods + * @since 1.2 + */ + public static class Speculation { + /** + * Returns an identifier for a new speculative buffer. + * + * @return new speculative buffer id + */ + public static int speculation() { + return BTraceRuntime.speculation(); + } + + /** + * Sets current speculative buffer id. + * + * @param id the speculative buffer id + */ + public static void speculate(int id) { + BTraceRuntime.speculate(id); + } + + /** + * Commits the speculative buffer associated with id. + * + * @param id the speculative buffer id + */ + public static void commit(int id) { + BTraceRuntime.commit(id); + } + + /** + * Discards the speculative buffer associated with id. + * + * @param id the speculative buffer id + */ + public static void discard(int id) { + BTraceRuntime.discard(id); + } + } + + /* + * Wraps the references related BTrace utility methods + * @since 1.2 + */ + public static class References { + /** + * Creates and returns a weak reference to the given object. + * + * @param obj object for which a weak reference is created. + * @return a weak reference to the given object. + */ + public static WeakReference weakRef(Object obj) { + return new WeakReference<>(obj); + } + + /** + * Creates and returns a soft reference to the given object. + * + * @param obj object for which a soft reference is created. + * @return a soft reference to the given object. + */ + public static SoftReference softRef(Object obj) { + return new SoftReference<>(obj); + } + + /** + * Returns the given reference object's referent. If the reference object has been cleared, + * either by the program or by the garbage collector, then this method returns null + * . + * + * @param ref reference object whose referent is returned. + * @return The object to which the reference refers, or null if the reference + * object has been cleared. + */ + public static Object deref(Reference ref) { + if (ref.getClass().getClassLoader() == null) { + return ref.get(); + } else { + throw new IllegalArgumentException(); + } + } + } + + /* + * Wraps the reflection related BTrace utility methods + * @since 1.2 + */ + public static class Reflective { + /** + * Returns the runtime class of the given Object. + * + * @param obj the Object whose Class is returned + * @return the Class object of given object + */ + public static Class classOf(Object obj) { + return obj.getClass(); + } + + /** + * Returns the Class object representing the class or interface that declares the field + * represented by the given Field object. + * + * @param field whose declaring Class is returned + */ + public static Class declaringClass(Field field) { + return field.getDeclaringClass(); + } + + /** Returns the name of the given Class object. */ + public static String name(Class clazz) { + return clazz.getName(); + } + + /** + * Returns the name of the Field object. + * + * @param field Field for which name is returned + * @return name of the given field + */ + public static String name(Field field) { + return field.getName(); + } + + /** + * Returns the type of the Field object. + * + * @param field Field for which type is returned + * @return type of the given field + */ + public static Class type(Field field) { + return field.getType(); + } + + /** Returns the access flags of the given Class. */ + public static int accessFlags(Class clazz) { + return clazz.getModifiers(); + } + + /** Returns the access flags of the given Field. */ + public static int accessFlags(Field field) { + return field.getModifiers(); + } + + /** Returns the current context class loader */ + public static ClassLoader contextClassLoader() { + return Thread.currentThread().getContextClassLoader(); + } + + // get Class of the given name + + /** Returns Class object for given class name. */ + public static Class classForName(String name) { + ClassLoader callerLoader = BTraceRuntime.getCallerClassloader(STACK_DEC); + return classForName(name, callerLoader); + } + + /** Returns the Class for the given class name using the given class loader. */ + public static Class classForName(String name, ClassLoader cl) { + try { + return Class.forName(name, false, cl); + } catch (ClassNotFoundException exp) { + throw translate(exp); + } + } + + /** + * Determines if the class or interface represented by the first Class object is + * either the same as, or is a superclass or superinterface of, the class or interface + * represented by the second Class parameter. It returns true if so; + * otherwise it returns false. + */ + public static boolean isAssignableFrom(Class a, Class b) { + return a.isAssignableFrom(b); + } + + /** + * Determines if the specified Object is assignment-compatible with the object + * represented by the specified Class. This method is the dynamic equivalent of the + * Java language instanceof operator. The method returns true if the + * specified Object argument is non-null and can be cast to the reference type + * represented by this Class object without raising a ClassCastException. + * It returns false otherwise. + * + * @param clazz the class that is checked. + * @param obj the object to check. + * @return true if obj is an instance of the given class. + */ + public static boolean isInstance(Class clazz, Object obj) { + return clazz.isInstance(obj); + } + + /** + * Returns the Class representing the superclass of the entity (class, interface, + * primitive type or void) represented by the given Class. If the given Class + * represents either the Object class, an interface, a primitive type, or + * void, then null is returned. If the given object represents an array class then the + * Class object representing the Object class is returned. + * + * @param clazz the Class whose super class is returned. + * @return the superclass of the class represented by the given object. + */ + public static Class getSuperclass(Class clazz) { + return clazz.getSuperclass(); + } + + /** + * Determines if the specified Class object represents an interface type. + * + * @param clazz the Class object to check. + * @return true if the Class represents an interface; false otherwise. + */ + public static boolean isInterface(Class clazz) { + return clazz.isInterface(); + } + + /** + * Determines if the given Class object represents an array class. + * + * @param clazz Class object to check. + * @return true if the given object represents an array class; false + * otherwise. + */ + public static boolean isArray(Class clazz) { + return clazz.isArray(); + } + + /** Returns whether the given Class represent primitive type or not. */ + public static boolean isPrimitive(Class clazz) { + return clazz.isPrimitive(); + } + + /** returns component type of an array Class. */ + public static Class getComponentType(Class clazz) { + return clazz.getComponentType(); + } + + // Accessing fields by reflection + + /** + * Returns a Field object that reflects the specified declared field of the class + * or interface represented by the given Class object. The name + * parameter is a String that specifies the simple name of the desired field. + * Returns null on not finding field if throwException parameter is false + * . Else throws a RuntimeException when field is not found. + * + * @param clazz Class whose field is returned + * @param name the name of the field + * @param throwException whether to throw exception on failing to find field or not + * @return the Field object for the specified field in this class + */ + public static Field field(Class clazz, String name, boolean throwException) { + return getField(clazz, name, throwException); + } + + /** + * Returns a Field object that reflects the specified declared field of the class + * or interface represented by the given Class object. The name + * parameter is a String that specifies the simple name of the desired field. + * Throws a RuntimeException when field is not found. + * + * @param clazz Class whose field is returned + * @param name the name of the field + * @return the Field object for the specified field in this class + */ + public static Field field(Class clazz, String name) { + return field(clazz, name, true); + } + + /** + * Returns a Field object that reflects the specified declared field of the class + * or interface represented by the given Class object. The name + * parameter is a String that specifies the simple name of the desired field. + * Returns null on not finding field if throwException parameter is false + * . Else throws a RuntimeException when field is not found. + * + * @param clazz Class whose field is returned + * @param name the name of the field + * @param throwException whether to throw exception on failing to find field or not + * @return the Field object for the specified field in this class + */ + public static Field field(String clazz, String name, boolean throwException) { + ClassLoader callerLoader = BTraceRuntime.getCallerClassloader(STACK_DEC); + return field(classForName(clazz, callerLoader), name, throwException); + } + + /** + * Returns a Field object that reflects the specified declared field of the class + * or interface represented by the given Class object. The name + * parameter is a String that specifies the simple name of the desired field. + * Throws a RuntimeException when field is not found. + * + * @param clazz Class whose field is returned + * @param name the name of the field + * @return the Field object for the specified field in this class + */ + public static Field field(String clazz, String name) { + ClassLoader callerLoader = BTraceRuntime.getCallerClassloader(STACK_DEC); + return field(classForName(clazz, callerLoader), name); + } + + // field value get methods + + /** + * Gets the value of a static byte field. + * + * @param field Field object whose value is returned. + * @return the value of the byte field + */ + public static byte getByte(Field field) { + checkStatic(field); + try { + return field.getByte(null); + } catch (Exception exp) { + throw translate(exp); + } + } + + /** + * Gets the value of an instance byte field. + * + * @param field Field object whose value is returned. + * @param obj the object to extract the byte value from + * @return the value of the byte field + */ + public static byte getByte(Field field, Object obj) { + try { + return field.getByte(obj); + } catch (Exception exp) { + throw translate(exp); + } + } + + /** + * Gets the value of an instance byte field. + * + * @param name name of the field whose value is returned. + * @param instance the object to extract the byte value from + * @return the value of the byte field + * @since 1.3.5 + */ + public static byte getByte(String name, Object instance) { + Field f = getField(instance.getClass(), name, true); + return getByte(f, instance); + } + + /** + * Gets the value of a static byte field. + * + * @param name name of the field whose value is returned. + * @param clazz the class to extract the byte value from + * @return the value of the byte field + * @since 1.3.5 + */ + public static byte getByteStatic(String name, Class clazz) { + Field f = getField(clazz, name, true); + return getByte(f, null); + } + + /** + * Gets the value of a static short field. + * + * @param field Field object whose value is returned. + * @return the value of the short field + */ + public static short getShort(Field field) { + checkStatic(field); + try { + return field.getShort(null); + } catch (Exception exp) { + throw translate(exp); + } + } + + /** + * Gets the value of an instance short field. + * + * @param field Field object whose value is returned. + * @param obj the object to extract the short value from + * @return the value of the short field + */ + public static short getShort(Field field, Object obj) { + try { + return field.getShort(obj); + } catch (Exception exp) { + throw translate(exp); + } + } + + /** + * Gets the value of an instance short field. + * + * @param name name of the field whose value is returned. + * @param instance the object to extract the short value from + * @return the value of the short field + * @since 1.3.5 + */ + public static short getShort(String name, Object instance) { + Field f = getField(instance.getClass(), name, true); + return getShort(f, instance); + } + + /** + * Gets the value of a static short field. + * + * @param name name of the field whose value is returned. + * @param clazz the class to extract the short value from + * @return the value of the short field + * @since 1.3.5 + */ + public static short getShortStatic(String name, Class clazz) { + Field f = getField(clazz, name, true); + return getShort(f, null); + } + + /** + * Gets the value of a static int field. + * + * @param field Field object whose value is returned. + * @return the value of the int field + */ + public static int getInt(Field field) { + checkStatic(field); + try { + return field.getInt(null); + } catch (Exception exp) { + throw translate(exp); + } + } + + /** + * Gets the value of an instance int field. + * + * @param field Field object whose value is returned. + * @param obj the object to extract the int value from + * @return the value of the int field + */ + public static int getInt(Field field, Object obj) { + try { + return field.getInt(obj); + } catch (Exception exp) { + throw translate(exp); + } + } + + /** + * Gets the value of an instance int field. + * + * @param name name of the field whose value is returned. + * @param instance the object to extract the int value from + * @return the value of the int field + * @since 1.3.5 + */ + public static int getInt(String name, Object instance) { + Field f = getField(instance.getClass(), name, true); + return getInt(f, instance); + } + + /** + * Gets the value of a sttaic int field. + * + * @param name name of the field whose value is returned. + * @param clazz the class to extract the int value from + * @return the value of the int field + * @since 1.3.5 + */ + public static int getIntStatic(String name, Class clazz) { + Field f = getField(clazz, name, true); + return getInt(f, null); + } + + /** + * Gets the value of a static long field. + * + * @param field Field object whose value is returned. + * @return the value of the long field + */ + public static long getLong(Field field) { + checkStatic(field); + try { + return field.getLong(null); + } catch (Exception exp) { + throw translate(exp); + } + } + + /** + * Gets the value of an instance long field. + * + * @param field Field object whose value is returned. + * @param obj the object to extract the long value from + * @return the value of the long field + */ + public static long getLong(Field field, Object obj) { + try { + return field.getLong(obj); + } catch (Exception exp) { + throw translate(exp); + } + } + + /** + * Gets the value of an instance long field. + * + * @param name name of the field whose value is returned. + * @param instance the object to extract the long value from + * @return the value of the long field + * @since 1.3.5 + */ + public static long getLong(String name, Object instance) { + Field f = getField(instance.getClass(), name, true); + return getLong(f, instance); + } + + /** + * Gets the value of a static long field. + * + * @param name name of the field whose value is returned. + * @param clazz the class to extract the long value from + * @return the value of the long field + * @since 1.3.5 + */ + public static long getLongStatic(String name, Class clazz) { + Field f = getField(clazz, name, true); + return getLong(f, null); + } + + /** + * Gets the value of a static float field. + * + * @param field Field object whose value is returned. + * @return the value of the float field + */ + public static float getFloat(Field field) { + checkStatic(field); + try { + return field.getFloat(null); + } catch (Exception exp) { + throw translate(exp); + } + } + + /** + * Gets the value of an instance float field. + * + * @param field Field object whose value is returned. + * @param obj the object to extract the float value from + * @return the value of the float field + */ + public static float getFloat(Field field, Object obj) { + try { + return field.getFloat(obj); + } catch (Exception exp) { + throw translate(exp); + } + } + + /** + * Gets the value of an instance float field. + * + * @param name name of the field whose value is returned. + * @param instance the object to extract the float value from + * @return the value of the float field + * @since 1.3.5 + */ + public static float getFloat(String name, Object instance) { + Field f = getField(instance.getClass(), name, true); + return getFloat(f, instance); + } + + /** + * Gets the value of a static float field. + * + * @param name name of the field whose value is returned. + * @param clazz the class to extract the float value from + * @return the value of the float field + * @since 1.3.5 + */ + public static float getFloatStatic(String name, Class clazz) { + Field f = getField(clazz, name, true); + return getFloat(f, null); + } + + /** + * Gets the value of a static double field. + * + * @param field Field object whose value is returned. + * @return the value of the double field + */ + public static double getDouble(Field field) { + checkStatic(field); + try { + return field.getDouble(null); + } catch (Exception exp) { + throw translate(exp); + } + } + + /** + * Gets the value of an instance double field. + * + * @param field Field object whose value is returned. + * @param instance the object to extract the double value from + * @return the value of the double field + */ + public static double getDouble(Field field, Object instance) { + try { + return field.getDouble(instance); + } catch (Exception exp) { + throw translate(exp); + } + } + + /** + * Gets the value of an instance double field. + * + * @param name name of the field whose value is returned. + * @param instance the object to extract the double value from + * @return the value of the double field + * @since 1.3.5 + */ + public static double getDouble(String name, Object instance) { + Field f = getField(instance.getClass(), name, true); + return getDouble(f, instance); + } + + /** + * Gets the value of a static double field. + * + * @param name name of the field whose value is returned. + * @param clazz the class to extract the double value from + * @return the value of the double field + * @since 1.3.5 + */ + public static double getDouble(String name, Class clazz) { + Field f = getField(clazz, name, true); + return getDouble(f, null); + } + + /** + * Gets the value of a static boolean field. + * + * @param field Field object whose value is returned. + * @return the value of the boolean field + */ + public static boolean getBoolean(Field field) { + checkStatic(field); + try { + return field.getBoolean(null); + } catch (Exception exp) { + throw translate(exp); + } + } + + /** + * Gets the value of an instance boolean field. + * + * @param field Field object whose value is returned. + * @param obj the object to extract the boolean value from + * @return the value of the boolean field + */ + public static boolean getBoolean(Field field, Object obj) { + try { + return field.getBoolean(obj); + } catch (Exception exp) { + throw translate(exp); + } + } + + /** + * Gets the value of an instance boolean field. + * + * @param name name of the field whose value is returned. + * @param instance the object to extract the boolean value from + * @return the value of the boolean field + * @since 1.3.5 + */ + public static boolean getBoolean(String name, Object instance) { + Field f = getField(instance.getClass(), name, true); + return getBoolean(f, instance); + } + + /** + * Gets the value of a static boolean field. + * + * @param name name of the field whose value is returned. + * @param clazz the class to extract the boolean value from + * @return the value of the boolean field + * @since 1.3.5 + */ + public static boolean getBooleanStatic(String name, Class clazz) { + Field f = getField(clazz, name, true); + return getBoolean(f, null); + } + + /** + * Gets the value of a static char field. + * + * @param field Field object whose value is returned. + * @return the value of the char field + */ + public static char getChar(Field field) { + checkStatic(field); + try { + return field.getChar(null); + } catch (Exception exp) { + throw translate(exp); + } + } + + /** + * Gets the value of an instance char field. + * + * @param field Field object whose value is returned. + * @param obj the object to extract the char value from + * @return the value of the char field + */ + public static char getChar(Field field, Object obj) { + try { + return field.getChar(obj); + } catch (Exception exp) { + throw translate(exp); + } + } + + /** + * Gets the value of an instance char field. + * + * @param name name of the field whose value is returned. + * @param instance the object to extract the char value from + * @return the value of the char field + * @since 1.3.5 + */ + public static char getChar(String name, Object instance) { + Field f = getField(instance.getClass(), name, true); + return getChar(f, instance); + } + + /** + * Gets the value of a static char field. + * + * @param name name of the field whose value is returned. + * @param clazz the class to extract the char value from + * @return the value of the char field + * @since 1.3.5 + */ + public static char getCharStatic(String name, Class clazz) { + Field f = getField(clazz, name, true); + return getChar(f, null); + } + + /** + * Gets the value of a static reference field. + * + * @param field Field object whose value is returned. + * @return the value of the reference field + */ + public static Object get(Field field) { + checkStatic(field); + try { + return field.get(null); + } catch (Exception exp) { + throw translate(exp); + } + } + + /** + * Gets the value of an instance reference field. + * + * @param field Field object whose value is returned. + * @param obj the object to extract the reference value from + * @return the value of the reference field + */ + public static Object get(Field field, Object obj) { + try { + return field.get(obj); + } catch (Exception exp) { + throw translate(exp); + } + } + + /** + * Gets the value of an instance reference field. + * + * @param name name of the field whose value is returned. + * @param instance the object to extract the reference value from + * @return the value of the reference field + * @since 1.3.5 + */ + public static Object get(String name, Object instance) { + Field f = getField(instance.getClass(), name, true); + return get(f, instance); + } + + /** + * Gets the value of a static reference field. + * + * @param name name of the field whose value is returned. + * @param clazz the class to extract the reference value from + * @return the value of the reference field + * @since 1.3.5 + */ + public static Object getStatic(String name, Class clazz) { + Field f = getField(clazz, name, true); + return get(f, null); + } + + /** + * Print all instance fields of an object as name-value pairs. Includes the inherited fields as + * well. + * + * @param obj Object whose fields are printed. + */ + public static void printFields(Object obj) { + printFields(obj, false); + } + + /** + * Print all instance fields of an object as name-value pairs. Includes the inherited fields as + * well. Optionally, prints name of the declaring class before each field - so that if same + * named field in super class chain may be disambiguated. + * + * @param obj Object whose fields are printed. + * @param classNamePrefix flag to tell whether to prefix field names names by class name or not. + */ + public static void printFields(Object obj, boolean classNamePrefix) { + StringBuilder buf = new StringBuilder(); + buf.append('{'); + addFieldValues(buf, obj, obj.getClass(), classNamePrefix); + buf.append('}'); + println(buf.toString()); + } + + /** + * Print all static fields of the class as name-value pairs. Includes the inherited fields as + * well. + * + * @param clazz Class whose static fields are printed. + */ + public static void printStaticFields(Class clazz) { + printStaticFields(clazz, false); + } + + /** + * Print all static fields of the class as name-value pairs. Includes the inherited fields as + * well. Optionally, prints name of the declaring class before each field - so that if same + * named field in super class chain may be disambigated. + * + * @param clazz Class whose static fields are printed. + * @param classNamePrefix flag to tell whether to prefix field names names by class name or not. + */ + public static void printStaticFields(Class clazz, boolean classNamePrefix) { + StringBuilder buf = new StringBuilder(); + buf.append('{'); + addStaticFieldValues(buf, clazz, classNamePrefix); + buf.append('}'); + println(buf.toString()); + } + } + + /* + * Wraps the data export related BTrace utility methods + * @since 1.2 + */ + public static class Export { + /** + * Serialize a given object into the given file. Under the current dir of traced app, + * ./btrace<pid>/btrace-class/ directory is created. Under that directory, a file of given + * fileName is created. + * + * @param obj object that has to be serialized. + * @param fileName name of the file to which the object is serialized. + */ + public static void serialize(Serializable obj, String fileName) { + BTraceRuntime.serialize(obj, fileName); + } + + /** + * Creates an XML document to persist the tree of the all transitively reachable objects from + * given "root" object. + */ + public static String toXML(Object obj) { + return BTraceRuntime.toXML(obj); + } + + /** + * Writes an XML document to persist the tree of the all the transitively reachable objects from + * the given "root" object. Under the current dir of traced app, + * ./btrace<pid>/btrace-class/ directory is created. Under that directory, a file of the + * given fileName is created. + */ + public static void writeXML(Object obj, String fileName) { + BTraceRuntime.writeXML(obj, fileName); + } + + /** + * Writes a .dot document to persist the tree of the all the transitively reachable objects from + * the given "root" object. .dot documents can be viewed by Graphviz application + * (www.graphviz.org) Under the current dir of traced app, ./btrace<pid>/btrace-class/ + * directory is created. Under that directory, a file of the given fileName is created. + * + * @since 1.1 + */ + public static void writeDOT(Object obj, String fileName) { + BTraceRuntime.writeDOT(obj, fileName); + } + } + + /* + * Wraps the OS related BTrace utility methods + * @since 1.2 + */ + public static class Sys { + /** + * Returns n'th command line argument. null if not available. + * + * @param n command line argument index + * @return n'th command line argument + */ + public static String $(int n) { + return BTraceRuntime.$(n); + } + + /** + * Returns a command line argument value for the given key. {@code null} if not available.
+ * In order to provide a key-value pair on the command line it must have the following syntax - + * <key>=<value> + * + * @param key the argument key + * @return the corresponding value or {@code null} + */ + public static String $(String key) { + return BTraceRuntime.$(key); + } + + /** Returns the process id of the currently BTrace'd process. */ + public static int getpid() { + int pid = -1; + try { + pid = Integer.parseInt($(0)); + } catch (Exception ignored) { + } + return pid; + } + + /** Returns the number of command line arguments. */ + public static int $length() { + return BTraceRuntime.$length(); + } + + /** + * Exits the BTrace session -- note that the particular client's tracing session exits and not + * the observed/traced program! After exit call, the trace action method terminates immediately + * and no other probe action method (of that client) will be called after that. + * + * @param exitCode exit value sent to the client + */ + public static void exit(int exitCode) { + BTraceRuntime.exit(exitCode); + } + + /** + * This is same as exit(int) except that the exit code is zero. + * + * @see #exit(int) + */ + public static void exit() { + exit(0); + } + + /* + * Wraps the environment related BTrace utility methods + * @since 1.2 + */ + public static class Env { + /** + * Gets the system property indicated by the specified key. + * + * @param key the name of the system property. + * @return the string value of the system property, or null if there is no + * property with that key. + * @throws NullPointerException if key is null. + * @throws IllegalArgumentException if key is empty. + */ + public static String property(String key) { + return BTraceRuntime.property(key); + } + + /** + * Returns all Sys properties. + * + * @return the system properties + */ + public static Properties properties() { + return BTraceRuntime.properties(); + } + + /** Prints all Sys properties. */ + public static void printProperties() { + BTraceRuntime.printMap(properties()); + } + + /** + * Gets the value of the specified environment variable. An environment variable is a + * system-dependent external named value. + * + * @param name the name of the environment variable + * @return the string value of the variable, or null if the variable is not + * defined in the system environment + * @throws NullPointerException if name is null + */ + public static String getenv(String name) { + return BTraceRuntime.getenv(name); + } + + /** + * Returns an unmodifiable string map view of the current system environment. The environment + * is a system-dependent mapping from names to values which is passed from parent to child + * processes. + * + * @return the environment as a map of variable names to values + */ + public static Map getenv() { + return BTraceRuntime.getenv(); + } + + /** Prints all system environment values. */ + public static void printEnv() { + BTraceRuntime.printMap(getenv()); + } + + /** + * Returns the number of processors available to the Java virtual machine. + * + *

This value may change during a particular invocation of the virtual machine. + * Applications that are sensitive to the number of available processors should therefore + * occasionally poll this property and adjust their resource usage appropriately. + * + * @return the maximum number of processors available to the virtual machine; never smaller + * than one + */ + public static long availableProcessors() { + return Runtime.getRuntime().availableProcessors(); + } + } + + /* + * Wraps the memory related BTrace utility methods + * @since 1.2 + */ + public static class Memory { + // memory usage + + /** + * Returns the amount of free memory in the Java Virtual Machine. Calling the gc + * method may result in increasing the value returned by freeMemory. + * + * @return an approximation to the total amount of memory currently available for future + * allocated objects, measured in bytes. + */ + public static long freeMemory() { + return Runtime.getRuntime().freeMemory(); + } + + /** + * Returns the total amount of memory in the Java virtual machine. The value returned by this + * method may vary over time, depending on the host environment. + * + *

Note that the amount of memory required to hold an object of any given type may be + * implementation-dependent. + * + * @return the total amount of memory currently available for current and future objects, + * measured in bytes. + */ + public static long totalMemory() { + return Runtime.getRuntime().totalMemory(); + } + + /** + * Returns the maximum amount of memory that the Java virtual machine will attempt to use. If + * there is no inherent limit then the value {@link java.lang.Long#MAX_VALUE} will be + * returned. + * + * @return the maximum amount of memory that the virtual machine will attempt to use, measured + * in bytes + */ + public static long maxMemory() { + return Runtime.getRuntime().maxMemory(); + } + + /** Returns heap memory usage */ + public static MemoryUsage heapUsage() { + return BTraceRuntime.heapUsage(); + } + + /** Returns non-heap memory usage */ + public static MemoryUsage nonHeapUsage() { + return BTraceRuntime.nonHeapUsage(); + } + + /** + * Returns the amount of memory in bytes that the Java virtual machine initially requests from + * the operating system for memory management. + */ + public static long init(MemoryUsage mu) { + return mu.getInit(); + } + + /** + * Returns the amount of memory in bytes that is committed for the Java virtual machine to + * use. This amount of memory is guaranteed for the Java virtual machine to use. + */ + public static long committed(MemoryUsage mu) { + return mu.getCommitted(); + } + + /** + * Returns the maximum amount of memory in bytes that can be used for memory management. This + * method returns -1 if the maximum memory size is undefined. + */ + public static long max(MemoryUsage mu) { + return mu.getMax(); + } + + /** Returns the amount of used memory in bytes. */ + public static long used(MemoryUsage mu) { + return mu.getUsed(); + } + + /** Returns the approximate number of objects for which finalization is pending. */ + public static long finalizationCount() { + return BTraceRuntime.finalizationCount(); + } + + /** + * Dump the snapshot of the Java heap to a file in hprof binary format. Only the live objects + * are dumped. Under the current dir of traced app, ./btrace<pid>/btrace-class/ + * directory is created. Under that directory, a file of given fileName is created. + * + * @param fileName name of the file to which heap is dumped + */ + public static void dumpHeap(String fileName) { + dumpHeap(fileName, true); + } + + /** + * Dump the snapshot of the Java heap to a file in hprof binary format. Under the current dir + * of traced app, ./btrace<pid>/btrace-class/ directory is created. Under that + * directory, a file of given fileName is created. + * + * @param fileName name of the file to which heap is dumped + * @param live flag that tells whether only live objects are to be dumped or all objects are + * to be dumped. + */ + public static void dumpHeap(String fileName, boolean live) { + BTraceRuntime.dumpHeap(fileName, live); + } + + /** + * Runs the garbage collector. + * + *

Calling the gc method suggests that the Java Virtual Machine expend effort + * toward recycling unused objects in order to make the memory they currently occupy available + * for quick reuse. When control returns from the method call, the Java Virtual Machine has + * made a best effort to reclaim space from all discarded objects. This method calls Sys.gc() + * to perform GC. + */ + public static void gc() { + java.lang.System.gc(); + } + + /** + * Returns the total amount of time spent in GarbageCollection up to this point since the + * application was started. + * + * @return Returns the amount of overall time spent in GC + */ + public static long getTotalGcTime() { + return BTraceRuntime.getTotalGcTime(); + } + + /** + * Returns an overview of available memory pools
+ * It is possible to provide a text format the overview will use + * + * @param poolFormat The text format string to format the overview.
+ * Exactly 5 arguments are passed to the format function.
+ * The format defaults to ";%1$s;%2$d;%3$d;%4$d;%5$d;Memory]" + * @return Returns the formatted value of memory pools overview + * @since 1.2 + */ + public static String getMemoryPoolUsage(String poolFormat) { + return BTraceRuntime.getMemoryPoolUsage(poolFormat); + } + + /** + * Runs the finalization methods of any objects pending finalization. + * + *

Calling this method suggests that the Java Virtual Machine expend effort toward running + * the finalize methods of objects that have been found to be discarded but whose + * finalize methods have not yet been run. When control returns from the method + * call, the Java Virtual Machine has made a best effort to complete all outstanding + * finalizations. This method calls Sys.runFinalization() to run finalization. + */ + public static void runFinalization() { + java.lang.System.runFinalization(); + } + } + + /* + * Wraps the VM related BTrace utility methods + * @since 1.2 + */ + public static class VM { + /** + * Returns the input arguments passed to the Java virtual machine which does not include the + * arguments to the main method. This method returns an empty list if there is no + * input argument to the Java virtual machine. + * + *

Some Java virtual machine implementations may take input arguments from multiple + * different sources: for examples, arguments passed from the application that launches the + * Java virtual machine such as the 'java' command, environment variables, configuration + * files, etc. + * + *

Typically, not all command-line options to the 'java' command are passed to the Java + * virtual machine. Thus, the returned input arguments may not include all command-line + * options. + * + * @return a list of String objects; each element is an argument passed to the Java + * virtual machine. + */ + public static List vmArguments() { + return BTraceRuntime.getInputArguments(); + } + + /** + * Prints VM input arguments list. + * + * @see #vmArguments + */ + public static void printVmArguments() { + println(vmArguments()); + } + + /** + * Returns the Java virtual machine implementation version. This method is equivalent to + * Sys.getProperty("java.vm.version")}. + * + * @return the Java virtual machine implementation version. + */ + public static String vmVersion() { + return BTraceRuntime.getVmVersion(); + } + + /** + * Tests if the Java virtual machine supports the boot class path mechanism used by the + * bootstrap class loader to search for class files. + * + * @return true if the Java virtual machine supports the class path mechanism; + * false otherwise. + */ + public static boolean isBootClassPathSupported() { + return BTraceRuntime.isBootClassPathSupported(); + } + + /** + * Returns the boot class path that is used by the bootstrap class loader to search for class + * files. + * + *

Multiple paths in the boot class path are separated by the path separator character of + * the platform on which the Java virtual machine is running. + * + *

A Java virtual machine implementation may not support the boot class path mechanism for + * the bootstrap class loader to search for class files. The {@link #isBootClassPathSupported} + * method can be used to determine if the Java virtual machine supports this method. + * + * @return the boot class path. + * @throws java.lang.UnsupportedOperationException if the Java virtual machine does not + * support this operation. + */ + public static String bootClassPath() { + return BTraceRuntime.getBootClassPath(); + } + + /** + * Returns the Java class path that is used by the system class loader to search for class + * files. This method is equivalent to Sys.getProperty("java.class.path"). + * + * @return the Java class path. + */ + public static String classPath() { + return Sys.Env.property("java.class.path"); + } + + /** + * Returns the Java library path. This method is equivalent to + * Sys.getProperty("java.library.path"). + * + *

Multiple paths in the Java library path are separated by the path separator character of + * the platform of the Java virtual machine being monitored. + * + * @return the Java library path. + */ + public static String libraryPath() { + return Sys.Env.property("java.library.path"); + } + + /** + * Returns the current number of live threads including both daemon and non-daemon threads. + * + * @return the current number of live threads. + */ + public static long threadCount() { + return BTraceRuntime.getThreadCount(); + } + + /** + * Returns the peak live thread count since the Java virtual machine started or peak was + * reset. + * + * @return the peak live thread count. + */ + public static long peakThreadCount() { + return BTraceRuntime.getPeakThreadCount(); + } + + /** + * Returns the total number of threads created and also started since the Java virtual machine + * started. + * + * @return the total number of threads started. + */ + public static long totalStartedThreadCount() { + return BTraceRuntime.getTotalStartedThreadCount(); + } + + /** + * Returns the current number of live daemon threads. + * + * @return the current number of live daemon threads. + */ + public static long daemonThreadCount() { + return BTraceRuntime.getDaemonThreadCount(); + } + + /** + * Returns the system load average for the last minute + * + * @return the system load average for the last minute + */ + public static double systemLoadAverage() { + return BTraceRuntime.getSystemLoadAverage(); + } + + /** + * Returns the CPU time used by the process on which the Java virtual machine is running in + * nanoseconds. + * + * @return the CPU time used by the process on which the JVM is running in nanoseconds. + * Returns -1 if this operation is not supported on this platform + */ + public static long processCPUTime() { + return BTraceRuntime.getProcessCPUTime(); + } + + /** + * Returns the start time of the Java virtual machine in milliseconds. This method returns the + * approximate time when the Java virtual machine started. + * + * @return start time of the Java virtual machine in milliseconds. + */ + public static long vmStartTime() { + return BTraceRuntime.vmStartTime(); + } + + /** + * Returns the uptime of the Java virtual machine in milliseconds. + * + * @return uptime of the Java virtual machine in milliseconds. + */ + public static long vmUptime() { + return BTraceRuntime.vmUptime(); + } + + /** + * Returns the total CPU time for the current thread in nanoseconds. The returned value is of + * nanoseconds precision but not necessarily nanoseconds accuracy. If the implementation + * distinguishes between user mode time and system mode time, the returned CPU time is the + * amount of time that the current thread has executed in user mode or system mode. + */ + public static long currentThreadCpuTime() { + return BTraceRuntime.getCurrentThreadCpuTime(); + } + + /** + * Returns the CPU time that the current thread has executed in user mode in nanoseconds. The + * returned value is of nanoseconds precision but not necessarily nanoseconds accuracy. + */ + public static long currentThreadUserTime() { + return BTraceRuntime.getCurrentThreadUserTime(); + } + } + } + + /* + * Wraps the jvmstat counters related BTrace utility methods + * @since 1.2 + */ + public static class Counters { + /** accessing jvmstat (perf) int counter */ + public static long perfInt(String name) { + return BTraceRuntime.perfInt(name); + } + + /** accessing jvmstat (perf) long counter */ + public static long perfLong(String name) { + return BTraceRuntime.perfLong(name); + } + + /** accessing jvmstat (perf) String counter */ + public static String perfString(String name) { + return BTraceRuntime.perfString(name); + } + } + + /* + * Wraps the dtrace related BTrace utility methods + * @since 1.2 + */ + public static class D { + /** + * BTrace to DTrace communication chennal. Raise DTrace USDT probe from BTrace. + * + * @see #dtraceProbe(String, String, int, int) + */ + public static int probe(String str1, String str2) { + return probe(str1, str2, -1, -1); + } + + /** + * BTrace to DTrace communication chennal. Raise DTrace USDT probe from BTrace. + * + * @see #dtraceProbe(String, String, int, int) + */ + public static int probe(String str1, String str2, int i1) { + return probe(str1, str2, i1, -1); + } + + /** + * BTrace to DTrace communication channel. Raise DTrace USDT probe from BTrace. + * + * @param str1 first String param to DTrace probe + * @param str2 second String param to DTrace probe + * @param i1 first int param to DTrace probe + * @param i2 second int param to DTrace probe + */ + public static int probe(String str1, String str2, int i1, int i2) { + return BTraceRuntime.dtraceProbe(str1, str2, i1, i2); + } + } + + /** + * Support for JFR integration. + * + * @since 2.1.0 + */ + public static final class Jfr { + /** + * Create a new event instance using the factory field annotated by {@linkplain + * org.openjdk.btrace.core.annotations.Event} + * + * @param eventFactory the event factory + * @return new event instance + */ + public static JfrEvent prepareEvent(JfrEvent.Factory eventFactory) { + return eventFactory.newEvent(); + } + + /** + * Set an event field value + * + * @param event event instance + * @param fieldName field name + * @param fieldValue field value + */ + public static void setEventField(JfrEvent event, String fieldName, byte fieldValue) { + event.withValue(fieldName, fieldValue); + } + + /** + * Set an event field value + * + * @param event event instance + * @param fieldName field name + * @param fieldValue field value + */ + public static void setEventField(JfrEvent event, String fieldName, char fieldValue) { + event.withValue(fieldName, fieldValue); + } + + /** + * Set an event field value + * + * @param event event instance + * @param fieldName field name + * @param fieldValue field value + */ + public static void setEventField(JfrEvent event, String fieldName, short fieldValue) { + event.withValue(fieldName, fieldValue); + } + + /** + * Set an event field value + * + * @param event event instance + * @param fieldName field name + * @param fieldValue field value + */ + public static void setEventField(JfrEvent event, String fieldName, int fieldValue) { + event.withValue(fieldName, fieldValue); + } + + /** + * Set an event field value + * + * @param event event instance + * @param fieldName field name + * @param fieldValue field value + */ + public static void setEventField(JfrEvent event, String fieldName, long fieldValue) { + event.withValue(fieldName, fieldValue); + } + + /** + * Set an event field value + * + * @param event event instance + * @param fieldName field name + * @param fieldValue field value + */ + public static void setEventField(JfrEvent event, String fieldName, float fieldValue) { + event.withValue(fieldName, fieldValue); + } + + /** + * Set an event field value + * + * @param event event instance + * @param fieldName field name + * @param fieldValue field value + */ + public static void setEventField(JfrEvent event, String fieldName, double fieldValue) { + event.withValue(fieldName, fieldValue); + } + + /** + * Set an event field value + * + * @param event event instance + * @param fieldName field name + * @param fieldValue field value + */ + public static void setEventField(JfrEvent event, String fieldName, boolean fieldValue) { + event.withValue(fieldName, fieldValue); + } + + /** + * Set an event field value + * + * @param event event instance + * @param fieldName field name + * @param fieldValue field value + */ + public static void setEventField(JfrEvent event, String fieldName, String fieldValue) { + event.withValue(fieldName, fieldValue); + } + + /** + * Check whether the event should be committed. + * + * @see Event#shouldCommit() + * @param event event to check + * @return {@literal true} if the event should be committed + */ + public static boolean shouldCommit(JfrEvent event) { + return event.shouldCommit(); + } + + /** + * Commit the event + * + * @see Event#commit() + * @param event the event to commit + */ + public static void commit(JfrEvent event) { + event.commit(); + } + + public static void begin(JfrEvent event) { + event.begin(); + } + + public static void end(JfrEvent event) { + event.end(); + } + } +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/CircularBuffer.java b/btrace-core/src/main/java/org/openjdk/btrace/core/CircularBuffer.java new file mode 100644 index 000000000..4ef001aaf --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/CircularBuffer.java @@ -0,0 +1,57 @@ +package org.openjdk.btrace.core; + +public final class CircularBuffer { + private final T[] elements; + private final int size; + private long readIndex = 0; + private long writeIndex = -1; + private int length = 0; + + @SuppressWarnings("unchecked") + public CircularBuffer(int size) { + this.size = size; + elements = (T[]) new Object[size]; + } + + public void add(T element) { + int newIndex = (int) (++writeIndex) % size; + elements[newIndex] = element; + int nextIndex = (newIndex + 1) % size; + if (elements[nextIndex] != null) { + readIndex = nextIndex; + } + if (++length > size) { + length = size; + } + } + + public boolean forEach(Function functor) { + int cntr = 0; + while (cntr < size && writeIndex >= readIndex) { + if (functor.apply(elements[(int) readIndex % size])) { + readIndex++; + if (--length < 0) { + length = 0; + } + } else { + return false; + } + cntr++; + } + return true; + } + + public boolean doNext(Function nextWork) { + if (writeIndex >= readIndex) { + if (nextWork.apply(elements[(int) readIndex % size])) { + readIndex++; + return true; + } + } + return false; + } + + public int getLength() { + return length; + } +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/DebugSupport.java b/btrace-core/src/main/java/org/openjdk/btrace/core/DebugSupport.java new file mode 100644 index 000000000..58e8bffee --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/DebugSupport.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.btrace.core; + +import java.io.File; +import java.io.FileOutputStream; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import org.slf4j.Logger; +import org.slf4j.impl.SimpleLogger; + +/** + * Centralized support for logging various debug information. + * + * @author Jaroslav Bachorik + */ +public final class DebugSupport { + public static void initLoggers(boolean debug, Logger logger) { + String logFile = System.getProperty("org.slf4j.simpleLogger.logFile"); + System.setProperty("org.slf4j.simpleLogger.logFile", logFile != null ? logFile : "System.out"); + String defaultLevel = + System.getProperty("org.slf4j.simpleLogger.log.org.openjdk.btrace", "info"); + System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", debug ? "debug" : defaultLevel); + try { + Method mthd = SimpleLogger.class.getDeclaredMethod("init"); + mthd.setAccessible(true); + mthd.invoke(null); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + System.err.println("[btrace] Unable to reload logger config"); + } + if (logger != null) { + try { + Field fld = logger.getClass().getDeclaredField("currentLogLevel"); + fld.setAccessible(true); + fld.set( + logger, + debug + ? 10 + : 20); // 10 is the 'debug' level for SLF4J SimpleLogger, 20 is the info level + } catch (NoSuchFieldException | IllegalAccessException e) { + System.err.println("[btrace] Unable to set debug log level"); + } + } + } + + private final SharedSettings settings; + + public DebugSupport(SharedSettings s) { + settings = s != null ? s : SharedSettings.GLOBAL; + } + + public boolean isDebug() { + return settings.isDebug(); + } + + public boolean isDumpClasses() { + return settings.isDumpClasses(); + } + + public String getDumpClassDir() { + return settings.getDumpDir(); + } + + public void dumpClass(String className, byte[] code) { + if (settings.isDumpClasses()) { + try { + className = className.replace(".", File.separator).replace("/", File.separator); + int index = className.lastIndexOf(File.separatorChar); + StringBuilder buf = new StringBuilder(); + if (!settings.getDumpDir().equals(".")) { + buf.append(settings.getDumpDir()); + buf.append(File.separatorChar); + } + String dir = buf.toString(); + if (index != -1) { + dir += className.substring(0, index); + } + new File(dir).mkdirs(); + String file; + if (index != -1) { + file = className.substring(index + 1); + } else { + file = className; + } + file += ".class"; + new File(dir).mkdirs(); + File out = new File(dir, file); + try (FileOutputStream fos = new FileOutputStream(out)) { + fos.write(code); + } + } catch (Exception exp) { + exp.printStackTrace(); + } + } + } +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/Function.java b/btrace-core/src/main/java/org/openjdk/btrace/core/Function.java new file mode 100644 index 000000000..02156d554 --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/Function.java @@ -0,0 +1,5 @@ +package org.openjdk.btrace.core; + +public interface Function { + R apply(T value); +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/HandlerRepository.java b/btrace-core/src/main/java/org/openjdk/btrace/core/HandlerRepository.java new file mode 100644 index 000000000..e02f28faa --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/HandlerRepository.java @@ -0,0 +1,11 @@ +package org.openjdk.btrace.core; + +/** + * A bridge interface between a handler repository implementation and the invoke dynamic bootstrap + * class doing the handler lookup. + */ +@FunctionalInterface +public interface HandlerRepository { + byte[] getProbeHandler( + String callerName, String probeName, String handlerName, String handlerDesc); +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/Messages.java b/btrace-core/src/main/java/org/openjdk/btrace/core/Messages.java new file mode 100644 index 000000000..f7e2c067c --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/Messages.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.core; + +import java.text.MessageFormat; +import java.util.Locale; +import java.util.ResourceBundle; + +public final class Messages { + private static final ResourceBundle messages; + + static { + messages = + ResourceBundle.getBundle( + "org.openjdk.btrace.core.messages", + Locale.getDefault(), + Messages.class.getClassLoader()); + } + + private Messages() {} + + public static String get(String key) { + return messages.getString(key); + } + + public static String format(String key, Object... args) { + return MessageFormat.format(get(key), args); + } +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/MethodID.java b/btrace-core/src/main/java/org/openjdk/btrace/core/MethodID.java new file mode 100644 index 000000000..b06f7b6b0 --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/MethodID.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.core; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * A factory class for shared method ids + * + * @author Jaroslav Bachorik + */ +public class MethodID { + static final AtomicInteger lastMehodId = new AtomicInteger(1); + private static final Map methodIds = new HashMap<>(); + + /** + * Generates a unique method id based on the provided method tag + * + * @param methodTag The tag used to distinguish between methods + * @return An ID belonging to the provided method tag + */ + public static int getMethodId(String methodTag) { + synchronized (methodIds) { + if (!methodIds.containsKey(methodTag)) { + methodIds.put(methodTag, lastMehodId.getAndIncrement()); + } + return methodIds.get(methodTag); + } + } + + public static int getMethodId(String className, String method, String desc) { + return getMethodId(className + "#" + method + "#" + desc); + } +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/PrefixMap.java b/btrace-core/src/main/java/org/openjdk/btrace/core/PrefixMap.java new file mode 100644 index 000000000..6c21a4c82 --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/PrefixMap.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2017, Jaroslav Bachorik . + * All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Copyright owner designates + * this particular file as subject to the "Classpath" exception as provided + * by the owner in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ +package org.openjdk.btrace.core; + +import java.util.HashMap; +import java.util.Map; + +/** Simplified trie-based prefix map */ +public class PrefixMap { + private final Node root = new Node(); + + public void add(CharSequence val) { + Node n = root; + for (int i = 0; i < val.length(); i++) { + char ch = val.charAt(i); + Node child = n.getReferencedNode(ch); + if (child == null) { + child = new Node(); + n.addReferencedNode(ch, child); + } + n = child; + } + n.setValue(val); + } + + public boolean contains(CharSequence val) { + Node n = root; + for (int i = 0; i < val.length(); i++) { + char ch = val.charAt(i); + Node child = n.getReferencedNode(ch); + if (child == null) { + return false; + } + if (child.value != null) { + return true; + } + n = child; + } + return false; + } + + private static final class Node { + private final Map refs = new HashMap<>(); + private CharSequence value; + + public Node() { + value = null; + } + + public Node getReferencedNode(char ch) { + return refs.get(ch); + } + + public void addReferencedNode(char ch, Node n) { + if (!refs.containsKey(ch)) { + refs.put(ch, n); + } + } + + public void setValue(CharSequence val) { + value = val; + } + } +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/Profiler.java b/btrace-core/src/main/java/org/openjdk/btrace/core/Profiler.java new file mode 100644 index 000000000..9d872cc31 --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/Profiler.java @@ -0,0 +1,258 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.core; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import org.openjdk.btrace.core.annotations.Property; + +/** + * Profiler is a highly specialized aggregation-like data collector optimized for high-speed + * collection of the application execution call tree data.
+ *
+ * It is exposable as MBean via {@linkplain Property} annotation + * + * @author Jaroslav Bachorik + * @since 1.2 + */ +public abstract class Profiler { + /** + * This property exposes the time of creating this particular {@linkplain Profiler} instance.
+ * The unit is milliseconds. + */ + public final long START_TIME; + + /** Creates a new {@linkplain Profiler} instance */ + public Profiler() { + START_TIME = System.currentTimeMillis(); + } + + /** + * Records the event of entering an execution unit (eg. method)
+ * Must be paired with a call to {@linkplain Profiler#recordExit(java.lang.String, long) } with + * the same blockName, eventually + * + * @param blockName The execution unit identifier (eg. method FQN) + */ + public abstract void recordEntry(String blockName); + + /** + * Records the event of exiting an execution unit (eg. method)
+ * Must be preceded by a call to {@linkplain Profiler#recordEntry(java.lang.String) } with the + * same blockName + * + * @param blockName The execution unit identifier (eg. method FQN) + * @param duration Invocation duration in nanoseconds + */ + public abstract void recordExit(String blockName, long duration); + + /** + * Creates an immutable snapshot of the collected profiling data + * + * @return Returns the immutable {@linkplain Snapshot} instance + */ + public final Snapshot snapshot() { + return snapshot(false); + } + + /** + * Creates an immutable snapshot of the collected profiling data.
+ * Makes it possible to reset the profiler after creating the snapshot, eventually + * + * @param reset Signals the profiler to perform reset right after getting the snapshot (in an + * atomic transaction) + * @return Returns the immutable {@linkplain Snapshot} instance + */ + public abstract Snapshot snapshot(boolean reset); + + /** Resets all the collected data */ + public abstract void reset(); + + /** Helper interface to make accessing a {@linkplain Profiler} as an MBean type safe. */ + public interface MBeanValueProvider { + Snapshot getMBeanValue(); + } + + /** + * Record represents an atomic unit in the application execution call tree + * + * @since 1.2 + */ + public static final class Record { + public static final Comparator COMPARATOR = + (o1, o2) -> { + if (o1 == null && o2 != null) return 1; + if (o1 != null && o2 == null) return -1; + if (o1 == null && o2 == null) return 0; + return o1.blockName.compareTo(o2.blockName); + }; + + public final String blockName; + public long wallTime = 0, wallTimeMax = 0, wallTimeMin = Long.MAX_VALUE; + public long selfTime = 0, selfTimeMax = 0, selfTimeMin = Long.MAX_VALUE; + public long invocations = 1; + public boolean onStack = false; + public Record referring = null; + + public Record(String blockName) { + this.blockName = blockName; + } + + public Record duplicate() { + Record r = new Record(blockName); + r.invocations = invocations; + r.selfTime = selfTime; + r.selfTimeMax = selfTimeMax; + r.selfTimeMin = selfTimeMin; + r.wallTime = wallTime; + r.wallTimeMax = wallTimeMax; + r.wallTimeMin = wallTimeMin; + return r; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Record other = (Record) obj; + if (!Objects.equals(blockName, other.blockName)) { + return false; + } + if (wallTime != other.wallTime) { + return false; + } + if (selfTime != other.selfTime) { + return false; + } + return invocations == other.invocations; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 17 * hash + (blockName != null ? blockName.hashCode() : 0); + hash = 17 * hash + (int) (wallTime ^ (wallTime >>> 32)); + hash = 17 * hash + (int) (selfTime ^ (selfTime >>> 32)); + hash = 17 * hash + (int) (invocations ^ (invocations >>> 32)); + return hash; + } + + @Override + public String toString() { + return "Record{\n" + + "\tblockName=" + + blockName + + "\n," + + "\twallTime=" + + wallTime + + ",\n" + + "\twallTime.min=" + + wallTimeMin + + ",\n" + + "\twallTime.max=" + + wallTimeMax + + ",\n" + + "\tselfTime=" + + selfTime + + ",\n" + + "\tselfTime.min=" + + selfTimeMin + + ",\n" + + "\tselfTime.max=" + + selfTimeMax + + ",\n" + + "\tinvocations=" + + invocations + + ",\nonStack=" + + onStack + + '}'; + } + } + + /** + * Snapshot is an immutable image of the current profiling data collected by the {@linkplain + * Profiler}
+ *
+ * It is created by calling {@linkplain Profiler#snapshot()} method + * + * @since 1.2 + */ + public static final class Snapshot { + public final long timeStamp; + public final long timeInterval; + public final Record[] total; + + public Snapshot(Record[] data, long startTs, long stopTs) { + timeStamp = stopTs; + timeInterval = stopTs - startTs; + total = data; + } + + public List getGridData() { + List rslt = new ArrayList<>(); + + Object[] titleRow = { + "Block", + "Invocations", + "SelfTime.Total", + "SelfTime.Avg", + "SelfTime.Min", + "SelfTime.Max", + "WallTime.Total", + "WallTime.Avg", + "WallTime.Min", + "WallTime.Max" + }; + + rslt.add(titleRow); + + for (Record r : total) { + if (r != null) { + Object[] row = { + r.blockName, + r.invocations, + r.selfTime, + r.selfTime / r.invocations, + r.selfTimeMin < Long.MAX_VALUE ? r.selfTimeMin : "N/A", + r.selfTimeMax > 0 ? r.selfTimeMax : "N/A", + r.wallTime, + r.wallTime / r.invocations, + r.wallTimeMin < Long.MAX_VALUE ? r.wallTimeMin : "N/A", + r.wallTimeMax > 0 ? r.wallTimeMax : "N/A" + }; + rslt.add(row); + } + } + return rslt; + } + } +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/SharedSettings.java b/btrace-core/src/main/java/org/openjdk/btrace/core/SharedSettings.java new file mode 100644 index 000000000..06df1217c --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/SharedSettings.java @@ -0,0 +1,263 @@ +/* + * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.btrace.core; + +import java.util.Map; + +/** @author Jaroslav Bachorik */ +public final class SharedSettings { + public static final String DEBUG_KEY = "debug"; + public static final String DUMP_DIR_KEY = "dumpDir"; + @Deprecated public static final String UNSAFE_KEY = "unsafe"; + public static final String TRUSTED_KEY = "trusted"; + public static final String TRACK_RETRANSFORMS_KEY = "trackRetransforms"; + public static final String PROBE_DESC_PATH_KEY = "probeDescPath"; + public static final String STATSD_HOST_KEY = "statsdHost"; + public static final String STATSD_PORT_KEY = "statsdPort"; + public static final String FILEROLL_INTERVAL_KEY = "fileRollMilliseconds"; + public static final String FILEROLL_MAXROLLS_KEY = "fileRollMaxRolls"; + public static final String OUTPUT_FILE_KEY = "scriptOutputFile"; + public static final String OUTPUT_DIR_KEY = "scriptOutputDir"; + + public static final SharedSettings GLOBAL = new SharedSettings(); + + private boolean debug = false; + private boolean trusted = false; + private boolean trackRetransforms = false; + private boolean retransformStartup = true; + private String dumpDir = null; + private String probeDescPath = "."; + private String bootClassPath = ""; + private final String systemClassPath = ""; + private String statsdHost = null; + private int statsdPort = 8125; // default statsd port + private int fileRollMilliseconds = Integer.MIN_VALUE; + private int fileRollMaxRolls = 5; // default hold max 100 logs + private String outputFile; + private String scriptDir; + private String scriptOutputDir; + private String clientName; + + public void from(Map params) { + Boolean b = (Boolean) params.get(DEBUG_KEY); + if (b != null) { + debug = b; + } + b = (Boolean) params.get(TRACK_RETRANSFORMS_KEY); + if (b != null) { + trackRetransforms = b; + } + b = (Boolean) params.get(UNSAFE_KEY); + if (b != null) { + trusted = b; + } + b = (Boolean) params.get(TRUSTED_KEY); + if (b != null) { + trusted |= b; + } + String s = (String) params.get(DUMP_DIR_KEY); + if (s != null && !s.isEmpty()) { + dumpDir = s; + } + s = (String) params.get(PROBE_DESC_PATH_KEY); + if (s != null && !s.isEmpty()) { + probeDescPath = s; + } + + s = (String) params.get(Args.BOOT_CLASS_PATH); + if (s != null && !s.isEmpty()) { + bootClassPath = s; + } + + s = (String) params.get(STATSD_HOST_KEY); + if (s != null && !s.isEmpty()) { + statsdHost = s; + } + Integer i = (Integer) params.get(STATSD_PORT_KEY); + if (i != null) { + statsdPort = i; + } + i = (Integer) params.get(FILEROLL_INTERVAL_KEY); + if (i != null) { + fileRollMilliseconds = i; + } + i = (Integer) params.get(FILEROLL_MAXROLLS_KEY); + if (i != null) { + fileRollMaxRolls = i; + } + s = (String) params.get(OUTPUT_FILE_KEY); + if (s != null && !s.isEmpty()) { + outputFile = s; + } + s = (String) params.get(OUTPUT_DIR_KEY); + if (s != null && !s.isEmpty()) { + scriptOutputDir = s; + } + } + + public void from(SharedSettings other) { + clientName = other.clientName; + debug = other.debug; + dumpDir = other.dumpDir; + fileRollMilliseconds = other.fileRollMilliseconds; + fileRollMaxRolls = other.fileRollMaxRolls; + outputFile = other.outputFile; + scriptDir = other.scriptDir; + scriptOutputDir = other.scriptOutputDir; + probeDescPath = other.probeDescPath; + bootClassPath = other.bootClassPath; + retransformStartup = other.retransformStartup; + statsdHost = other.statsdHost; + statsdPort = other.statsdPort; + trackRetransforms = other.trackRetransforms; + trusted = other.trusted; + } + + public boolean isDebug() { + return debug; + } + + public void setDebug(boolean value) { + debug = value; + } + + public boolean isDumpClasses() { + return dumpDir != null; + } + + @Deprecated + /* @deprecated use {@linkplain SharedSettings#isTrusted()} instead */ + public boolean isUnsafe() { + return trusted; + } + + public boolean isTrusted() { + return trusted; + } + + public void setTrusted(boolean value) { + trusted = value; + } + + public String getDumpDir() { + return dumpDir; + } + + public void setDumpDir(String value) { + dumpDir = value; + } + + public boolean isTrackRetransforms() { + return trackRetransforms; + } + + public void setTrackRetransforms(boolean value) { + trackRetransforms = value; + } + + public String getProbeDescPath() { + return probeDescPath; + } + + public void setProbeDescPath(String probeDescPath) { + this.probeDescPath = probeDescPath; + } + + public String getBootClassPath() { + return bootClassPath; + } + + public void setBootClassPath(String bootClassPath) { + this.bootClassPath = bootClassPath; + } + + public String getStatsdHost() { + return statsdHost; + } + + public void setStatsdHost(String statsdHost) { + this.statsdHost = statsdHost; + } + + public int getStatsdPort() { + return statsdPort; + } + + public void setStatsdPort(int statsdPort) { + this.statsdPort = statsdPort; + } + + public int getFileRollMilliseconds() { + return fileRollMilliseconds; + } + + public void setFileRollMilliseconds(int fileRollMilliseconds) { + this.fileRollMilliseconds = fileRollMilliseconds; + } + + public int getFileRollMaxRolls() { + return fileRollMaxRolls; + } + + public void setFileRollMaxRolls(int fileRollMaxRolls) { + this.fileRollMaxRolls = fileRollMaxRolls; + } + + public boolean isRetransformStartup() { + return retransformStartup; + } + + public void setRetransformStartup(boolean val) { + retransformStartup = val; + } + + public String getScriptDir() { + return scriptDir; + } + + public String getOutputFile() { + return outputFile; + } + + public void setOutputFile(String outputFile) { + this.outputFile = outputFile; + } + + public String getScriptOutputDir() { + return scriptOutputDir; + } + + public void setScriptOutputDir(String scriptOutputDir) { + this.scriptOutputDir = scriptOutputDir; + } + + public String getClientName() { + return clientName; + } + + public void setClientName(String clientName) { + this.clientName = clientName; + } +} diff --git a/src/share/classes/com/sun/btrace/VerifierException.java b/btrace-core/src/main/java/org/openjdk/btrace/core/VerifierException.java similarity index 81% rename from src/share/classes/com/sun/btrace/VerifierException.java rename to btrace-core/src/main/java/org/openjdk/btrace/core/VerifierException.java index 3908a49d8..e4f9eb687 100644 --- a/src/share/classes/com/sun/btrace/VerifierException.java +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/VerifierException.java @@ -23,20 +23,20 @@ * questions. */ -package com.sun.btrace; +package org.openjdk.btrace.core; /** - * Instance of this exception type is thrown by BTrace - * Verifier when an input .class is not a valid BTrace program. + * Instance of this exception type is thrown by BTrace Verifier when an input .class is not a valid + * BTrace program. * * @author A. Sundararajan */ public class VerifierException extends RuntimeException { - public VerifierException(String msg) { - super(msg); - } + public VerifierException(String msg) { + super(msg); + } - public VerifierException(Throwable cause) { - super(cause); - } -} \ No newline at end of file + public VerifierException(Throwable cause) { + super(cause); + } +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/aggregation/Aggregation.java b/btrace-core/src/main/java/org/openjdk/btrace/core/aggregation/Aggregation.java new file mode 100644 index 000000000..1bc86edbb --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/aggregation/Aggregation.java @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.btrace.core.aggregation; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; + +/** + * BTrace stores the results of aggregating functions in an Aggregation. The aggregated values may + * be grouped using a composite {@link AggregationKey}. + * + *

+ * + * @author Christian Glencross + */ +public class Aggregation implements Cloneable { + + private static final AggregationKey NULL_AGGREGATION_KEY = new AggregationKey(new Object[0]); + private final AggregationFunction type; + private final ConcurrentHashMap values = + new ConcurrentHashMap<>(); + + /** + * Creates an aggregation. + * + * @param type the type of aggregation function to use + */ + public Aggregation(AggregationFunction type) { + this.type = type; + } + + /** + * Adds an item of data to the aggregation with an empty key. This method is recommended if the + * aggregation will contain only a single value. + * + * @param data the value to be added + */ + public void add(long data) { + add(NULL_AGGREGATION_KEY, data); + } + + /** + * Adds an item of data to the aggregation with the specified grouping key. + * + * @param key the aggregation key + * @param data the value to be added + */ + public void add(AggregationKey key, long data) { + AggregationValue aggregationValue = values.get(key); + if (aggregationValue == null) { + aggregationValue = type.newValue(); + AggregationValue existing = values.putIfAbsent(key, aggregationValue); + if (existing != null) { + aggregationValue = existing; + } + } + aggregationValue.add(data); + } + + /** Resets all values in the aggregation to their default. */ + public void clear() { + for (AggregationValue value : values.values()) { + value.clear(); + } + } + + /** + * Reduces the size of the aggregation to the absolute value of count. If count is + * greater than zero, the largest aggregated values are preserved. If it is less than zero, the + * smallest aggregated values are preserved. Passing a value of zero clears the aggregation + * completely. + * + * @param count the absolute number indicates the number of aggregated values to preserve. + */ + public void truncate(int count) { + if (count == 0) { + values.clear(); + } else { + List> sortedContents = sort(); + + int collectionSize = sortedContents.size(); + int numberToRemove = collectionSize - Math.abs(count); + if (numberToRemove < 0) { + return; + } + List> removeContents; + if (count > 0) { + // Remove from the start of the list + removeContents = sortedContents.subList(0, numberToRemove); + } else { + removeContents = sortedContents.subList(collectionSize - numberToRemove, collectionSize); + } + for (Entry removeContent : removeContents) { + values.remove(removeContent.getKey()); + } + } + } + + /** + * Returns details of the aggregation in a tabular format which can be serialized across the wire + * and formatted for display. The data is represented as a List of rows. The last element in each + * row represents the aggregated value, the elements before this in the row contain the elements + * of the aggregating key. + * + * @return details of the aggregation in a tabular format. + */ + public List getData() { + List> sortedContents = sort(); + List result = new ArrayList<>(sortedContents.size()); + + for (Entry item : sortedContents) { + + Object[] keyElements = item.getKey().getElements(); + int rowSize = keyElements.length + 1; + + Object[] row = new Object[rowSize]; + System.arraycopy(keyElements, 0, row, 0, keyElements.length); + row[rowSize - 1] = item.getValue().getData(); + result.add(row); + } + + return result; + } + + /** + * Returns a list of the AggregationKeys that belong to this aggregation. + * + * @return a list of aggregationsKeys belonging to this aggregation. + */ + public List getKeyData() { + List keyList = new ArrayList<>(); + List> sortedContents = sort(); + for (Entry item : sortedContents) { + keyList.add(item.getKey()); + } + + return keyList; + } + + /** + * Returns a value for the given key if the key has a value associated with it. Returns zero if + * the key is not valid for this Aggregation. + * + * @param key the aggregation key + * @return the value for the given key, or zero. + */ + public Long getValueForKey(AggregationKey key) { + AggregationValue aggregationValue = values.get(key); + if (aggregationValue != null) { + return aggregationValue.getValue(); + } else { + return 0L; + } + } + + /** + * @return a list of key/value pairs contained in this aggregation by sorted by ascending value. + */ + private List> sort() { + ArrayList> result = + new ArrayList<>(values.entrySet()); + result.sort( + (o1, o2) -> { + long i1 = o1.getValue().getValue(); + long i2 = o2.getValue().getValue(); + if (i1 < i2) { + return -1; + } else if (i1 == i2) { + return 0; + } else { + return 1; + } + }); + return result; + } + + @SuppressWarnings({"RedundantThrows", "MethodDoesntCallSuperMethod"}) + @Override + protected Object clone() throws CloneNotSupportedException { + return new Aggregation(type); + } +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/aggregation/AggregationFunction.java b/btrace-core/src/main/java/org/openjdk/btrace/core/aggregation/AggregationFunction.java new file mode 100644 index 000000000..97777ee4f --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/aggregation/AggregationFunction.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.btrace.core.aggregation; + +/** + * The type of aggregation functions. + * + *

+ * + * @author Christian Glencross + */ +public enum AggregationFunction { + COUNT { + @Override + public Count newValue() { + return new Count(); + } + }, + SUM { + @Override + public Sum newValue() { + return new Sum(); + } + }, + MINIMUM { + @Override + public Minimum newValue() { + return new Minimum(); + } + }, + MAXIMUM { + @Override + public Maximum newValue() { + return new Maximum(); + } + }, + AVERAGE { + @Override + public Average newValue() { + return new Average(); + } + }, + QUANTIZE { + @Override + public Quantize newValue() { + return new Quantize(); + } + }; + + public abstract AggregationValue newValue(); +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/aggregation/AggregationKey.java b/btrace-core/src/main/java/org/openjdk/btrace/core/aggregation/AggregationKey.java new file mode 100644 index 000000000..951528ae1 --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/aggregation/AggregationKey.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.btrace.core.aggregation; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * A key identifying an element of data in an aggregation. This represents a tuple of object values + * contained in an Object[] array. Elements in the tuple may be null or of type {@link String} or + * {@link Number}. + * + *

+ * + * @author Christian Glencross + */ +public final class AggregationKey { + + private static final Set> validKeyElementTypes = new HashSet<>(); + + static { + validKeyElementTypes.add(String.class); + validKeyElementTypes.add(Boolean.class); + validKeyElementTypes.add(Byte.class); + validKeyElementTypes.add(Character.class); + validKeyElementTypes.add(Short.class); + validKeyElementTypes.add(Integer.class); + validKeyElementTypes.add(Long.class); + } + + private final Object[] elements; + + public AggregationKey(Object[] elements) { + + // Validate that no unusual datatypes are in the key. These + // values may end up getting serialized to the client so we do not want + // anything unusual. + for (Object element : elements) { + if (element != null + && (element.getClass() != String.class) + && (element.getClass() != Boolean.class) + && (element.getClass() != Byte.class) + && (element.getClass() != Character.class) + && (element.getClass() != Short.class) + && (element.getClass() != Integer.class) + && (element.getClass() != Long.class)) { + throw new IllegalArgumentException( + "Aggregation key element type '" + element.getClass().getName() + "' is not supported"); + } + } + + this.elements = elements; + } + + public Object[] getElements() { + return elements; + } + + @Override + public int hashCode() { + int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(elements); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + AggregationKey other = (AggregationKey) obj; + return Arrays.equals(elements, other.elements); + } +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/aggregation/AggregationValue.java b/btrace-core/src/main/java/org/openjdk/btrace/core/aggregation/AggregationValue.java new file mode 100644 index 000000000..277054ac1 --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/aggregation/AggregationValue.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.btrace.core.aggregation; + +/** + * An element of aggregated data stored in an {@link Aggregation}. + * + *

Concrete implementations of this interface will implement different aggregating functions. + * + *

+ * + * @author Christian Glencross + */ +public interface AggregationValue { + + /** + * Adds a data item to the aggregated value. + * + * @param data the data value + */ + void add(long data); + + /** Removes all data items previously added. */ + void clear(); + + /** + * @return the aggregated value of all data items added since the aggregation was created or last + * cleared. The aggregation function is determined by the concrete implementation of the + * interface. + */ + long getValue(); + + /** + * @return an object representation of the aggregated value. For most implementations this may be + * equivalent to Integer.valueOf( getValue() ). More complex aggregations such + * may return objects representing histograms, etc. + */ + Object getData(); +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/aggregation/Average.java b/btrace-core/src/main/java/org/openjdk/btrace/core/aggregation/Average.java new file mode 100644 index 000000000..032a732a9 --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/aggregation/Average.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.btrace.core.aggregation; + +/** + * Aggregation function for computing the mean value. + * + *

+ * + * @author Christian Glencross + */ +class Average implements AggregationValue { + + long sum = 0; + int count = 0; + + @Override + public synchronized void clear() { + sum = 0; + count = 0; + } + + @Override + public synchronized void add(long delta) { + sum += delta; + count++; + } + + @Override + public synchronized long getValue() { + if (count == 0) { + return 0; // Avoid division by zero + } + return (int) (sum / count); + } + + @Override + public Object getData() { + return getValue(); + } +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/aggregation/Count.java b/btrace-core/src/main/java/org/openjdk/btrace/core/aggregation/Count.java new file mode 100644 index 000000000..0010fceca --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/aggregation/Count.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.btrace.core.aggregation; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Aggregation function for counting the number of values. + * + *

+ * + * @author Christian Glencross + */ +class Count implements AggregationValue { + + final AtomicInteger value = new AtomicInteger(); + + @Override + public void clear() { + value.set(0); + } + + public void add() { + value.incrementAndGet(); + } + + @Override + public void add(long delta) { + if (delta >= 0) { + value.incrementAndGet(); + } else { + value.decrementAndGet(); + } + } + + @Override + public long getValue() { + return value.get(); + } + + @Override + public Object getData() { + return getValue(); + } +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/aggregation/HistogramData.java b/btrace-core/src/main/java/org/openjdk/btrace/core/aggregation/HistogramData.java new file mode 100644 index 000000000..ec7f08cbc --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/aggregation/HistogramData.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.btrace.core.aggregation; + +import java.io.PrintWriter; +import java.io.Serializable; + +/** + * A wire data structure describing histogram data. + * + *

+ * + * @author Christian Glencross + */ +public class HistogramData implements Serializable { + + private static final long serialVersionUID = 1L; + private final long[] values; + private final long[] counts; + + public HistogramData(long[] values, long[] counts) { + if (values.length != counts.length) { + throw new IllegalArgumentException("values and counts are different lengths"); + } + this.values = values; + this.counts = counts; + } + + public long[] getValues() { + return values; + } + + public long[] getCounts() { + return counts; + } + + public void print(PrintWriter p) { + int totalCount = 0; + for (long count : counts) { + totalCount += count; + } + + p.println(" value ------------- Distribution ------------- count"); + for (int i = 0; i < values.length; i++) { + p.print(String.format("%15d", values[i])); + p.print(" |"); + long lineLength = (40 * counts[i]) / totalCount; + for (int j = 0; j < 40; j++) { + p.print(j < lineLength ? "@" : " "); + } + p.print(" "); + p.println(counts[i]); + } + } +} diff --git a/src/share/classes/com/sun/btrace/aggregation/Maximum.java b/btrace-core/src/main/java/org/openjdk/btrace/core/aggregation/Maximum.java similarity index 75% rename from src/share/classes/com/sun/btrace/aggregation/Maximum.java rename to btrace-core/src/main/java/org/openjdk/btrace/core/aggregation/Maximum.java index a659964a3..6c1d9b21f 100644 --- a/src/share/classes/com/sun/btrace/aggregation/Maximum.java +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/aggregation/Maximum.java @@ -22,37 +22,38 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.sun.btrace.aggregation; +package org.openjdk.btrace.core.aggregation; /** * Aggregation function for computing the maximum value. + * *

- * + * * @author Christian Glencross */ class Maximum implements AggregationValue { - long max = Long.MIN_VALUE; + long max = Long.MIN_VALUE; - @Override - public synchronized void clear() { - max = Integer.MIN_VALUE; - } + @Override + public synchronized void clear() { + max = Integer.MIN_VALUE; + } - @Override - public synchronized void add(long value) { - if (value > max) { - max = value; - } + @Override + public synchronized void add(long value) { + if (value > max) { + max = value; } + } - @Override - public synchronized long getValue() { - return max; - } + @Override + public synchronized long getValue() { + return max; + } - @Override - public Object getData() { - return getValue(); - } + @Override + public Object getData() { + return getValue(); + } } diff --git a/src/share/classes/com/sun/btrace/aggregation/Minimum.java b/btrace-core/src/main/java/org/openjdk/btrace/core/aggregation/Minimum.java similarity index 75% rename from src/share/classes/com/sun/btrace/aggregation/Minimum.java rename to btrace-core/src/main/java/org/openjdk/btrace/core/aggregation/Minimum.java index 2382642bf..9ec764390 100644 --- a/src/share/classes/com/sun/btrace/aggregation/Minimum.java +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/aggregation/Minimum.java @@ -22,37 +22,38 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.sun.btrace.aggregation; +package org.openjdk.btrace.core.aggregation; /** * Aggregation function for computing the minimum value. + * *

- * + * * @author Christian Glencross */ class Minimum implements AggregationValue { - long min = Long.MAX_VALUE; + long min = Long.MAX_VALUE; - @Override - public synchronized void clear() { - min = Integer.MAX_VALUE; - } + @Override + public synchronized void clear() { + min = Integer.MAX_VALUE; + } - @Override - public synchronized void add(long value) { - if (value < min) { - min = value; - } + @Override + public synchronized void add(long value) { + if (value < min) { + min = value; } + } - @Override - public synchronized long getValue() { - return min; - } + @Override + public synchronized long getValue() { + return min; + } - @Override - public Object getData() { - return getValue(); - } + @Override + public Object getData() { + return getValue(); + } } diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/aggregation/Quantize.java b/btrace-core/src/main/java/org/openjdk/btrace/core/aggregation/Quantize.java new file mode 100644 index 000000000..0aef221b5 --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/aggregation/Quantize.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.btrace.core.aggregation; + +import java.util.concurrent.atomic.AtomicLong; + +/** + * Aggregation function that calculates a power-of-two frequency distribution of the values. + * + *

+ * + * @author Christian Glencross + */ +class Quantize implements AggregationValue { + + private static final int ZERO_INDEX = 64; + + // Array of buckets, where each bucket contains a count of the number of + // occurrences in a certain range determined by a base 2 logarithmic function. + // For example: + // buckets[ZERO_INDEX - 3] counts numbers in the range -4 to -7 inclusive + // buckets[ZERO_INDEX - 2] counts -2s and -3s, + // buckets[ZERO_INDEX - 1] counts the number of -1s + // buckets[ZERO_INDEX] (the mid point of the array) counts the number of zeroes + // buckets[ZERO_INDEX + 1] counts the number of 1s + // buckets[ZERO_INDEX + 2] counts 2s and 3s, + // buckets[ZERO_INDEX + 3] counts numbers in the range 4 to 7 + private final AtomicLong[] buckets = new AtomicLong[ZERO_INDEX * 2]; + + public Quantize() { + for (int i = 0; i < buckets.length; i++) { + buckets[i] = new AtomicLong(); + } + } + + /** + * Computes log to base two of the value. + * + * @param value the value for which to calculate the log, must be positive + */ + static int logBase2(long value) { + int pos = 0; + for (int off = (ZERO_INDEX >> 1); off > 0; off >>= 1) { + if (value >= 1L << off) { + value >>= off; + pos += off; + } + } + return pos; + } + + private static int getBucketIndex(long data) { + if (data == 0) { + return ZERO_INDEX; + } else if (data > 0) { + return ZERO_INDEX + 1 + logBase2(data); + } else if (data == Integer.MIN_VALUE) { + // Special case since 0 - MIN_VALUE overflows + return 0; + } else { + return ZERO_INDEX - 1 - logBase2(-data); + } + } + + private static long getBucketLabel(int index) { + if (index == ZERO_INDEX) { + return 0; + } else if (index == 0) { + return Long.MIN_VALUE; + } else if (index > ZERO_INDEX) { + index = index - ZERO_INDEX - 1; + return 1L << index; + } else { + index = ZERO_INDEX - index - 1; + return -(1L << index); + } + } + + /* + * (non-Javadoc) + * + * @see Aggregation#add(int) + */ + @Override + public void add(long data) { + int pos = getBucketIndex(data); + buckets[pos].incrementAndGet(); + } + + /** + * This implementation of get value returns the label of the bucket containing the largest value. + * This is used by the {@link Aggregation#truncate(int)} method to sort values in the aggregation + * when determining which elements to delete. + */ + @Override + public long getValue() { + for (int i = buckets.length - 1; i >= 0; i--) { + long value = buckets[i].get(); + if (value > 0) { + return getBucketLabel(i); + } + } + return 0; + } + + /* + * (non-Javadoc) + * + * @see Aggregation#clear() + */ + @Override + public void clear() { + for (AtomicLong bucket : buckets) { + bucket.set(0); + } + } + + @Override + public HistogramData getData() { + int minIndex = buckets.length; + int maxIndex = -1; + for (int i = 0; i < buckets.length; i++) { + if (buckets[i].get() != 0) { + minIndex = Math.min(i, minIndex); + maxIndex = Math.max(i, maxIndex); + } + } + if (minIndex > maxIndex) { + // No data points + return null; + } + if (maxIndex < buckets.length - 1) { + maxIndex++; + } + if (minIndex > 0) { + minIndex--; + } + int rows = maxIndex - minIndex + 1; + long[] values = new long[rows]; + long[] counts = new long[rows]; + for (int i = 0; i < rows; i++) { + values[i] = getBucketLabel(minIndex + i); + counts[i] = buckets[minIndex + i].get(); + } + return new HistogramData(values, counts); + } +} diff --git a/src/share/classes/com/sun/btrace/aggregation/Sum.java b/btrace-core/src/main/java/org/openjdk/btrace/core/aggregation/Sum.java similarity index 78% rename from src/share/classes/com/sun/btrace/aggregation/Sum.java rename to btrace-core/src/main/java/org/openjdk/btrace/core/aggregation/Sum.java index f2226aa1f..c0c429f2c 100644 --- a/src/share/classes/com/sun/btrace/aggregation/Sum.java +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/aggregation/Sum.java @@ -22,37 +22,38 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.sun.btrace.aggregation; +package org.openjdk.btrace.core.aggregation; import java.util.concurrent.atomic.AtomicLong; /** * Aggregation function for computing the sum of values. + * *

- * + * * @author Christian Glencross */ class Sum implements AggregationValue { - AtomicLong value = new AtomicLong(); + final AtomicLong value = new AtomicLong(); - @Override - public void clear() { - value.set(0); - } + @Override + public void clear() { + value.set(0); + } - @Override - public void add(long delta) { - value.addAndGet(delta); - } + @Override + public void add(long delta) { + value.addAndGet(delta); + } - @Override - public long getValue() { - return value.get(); - } + @Override + public long getValue() { + return value.get(); + } - @Override - public Object getData() { - return getValue(); - } + @Override + public Object getData() { + return getValue(); + } } diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/BTrace.java b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/BTrace.java new file mode 100644 index 000000000..63e12d158 --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/BTrace.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.core.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Top-level annotation that identifies a BTrace class. + * + * @author A. Sundararajan + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface BTrace { + // If this trace is exposed as MXBean, then this tells + // the name of the MXBean. + String name() default ""; + + // description of this trace class. + String description() default ""; + // having "unsafe" set to true the script will be run in unsafe mode + + @Deprecated + /* @deprecated use {@linkplain BTrace#trusted()} instead */ + boolean unsafe() default false; + + boolean trusted() default false; +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/DTrace.java b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/DTrace.java new file mode 100644 index 000000000..d544e0639 --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/DTrace.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.core.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation for BTrace program to associate a D-script with it. D-script is specified as (inline) + * String value. + * + * @author A. Sundararajan + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface DTrace { + /** "one-liner" D-script String */ + String value(); +} diff --git a/src/share/classes/com/sun/btrace/annotations/DTraceRef.java b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/DTraceRef.java similarity index 88% rename from src/share/classes/com/sun/btrace/annotations/DTraceRef.java rename to btrace-core/src/main/java/org/openjdk/btrace/core/annotations/DTraceRef.java index 833880d6b..057512e72 100644 --- a/src/share/classes/com/sun/btrace/annotations/DTraceRef.java +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/DTraceRef.java @@ -23,7 +23,7 @@ * questions. */ -package com.sun.btrace.annotations; +package org.openjdk.btrace.core.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -31,16 +31,14 @@ import java.lang.annotation.Target; /** - * Annotation for BTrace program to associate a D-script with it. - * D-script is referred by a relative or absolute file path. + * Annotation for BTrace program to associate a D-script with it. D-script is referred by a relative + * or absolute file path. * * @author A. Sundararajan */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface DTraceRef { - /** - * Locates D-script by a relative or absolute file path. - */ - String value(); + /** Locates D-script by a relative or absolute file path. */ + String value(); } diff --git a/src/share/classes/com/sun/btrace/annotations/Duration.java b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Duration.java similarity index 87% rename from src/share/classes/com/sun/btrace/annotations/Duration.java rename to btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Duration.java index 4ed6a26a0..ef341276a 100644 --- a/src/share/classes/com/sun/btrace/annotations/Duration.java +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Duration.java @@ -23,7 +23,7 @@ * questions. */ -package com.sun.btrace.annotations; +package org.openjdk.btrace.core.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -32,15 +32,14 @@ /** * It is used to mark a probe method argument as the receiver of the duration value
- * Applicable only for {@linkplain OnMethod} annotation with {@linkplain Location} value - * of {@linkplain Kind#RETURN} or {@linkplain Kind#ERROR} - *

- * The duration is reported in nanoseconds, using resolution available by OS + * Applicable only for {@linkplain OnMethod} annotation with {@linkplain Location} value of + * {@linkplain Kind#RETURN} or {@linkplain Kind#ERROR} + * + *

The duration is reported in nanoseconds, using resolution available by OS * * @author Jaroslav Bachorik jaroslav.bachorik@sun.com * @since 1.1 */ @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) -public @interface Duration { -} +public @interface Duration {} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Event.java b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Event.java new file mode 100644 index 000000000..0a3ce7b60 --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Event.java @@ -0,0 +1,156 @@ +package org.openjdk.btrace.core.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import jdk.jfr.Category; +import jdk.jfr.Description; +import jdk.jfr.Label; +import jdk.jfr.Name; + +/** + * Mark a field as a custom event factory. + * + *

+ *     
+ * \@Event(name="myCustomEvent", fields={|@Event.Field(type = FieldType.INT, name = "f1"), |@Event.Field(type = FieldType.STRING, name = "f2"), |@Event.Field(type = FieldType.BOOLEAN, name = "f3")})
+ * private static JfrEvent myCustomEvent = null;
+ *
+ * ...
+ * \@OnMethod(...)
+ * public static void onprobe() {
+ *     JfrEvent event = BTraceUtils.Jfr.prepareEvent(myCustomEvent);
+ *     if (event.shouldCommit()) {
+ *       BTraceUtils.Jfr.setEventField(event, "f1", 10);
+ *       BTraceUtils.Jfr.setEventField(event, "f2", "hello");
+ *       BTraceUtils.Jfr.setEventField(event, 2, false);
+ *       BTraceUtils.Jfr.commit(event);
+ *     }
+ * }
+ *     
+ * 
+ * + * @since 2.1.0 + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Event { + /** Event field type */ + enum FieldType { + BYTE("byte"), + CHAR("char"), + SHORT("short"), + INT("int"), + LONG("long"), + FLOAT("float"), + DOUBLE("double"), + BOOLEAN("boolean"), + STRING("java.lang.String"), + CLASS("java.lang.Class"), + THREAD("java.lang.Thread"); + + private final String type; + + FieldType(String type) { + this.type = type; + } + + public String getType() { + return type; + } + } + + /** Field kind */ + enum FieldKind { + /** @see jdk.jfr.Timestamp */ + TIMESTAMP, + /** @see jdk.jfr.Timespan */ + TIMESPAN, + /** @see jdk.jfr.DataAmount */ + DATAAMOUNT, + /** @see jdk.jfr.Frequency */ + FREQUENCY, + /** @see jdk.jfr.MemoryAddress */ + MEMORYADDRESS, + /** @see jdk.jfr.Percentage */ + PERCENTAGE, + /** @see jdk.jfr.BooleanFlag */ + BOOLEANFLAG, + /** @see jdk.jfr.Unsigned */ + UNSIGNED, + /** No additional field kind specification */ + NONE + } + + /** Event field definition */ + @interface Field { + /** Additional field kind */ + @interface Kind { + FieldKind name(); + + String value() default ""; + } + + /** @return field type */ + FieldType type(); + + /** @return field name */ + String name(); + + /** @return field label */ + String label() default ""; + + /** @return field description */ + String description() default ""; + + /** @return additional field kind */ + Kind kind() default @Kind(name = FieldKind.NONE); + } + + /** + * Event name + * + * @return event name + * @see Name#value() + */ + String name(); + + /** + * Event label + * + * @return event label + * @see Label#value() + */ + String label() default ""; + + /** + * Event description + * + * @return event description + * @see Description#value() + */ + String description() default ""; + + /** + * Event category + * + * @return event category + * @see Category#value() + */ + String[] category() default ""; + + /** + * Each event should have a stacktrace associated with it + * + * @return {@literal true} if each event should have a stacktrace associated with it + */ + boolean stacktrace() default true; + + /** + * Event fields + * + * @return event field definitions + */ + Field[] fields(); +} diff --git a/src/share/classes/com/sun/btrace/annotations/Export.java b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Export.java similarity index 88% rename from src/share/classes/com/sun/btrace/annotations/Export.java rename to btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Export.java index f46ba54ff..d4863ca9d 100644 --- a/src/share/classes/com/sun/btrace/annotations/Export.java +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Export.java @@ -23,7 +23,7 @@ * questions. */ -package com.sun.btrace.annotations; +package org.openjdk.btrace.core.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -31,12 +31,11 @@ import java.lang.annotation.Target; /** - * BTrace fields with this annotation are exposed to - * out-of-process tools using mechanisms such as jvmstat. + * BTrace fields with this annotation are exposed to out-of-process tools using mechanisms such as + * jvmstat. * * @author A. Sundararajan */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) -public @interface Export { -} +public @interface Export {} diff --git a/src/share/classes/com/sun/btrace/annotations/Injected.java b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Injected.java similarity index 76% rename from src/share/classes/com/sun/btrace/annotations/Injected.java rename to btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Injected.java index 226210d1f..c5b4d605e 100644 --- a/src/share/classes/com/sun/btrace/annotations/Injected.java +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Injected.java @@ -22,7 +22,7 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.sun.btrace.annotations; +package org.openjdk.btrace.core.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -31,21 +31,26 @@ /** * Annotates a field as an injected service. + * * @author Jaroslav Bachorik */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.CLASS) public @interface Injected { - /** - * The injected service type - */ - ServiceType value() default ServiceType.SIMPLE; - /** - * The factory method to be used. - *

- * It must be a static method declared by the service class - * and returning the service class instance - * @return The name of the static method to be used as the factory method or an empty string - */ - String factoryMethod() default ""; + /** + * The injected service type + * + * @return the service type + */ + ServiceType value() default ServiceType.SIMPLE; + + /** + * The factory method to be used. + * + *

It must be a static method declared by the service class and returning the service class + * instance + * + * @return The name of the static method to be used as the factory method or an empty string + */ + String factoryMethod() default ""; } diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Kind.java b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Kind.java new file mode 100644 index 000000000..7c6fd1474 --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Kind.java @@ -0,0 +1,438 @@ +/* + * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.core.annotations; + +import org.openjdk.btrace.core.types.AnyType; + +/** + * This enum is specified in the Location annotation to specify probe point kind. This enum + * identifies various "points" of interest within a Java method's bytecode. + * + * @author A. Sundararajan + */ +public enum Kind { + /** + * + * + *

Array element load

+ * + *

Unannotated probe handler parameters:

+ * + *
    + *
  1. {@code type[]} - the array instance + *
  2. {@link int int} - array index + *
+ * + *

Allowed probe handler parameter annotations:

+ * + *
    + *
  • {@linkplain ProbeClassName} - the name of the enclosing class + *
  • {@linkplain ProbeMethodName} - the name of the enclosing method + *
  • {@linkplain Self} - the instance enclosing the declaring method or null if that method is + * static + *
  • {@linkplain Return} - the return value of the method call (only for {@linkplain + * Where#AFTER}) + *
+ */ + ARRAY_GET, + + /** + * + * + *

Array element store

+ * + *

Unannotated probe handler parameters:

+ * + *
    + *
  1. {@code type[]} - the array instance + *
  2. {@link int int} - array index + *
  3. {@link java.lang.Object Object} - new value + *
+ * + *

Allowed probe handler parameter annotations:

+ * + *
    + *
  • {@linkplain ProbeClassName} - the name of the enclosing class + *
  • {@linkplain ProbeMethodName} - the name of the enclosing method + *
  • {@linkplain Self} - the instance enclosing the declaring method or null if that method is + * static + *
+ */ + ARRAY_SET, + + /** + * + * + *

Method call

+ * + *

The order and number of unannotated parameters (if provided) must fully match the called + * method signature. Instead of specific parameter types one can use {@linkplain AnyType} to match + * any type. + * + *

If the only unannotated parameter is of type {@link AnyType AnyType[]} it will contain the + * called method parameters in the order defined by its signature. + * + *

Allowed probe handler parameter annotations:

+ * + *
    + *
  • {@linkplain ProbeClassName} - the name of the enclosing class + *
  • {@linkplain ProbeMethodName} - the name of the enclosing method + *
  • {@linkplain Self} - the instance enclosing the declaring method or null if that method is + * static + *
  • {@linkplain TargetInstance} - the target instance of the method call or null if the + * method is static + *
  • {@linkplain TargetMethodOrField} - the name of the method which is called + *
  • {@linkplain Return} - the return value of the method call (only for {@linkplain + * Where#AFTER}) + *
  • {@linkplain Duration} - the method call duration in nanoseconds (only for {@linkplain + * Where#AFTER} + *
+ */ + CALL, + + /** + * + * + *

Exception catch

+ * + *

Unannotated probe handler parameters:

+ * + *
    + *
  1. {@link java.lang.Throwable Throwable} - caught throwable + *
+ * + *

Allowed probe handler parameter annotations:

+ * + *
    + *
  • {@linkplain ProbeClassName} - the name of the enclosing class + *
  • {@linkplain ProbeMethodName} - the name of the enclosing method + *
  • {@linkplain Self} - the instance enclosing the declaring method or null if that method is + * static + *
+ */ + CATCH, + + /** + * + * + *

Checkcast

+ * + *

Unannotated probe handler parameters:

+ * + *
    + *
  1. {@link java.lang.String String} - type to cast to + *
+ * + *

Allowed probe handler parameter annotations:

+ * + *
    + *
  • {@linkplain ProbeClassName} - the name of the enclosing class + *
  • {@linkplain ProbeMethodName} - the name of the enclosing method + *
  • {@linkplain Self} - the instance enclosing the declaring method or null if that method is + * static + *
  • {@linkplain TargetInstance} - the casted instance ({@linkplain AnyType}) + *
+ */ + CHECKCAST, + + /** + * + * + *

Method entry

+ * + *

The order and number of unannotated parameters (if provided) must fully match the probed + * method signature. Instead of specific parameter types one can use {@linkplain AnyType} to match + * any type. + * + *

If the only unannotated parameter is of type {@link AnyType AnyType[]} it will contain the + * probed method parameters in the order defined by its signature. + * + *

Allowed probe handler parameter annotations:

+ * + *
    + *
  • {@linkplain ProbeClassName} - the name of the enclosing class + *
  • {@linkplain ProbeMethodName} - the name of the enclosing method + *
  • {@linkplain Self} - the instance enclosing the declaring method or null if that method is + * static + *
+ */ + ENTRY, + + /** + * + * + *

"return" because of no-catch

+ * + *

Unannotated probe handler parameters:

+ * + *
    + *
  1. {@link java.lang.Throwable Throwable} - the thrown throwable + *
+ * + *

Allowed probe handler parameter annotations:

+ * + *
    + *
  • {@linkplain ProbeClassName} - the name of the enclosing class + *
  • {@linkplain ProbeMethodName} - the name of the enclosing method + *
  • {@linkplain Self} - the instance enclosing the declaring method or null if that method is + * static + *
  • {@linkplain Duration} - the method call duration in nanoseconds (only for {@linkplain + * Where#AFTER} + *
+ */ + ERROR, + + /** + * + * + *

Getting a field value

+ * + *

Allowed probe handler parameter annotations:

+ * + *
    + *
  • {@linkplain ProbeClassName} - the name of the enclosing class + *
  • {@linkplain ProbeMethodName} - the name of the enclosing method + *
  • {@linkplain Self} - the instance enclosing the declaring method or null if that method is + * static + *
  • {@linkplain TargetInstance} - the field owner instance or null if the field is static + *
  • {@linkplain TargetMethodOrField} - the name of the method which is called + *
  • {@linkplain Return} - the return value of the method call (only for {@linkplain + * Where#AFTER}) + *
+ */ + FIELD_GET, + + /** + * + * + *

Setting a field value

+ * + *

Unannotated probe handler parameters:

+ * + *
    + *
  1. {@link java.lang.Object Object} - new field value + *
+ * + *

Allowed probe handler parameter annotations:

+ * + *
    + *
  • {@linkplain ProbeClassName} - the name of the enclosing class + *
  • {@linkplain ProbeMethodName} - the name of the enclosing method + *
  • {@linkplain Self} - the instance enclosing the declaring method or null if that field is + * static + *
  • {@linkplain TargetInstance} - the field owner instance or null if the field is static + *
  • {@linkplain TargetMethodOrField} - the name of the method which is called + *
+ */ + FIELD_SET, + + /** + * + * + *

instanceof check

+ * + *

Unannotated probe handler parameters:

+ * + *
    + *
  1. {@link java.lang.String String} - type to check against + *
+ * + *

Allowed probe handler parameter annotations:

+ * + *
    + *
  • {@linkplain ProbeClassName} - the name of the enclosing class + *
  • {@linkplain ProbeMethodName} - the name of the enclosing method + *
  • {@linkplain Self} - the instance enclosing the declaring method or null if that method is + * static + *
  • {@linkplain TargetInstance} - the checked instance ({@linkplain AnyType}) + *
+ */ + INSTANCEOF, + + /** + * + * + *

Source line number

+ * + *

Unannotated probe handler parameters:

+ * + *
    + *
  1. {@link int int} - line number + *
+ * + *

Allowed probe handler parameter annotations:

+ * + *
    + *
  • {@linkplain ProbeClassName} - the name of the enclosing class + *
  • {@linkplain ProbeMethodName} - the name of the enclosing method + *
  • {@linkplain Self} - the instance enclosing the declaring method or null if that method is + * static + *
+ */ + LINE, + + /** + * + * + *

New object created

+ * + *

Unannotated probe handler parameters:

+ * + *
    + *
  1. {@link java.lang.String String} - object type name + *
+ * + *

Allowed probe handler parameter annotations:

+ * + *
    + *
  • {@linkplain ProbeClassName} - the name of the enclosing class + *
  • {@linkplain ProbeMethodName} - the name of the enclosing method + *
  • {@linkplain Self} - the instance enclosing the declaring method or null if that method is + * static + *
  • {@linkplain Return} - the return value of the method call (only for {@linkplain + * Where#AFTER}) + *
+ */ + NEW, + + /** + * + * + *

New array created

+ * + *

Unannotated probe handler parameters:

+ * + *
    + *
  1. {@link java.lang.String String} - array type name + *
  2. {@link int int} - number of dimensions + *
+ * + *

Allowed probe handler parameter annotations:

+ * + *
    + *
  • {@linkplain ProbeClassName} - the name of the enclosing class + *
  • {@linkplain ProbeMethodName} - the name of the enclosing method + *
  • {@linkplain Self} - the instance enclosing the declaring method or null if that method is + * static + *
  • {@linkplain Return} - the return value of the method call (only for {@linkplain + * Where#AFTER}) + *
+ */ + NEWARRAY, + + /** + * + * + *

Return from method

+ * + *

The order and number of unannotated probe handler parameters (if provided) must fully match + * the probed method signature. Instead of specific parameter types one can use {@linkplain + * AnyType} to match any type. + * + *

If the only unannotated parameter is of type {@link AnyType AnyType[]} it will contain the + * probed method parameters in the order defined by its signature. + * + *

Allowed probe handler parameter annotations:

+ * + *
    + *
  • {@linkplain ProbeClassName} - the name of the enclosing class + *
  • {@linkplain ProbeMethodName} - the name of the enclosing method + *
  • {@linkplain Self} - the instance enclosing the declaring method or null if that method is + * static + *
  • {@linkplain Return} - the return value of the method call (only for {@linkplain + * Where#AFTER}) + *
  • {@linkplain Duration} - the method call duration in nanoseconds (only for {@linkplain + * Where#AFTER} + *
+ */ + RETURN, + + /** + * + * + *

Entry into a synchronized block

+ * + *

Unannotated probe handler parameters:

+ * + *
    + *
  1. {@link java.lang.Object Object} - lock object + *
+ * + *

Allowed probe handler parameter annotations:

+ * + *
    + *
  • {@linkplain ProbeClassName} - the name of the enclosing class + *
  • {@linkplain ProbeMethodName} - the name of the enclosing method + *
  • {@linkplain Self} - the instance enclosing the declaring method or null if that method is + * static + *
+ */ + SYNC_ENTRY, + + /** + * + * + *

Exit from a synchronized block

+ * + *

Unannotated probe handler parameters:

+ * + *
    + *
  1. {@link java.lang.Object Object} - lock object + *
+ * + *

Allowed probe handler parameter annotations:

+ * + *
    + *
  • {@linkplain ProbeClassName} - the name of the enclosing class + *
  • {@linkplain ProbeMethodName} - the name of the enclosing method + *
  • {@linkplain Self} - the instance enclosing the declaring method or null if that method is + * static + *
+ */ + SYNC_EXIT, + + /** + * + * + *

Throwing an exception

+ * + *

Unannotated probe handler parameters:

+ * + *
    + *
  1. {@linkplain java.lang.Throwable Throwable} - thrown exception + *
+ * + *

Allowed probe handler parameter annotations:

+ * + *
    + *
  • {@linkplain ProbeClassName} - the name of the enclosing class + *
  • {@linkplain ProbeMethodName} - the name of the enclosing method + *
  • {@linkplain Self} - the instance enclosing the declaring method or null if that method is + * static + *
+ */ + THROW +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Level.java b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Level.java new file mode 100644 index 000000000..e8c38670b --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Level.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.btrace.core.annotations; + +/** + * Allows specifying a probe handler instrumentation level matching expression. + * + *

See {@linkplain Level#value()} for the allowed expression syntax. + * + * @author Jaroslav Bachorik + */ +public @interface Level { + /** + * The level check expression.
+ * Allowed syntax is one of the following + * + *

    + *
  • {@code @Level("NUMBER")} - the same as {@code @Level(">=NUMBER")} + *
  • {@code @Level("=NUMBER")} - handler is enabled when instrumentation level equals + * NUMBER + *
  • {@code @Level(">NUMBER")} - handler is enabled when instrumentation level is greater than + * NUMBER + *
  • {@code @Level(">=NUMBER")} - handler is enabled when instrumentation level is greater + * than or equal to NUMBER + *
  • {@code @Level("NUMBER + *
  • {@code @Level("<=NUMBER")} - handler is enabled when instrumentation level is less than + * or equal to NUMBER + *
+ * + *

Where NUMBER is a non-negative integer number. + * + * @return the level + */ + String value() default ""; +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Location.java b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Location.java new file mode 100644 index 000000000..41322d878 --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Location.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.core.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation specifies a particular "location" within a traced/probed java method for BTrace + * probe specifications. + * + * @author A. Sundararajan + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Location { + /** + * Kind of the location. + * + * @see Kind + */ + Kind value() default Kind.ENTRY; + + /** + * Specifies where do want to probe with respect to the location of interest. + * + * @see Where + */ + Where where() default Where.BEFORE; + + /** Specifies the fully qualified class name for certain kind of probe locations. */ + String clazz() default ""; + + /** Specifies the method name for certain kind of probe locations. */ + String method() default ""; + + /** + * Specifies the field name for Kind.FIELD_SET and Kind.FIELD_GET probes. + * + * @see Kind#FIELD_GET + * @see Kind#FIELD_SET + */ + String field() default ""; + + /** + * Specifies field or method type for certain kind of probe locations. The type is specified like + * in Java source - except the method or field name and parameter names are not included. + */ + String type() default ""; + + /** + * Specifies the line number for Kind.LINE probes. + * + * @see Kind#LINE + */ + int line() default 0; +} diff --git a/src/share/classes/com/sun/btrace/annotations/OnError.java b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/OnError.java similarity index 88% rename from src/share/classes/com/sun/btrace/annotations/OnError.java rename to btrace-core/src/main/java/org/openjdk/btrace/core/annotations/OnError.java index e6cf37ed9..f27135e83 100644 --- a/src/share/classes/com/sun/btrace/annotations/OnError.java +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/OnError.java @@ -23,7 +23,7 @@ * questions. */ -package com.sun.btrace.annotations; +package org.openjdk.btrace.core.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -31,12 +31,11 @@ import java.lang.annotation.Target; /** - * BTrace method annotated by this annotation is called when - * any exception is thrown by any of the BTrace action methods. + * BTrace method annotated by this annotation is called when any exception is thrown by any of the + * BTrace action methods. * * @author A. Sundararajan */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) -public @interface OnError { -} +public @interface OnError {} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/OnEvent.java b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/OnEvent.java new file mode 100644 index 000000000..c0e309406 --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/OnEvent.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.core.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * BTrace methods annotated by this annotation are called when BTrace client sends "event" command. + * Client may send an event based on some form of user request to send (like pressing Ctrl-C or a + * GUI menu). String value may used as the name of the event. + * + * @author A. Sundararajan + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface OnEvent { + /** The event name. */ + String value() default ""; +} diff --git a/src/share/classes/com/sun/btrace/annotations/OnExit.java b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/OnExit.java similarity index 87% rename from src/share/classes/com/sun/btrace/annotations/OnExit.java rename to btrace-core/src/main/java/org/openjdk/btrace/core/annotations/OnExit.java index 7b6efe5ec..a47cc47d3 100644 --- a/src/share/classes/com/sun/btrace/annotations/OnExit.java +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/OnExit.java @@ -23,7 +23,7 @@ * questions. */ -package com.sun.btrace.annotations; +package org.openjdk.btrace.core.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -31,13 +31,11 @@ import java.lang.annotation.Target; /** - * BTrace method annotated by this annotation is called when - * BTrace "exit(int)" built-in function is called by some - * other BTrace action method. + * BTrace method annotated by this annotation is called when BTrace "exit(int)" built-in function is + * called by some other BTrace action method. * * @author A. Sundararajan */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) -public @interface OnExit { -} +public @interface OnExit {} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/OnLowMemory.java b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/OnLowMemory.java new file mode 100644 index 000000000..b49f979f6 --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/OnLowMemory.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.core.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * BTrace methods annotated by this annotation are called when the traced JVM's specified memory + * pool exceeds specified threshold size. + * + * @author A. Sundararajan + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface OnLowMemory { + /** The memory pool name. */ + String pool(); + + /** The threashold size to watch for. */ + long threshold(); +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/OnMethod.java b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/OnMethod.java new file mode 100644 index 000000000..cd1ac36de --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/OnMethod.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + * Copyright (c) 2017, Jaroslav Bachorik . + * All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Copyright owner designates + * this particular file as subject to the "Classpath" exception as provided + * by the owner in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +package org.openjdk.btrace.core.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation specifies a BTrace probe point by specifying a java class (or classes), a method + * (or methods in it) and a specific location within it. A BTrace trace action method annotated by + * this annotation is called when matching the traced program reaches the specified location. + * + * @author A. Sundararajan + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface OnMethod { + /** + * The probed (or traced) class name. This is either fully qualified name of the class or regular + * expression within two forward slash characters [like /java\\.awt\\..+/] + * or @annotation_of_the_class. i.e., specify a class indirectly as a class annotated by specified + * annotation. + */ + String clazz(); + + /** + * The probed (or traced) method name. This is either the name of the method or regular expression + * within two forward slash characters [like /read.+/] or @annotation_of_the_method. i.e., specify + * a method indirectly as a method annotated by specified annotation. + */ + String method() default ""; + + /** + * When set to {@code true} type checks will not involve assignability checks based on class + * hierarchy and only exactly matching types will be resolved as assignable. + * + * @return {@code true} if exact type matching is requested; {@code false} otherwise (default) + */ + boolean exactTypeMatch() default false; + + /** + * This is method type declaration. This is like Java method declaration but not including method + * name, parameter names and throws clause. * + * + *

Eg. public void myMethod(java.lang.String param) will become void + * (java.lang.String) + */ + String type() default ""; + + /** Identifies exact "location" or "point" of interest to probe within the set of methods. */ + Location location() default @Location; + + /** + * Activate this probe according to instrumentation level. + * + *

It is possible to define enable/disable the handler according to the current instrumentation + * level. Eg. {@code @OnMethod(clazz="class", method="method", enableAt=@Level(">1")} + * + *

The developer must make sure that all the handlers which are interconnected in any way (eg. + * method entry/exit) will be enabled/disabled at a compatible instrumentation level. + * + * @return The instrumentation level (default {@code @Level("0")}) + * @see Level + * @see org.openjdk.btrace.core.BTraceUtils#getInstrumentationLevel() + * @since 1.3.4 + */ + Level enableAt() default @Level; +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/OnProbe.java b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/OnProbe.java new file mode 100644 index 000000000..6cd293ba2 --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/OnProbe.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.core.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation is used to specify a BTrace probe point in an abstract fashion instead of + * specifying the class and the method names. This abstract namespace and (local) name are mapped to + * one or more concrete probe points by the BTrace agent at runtime. + * + * @author A. Sundararajan + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface OnProbe { + /** + * Namespace for this abstract (a.k.a logical) probe point. Namespace follows java package name + * conventions. + */ + String namespace(); + + /** Name within the namespace. Identifies a probe uniquely within the namespace. */ + String name(); +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/OnTimer.java b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/OnTimer.java new file mode 100644 index 000000000..6c347b435 --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/OnTimer.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.core.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * BTrace methods annotated by this annotation are called when a timer reaches the specified period + * value. This can be used to run periodic tracing actions. + * + * @author A. Sundararajan + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface OnTimer { + /** + * Time period of the timer in milliseconds. Defaults to 1 second + * + * @return timer period + */ + long value() default 1000L; + + /** + * If specified the timer period will be taken from btrace arguments. The format is Ant-like + * property reference - eg. {@code from = "${timer}"} + * + * @return btrace argument name holding timer period + * @since 1.3.11 + */ + String from() default ""; +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/PeriodicEvent.java b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/PeriodicEvent.java new file mode 100644 index 000000000..d05dc80ed --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/PeriodicEvent.java @@ -0,0 +1,77 @@ +package org.openjdk.btrace.core.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import jdk.jfr.Category; +import jdk.jfr.Description; +import jdk.jfr.Label; +import jdk.jfr.Name; +import jdk.jfr.Period; + +/** + * Mark a method as a JFR periodic event handler. + * + *

+ *     
+ * \@PeriodicEvent(name="periodicEvent", fields="boolean hit", period="eachChunk")
+ * public static void periodicEventHandler(JfrEvent periodic) {
+ *     BTraceUtils.Jfr.setEventField(periodic, "hit", true);
+ *     BTraceUtils.Jfr.commit(periodic);
+ * }
+ *     
+ * 
+ * + * @since 2.1.0 + */ +@Target({ElementType.METHOD, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface PeriodicEvent { + /** + * Event name + * + * @return event name + * @see Name#value() + */ + String name(); + + /** + * Event label + * + * @return event label + * @see Label#value() + */ + String label() default ""; + + /** + * Event description + * + * @return event description + * @see Description#value() + */ + String description() default ""; + + /** + * Event category + * + * @return event category + * @see Category#value() + */ + String[] category() default ""; + + /** + * Event fields + * + * @return event field definitions + */ + Event.Field[] fields(); + + /** + * Event period + * + * @return event period + * @see Period#value() + */ + String period() default "eachChunk"; +} diff --git a/src/share/classes/com/sun/btrace/annotations/ProbeClassName.java b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/ProbeClassName.java similarity index 95% rename from src/share/classes/com/sun/btrace/annotations/ProbeClassName.java rename to btrace-core/src/main/java/org/openjdk/btrace/core/annotations/ProbeClassName.java index 56f21efd6..a739920e7 100644 --- a/src/share/classes/com/sun/btrace/annotations/ProbeClassName.java +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/ProbeClassName.java @@ -23,7 +23,7 @@ * questions. */ -package com.sun.btrace.annotations; +package org.openjdk.btrace.core.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -39,6 +39,4 @@ */ @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) -public @interface ProbeClassName { - -} +public @interface ProbeClassName {} diff --git a/src/share/classes/com/sun/btrace/annotations/ProbeMethodName.java b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/ProbeMethodName.java similarity index 90% rename from src/share/classes/com/sun/btrace/annotations/ProbeMethodName.java rename to btrace-core/src/main/java/org/openjdk/btrace/core/annotations/ProbeMethodName.java index 60e42858c..697443656 100644 --- a/src/share/classes/com/sun/btrace/annotations/ProbeMethodName.java +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/ProbeMethodName.java @@ -23,7 +23,7 @@ * questions. */ -package com.sun.btrace.annotations; +package org.openjdk.btrace.core.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -40,8 +40,6 @@ @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface ProbeMethodName { - /** - * Flag indicating whether a fully qualified name (FQN) or a simple method name should be used - */ - boolean fqn() default false; + /** Flag indicating whether a fully qualified name (FQN) or a simple method name should be used */ + boolean fqn() default false; } diff --git a/src/share/classes/com/sun/btrace/annotations/Property.java b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Property.java similarity index 83% rename from src/share/classes/com/sun/btrace/annotations/Property.java rename to btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Property.java index bf74e9c12..59b45fa5e 100644 --- a/src/share/classes/com/sun/btrace/annotations/Property.java +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Property.java @@ -23,7 +23,7 @@ * questions. */ -package com.sun.btrace.annotations; +package org.openjdk.btrace.core.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -31,17 +31,18 @@ import java.lang.annotation.Target; /** - * BTrace fields with this annotation are exposed as attributes of - * the dynamic JMX bean that wraps the BTrace class. + * BTrace fields with this annotation are exposed as attributes of the dynamic JMX bean that wraps + * the BTrace class. * * @author A. Sundararajan */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Property { - // by default, the name of the attribute is same as the name - // of the field of the BTrace class. - public String name() default ""; - // description of this attribute - public String description() default ""; + // by default, the name of the attribute is same as the name + // of the field of the BTrace class. + String name() default ""; + + // description of this attribute + String description() default ""; } diff --git a/src/share/classes/com/sun/btrace/annotations/Return.java b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Return.java similarity index 95% rename from src/share/classes/com/sun/btrace/annotations/Return.java rename to btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Return.java index 3720ec75c..51f6be3c6 100644 --- a/src/share/classes/com/sun/btrace/annotations/Return.java +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Return.java @@ -23,7 +23,7 @@ * questions. */ -package com.sun.btrace.annotations; +package org.openjdk.btrace.core.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -32,10 +32,9 @@ /** * Marks a method parameter as the one that should contain the return value + * * @author Jaroslav Bachorik */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) -public @interface Return { - -} +public @interface Return {} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Sampled.java b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Sampled.java new file mode 100644 index 000000000..006e0aa7d --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Sampled.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.core.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks an {@linkplain OnMethod} handler as a sampled one. When a handler is sampled not all events + * will be traced - only a statistical sample with the given mean. + * + *

By default an adaptive sampling is used. BTrace will increase or decrease the number of + * invocations between samples to keep the mean time window, thus decreasing the overall overhead. + * + * @author Jaroslav Bachorik + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface Sampled { + int MEAN_DEFAULT = 10; + + /** + * The sampler kind + * + * @return The sampler kind + */ + Sampler kind() default Sampler.Const; + + /** + * The sampler mean. + * + *

For {@code Sampler.Const} it is the average number of events between samples.
+ * For {@code Sampler.Adaptive} it is the average time (in ns) between samples
+ * + * @return The sampler mean + */ + int mean() default MEAN_DEFAULT; + + /** + * Specifies the sampler kind + * + *

    + *
  • {@code None} - no sampling + *
  • {@code Const} - keeps the average number of events between samples + *
  • {@code Adaptive} - increases or decreases the average number of events between samples to + * lower overhead + *
+ */ + enum Sampler { + /** No Sampling */ + None, + /** Keeps the average number of events between samples */ + Const, + /** Increases or decreases the average number of events between samples to lower overhead */ + Adaptive + } +} diff --git a/src/share/classes/com/sun/btrace/annotations/Self.java b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Self.java similarity index 95% rename from src/share/classes/com/sun/btrace/annotations/Self.java rename to btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Self.java index 0c0fa82ad..e8612eb24 100644 --- a/src/share/classes/com/sun/btrace/annotations/Self.java +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Self.java @@ -23,7 +23,7 @@ * questions. */ -package com.sun.btrace.annotations; +package org.openjdk.btrace.core.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -32,10 +32,9 @@ /** * Marks a method parameter as the one that should contain *this* instance + * * @author Jaroslav Bachorik */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) -public @interface Self { - -} +public @interface Self {} diff --git a/src/share/classes/com/sun/btrace/annotations/ServiceType.java b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/ServiceType.java similarity index 82% rename from src/share/classes/com/sun/btrace/annotations/ServiceType.java rename to btrace-core/src/main/java/org/openjdk/btrace/core/annotations/ServiceType.java index 73cb5dcd7..e29e76529 100644 --- a/src/share/classes/com/sun/btrace/annotations/ServiceType.java +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/ServiceType.java @@ -22,21 +22,18 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.sun.btrace.annotations; +package org.openjdk.btrace.core.annotations; -import com.sun.btrace.BTraceRuntime; +import org.openjdk.btrace.core.BTraceRuntime; /** * Service type enumeration + * * @author Jaroslav Bachorik */ public enum ServiceType { - /** - * A simple service; possibly a globally shared singleton - */ - SIMPLE, - /** - * A runtime-aware service; requires an instance per {@linkplain BTraceRuntime} - */ - RUNTIME + /** A simple service; possibly a globally shared singleton */ + SIMPLE, + /** A runtime-aware service; requires an instance per {@linkplain BTraceRuntime} */ + RUNTIME } diff --git a/src/share/classes/com/sun/btrace/annotations/TLS.java b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/TLS.java similarity index 81% rename from src/share/classes/com/sun/btrace/annotations/TLS.java rename to btrace-core/src/main/java/org/openjdk/btrace/core/annotations/TLS.java index 570cbe5c1..b00ad57b3 100644 --- a/src/share/classes/com/sun/btrace/annotations/TLS.java +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/TLS.java @@ -23,7 +23,7 @@ * questions. */ -package com.sun.btrace.annotations; +package org.openjdk.btrace.core.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -31,18 +31,17 @@ import java.lang.annotation.Target; /** - * Annotation for thread-local BTrace fields. BTrace fields - * annotated with this annotation are stored in thread local - * storage. Field get/set are transparently converted to - * thread local get and set respectively. + * Annotation for thread-local BTrace fields. BTrace fields annotated with this annotation are + * stored in thread local storage. Field get/set are transparently converted to thread local get and + * set respectively. * *

Important!!!

- * It is not possible to access the data stored in the thread local storage - * from any other handler than {@linkplain OnMethod} + * + * It is not possible to access the data stored in the thread local storage from any other handler + * than {@linkplain OnMethod} * * @author A. Sundararajan */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) -public @interface TLS { -} +public @interface TLS {} diff --git a/src/share/classes/com/sun/btrace/annotations/TargetInstance.java b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/TargetInstance.java similarity index 87% rename from src/share/classes/com/sun/btrace/annotations/TargetInstance.java rename to btrace-core/src/main/java/org/openjdk/btrace/core/annotations/TargetInstance.java index 0fb6868a5..5af236078 100644 --- a/src/share/classes/com/sun/btrace/annotations/TargetInstance.java +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/TargetInstance.java @@ -23,7 +23,7 @@ * questions. */ -package com.sun.btrace.annotations; +package org.openjdk.btrace.core.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -31,14 +31,13 @@ import java.lang.annotation.Target; /** - * It is used to mark a probe method argument as the receiver of called instance - * with {@linkplain Location} value of {@linkplain Kind#CALL}, {@linkplain Kind#FIELD_GET} - * or {@linkplain Kind#FIELD_SET}. + * It is used to mark a probe method argument as the receiver of called instance with {@linkplain + * Location} value of {@linkplain Kind#CALL}, {@linkplain Kind#FIELD_GET} or {@linkplain + * Kind#FIELD_SET}. + * * @author Jaroslav Bachorik * @since 1.1 */ @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) -public @interface TargetInstance { - -} +public @interface TargetInstance {} diff --git a/src/share/classes/com/sun/btrace/annotations/TargetMethodOrField.java b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/TargetMethodOrField.java similarity index 85% rename from src/share/classes/com/sun/btrace/annotations/TargetMethodOrField.java rename to btrace-core/src/main/java/org/openjdk/btrace/core/annotations/TargetMethodOrField.java index 3fb0039d6..3020dd6db 100644 --- a/src/share/classes/com/sun/btrace/annotations/TargetMethodOrField.java +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/TargetMethodOrField.java @@ -23,7 +23,7 @@ * questions. */ -package com.sun.btrace.annotations; +package org.openjdk.btrace.core.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -31,16 +31,18 @@ import java.lang.annotation.Target; /** - * It is used to mark a probe method argument as the receiver of called method name - * in {@linkplain Location} = {@linkplain Kind#CALL} + * It is used to mark a probe method argument as the receiver of called method name in {@linkplain + * Location} = {@linkplain Kind#CALL} + * * @author Jaroslav Bachorik * @since 1.1 */ @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface TargetMethodOrField { - /** - * Flag indicating whether a fully qualified name (FQN) or a simple method/field name should be used - */ - boolean fqn() default false; + /** + * Flag indicating whether a fully qualified name (FQN) or a simple method/field name should be + * used + */ + boolean fqn() default false; } diff --git a/src/share/classes/com/sun/btrace/annotations/Where.java b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Where.java similarity index 80% rename from src/share/classes/com/sun/btrace/annotations/Where.java rename to btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Where.java index d2284ccfb..cd344105d 100644 --- a/src/share/classes/com/sun/btrace/annotations/Where.java +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/annotations/Where.java @@ -23,23 +23,18 @@ * questions. */ -package com.sun.btrace.annotations; +package org.openjdk.btrace.core.annotations; /** - * This enum is specified in the Location - * annotation to specify whether a probe point - * is after or before specific point of interest. + * This enum is specified in the Location annotation to specify whether a probe point is after or + * before specific point of interest. * * @author A. Sundararajan */ public enum Where { - /** - * after the location of interest - */ - AFTER, + /** after the location of interest */ + AFTER, - /** - * before the location of interest - */ - BEFORE + /** before the location of interest */ + BEFORE } diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/comm/Command.java b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/Command.java new file mode 100644 index 000000000..e12d4f807 --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/Command.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.core.comm; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.Serializable; + +public abstract class Command implements Serializable { + public static final byte ERROR = 0; + public static final byte EVENT = 1; + public static final byte EXIT = 2; + public static final byte INSTRUMENT = 3; + public static final byte MESSAGE = 4; + public static final byte RENAME = 5; + public static final byte STATUS = 6; + public static final byte NUMBER_MAP = 7; + public static final byte STRING_MAP = 8; + public static final byte NUMBER = 9; + public static final byte GRID_DATA = 10; + public static final byte RETRANSFORMATION_START = 11; + public static final byte RETRANSFORM_CLASS = 12; + public static final byte SET_PARAMS = 13; + public static final byte LIST_PROBES = 14; + public static final byte DISCONNECT = 15; + public static final byte RECONNECT = 16; + + public static final byte FIRST_COMMAND = ERROR; + public static final byte LAST_COMMAND = RECONNECT; + + @SuppressWarnings("RedundantThrows") + public static final Command NULL = + new Command() { + @Override + protected void write(ObjectOutput out) throws IOException {} + + @Override + protected void read(ObjectInput in) throws IOException, ClassNotFoundException {} + }; + + protected byte type; + private boolean urgent; + + protected Command(byte type) { + this(type, true); + } + + protected Command(byte type, boolean urgent) { + if (type < FIRST_COMMAND || type > LAST_COMMAND) { + throw new IllegalArgumentException(); + } + this.type = type; + this.urgent = urgent; + } + + private Command() { + type = -1; + urgent = true; + } + + protected abstract void write(ObjectOutput out) throws IOException; + + protected abstract void read(ObjectInput in) throws IOException, ClassNotFoundException; + + public final byte getType() { + return type; + } + + public final boolean isUrgent() { + return urgent; + } + + final void setUrgent() { + urgent = true; + } +} diff --git a/src/share/classes/com/sun/btrace/CommandListener.java b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/CommandListener.java similarity index 87% rename from src/share/classes/com/sun/btrace/CommandListener.java rename to btrace-core/src/main/java/org/openjdk/btrace/core/comm/CommandListener.java index fb10ae7c1..de73b6d92 100644 --- a/src/share/classes/com/sun/btrace/CommandListener.java +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/CommandListener.java @@ -23,17 +23,15 @@ * questions. */ -package com.sun.btrace; +package org.openjdk.btrace.core.comm; -import com.sun.btrace.comm.Command; import java.io.IOException; /** - * Callback interface called to notify wire - * protocol commands. + * Callback interface called to notify wire protocol commands. * * @author A. Sundararajan */ public interface CommandListener { - public void onCommand(Command cmd) throws IOException; -} \ No newline at end of file + void onCommand(Command cmd) throws IOException; +} diff --git a/src/share/classes/com/sun/btrace/comm/DataCommand.java b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/DataCommand.java similarity index 84% rename from src/share/classes/com/sun/btrace/comm/DataCommand.java rename to btrace-core/src/main/java/org/openjdk/btrace/core/comm/DataCommand.java index 63b112c17..452eeca67 100644 --- a/src/share/classes/com/sun/btrace/comm/DataCommand.java +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/DataCommand.java @@ -23,22 +23,22 @@ * questions. */ -package com.sun.btrace.comm; +package org.openjdk.btrace.core.comm; /** * Command that carries arbitrary "result/output" data. * - * @author Sundararajan + * @author A. Sundararajan */ public abstract class DataCommand extends Command implements PrintableCommand { - protected String name; + protected String name; - public DataCommand(byte type,String name) { - super(type); - this.name = name; - } + public DataCommand(byte type, String name, boolean urgent) { + super(type, urgent); + this.name = name; + } - public String getName() { - return name; - } -} \ No newline at end of file + public String getName() { + return name; + } +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/comm/DisconnectCommand.java b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/DisconnectCommand.java new file mode 100644 index 000000000..3897bce21 --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/DisconnectCommand.java @@ -0,0 +1,40 @@ +package org.openjdk.btrace.core.comm; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.PrintWriter; + +/** @since WireIO v.1 */ +public final class DisconnectCommand extends Command implements PrintableCommand { + private String probeId = ""; + + public DisconnectCommand() { + super(Command.DISCONNECT, true); + } + + public DisconnectCommand(String probeId) { + super(Command.DISCONNECT, true); + this.probeId = probeId; + } + + @Override + protected void write(ObjectOutput out) throws IOException { + out.writeUTF(probeId); + } + + @SuppressWarnings("RedundantThrows") + @Override + protected void read(ObjectInput in) throws IOException, ClassNotFoundException { + probeId = in.readUTF(); + } + + public void setProbeId(String probeId) { + this.probeId = probeId; + } + + @Override + public void print(PrintWriter out) { + out.println("BTrace Probe: " + probeId); + } +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/comm/ErrorCommand.java b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/ErrorCommand.java new file mode 100644 index 000000000..f1d61c409 --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/ErrorCommand.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.core.comm; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.PrintWriter; + +public class ErrorCommand extends Command implements PrintableCommand { + private Throwable cause; + + public ErrorCommand(Throwable cause) { + super(ERROR, true); + this.cause = cause; + } + + protected ErrorCommand() { + this(null); + } + + @Override + protected void write(ObjectOutput out) throws IOException { + out.writeObject(cause); + } + + @Override + protected void read(ObjectInput in) throws IOException, ClassNotFoundException { + cause = (Throwable) in.readObject(); + } + + public Throwable getCause() { + return cause; + } + + @Override + public void print(PrintWriter out) { + out.append("! ERROR\n"); + getCause().printStackTrace(out); + } +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/comm/EventCommand.java b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/EventCommand.java new file mode 100644 index 000000000..b6eabc296 --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/EventCommand.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.core.comm; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; + +public class EventCommand extends Command { + private String event; + + public EventCommand(String event) { + super(EVENT, true); + this.event = event; + } + + protected EventCommand() { + this(null); + } + + @Override + protected void write(ObjectOutput out) throws IOException { + out.writeUTF(event); + } + + @Override + protected void read(ObjectInput in) throws ClassNotFoundException, IOException { + event = in.readUTF(); + } + + public String getEvent() { + return event; + } +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/comm/ExitCommand.java b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/ExitCommand.java new file mode 100644 index 000000000..ab0d08c32 --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/ExitCommand.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.core.comm; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; + +public class ExitCommand extends Command { + private int exitCode; + + public ExitCommand(int exitCode) { + super(EXIT, true); + this.exitCode = exitCode; + } + + protected ExitCommand() { + this(0); + } + + @Override + protected void write(ObjectOutput out) throws IOException { + out.writeInt(exitCode); + } + + @Override + protected void read(ObjectInput in) throws IOException { + exitCode = in.readInt(); + } + + public int getExitCode() { + return exitCode; + } +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/comm/GridDataCommand.java b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/GridDataCommand.java new file mode 100644 index 000000000..bfb471afb --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/GridDataCommand.java @@ -0,0 +1,252 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.core.comm; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; +import org.openjdk.btrace.core.aggregation.HistogramData; + +/** + * A data command that holds tabular data. + * + *

The elements contained within the grid must be of type Number, String or HistogramData. + * + * @author Christian Glencross + */ +public class GridDataCommand extends DataCommand { + private static final Pattern INDEX_PATTERN = Pattern.compile("%(\\d)+\\$"); + private static final HashMap, String> typeFormats = new HashMap<>(); + + static { + typeFormats.put(Integer.class, "%15d"); + typeFormats.put(Short.class, "%15d"); + typeFormats.put(Byte.class, "%15d"); + typeFormats.put(Long.class, "%15d"); + typeFormats.put(BigInteger.class, "%15d"); + typeFormats.put(Double.class, "%15f"); + typeFormats.put(Float.class, "%15f"); + typeFormats.put(BigDecimal.class, "%15f"); + typeFormats.put(String.class, "%-50s"); + } + + private List data; + private String format; + + /** + * Used when deserializing a {@linkplain GridDataCommand} instance.
+ * The instance is then initialized by calling the {@linkplain + * GridDataCommand#read(java.io.ObjectInput) } method + */ + public GridDataCommand() { + this(null, null); + } + + /** + * Creates a new instance of {@linkplain GridDataCommand} with implicit format + * + * @param name The aggregation name + * @param data The aggregation data + */ + public GridDataCommand(String name, List data) { + this(name, data, null); + } + + /** + * Creates a new instance of {@linkplain GridDataCommand} with explicit format + * + * @param name The aggregation name + * @param data The aggregation data + * @param format The format to use. It mimics {@linkplain String#format(java.lang.String, + * java.lang.Object[]) } behaviour with the addition of the ability to address the key title + * as a 0-indexed item + * @see String#format(java.lang.String, java.lang.Object[]) + */ + public GridDataCommand(String name, List data, String format) { + super(GRID_DATA, name, false); + this.data = data; + this.format = format; + } + + public List getData() { + return data; + } + + /** + * Calculates the necessary field width of each column + * + * @param objects a list of objects + * @return a map containing as key the column number and as value the width of the longest value + */ + private Map getColumnWidth(List objects) { + Map columnWidth = new LinkedHashMap<>(); + for (Object[] obj : objects) { + for (int column = 0; column < obj.length; ++column) { + int length = obj[column].toString().length(); + Integer width = 0; + if (columnWidth.containsKey(column)) { + width = columnWidth.get(column); + } + if (length > width) { + columnWidth.put(column, length); + } + } + } + return columnWidth; + } + + @Override + public void print(PrintWriter out) { + + if (data != null) { + if (name != null && !name.isEmpty()) { + out.println(name); + } + Map columnWidth = getColumnWidth(data); + for (Object[] dataRow : data) { + + // Convert histograms to strings, and pretty-print multi-line text + Object[] printRow = dataRow.clone(); + for (int i = 0; i < printRow.length; i++) { + if (printRow[i] == null) { + printRow[i] = ""; + } + if (printRow[i] instanceof HistogramData) { + StringWriter buffer = new StringWriter(); + PrintWriter writer = new PrintWriter(buffer); + ((HistogramData) printRow[i]).print(writer); + writer.flush(); + printRow[i] = buffer.toString(); + } + if (printRow[i] instanceof String) { + String value = (String) printRow[i]; + if (value.contains("\n")) { + printRow[i] = reformatMultilineValue(value); + } + } + } + + // Format the text + String usedFormat = format; + if (usedFormat == null || usedFormat.length() == 0) { + StringBuilder buffer = new StringBuilder(); + for (int i = 0; i < printRow.length; i++) { + buffer.append(" "); + buffer.append(getFormat(printRow[i], columnWidth, i)); + } + usedFormat = buffer.toString(); + } + String line = String.format(usedFormat, printRow); + + out.println(line); + } + } + out.flush(); + } + + /** + * Get the format of an object + * + * @param object get the format of this object + * @param columnWidth a map containing as key the column number and as value the width of the + * longest value + * @param column get the format of that column number + * @return the format of an object + */ + private String getFormat(Object object, Map columnWidth, Integer column) { + if (object == null) { + return "%-15s"; + } + String usedFormat = typeFormats.get(object.getClass()); + if (usedFormat == null) { + return "%-15s"; + } + if (columnWidth != null && column != null && columnWidth.containsKey(column)) { + usedFormat = usedFormat.replaceFirst("\\d+", String.valueOf(columnWidth.get(column))); + } + return usedFormat; + } + + /** + * Takes a multi-line value, prefixes and appends a blank line, and inserts tab characters at the + * start of every line. This is derived from how dtrace displays stack traces, and it makes for + * pretty readable output. + */ + private String reformatMultilineValue(String value) { + StringBuilder result = new StringBuilder(); + result.append("\n"); + for (String line : value.split("\n")) { + result.append("\t").append(line); + result.append("\n"); + } + return result.toString(); + } + + @Override + protected void write(ObjectOutput out) throws IOException { + out.writeUTF(name != null ? name : ""); + if (data != null) { + out.writeUTF(format != null ? format : ""); + out.writeInt(data.size()); + for (Object[] row : data) { + out.writeInt(row.length); + for (Object cell : row) { + out.writeObject(cell); + } + } + } else { + out.writeInt(0); + } + } + + @Override + protected void read(ObjectInput in) throws IOException, ClassNotFoundException { + name = in.readUTF(); + format = in.readUTF(); + if (format.length() == 0) format = null; + + int rowCount = in.readInt(); + data = new ArrayList<>(rowCount); + for (int i = 0; i < rowCount; i++) { + int cellCount = in.readInt(); + Object[] row = new Object[cellCount]; + for (int j = 0; j < cellCount; j++) { + row[j] = in.readObject(); + } + data.add(row); + } + } +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/comm/InstrumentCommand.java b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/InstrumentCommand.java new file mode 100644 index 000000000..0f87ed0a7 --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/InstrumentCommand.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.core.comm; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.util.Map; +import org.openjdk.btrace.core.ArgsMap; + +public class InstrumentCommand extends Command { + + private byte[] code; + private ArgsMap args; + + public InstrumentCommand(byte[] code, ArgsMap args) { + super(INSTRUMENT, true); + this.code = code; + this.args = args; + } + + public InstrumentCommand(byte[] code, String[] args) { + this(code, new ArgsMap(args)); + } + + public InstrumentCommand(byte[] code, Map args) { + this(code, new ArgsMap(args)); + } + + protected InstrumentCommand() { + this(null, (Map) null); + } + + @Override + protected void write(ObjectOutput out) throws IOException { + out.writeInt(code.length); + out.write(code); + out.writeInt(args.size()); + for (Map.Entry e : args) { + out.writeUTF(e.getKey()); + String val = e.getValue(); + out.writeUTF(val != null ? val : ""); + } + } + + @Override + protected void read(ObjectInput in) throws IOException { + int len = in.readInt(); + code = new byte[len]; + in.readFully(code); + len = in.readInt(); + args = new ArgsMap(len); + for (int i = 0; i < len; i++) { + String key = in.readUTF(); + String val = in.readUTF(); + args.put(key, val); + } + } + + public byte[] getCode() { + return code; + } + + public ArgsMap getArguments() { + return args; + } +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/comm/ListProbesCommand.java b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/ListProbesCommand.java new file mode 100644 index 000000000..ac06176c3 --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/ListProbesCommand.java @@ -0,0 +1,48 @@ +package org.openjdk.btrace.core.comm; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** @since WireIO v.1 */ +public class ListProbesCommand extends Command implements PrintableCommand { + private final List probes = new ArrayList<>(); + + public ListProbesCommand() { + super(Command.LIST_PROBES, true); + } + + public void setProbes(Collection probes) { + this.probes.clear(); + this.probes.addAll(probes); + } + + @Override + protected void write(ObjectOutput out) throws IOException { + out.writeInt(probes.size()); + for (String probe : probes) { + out.writeUTF(probe); + } + } + + @SuppressWarnings("RedundantThrows") + @Override + protected void read(ObjectInput in) throws IOException, ClassNotFoundException { + int numProbes = in.readInt(); + for (int i = 0; i < numProbes; i++) { + probes.add(in.readUTF()); + } + } + + @Override + public void print(PrintWriter out) { + int cntr = 1; + for (String probe : probes) { + out.println(cntr++ + ": " + probe); + } + } +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/comm/MessageCommand.java b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/MessageCommand.java new file mode 100644 index 000000000..2d843413e --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/MessageCommand.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.core.comm; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.util.Date; + +public class MessageCommand extends DataCommand { + private static final ThreadLocal DATE_FORMAT = + ThreadLocal.withInitial(() -> new SimpleDateFormat("HH:mm:ss:SSS")); + + private long time; + private String msg; + + public MessageCommand(long time, String msg) { + this(time, msg, false); + } + + public MessageCommand(long time, String msg, boolean urgent) { + super(MESSAGE, null, urgent); + this.time = time; + this.msg = msg; + } + + public MessageCommand(String msg) { + this(msg, false); + } + + public MessageCommand(String msg, boolean urgent) { + this(0L, msg, urgent); + } + + protected MessageCommand() { + this(0L, null); + } + + @Override + protected void write(ObjectOutput out) throws IOException { + out.writeBoolean(isUrgent()); + out.writeLong(time); + byte[] bytes = msg != null ? msg.getBytes(StandardCharsets.UTF_8) : new byte[0]; + out.writeInt(bytes.length); + if (bytes.length > 0) { + out.write(bytes); + } + } + + @Override + protected void read(ObjectInput in) throws ClassNotFoundException, IOException { + if (in.readBoolean()) { + setUrgent(); + } + time = in.readLong(); + int len = in.readInt(); + byte[] bytes = new byte[len]; + + int ptr = 0; + while (ptr < len) { + ptr += in.read(bytes, ptr, len - ptr); + } + + msg = new String(bytes, StandardCharsets.UTF_8); + } + + public long getTime() { + return time; + } + + public String getMessage() { + return msg; + } + + @Override + public void print(PrintWriter out) { + if (time != 0L) { + out.print(DATE_FORMAT.get().format(new Date(time))); + out.print(" : "); + } + if (msg != null) { + out.println(msg); + } + if (isUrgent()) { + out.flush(); + } + } +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/comm/NumberDataCommand.java b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/NumberDataCommand.java new file mode 100644 index 000000000..87a7256f4 --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/NumberDataCommand.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.core.comm; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.PrintWriter; + +/** + * A simple data command that has one number value. + * + * @author A. Sundararajan + */ +public class NumberDataCommand extends DataCommand { + private Number value; + + public NumberDataCommand() { + this(null, 0); + } + + public NumberDataCommand(String name, Number value) { + super(NUMBER, name, false); + this.value = value; + } + + @Override + public void print(PrintWriter out) { + if (name != null && !name.isEmpty()) { + out.print(name); + out.print(" = "); + } + out.println(value); + } + + public Number getValue() { + return value; + } + + @Override + protected void write(ObjectOutput out) throws IOException { + out.writeUTF(name != null ? name : ""); + out.writeObject(value); + } + + @Override + protected void read(ObjectInput in) throws IOException, ClassNotFoundException { + name = in.readUTF(); + value = (Number) in.readObject(); + } +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/comm/NumberMapDataCommand.java b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/NumberMapDataCommand.java new file mode 100644 index 000000000..d1ba485e0 --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/NumberMapDataCommand.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.btrace.core.comm; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.PrintWriter; +import java.util.HashMap; +import java.util.Map; + +/** + * A data command that hold data of type Map<String, Number>. + * + * @author A. Sundararajan + */ +public class NumberMapDataCommand extends DataCommand { + + private Map data; + + public NumberMapDataCommand() { + this(null, null); + } + + public NumberMapDataCommand(String name, Map data) { + super(NUMBER_MAP, name, false); + this.data = (data != null) ? new HashMap(data) : null; + } + + public Map getData() { + return data; + } + + @Override + public void print(PrintWriter out) { + if (name != null && !name.isEmpty()) { + out.println(name); + } + if (data != null) { + for (Map.Entry e : data.entrySet()) { + out.print(e.getKey()); + out.print(" = "); + out.println(e.getValue()); + } + } + } + + @Override + protected void write(ObjectOutput out) throws IOException { + out.writeUTF(name != null ? name : ""); + if (data != null) { + out.writeInt(data.size()); + for (String key : data.keySet()) { + out.writeUTF(key); + out.writeObject(data.get(key)); + } + } else { + out.writeInt(0); + } + } + + @Override + protected void read(ObjectInput in) throws IOException, ClassNotFoundException { + name = in.readUTF(); + Map map = new HashMap<>(); + int sz = in.readInt(); + for (int i = 0; i < sz; i++) { + map.put(in.readUTF(), (Number) in.readObject()); + } + data = map; + } +} diff --git a/src/share/classes/com/sun/btrace/comm/PrintableCommand.java b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/PrintableCommand.java similarity index 85% rename from src/share/classes/com/sun/btrace/comm/PrintableCommand.java rename to btrace-core/src/main/java/org/openjdk/btrace/core/comm/PrintableCommand.java index f01d2ce71..ce97041e8 100644 --- a/src/share/classes/com/sun/btrace/comm/PrintableCommand.java +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/PrintableCommand.java @@ -22,19 +22,21 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.sun.btrace.comm; +package org.openjdk.btrace.core.comm; import java.io.PrintWriter; /** - * Marks any command which is able to print its internal info into - * the provided {@linkplain PrintWriter} + * Marks any command which is able to print its internal info into the provided {@linkplain + * PrintWriter} + * * @author Jaroslav Bachorik */ public interface PrintableCommand { - /** - * Print the command internal info - * @param out the associated {@linkplain PrintWriter} - */ - void print(PrintWriter out); + /** + * Print the command internal info + * + * @param out the associated {@linkplain PrintWriter} + */ + void print(PrintWriter out); } diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/comm/ReconnectCommand.java b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/ReconnectCommand.java new file mode 100644 index 000000000..1c240a657 --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/ReconnectCommand.java @@ -0,0 +1,36 @@ +package org.openjdk.btrace.core.comm; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; + +/** @since WireIO v.1 */ +public class ReconnectCommand extends Command { + public static final int STATUS_FLAG = 8; + + private String probeId; + + public ReconnectCommand() { + super(Command.RECONNECT); + } + + public ReconnectCommand(String probeId) { + super(Command.RECONNECT); + this.probeId = probeId; + } + + @Override + protected void write(ObjectOutput out) throws IOException { + out.writeUTF(probeId); + } + + @SuppressWarnings("RedundantThrows") + @Override + protected void read(ObjectInput in) throws IOException, ClassNotFoundException { + probeId = in.readUTF(); + } + + public String getProbeId() { + return probeId; + } +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/comm/RenameCommand.java b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/RenameCommand.java new file mode 100644 index 000000000..c9de8566a --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/RenameCommand.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.core.comm; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; + +public class RenameCommand extends Command { + private String newName; + + public RenameCommand(String newName) { + super(RENAME, true); + this.newName = newName; + } + + protected RenameCommand() { + this(null); + } + + @Override + protected void write(ObjectOutput out) throws IOException { + out.writeUTF(newName); + } + + @Override + protected void read(ObjectInput in) throws IOException { + newName = in.readUTF(); + } + + public String getNewName() { + return newName; + } +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/comm/RetransformClassNotification.java b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/RetransformClassNotification.java new file mode 100644 index 000000000..4db93530f --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/RetransformClassNotification.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.core.comm; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.PrintWriter; + +/** + * This command is sent out as a notification that a class is going to be transformed + * + * @author Jaroslav Bachorik jaroslav.bachorik@sun.com + */ +public class RetransformClassNotification extends Command implements PrintableCommand { + private String className; + + public RetransformClassNotification(String className) { + super(RETRANSFORM_CLASS, true); + this.className = className; + } + + public RetransformClassNotification() { + super(RETRANSFORM_CLASS); + } + + @Override + protected void write(ObjectOutput out) throws IOException { + out.writeObject(className); + } + + @Override + protected void read(ObjectInput in) throws IOException, ClassNotFoundException { + className = (String) in.readObject(); + } + + public String getClassName() { + return className; + } + + @Override + public void print(PrintWriter out) { + out.append("Going to retransform class ").append(className).append('\n'); + } +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/comm/RetransformationStartNotification.java b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/RetransformationStartNotification.java new file mode 100644 index 000000000..266d17f1a --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/RetransformationStartNotification.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.core.comm; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.lang.instrument.Instrumentation; + +/** + * This command is sent out when the BTrace engine calls {@linkplain + * Instrumentation#retransformClasses(Class[])} method. It is followed by {@linkplain StatusCommand} + * command when the retransformation ends. + * + * @author Jaroslav Bachorik jaroslav.bachorik@sun.com + */ +public class RetransformationStartNotification extends Command { + private int numClasses; + + public RetransformationStartNotification() { + super(RETRANSFORMATION_START); + } + + public RetransformationStartNotification(int numClasses) { + super(RETRANSFORMATION_START, true); + this.numClasses = numClasses; + } + + @Override + protected void write(ObjectOutput out) throws IOException { + out.writeInt(numClasses); + } + + @SuppressWarnings("RedundantThrows") + @Override + protected void read(ObjectInput in) throws IOException, ClassNotFoundException { + numClasses = in.readInt(); + } + + public int getNumClasses() { + return numClasses; + } +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/comm/SetSettingsCommand.java b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/SetSettingsCommand.java new file mode 100644 index 000000000..1df464dd1 --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/SetSettingsCommand.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.core.comm; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.util.HashMap; +import java.util.Map; + +/** + * This command is used to remotely set custom settings (trusted mode, debug, etc.) + * + * @author Jaroslav Bachorik + */ +public class SetSettingsCommand extends Command { + private final Map params; + + public SetSettingsCommand(Map params) { + super(SET_PARAMS, true); + this.params = params != null ? new HashMap<>(params) : new HashMap<>(); + } + + protected SetSettingsCommand() { + this(null); + } + + public Map getParams() { + return params; + } + + @Override + protected void write(ObjectOutput out) throws IOException { + out.writeInt(params.size()); + for (Map.Entry e : params.entrySet()) { + out.writeUTF(e.getKey()); + out.writeObject(e.getValue()); + } + } + + @Override + protected void read(ObjectInput in) throws IOException, ClassNotFoundException { + int size = in.readInt(); + for (int i = 0; i < size; i++) { + String k = in.readUTF(); + Object v = in.readObject(); + + params.put(k, v); + } + } +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/comm/StatusCommand.java b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/StatusCommand.java new file mode 100644 index 000000000..8cecc9971 --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/StatusCommand.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.core.comm; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; + +public class StatusCommand extends Command { + public static final int STATUS_FLAG = 1; + // custom flag + private int flag; + + public StatusCommand(int flag) { + super(STATUS, true); + this.flag = flag; + } + + public StatusCommand() { + this((byte) 0); + } + + @Override + protected void write(ObjectOutput out) throws IOException { + out.writeInt(flag); + } + + @SuppressWarnings("RedundantThrows") + @Override + protected void read(ObjectInput in) throws IOException, ClassNotFoundException { + flag = in.readInt(); + } + + public int getFlag() { + return Math.abs(flag); + } + + public boolean isSuccess() { + return flag >= 0; + } +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/comm/StringMapDataCommand.java b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/StringMapDataCommand.java new file mode 100644 index 000000000..5f6824e69 --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/StringMapDataCommand.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.btrace.core.comm; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.PrintWriter; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * A data command that hold data of type Map<String, String>. + * + * @author A. Sundararajan + */ +public class StringMapDataCommand extends DataCommand { + + private Map data; + + public StringMapDataCommand() { + this(null, null); + } + + public StringMapDataCommand(String name, Map data) { + super(STRING_MAP, name, false); + if (data != null) { + this.data = new HashMap<>(data); + } else { + this.data = Collections.emptyMap(); + } + } + + public Map getData() { + return data; + } + + @Override + public void print(PrintWriter out) { + if (name != null && !name.isEmpty()) { + out.println(name); + } + for (Map.Entry e : data.entrySet()) { + out.print(e.getKey()); + out.print(" = "); + out.println(e.getValue()); + } + } + + @Override + protected void write(ObjectOutput out) throws IOException { + out.writeUTF(name != null ? name : ""); + out.writeInt(data.size()); + for (String key : data.keySet()) { + out.writeUTF(key); + out.writeUTF(data.get(key)); + } + } + + @SuppressWarnings("RedundantThrows") + @Override + protected void read(ObjectInput in) throws IOException, ClassNotFoundException { + name = in.readUTF(); + data = new HashMap<>(); + int sz = in.readInt(); + for (int i = 0; i < sz; i++) { + data.put(in.readUTF(), in.readUTF()); + } + } +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/comm/WireIO.java b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/WireIO.java new file mode 100644 index 000000000..7f2373205 --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/comm/WireIO.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.btrace.core.comm; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; + +public class WireIO { + public static final int VERSION = 1; + + private WireIO() {} + + public static Command read(ObjectInput in) throws IOException { + byte type = in.readByte(); + Command cmd; + switch (type) { + case Command.ERROR: + cmd = new ErrorCommand(); + break; + case Command.EVENT: + cmd = new EventCommand(); + break; + case Command.EXIT: + cmd = new ExitCommand(); + break; + case Command.INSTRUMENT: + cmd = new InstrumentCommand(); + break; + case Command.MESSAGE: + cmd = new MessageCommand(); + break; + case Command.RENAME: + cmd = new RenameCommand(); + break; + case Command.STATUS: + cmd = new StatusCommand(); + break; + case Command.NUMBER_MAP: + cmd = new NumberMapDataCommand(); + break; + case Command.STRING_MAP: + cmd = new StringMapDataCommand(); + break; + case Command.NUMBER: + cmd = new NumberDataCommand(); + break; + case Command.GRID_DATA: + cmd = new GridDataCommand(); + break; + case Command.RETRANSFORMATION_START: + cmd = new RetransformationStartNotification(); + break; + case Command.RETRANSFORM_CLASS: + cmd = new RetransformClassNotification(); + break; + case Command.SET_PARAMS: + cmd = new SetSettingsCommand(); + break; + case Command.LIST_PROBES: + cmd = new ListProbesCommand(); + break; + case Command.DISCONNECT: + cmd = new DisconnectCommand(); + break; + case Command.RECONNECT: + cmd = new ReconnectCommand(); + break; + default: + throw new RuntimeException("invalid command: " + type); + } + try { + cmd.read(in); + } catch (ClassNotFoundException cnfe) { + throw new IOException(cnfe); + } + return cmd; + } + + @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") + public static void write(ObjectOutput out, Command cmd) throws IOException { + synchronized (out) { + out.writeByte(cmd.getType()); + cmd.write(out); + if (cmd.isUrgent()) { + out.flush(); + } + } + } +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/handlers/ErrorHandler.java b/btrace-core/src/main/java/org/openjdk/btrace/core/handlers/ErrorHandler.java new file mode 100644 index 000000000..b8e08f876 --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/handlers/ErrorHandler.java @@ -0,0 +1,15 @@ +package org.openjdk.btrace.core.handlers; + +import java.lang.reflect.Method; + +public final class ErrorHandler { + public final String method; + + public ErrorHandler(String method) { + this.method = method; + } + + public Method getMethod(Class clz) throws NoSuchMethodException { + return clz.getMethod(method, Throwable.class); + } +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/handlers/EventHandler.java b/btrace-core/src/main/java/org/openjdk/btrace/core/handlers/EventHandler.java new file mode 100644 index 000000000..faa8ca862 --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/handlers/EventHandler.java @@ -0,0 +1,23 @@ +package org.openjdk.btrace.core.handlers; + +import java.lang.reflect.Method; + +public final class EventHandler { + public static final String ALL_EVENTS = ""; + + public final String method; + private final String event; + + public EventHandler(String method, String event) { + this.method = method; + this.event = event; + } + + public String getEvent() { + return event != null ? event : ALL_EVENTS; + } + + public Method getMethod(Class clz) throws NoSuchMethodException { + return clz.getMethod(method); + } +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/handlers/ExitHandler.java b/btrace-core/src/main/java/org/openjdk/btrace/core/handlers/ExitHandler.java new file mode 100644 index 000000000..48033babc --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/handlers/ExitHandler.java @@ -0,0 +1,15 @@ +package org.openjdk.btrace.core.handlers; + +import java.lang.reflect.Method; + +public class ExitHandler { + public final String method; + + public ExitHandler(String method) { + this.method = method; + } + + public Method getMethod(Class clz) throws NoSuchMethodException { + return clz.getMethod(method, int.class); + } +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/handlers/LowMemoryHandler.java b/btrace-core/src/main/java/org/openjdk/btrace/core/handlers/LowMemoryHandler.java new file mode 100644 index 000000000..b25683c88 --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/handlers/LowMemoryHandler.java @@ -0,0 +1,35 @@ +package org.openjdk.btrace.core.handlers; + +import java.lang.management.MemoryUsage; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public final class LowMemoryHandler { + public final String method; + public final String pool; + public final String thresholdProperty; + public final long threshold; + public final boolean trackUsage; + private Method executable; + + public LowMemoryHandler( + String method, String pool, long threshold, String thresholdProperty, boolean trackUsage) { + this.method = method; + this.pool = pool; + this.threshold = threshold; + this.thresholdProperty = thresholdProperty; + this.trackUsage = trackUsage; + } + + public synchronized Method getMethod(Class clz) throws NoSuchMethodException { + if (executable == null) { + executable = trackUsage ? clz.getMethod(method, MemoryUsage.class) : clz.getMethod(method); + } + return executable; + } + + public void invoke(Class clz, Object... args) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + getMethod(clz).invoke(clz, null, args); + } +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/handlers/TimerHandler.java b/btrace-core/src/main/java/org/openjdk/btrace/core/handlers/TimerHandler.java new file mode 100644 index 000000000..32fa71bc3 --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/handlers/TimerHandler.java @@ -0,0 +1,19 @@ +package org.openjdk.btrace.core.handlers; + +import java.lang.reflect.Method; + +public final class TimerHandler { + public final String method; + public final long period; + public final String periodArg; + + public TimerHandler(String method, long period, String periodArg) { + this.method = method; + this.period = period; + this.periodArg = periodArg; + } + + public Method getMethod(Class clz) throws NoSuchMethodException { + return clz.getMethod(method); + } +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/jfr/JfrEvent.java b/btrace-core/src/main/java/org/openjdk/btrace/core/jfr/JfrEvent.java new file mode 100644 index 000000000..0a76e161a --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/jfr/JfrEvent.java @@ -0,0 +1,213 @@ +package org.openjdk.btrace.core.jfr; + +@SuppressWarnings("UnusedReturnValue") +public abstract class JfrEvent { + public static final class Template { + public static final class Field { + private final String name; + private final String type; + private final String label; + private final String description; + private final String specificationName; + private final String specificationValue; + + public Field( + String name, + String type, + String label, + String description, + String specificationName, + String specificationValue) { + this.name = name; + this.type = type; + this.label = label; + this.description = description; + this.specificationName = specificationName; + this.specificationValue = specificationValue; + } + + public String getName() { + return name; + } + + public String getType() { + return type; + } + + public String getLabel() { + return label; + } + + public String getDescription() { + return description; + } + + public String getSpecificationName() { + return specificationName; + } + + public String getSpecificationValue() { + return specificationValue; + } + } + + private final String owner; + private final String name; + private final String label; + private final String description; + private final String[] category; + private final Field[] fields; + private final boolean stacktrace; + private final String period; + private final String periodicHandler; + + public Template( + String owner, + String name, + String label, + String description, + String[] category, + Field[] fields, + boolean stacktrace, + String period, + String periodicHandler) { + this.owner = owner; + this.name = name; + this.label = label; + this.description = description; + this.category = category; + this.fields = fields; + this.stacktrace = stacktrace; + this.period = period; + this.periodicHandler = periodicHandler; + } + + public String getOwner() { + return owner; + } + + public String getName() { + return name; + } + + public String getLabel() { + return label; + } + + public String getDescription() { + return description; + } + + public String[] getCategory() { + return category; + } + + public Field[] getFields() { + return fields; + } + + public boolean isStacktrace() { + return stacktrace; + } + + public String getPeriod() { + return period; + } + + public String getPeriodicHandler() { + return periodicHandler; + } + } + + public interface Factory { + JfrEvent newEvent(); + } + + public static final JfrEvent EMPTY = + new JfrEvent() { + @Override + public JfrEvent withValue(String fieldName, byte value) { + return this; + } + + @Override + public JfrEvent withValue(String fieldName, boolean value) { + return this; + } + + @Override + public JfrEvent withValue(String fieldName, char value) { + return this; + } + + @Override + public JfrEvent withValue(String fieldName, short value) { + return this; + } + + @Override + public JfrEvent withValue(String fieldName, int value) { + return this; + } + + @Override + public JfrEvent withValue(String fieldName, float value) { + return this; + } + + @Override + public JfrEvent withValue(String fieldName, long value) { + return this; + } + + @Override + public JfrEvent withValue(String fieldName, double value) { + return this; + } + + @Override + public JfrEvent withValue(String fieldName, String value) { + return this; + } + + @Override + public void commit() {} + + @Override + public boolean shouldCommit() { + return false; + } + + @Override + public void begin() {} + + @Override + public void end() {} + }; + + public abstract JfrEvent withValue(String fieldName, byte value); + + public abstract JfrEvent withValue(String fieldName, boolean value); + + public abstract JfrEvent withValue(String fieldName, char value); + + public abstract JfrEvent withValue(String fieldName, short value); + + public abstract JfrEvent withValue(String fieldName, int value); + + public abstract JfrEvent withValue(String fieldName, float value); + + public abstract JfrEvent withValue(String fieldName, long value); + + public abstract JfrEvent withValue(String fieldName, double value); + + public abstract JfrEvent withValue(String fieldName, String value); + + public abstract void commit(); + + public abstract boolean shouldCommit(); + + public abstract void begin(); + + public abstract void end(); +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/types/AnyType.java b/btrace-core/src/main/java/org/openjdk/btrace/core/types/AnyType.java new file mode 100644 index 000000000..a213bfce4 --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/types/AnyType.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.core.types; + +/** + * This interface type is used in BTrace programs to tell that any reference type [object or array] + * is allowed in the place where it is used. We use that for method signature matching when + * signature needs to be specified loosely. Note that we don't want to use java.lang.Object - + * because user may want to match java.lang.Object exactly. + * + * @author A. Sundararajan + */ +public interface AnyType { + AnyType VOID = new AnyType() {}; +} diff --git a/src/share/classes/com/sun/btrace/BTraceCollection.java b/btrace-core/src/main/java/org/openjdk/btrace/core/types/BTraceCollection.java similarity index 92% rename from src/share/classes/com/sun/btrace/BTraceCollection.java rename to btrace-core/src/main/java/org/openjdk/btrace/core/types/BTraceCollection.java index a762da4f9..b2ed729c1 100644 --- a/src/share/classes/com/sun/btrace/BTraceCollection.java +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/types/BTraceCollection.java @@ -23,14 +23,13 @@ * questions. */ -package com.sun.btrace; +package org.openjdk.btrace.core.types; import java.util.Collection; /** * Marker interface for BTrace defined collections + * * @author Jaroslav Bachorik */ -public interface BTraceCollection extends Collection { - -} +public interface BTraceCollection extends Collection {} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/types/BTraceDeque.java b/btrace-core/src/main/java/org/openjdk/btrace/core/types/BTraceDeque.java new file mode 100644 index 000000000..3fd70790c --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/types/BTraceDeque.java @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.core.types; + +import java.util.ArrayDeque; +import java.util.Collection; +import java.util.Deque; +import java.util.Iterator; + +/** @author Jaroslav Bachorik */ +public class BTraceDeque implements Deque, BTraceCollection, Cloneable { + private final Deque delegate; + + public BTraceDeque(Deque delegate) { + this.delegate = delegate; + } + + @Override + public synchronized String toString() { + return delegate.toString(); + } + + @Override + public synchronized T[] toArray(T[] a) { + return delegate.toArray(a); + } + + @Override + public synchronized Object[] toArray() { + return delegate.toArray(); + } + + @Override + public synchronized boolean retainAll(Collection c) { + return delegate.retainAll(c); + } + + @Override + public synchronized boolean removeAll(Collection c) { + return delegate.removeAll(c); + } + + @Override + public synchronized boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override + public synchronized boolean containsAll(Collection c) { + return delegate.containsAll(c); + } + + @Override + public synchronized void clear() { + delegate.clear(); + } + + @Override + public synchronized boolean addAll(Collection c) { + return delegate.addAll(c); + } + + @Override + public synchronized int size() { + return delegate.size(); + } + + @Override + public synchronized boolean removeLastOccurrence(Object o) { + return delegate.removeLastOccurrence(o); + } + + @Override + public synchronized V removeLast() { + return delegate.removeLast(); + } + + @Override + public synchronized boolean removeFirstOccurrence(Object o) { + return delegate.removeFirstOccurrence(o); + } + + @Override + public synchronized V removeFirst() { + return delegate.removeFirst(); + } + + @Override + public synchronized boolean remove(Object o) { + return delegate.remove(o); + } + + @Override + public synchronized V remove() { + return delegate.remove(); + } + + @Override + public synchronized void push(V e) { + delegate.push(e); + } + + @Override + public synchronized V pop() { + return delegate.pop(); + } + + @Override + public synchronized V pollLast() { + return delegate.pollLast(); + } + + @Override + public synchronized V pollFirst() { + return delegate.pollFirst(); + } + + @Override + public synchronized V poll() { + return delegate.poll(); + } + + @Override + public synchronized V peekLast() { + return delegate.peekLast(); + } + + @Override + public synchronized V peekFirst() { + return delegate.peekFirst(); + } + + @Override + public synchronized V peek() { + return delegate.peek(); + } + + @Override + public synchronized boolean offerLast(V e) { + return delegate.offerLast(e); + } + + @Override + public synchronized boolean offerFirst(V e) { + return delegate.offerFirst(e); + } + + @Override + public synchronized boolean offer(V e) { + return delegate.offer(e); + } + + @Override + public synchronized Iterator iterator() { + return delegate.iterator(); + } + + @Override + public synchronized V getLast() { + return delegate.getLast(); + } + + @Override + public synchronized V getFirst() { + return delegate.getFirst(); + } + + @Override + public synchronized V element() { + return delegate.element(); + } + + @Override + public synchronized Iterator descendingIterator() { + return delegate.descendingIterator(); + } + + @Override + public synchronized boolean contains(Object o) { + return delegate.contains(o); + } + + @Override + public synchronized void addLast(V e) { + delegate.addLast(e); + } + + @Override + public synchronized void addFirst(V e) { + delegate.addFirst(e); + } + + @Override + public synchronized boolean add(V e) { + return delegate.add(e); + } + + @Override + public synchronized int hashCode() { + return delegate.hashCode(); + } + + @Override + public synchronized boolean equals(Object obj) { + return delegate.equals(obj); + } + + @SuppressWarnings({"RedundantThrows", "MethodDoesntCallSuperMethod"}) + @Override + protected Object clone() throws CloneNotSupportedException { + return new BTraceDeque<>(new ArrayDeque<>()); + } +} diff --git a/btrace-core/src/main/java/org/openjdk/btrace/core/types/BTraceMap.java b/btrace-core/src/main/java/org/openjdk/btrace/core/types/BTraceMap.java new file mode 100644 index 000000000..efd69528a --- /dev/null +++ b/btrace-core/src/main/java/org/openjdk/btrace/core/types/BTraceMap.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.core.types; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; + +/** + * Instances of this class are used to store aggregate tracing data in BTrace. + * + * @author A. Sundararajan + */ +public final class BTraceMap implements Map, Cloneable { + // private int numItems; + private final Map m; + private final boolean isWeak; + private transient Set keySet = null; + private transient Set> entrySet = null; + private transient Collection values = null; + + public BTraceMap(Map m) { + if (m == null) { + throw new NullPointerException(); + } + this.m = m; + isWeak = (m instanceof WeakHashMap); + } + + @Override + public synchronized int size() { + return m.size(); + } + + @Override + public synchronized boolean isEmpty() { + return m.isEmpty(); + } + + @Override + public synchronized boolean containsKey(Object key) { + return m.containsKey(key); + } + + @Override + public synchronized boolean containsValue(Object value) { + return m.containsValue(value); + } + + @Override + public synchronized V get(Object key) { + return m.get(key); + } + + @Override + public synchronized V put(K key, V value) { + return m.put(key, value); + } + + @Override + public synchronized V remove(Object key) { + return m.remove(key); + } + + @Override + public synchronized void putAll(Map map) { + m.putAll(map); + } + + @Override + public synchronized void clear() { + m.clear(); + } + + @Override + public synchronized Set keySet() { + if (keySet == null) { + keySet = m.keySet(); + } + return keySet; + } + + @Override + public synchronized Set> entrySet() { + if (entrySet == null) { + entrySet = m.entrySet(); + } + return entrySet; + } + + @Override + public synchronized Collection values() { + if (values == null) { + values = m.values(); + } + return values; + } + + @Override + public synchronized boolean equals(Object o) { + return m.equals(o); + } + + @Override + public synchronized int hashCode() { + return m.hashCode(); + } + + @Override + public synchronized String toString() { + return m.toString(); + } + + @SuppressWarnings({"RedundantThrows", "MethodDoesntCallSuperMethod"}) + @Override + protected Object clone() throws CloneNotSupportedException { + return new BTraceMap<>(isWeak ? new WeakHashMap<>() : new HashMap<>()); + } +} diff --git a/btrace-core/src/main/resources/org/openjdk/btrace/core/annotations/jaxb.index b/btrace-core/src/main/resources/org/openjdk/btrace/core/annotations/jaxb.index new file mode 100644 index 000000000..bc3fc36d0 --- /dev/null +++ b/btrace-core/src/main/resources/org/openjdk/btrace/core/annotations/jaxb.index @@ -0,0 +1,2 @@ +Kind +Where diff --git a/btrace-core/src/main/resources/org/openjdk/btrace/core/messages.properties b/btrace-core/src/main/resources/org/openjdk/btrace/core/messages.properties new file mode 100644 index 000000000..c929e52de --- /dev/null +++ b/btrace-core/src/main/resources/org/openjdk/btrace/core/messages.properties @@ -0,0 +1,115 @@ +# BTrace "compile time" and runtime error messages. +return.type.should.be.void=btrace probe methods must return void +btrace.program.should.be.class=btrace program must be a class (not interface or enum) +no.complex.unsafe.value=@BTrace.trusted|unsafe value should be only a plain boolean +not.a.btrace.program=@BTrace annotation is missing +no.outer.class=btrace class should not be a local class +no.asserts=asserts are not allowed +no.catch=catching exception is not allowed +no.do.while=do..while loops are not allowed +no.enhanced.for=enhanced for statements are not allowed +no.loops=loops (backward jumps) are not allowed +no.for.loop=for loops are not allowed +no.array.creation=array creation is not allowed +no.new.object=object creation is not allowed +no.synchronized.blocks=synchronized blocks are not allowed +no.synchronized.methods=probe action methods should not be synchronized +no.throw=throwing exception is not allowed +no.try=try .. catch .. finally blocks are not allowed +no.while.loop=while loops are not allowed +no.other="other" statement(s) are not allowed +execution.loop.danger=execution of btrace program may lead to endless loop which is not allowed +method.should.be.public=btrace methods should be public +class.should.be.public=btrace class should be public +no.static.method=static methods are not allowed (using BTrace short syntax) +no.instance.method=instance methods are not allowed +no.method.calls=method calls are not allowed - only calls to BTraceUtils are allowed +no.assignment=side-effects to probed program are not allowed +no.nested.class=nested and inner classes are not allowed +no.local.class=local classes are not allowed +no.static.variables=static variables are not allowed (using BTrace short syntax) +no.instance.variables=instance variables are not allowed +object.superclass.required=btrace class should extend java.lang.Object +no.interface.implementation=btrace class can not implement interfaces +no.class.literals=class literals are not allowed +self.desc.invalid=@Self annotation applicable only for Kind.ENTRY and Kind.RETURN +probemethod.desc.invalid=@ProbeMethod annotation applicable only for Kind.ENTRY, Kind.RETURN and Kind.CALL +probeclass.desc.invalid=@ProbeClassName annotation applicable only for Kind.ENTRY, Kind.RETURN and Kind.CALL +return.desc.invalid=@Return annotation applicable only for Kind.RETURN +duration.desc.invalid=@Duration annotation applicable only for Kind.RETURN and Kind.ERROR +target-method.desc.invalid=@TargetMethodOrField annotation applicable only for Kind.CALL, Kind.FIELD_GET, Kind.FIELD_SET, Kind.ARRAY_GET and Kind.ARRAY_SET +target-instance.desc.invalid=@TargetInstance annotation applicable only for Kind.CALL, Kind.FIELD_GET, Kind.FIELD_SET, Kind.ARRAY_GET, Kind.ARRAY_SET, Kind.INSTANCEOF, Kind.CHECKCAST, Kind.ERROR, Kind.THROW, Kind.CATCH, Kind.SYNC_ENTRY or Kind.SYNC_EXIT +onexit.invalid=@OnExit annotation applicable only to methods with signature (int)void +onerror.invalid=@OnError annotation applicable only to methods with signature (java.lang.Throwable)void +multiple.param.annotation=Multiple parameters annotated by the same annotation are not allowed +sampler.invalid.location=@Sampled annotation supported only for @OnMethod annotated classes with Kind of [ENTRY, RETURN, ERROR, CALL] +missing.injected=Service fields must be annotated by @Injected +injected.no.initializer=Injected fields must not use initializer +injected.no.runtime=Injection kind is RUNTIME but the service is not a subclass of org.openjdk.btrace.services.spi.RuntimeService +injected.no.simple=Injection kind is SIMPLE but the service is not a subclass of org.openjdk.btrace.services.spi.SimpleService +service.injector.literals=May only use literals as Service.* methods parameters +missing.event=Event template fields must be annotated by either @Event or @PeriodicEvent +event.nonpublic.handler=Periodic event handler must be public +event.invalid.handler=Invalid periodic event handler specified - the handler method must take exactly one parameter of JfrEvent type +agent.no.instance.variables=instance variables are not allowed +agent.unsafe.not.allowed=Trusted mode, requested by the script, not allowed +jfr.event.invalid.field=Invalid JFR event field name +remote.commands.help=\ + BTrace remote commands:\n \ + - event : Send an event with an optional name\n \ + - exit : Terminate the BTrace probe + +# usage messages +btracec.usage=\ + Usage: btracec \n\ + where possible options include:\n \ + -classpath Specify where to find user class files and annotation processors\n \ + -cp Specify where to find user class files and annotation processors\n \ + -I Specify where to find include files\n \ + -d Specify where to place generated class files\n \ + -nopack Do not produce packed probes. Useful when targeting pre 1.3.10 versions.\n \ + -packext File extension for script packs (default '.class'). Valid only if '-nopack' is not specified.\n \ + -trusted Enable trusted script (eg. no checks) +btrace.usage=\ + Usage: btrace \n\ + where possible options include:\n \ + --version Show the version\n \ + -v Run in verbose mode\n \ + -l List all locally attachable JVMs\n \ + -lp List active probes in the given JVM\n \ + \t\t\tExpects PID or app name as the follow-up argument\n \ + \t\t\tAll other options are discarded\n \ + -r Reconnect to an active disconnected probe\n \ + \t\t\tExpects PID or app name as the follow-up argument\n \ + \t\t\tAll other options are discarded\n \ + -r help Show help on the remote commands\n \ + -o The path to store the probe output (will disable showing the output in console)\n \ + -u Run in trusted mode\n \ + -d Dump the instrumented classes to the specified path\n \ + -pd The search path for the probe XML descriptors\n \ + -classpath Specify where to find user class files and annotation processors\n \ + -cp Specify where to find user class files and annotation processors\n \ + -I Specify where to find include files\n \ + -p Specify port to which the btrace agent listens for clients\n \ + -statsd Specify the statsd server, if any\n \ + -x Run unattended\n \ + \t\t\tDeploy the given probe and disconnect +btrace.agent.usage=\ + Usage: java -javaagent:java-agent.jar=

\n\ + where arguments is comma separated name=value pairs. Argument names include:\n \ + bootClassPath boot classpath to be used\n \ + systemClassPath system classpath to be used\n \ + debug boolean flag to specify debug mode\n \ + trusted boolean flag to enable trusted mode\n \ + dumpClasses boolean flag to specify whether to dump .classes for instrumented classes\n \ + dumpDir directory where instrumented .class files are saved\n \ + help print this help message\n \ + noServer boolean flag to specify whether to start btrace server or not\n \ + port btrace agent server port\n \ + statsd statsd server, if any (format )\n \ + probeDescPath directories where @OnProbe mapping descriptor XML files are searched\n \ + stdout redirect the btrace output to stdout instead of writing it to an arbitrary file (true/false)\n \ + scriptdir the path to a directory containing scripts to be run at the agent startup\n \ + scriptOutputFile the path to a file the btrace agent will store its output\n \ + script comma separated list of compiled tracing scripts to be run at the agent startup; *MUST* be the last argument in the list +btrace.version=BTrace v.@btrace.version@ (@hash@) diff --git a/btrace-core/src/test/java/org/openjdk/btrace/core/CircularBufferTest.java b/btrace-core/src/test/java/org/openjdk/btrace/core/CircularBufferTest.java new file mode 100644 index 000000000..71b07139f --- /dev/null +++ b/btrace-core/src/test/java/org/openjdk/btrace/core/CircularBufferTest.java @@ -0,0 +1,115 @@ +package org.openjdk.btrace.core; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; + +public class CircularBufferTest { + + @Test + public void testAddOverflow() { + CircularBuffer cb = new CircularBuffer<>(3); + cb.add(1); + cb.add(2); + cb.add(3); + cb.add(4); + + assertEquals(3, cb.getLength()); + final List elements = new ArrayList<>(); + cb.forEach( + new Function() { + @Override + public Boolean apply(Integer value) { + elements.add(value); + return true; + } + }); + + assertEquals(0, cb.getLength()); + assertEquals(Arrays.asList(2, 3, 4), elements); + } + + @Test + public void testAddOverflowSeveral() { + CircularBuffer cb = new CircularBuffer<>(2); + cb.add(1); + cb.add(2); + cb.add(3); + cb.add(4); + cb.add(5); + cb.add(6); + cb.add(7); + + assertEquals(2, cb.getLength()); + final List elements = new ArrayList<>(); + cb.forEach( + new Function() { + @Override + public Boolean apply(Integer value) { + elements.add(value); + return true; + } + }); + assertEquals(0, cb.getLength()); + + assertEquals(Arrays.asList(6, 7), elements); + } + + @Test + public void testAdd() { + CircularBuffer cb = new CircularBuffer<>(2); + + cb.add(1); + assertEquals(1, cb.getLength()); + final List elements = new ArrayList<>(); + cb.forEach( + new Function() { + @Override + public Boolean apply(Integer value) { + elements.add(value); + return true; + } + }); + assertEquals(0, cb.getLength()); + assertEquals(Arrays.asList(1), elements); + } + + @Test + public void testAddFull() { + CircularBuffer cb = new CircularBuffer<>(2); + + cb.add(1); + cb.add(2); + assertEquals(2, cb.getLength()); + final List elements = new ArrayList<>(); + cb.forEach( + new Function() { + @Override + public Boolean apply(Integer value) { + elements.add(value); + return true; + } + }); + assertEquals(0, cb.getLength()); + assertEquals(Arrays.asList(1, 2), elements); + } + + @Test + public void testEmpty() { + CircularBuffer cb = new CircularBuffer<>(2); + + final List elements = new ArrayList<>(); + cb.forEach( + new Function() { + @Override + public Boolean apply(Integer value) { + elements.add(value); + return true; + } + }); + assertTrue(elements.isEmpty()); + } +} diff --git a/btrace-core/src/test/java/org/openjdk/btrace/core/ReflectiveFieldAccessTest.java b/btrace-core/src/test/java/org/openjdk/btrace/core/ReflectiveFieldAccessTest.java new file mode 100644 index 000000000..b92176ec9 --- /dev/null +++ b/btrace-core/src/test/java/org/openjdk/btrace/core/ReflectiveFieldAccessTest.java @@ -0,0 +1,27 @@ +package org.openjdk.btrace.core; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class ReflectiveFieldAccessTest { + + @Test + public void getIntTest() { + D a = new D(); + RuntimeException exception = + assertThrows(RuntimeException.class, () -> BTraceUtils.Reflective.getInt("notExist", a)); + assertTrue(exception.getMessage().contains("notExist")); + } + + static class A { + int a; + } + + static class B extends A {} + + static class C extends A {} + + static class D extends C {} +} diff --git a/btrace-dist/build.gradle b/btrace-dist/build.gradle new file mode 100644 index 000000000..c7e7cc1d6 --- /dev/null +++ b/btrace-dist/build.gradle @@ -0,0 +1,468 @@ +plugins { + id 'java' + id 'signing' + id 'maven-publish' + alias(libs.plugins.shadow) + alias(libs.plugins.ospackage) + alias(libs.plugins.sdkman.vendors) +} + +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import org.gradle.internal.os.OperatingSystem + +repositories { + mavenCentral() +} + +def distBase = "${project.buildDir}/resources/main" +def distTarget = "${distBase}/v${project.version}" +def libsDir = new File("${distTarget}/libs") + +def includes = [:] + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(8)) + } +} + +includes['agent'] = { + if (it.directory) { + return true + } + if (it.path.endsWith('.jar')) { + return true + } + return it.path.startsWith('org/jctools/') || + it.path.startsWith('org/openjdk/btrace/agent/') || + it.path.startsWith('org/openjdk/btrace/instr/') || + // include the messages resource bundle and class in the agent jar (bootstrap can't load resources) + it.path == 'org/openjdk/btrace/core/Messages.class' || + it.path == 'org/openjdk/btrace/core/messages.properties' +} + +includes['boot'] = { + if (it.directory) { + return true + } + if (it.path.endsWith('.jar')) { + return true + } + if (it.path.startsWith('org/openjdk/btrace/core/')) { + if (it.path == 'org/openjdk/btrace/core/Messages.class' || it.path == 'org/openjdk/btrace/core/messages.properties') { + // messages resource bundle&class are hoisted to agent and client jars + return false + } + return true + } + if (it.path.startsWith('org/objectweb/asm/')) { + if (it.path.startsWith('org/objectweb/asm/commons/') || + it.path.startsWith('org/objectweb/asm/util/') || + it.path.startsWith('org/objectweb/asm/xml/')) { + return false + } + return true + } + return it.path.startsWith("org/slf4j/") || it.path.startsWith('org/openjdk/btrace/runtime/') || + it.path.startsWith('org/jctools/') || + it.path.startsWith('org/openjdk/btrace/services/') || + it.path.startsWith('org/openjdk/btrace/statsd/') +} + +includes['client'] = { + if (it.directory) { + return true + } + if (it.path.endsWith('.jar')) { + return true + } + if (it.path.startsWith('org/openjdk/btrace')) { + return !it.path.startsWith('org/openjdk/btrace/agent') + } + if (it.path.startsWith('META-INF/services')) { + return !it.path.contains('com.sun.') && !it.path.contains('javax.annotation.') + } + return it.path.startsWith("org/slf4j") || + it.path.startsWith('org/objectweb/asm') || + it.path.startsWith('org/jctools') +} + + +configurations { + artifact.extendsFrom implementation +} + +dependencies { + implementation project(':btrace-agent') + implementation project(':btrace-client') + implementation project(':btrace-compiler') +} + +jar { + onlyIf { false } +} + +sourcesJar { + onlyIf { false } +} + +sourceSets { + main { + output.resourcesDir = file("${distTarget}") + } +} +artifacts { + archives file("${distTarget}/libs/btrace-agent.jar") +} + +task agentJar(type: ShadowJar) { + group 'Build' + archiveBaseName.set('btrace-agent') + archiveVersion.set('') + archiveClassifier.set('') + destinationDirectory = libsDir + + manifest { + attributes( + "Premain-Class": "org.openjdk.btrace.agent.Main", + "Agent-Class": "org.openjdk.btrace.agent.Main", + "Can-Redefine-Classes": true, + "Can-Retransform-Classes": true, + "Boot-Class-Path": "btrace-boot.jar" + ) + } + + include includes['agent'] + + configurations = [project.configurations.artifact] + relocate 'org.jctools', 'org.openjdk.btrace.libs.agent.org.jctools' + relocate 'org.objectweb.asm', 'org.openjdk.btrace.libs.org.objectweb.asm' + relocate 'org.slf4j', 'org.openjdk.btrace.libs.org.slf4j' +} + +task bootJar(type: ShadowJar) { + group 'Build' + archiveBaseName.set('btrace-boot') + archiveVersion.set('') + archiveClassifier.set('') + destinationDirectory = libsDir + + + include includes['boot'] + + configurations = [project.configurations.artifact] + relocate 'org.jctools', 'org.openjdk.btrace.libs.boot.org.jctools' + relocate 'org.objectweb.asm', 'org.openjdk.btrace.libs.org.objectweb.asm' + relocate 'org.slf4j', 'org.openjdk.btrace.libs.org.slf4j' +} + +task clientJar(type: ShadowJar) { + group 'Build' + archiveBaseName.set('btrace-client') + archiveVersion.set('') + archiveClassifier.set('') + destinationDirectory = libsDir + + include includes['client'] + + configurations = [project.configurations.artifact] + relocate 'org.jctools', 'org.openjdk.btrace.libs.client.org.jctools' + relocate 'org.objectweb.asm', 'org.openjdk.btrace.libs.client.org.objectweb.asm' + relocate 'org.slf4j', 'org.openjdk.btrace.libs.client.org.slf4j' +} + +task fixPermissions(type: Exec) { + onlyIf { + !OperatingSystem.current().isWindows() + } + commandLine 'chmod', '500', "${distTarget}/bin/btrace" + commandLine 'chmod', '500', "${distTarget}/bin/btracec" + commandLine 'chmod', '500', "${distTarget}/bin/btracer" +} + +task copyDtraceLib(type: Copy) { + from "${projectDir}/../btrace-dtrace/build/dtrace/libs" + into "${distTarget}/libs/" +} + +task buildZip(type: Zip) { + from "${distTarget}" + include "**/*" + + archiveBaseName.set('btrace') + archiveVersion.set("v${project.version}") + archiveClassifier.set("bin") + destinationDirectory = new File(project.buildDir, "distributions") +} + +task buildSdkmanZip(type: Zip) { + from "${distBase}" + include "**/*" + + archiveBaseName.set('btrace') + archiveVersion.set("v${project.version}") + archiveClassifier.set("sdkman-bin") + destinationDirectory = new File(project.buildDir, "distributions") +} + +task buildTgz(type: Tar) { + into ('/'){ + from "${distTarget}" + include '**/*' + } + + archiveBaseName.set('btrace') + archiveVersion.set("v${project.version}") + archiveClassifier.set("bin") + destinationDirectory = new File(project.buildDir, "distributions") + archiveExtension.set('tar.gz') + compression = Compression.GZIP +} + +ospackage { + packageName = 'btrace' + release = 1 + os = LINUX + + into '/opt/btrace' + + from("${distTarget}/bin") { + into 'bin' + fileMode 0550 + } + + from("${distTarget}/libs") { + into 'libs' + } + + from("${distTarget}/docs") { + into 'docs' + } + + from("${distTarget}/samples") { + into 'samples' + } + + link('/usr/local/bin/btrace', '/opt/btrace/bin/btrace') + link('/usr/local/bin/btracer', '/opt/btrace/bin/btracer') + link('/usr/local/bin/btracec', '/opt/btrace/bin/btracec') +} + +buildDeb { + requires('openjdk-8-jdk') +} + +copyDtraceLib.dependsOn project(':btrace-dtrace').build +shadowJar.dependsOn agentJar, bootJar, clientJar, copyDtraceLib +buildTgz.dependsOn agentJar, bootJar, clientJar, fixPermissions, copyDtraceLib, processResources +buildZip.dependsOn agentJar, bootJar, clientJar, fixPermissions, copyDtraceLib, processResources +buildSdkmanZip.dependsOn agentJar, bootJar, clientJar, fixPermissions, copyDtraceLib, processResources +buildDeb.dependsOn agentJar, bootJar, clientJar, fixPermissions, copyDtraceLib, processResources +buildRpm.dependsOn agentJar, bootJar, clientJar, fixPermissions, copyDtraceLib, processResources +build.dependsOn buildSdkmanZip, buildZip, buildTgz, buildDeb, buildRpm + +test { + doLast { + project(':btrace-instr').tasks.test.execute() + } +} + +['agent', 'boot', 'client'].each { name -> + // Define a temporary directory for the sources + def tempDir = new File(buildDir, "tmp/${name}Sources") + + // first, we will copy the classes and make sure there are no empty directories + tasks.create(name: "${name}CopySources", type: Copy) { + doFirst { + tempDir.deleteDir() // Ensure the directory is clean + } + + into tempDir + + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + gradle.projectsEvaluated { + rootProject.childProjects.each { projectName, siblingProject -> + // Skip the current project to avoid duplication + if (siblingProject != project) { + from(siblingProject.sourceSets.main.allSource) { + include includes["${name}"] + exclude 'META-INF/**' + } + } + } + } + + doLast { + // Recursively delete empty directories + def deleteEmptyDirs + deleteEmptyDirs = { dir -> + dir.eachDir { subDir -> + deleteEmptyDirs(subDir) + if (!subDir.listFiles().any()) { + subDir.deleteDir() + } + } + } + deleteEmptyDirs(tempDir) + } + } + + // we will use the output of the previous task to crate the JAR + tasks.create(name: "${name}SourcesJar", type: Jar, dependsOn: "${name}CopySources") { + group 'Documentation' + description "Build the btrace-${name} sources jar." + + archiveAppendix = "${name}" + archiveClassifier = "sources" + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + + from tempDir + } + + tasks.create(name: "${name}Javadoc", type: Javadoc) { + group 'Documentation' + description "Generates Javadoc API documentation for the btrace-${name}." + + title = "btrace-${name}" + destinationDir = file("${project.docsDir}/${name}/javadoc") + classpath = files(compileJava.destinationDirectory) + configurations.artifact.asFileTree + options.addStringOption('Xdoclint:all,-missing', '-quiet') + failOnError false + + project.parent.subprojects.each { subproject -> + // Skip the current project to avoid duplication + if (subproject != project) { + // Add source from sibling projects based on filter + source subproject.sourceSets.main.java.matching { + include includes["${name}"] + } + } + } + + // Include classpath of all sibling projects + project.parent.subprojects.each { subproject -> + // Skip the current project to avoid duplication + if (subproject != project) { + classpath += subproject.sourceSets.main.compileClasspath + } + } + } + + + tasks.create(name: "${name}JavadocJar", type: Jar) { + group 'Documentation' + description "Build the btrace-${name} javadoc jar." + + archiveAppendix = "${name}" + archiveClassifier = "javadoc" + from tasks["${name}Javadoc"].getOutputs() + } +} + +sdkman { + consumerKey = project.hasProperty("sdkman.key") ? project.property("sdkman.key") : System.getenv('SDKMAN_KEY') + consumerToken = project.hasProperty("sdkman.token") ? project.property("sdkman.token") : System.getenv('SDKMAN_TOKEN') + candidate = "btrace" + version = "${project.version}" + url = "https://github.com/btraceio/btrace/releases/download/v${project.version}/btrace-v${project.version}-sdkman-bin.zip" + hashtag = "btrace" +} + +["sdkReleaseVersion", "sdkAnnounceVersion"].forEach { + tasks[it].onlyIf { + !project.version.toString().endsWith("-SNAPSHOT") + } +} + +publishing { + repositories { + maven { + def releasesRepoUrl = "https://oss.sonatype.org/service/local/staging/deploy/maven2/" + def snapshotsRepoUrl = "https://oss.sonatype.org/content/repositories/snapshots/" + url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl + + credentials { + username = project.hasProperty("sonatype.user") ? project.property("sonatype.user") : System.getenv('BTRACE_SONATYPE_USER') + password = project.hasProperty("sonatype.password") ? project.property("sonatype.password") : System.getenv('BTRACE_SONATYPE_PWD') + } + } + } + + publications { + agent(MavenPublication) { + artifactId 'btrace-agent' + groupId 'io.btrace' + artifact agentJar + artifact agentSourcesJar { + archiveClassifier = "sources" + } + artifact agentJavadocJar { + archiveClassifier = "javadoc" + } + pom.withXml { + addPomDetails(asNode(), 'btrace-agent') + } + } + + client(MavenPublication) { + artifactId 'btrace-client' + groupId 'io.btrace' + artifact clientJar + artifact clientSourcesJar { + archiveClassifier = "sources" + } + artifact clientJavadocJar { + archiveClassifier = "javadoc" + } + pom.withXml { + addPomDetails(asNode(), 'btrace-client') + } + } + + boot(MavenPublication) { + artifactId 'btrace-boot' + groupId 'io.btrace' + artifact bootJar + artifact bootSourcesJar { + archiveClassifier = "sources" + } + artifact bootJavadocJar { + archiveClassifier = "javadoc" + } + pom.withXml { + addPomDetails(asNode(), 'btrace-boot') + } + } + } +} + +def addPomDetails(node, name) { + node.appendNode('name', name) + node.appendNode('url', 'https://github.com/btraceio/btrace') + node.appendNode('description', 'BTrace: A safe, dynamic tracing tool for the Java platform') + def scmNode = node.appendNode('scm') + scmNode.appendNode('url', 'https://github.com/btraceio/btrace') + scmNode.appendNode('connection', 'scm:git:https://github.com/btraceio/btrace.git') + scmNode.appendNode('developerConnection', 'scm:git:https://github.com/btraceio/btrace.git') + + def licenseNode = node.appendNode('licenses').appendNode('license') + licenseNode.appendNode('name', 'GNU General Public License, version 2, with the Classpath Exception') + licenseNode.appendNode('url', 'http://openjdk.java.net/legal/gplv2+ce.html') + + def developerNode = node.appendNode('developers').appendNode('developer') + developerNode.appendNode('id', 'jbachorik') + developerNode.appendNode('name', 'Jaroslav Bachorik') + developerNode.appendNode('email', 'j.bachorik@btrace.io') +} + +signing { + def signingKey = project.hasProperty('gpg.signing.key') ? project.property('gpg.signing.key') : System.getenv("GPG_SIGNING_KEY") + def signingPwd = project.hasProperty('gpg.signing.pwd') ? project.property('gpg.signing.pwd') : System.getenv("GPG_SIGNING_PWD") + + if (signingKey != null && signingPwd != null) { + useInMemoryPgpKeys(signingKey, signingPwd) + } + sign publishing.publications.agent + sign publishing.publications.boot + sign publishing.publications.client +} \ No newline at end of file diff --git a/COPYRIGHT b/btrace-dist/src/main/resources/COPYRIGHT similarity index 99% rename from COPYRIGHT rename to btrace-dist/src/main/resources/COPYRIGHT index b39017e9a..ad4ab6a27 100644 --- a/COPYRIGHT +++ b/btrace-dist/src/main/resources/COPYRIGHT @@ -1,27 +1,27 @@ -Copyright © 2008 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, California 95054, U.S.A. All rights reserved. - -Sun Microsystems, Inc. has intellectual property rights relating to technology embodied in the product that is described in this document. In particular, and without limitation, these intellectual property rights may include one or more of the U.S. patents listed at http://www.sun.com/patents and one or more additional patents or pending patent applications in the U.S. and in other countries. - -U.S. Government Rights - Commercial software. Government users are subject to the Sun Microsystems, Inc. standard license agreement and applicable provisions of the FAR and its supplements. - -Use is subject to license terms. - -This distribution may include materials developed by third parties. - -Sun, Sun Microsystems, the Sun logo, Java, Jini, Solaris and BTrace are trademarks or registered trademarks of Sun Microsystems, Inc. in the U.S. and other countries. - -This product is covered and controlled by U.S. Export Control laws and may be subject to the export or import laws in other countries. Nuclear, missile, chemical biological weapons or nuclear maritime end uses or end users, whether direct or indirect, are strictly prohibited. Export or reexport to countries subject to U.S. embargo or to entities identified on U.S. export exclusion lists, including, but not limited to, the denied persons and specially designated nationals lists is strictly prohibited. - - -Copyright © 2008 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, California 95054, Etats-Unis. Tous droits réservés. - -Sun Microsystems, Inc. détient les droits de propriété intellectuels relatifs à la technologie incorporée dans le produit qui est décrit dans ce document. En particulier, et ce sans limitation, ces droits de propriété intellectuelle peuvent inclure un ou plus des brevets américains listés à l'adresse http://www.sun.com/patents et un ou les brevets supplémentaires ou les applications de brevet en attente aux Etats - Unis et dans les autres pays. - -L'utilisation est soumise aux termes de la Licence. - -Cette distribution peut comprendre des composants développés par des tierces parties. - -Sun, Sun Microsystems, le logo Sun, Java, Jini, Solaris et BTrace sont des marques de fabrique ou des marques déposées de Sun Microsystems, Inc. aux Etats-Unis et dans d'autres pays. - -Ce produit est soumis à la législation américaine en matière de contrôle des exportations et peut être soumis à la règlementation en vigueur dans d'autres pays dans le domaine des exportations et importations. Les utilisations, ou utilisateurs finaux, pour des armes nucléaires,des missiles, des armes biologiques et chimiques ou du nucléaire maritime, directement ou indirectement, sont strictement interdites. Les exportations ou réexportations vers les pays sous embargo américain, ou vers des entités figurant sur les listes d'exclusion d'exportation américaines, y compris, mais de manière non exhaustive, la liste de personnes qui font objet d'un ordre de ne pas participer, d'une façon directe ou indirecte, aux exportations des produits ou des services qui sont régis par la législation américaine en matière de contrôle des exportations et la liste de ressortissants spécifiquement désignés, sont rigoureusement interdites. - +Copyright © 2008 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, California 95054, U.S.A. All rights reserved. + +Sun Microsystems, Inc. has intellectual property rights relating to technology embodied in the product that is described in this document. In particular, and without limitation, these intellectual property rights may include one or more of the U.S. patents listed at http://www.sun.com/patents and one or more additional patents or pending patent applications in the U.S. and in other countries. + +U.S. Government Rights - Commercial software. Government users are subject to the Sun Microsystems, Inc. standard license agreement and applicable provisions of the FAR and its supplements. + +Use is subject to license terms. + +This distribution may include materials developed by third parties. + +Sun, Sun Microsystems, the Sun logo, Java, Jini, Solaris and BTrace are trademarks or registered trademarks of Sun Microsystems, Inc. in the U.S. and other countries. + +This product is covered and controlled by U.S. Export Control laws and may be subject to the export or import laws in other countries. Nuclear, missile, chemical biological weapons or nuclear maritime end uses or end users, whether direct or indirect, are strictly prohibited. Export or reexport to countries subject to U.S. embargo or to entities identified on U.S. export exclusion lists, including, but not limited to, the denied persons and specially designated nationals lists is strictly prohibited. + + +Copyright © 2008 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, California 95054, Etats-Unis. Tous droits réservés. + +Sun Microsystems, Inc. détient les droits de propriété intellectuels relatifs à la technologie incorporée dans le produit qui est décrit dans ce document. En particulier, et ce sans limitation, ces droits de propriété intellectuelle peuvent inclure un ou plus des brevets américains listés à l'adresse http://www.sun.com/patents et un ou les brevets supplémentaires ou les applications de brevet en attente aux Etats - Unis et dans les autres pays. + +L'utilisation est soumise aux termes de la Licence. + +Cette distribution peut comprendre des composants développés par des tierces parties. + +Sun, Sun Microsystems, le logo Sun, Java, Jini, Solaris et BTrace sont des marques de fabrique ou des marques déposées de Sun Microsystems, Inc. aux Etats-Unis et dans d'autres pays. + +Ce produit est soumis à la législation américaine en matière de contrôle des exportations et peut être soumis à la règlementation en vigueur dans d'autres pays dans le domaine des exportations et importations. Les utilisations, ou utilisateurs finaux, pour des armes nucléaires,des missiles, des armes biologiques et chimiques ou du nucléaire maritime, directement ou indirectement, sont strictement interdites. Les exportations ou réexportations vers les pays sous embargo américain, ou vers des entités figurant sur les listes d'exclusion d'exportation américaines, y compris, mais de manière non exhaustive, la liste de personnes qui font objet d'un ordre de ne pas participer, d'une façon directe ou indirecte, aux exportations des produits ou des services qui sont régis par la législation américaine en matière de contrôle des exportations et la liste de ressortissants spécifiquement désignés, sont rigoureusement interdites. + diff --git a/LICENSE b/btrace-dist/src/main/resources/LICENSE similarity index 100% rename from LICENSE rename to btrace-dist/src/main/resources/LICENSE diff --git a/LICENSE-3RD-PARTY.txt b/btrace-dist/src/main/resources/LICENSE-3RD-PARTY.txt similarity index 100% rename from LICENSE-3RD-PARTY.txt rename to btrace-dist/src/main/resources/LICENSE-3RD-PARTY.txt diff --git a/THIRDPARTYLICENSEREADME.txt b/btrace-dist/src/main/resources/THIRDPARTYLICENSEREADME.txt similarity index 100% rename from THIRDPARTYLICENSEREADME.txt rename to btrace-dist/src/main/resources/THIRDPARTYLICENSEREADME.txt diff --git a/btrace-dist/src/main/resources/bin/btrace b/btrace-dist/src/main/resources/bin/btrace new file mode 100755 index 000000000..35626af2a --- /dev/null +++ b/btrace-dist/src/main/resources/bin/btrace @@ -0,0 +1,70 @@ +#! /bin/sh + +if [ -z "$BTRACE_HOME" -o ! -d "$BTRACE_HOME" ]; then + # resolve links - $0 could be a link to btrace's home + PRG="$0" + progname=$(basename "$0") + PRG=$(readlink "$PRG") + + if [ -z "$PRG" ]; then + PRG=$0 + fi + BTRACE_HOME=$(dirname "$PRG")/.. + BTRACE_HOME=$(cd "$BTRACE_HOME" && pwd) +fi + +JAVA_ARGS="-XX:+IgnoreUnrecognizedVMOptions" +if [ -d "${JAVA_HOME}/jmods" ]; then + JAVA_ARGS="$JAVA_ARGS -XX:+AllowRedefinitionToAddDeleteMethods" + JAVA_ARGS="$JAVA_ARGS --add-exports jdk.internal.jvmstat/sun.jvmstat.monitor=ALL-UNNAMED" +fi + +if [ -f "${BTRACE_HOME}/libs/btrace-client.jar" ]; then + if [ -d "${JAVA_HOME}" ]; then + TOOLS_JAR="$JAVA_HOME/lib/tools.jar" + + if [ ! -f "$TOOLS_JAR" ]; then + # probably running on JRE - try to localize JDK + TOOLS_JAR="$JAVA_HOME/../lib/tools.jar" + fi + + if [ ! -f "${TOOLS_JAR}" ] && [ ! -d "${JAVA_HOME}/jmods" ]; then + # old Java versions on MacOS don't have tools.jar at all + case "$(uname)" in + Darwin*) + # In older JDK versions for Mac OS X, tools.jar is classes.jar + # and is kept in a different location. Check if we can locate + # classes.jar based on ${JAVA_VERSION} + TOOLS_JAR="/System/Library/Frameworks/JavaVM.framework/Versions/${JAVA_VERSION}/Classes/classes.jar" + + # if we can't find, try relative path from ${JAVA_HOME}. Usually, + # /System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home + # is JAVA_HOME. (or whatever version beyond 1.6.0!) + if [ ! -f "${TOOLS_JAR}" ]; then + TOOLS_JAR="${JAVA_HOME}/../Classes/classes.jar" + fi + + # If we still can't find, tell the user to set JAVA_VERSION. + # This way, we can avoid zip file errors from the agent side + # and "connection refused" message from client. + if [ ! -f "${TOOLS_JAR}" ]; then + echo "Please set JAVA_VERSION to the target java version" + exit 1 + fi + ;; + esac + fi + if [ ! -f "${TOOLS_JAR}" ] && [ ! -d "${JAVA_HOME}/jmods" ]; then + # in non-jigsaw world TOOLS_JAR must point to a valid file + echo "Unable to locate tools.jar. Please, make sure JAVA_HOME points to a valid JDK installation" + exit 1 + fi + ${JAVA_HOME}/bin/java ${JAVA_ARGS} -cp ${BTRACE_HOME}/libs/btrace-client.jar:${TOOLS_JAR}:/usr/share/lib/java/dtrace.jar org.openjdk.btrace.client.Main $* + else + echo "Please set a valid JAVA_HOME before running this script" + exit 1 + fi +else + echo "Please set BTRACE_HOME before running this script" + exit 1 +fi diff --git a/btrace-dist/src/main/resources/bin/btrace.bat b/btrace-dist/src/main/resources/bin/btrace.bat new file mode 100644 index 000000000..6b5bf08c7 --- /dev/null +++ b/btrace-dist/src/main/resources/bin/btrace.bat @@ -0,0 +1,24 @@ +@echo off + +rem %~dp0 is expanded pathname of the current script under NT +set DEFAULT_BTRACE_HOME=%~dp0.. + +if "%BTRACE_HOME%"=="" set BTRACE_HOME=%DEFAULT_BTRACE_HOME% +set DEFAULT_BTRACE_HOME= + +if not exist "%BTRACE_HOME%\libs\btrace-client.jar" goto noBTraceHome + +if "%JAVA_HOME%" == "" goto noJavaHome + set JAVA_ARGS="-XX:+IgnoreUnrecognizedVMOptions" + if exist "%JAVA_HOME%/jmods/" ( + set JAVA_ARGS="%JAVA_ARGS% -XX:+AllowRedefinitionToAddDeleteMethods" + set JAVA_ARGS="%JAVA_ARGS% --add-exports jdk.internal.jvmstat/sun.jvmstat.monitor=ALL-UNNAMED" + ) + "%JAVA_HOME%/bin/java" "%JAVA_ARGS%" -cp "%BTRACE_HOME%/libs/btrace-client.jar;%JAVA_HOME%/lib/tools.jar" org.openjdk.btrace.client.Main %* + goto end +:noJavaHome + echo Please set JAVA_HOME before running this script + goto end +:noBTraceHome + echo Please set BTRACE_HOME before running this script +:end diff --git a/btrace-dist/src/main/resources/bin/btracec b/btrace-dist/src/main/resources/bin/btracec new file mode 100755 index 000000000..8a0aa7aa1 --- /dev/null +++ b/btrace-dist/src/main/resources/bin/btracec @@ -0,0 +1,61 @@ +#! /bin/sh + +if [ -z "$BTRACE_HOME" -o ! -d "$BTRACE_HOME" ]; then + # resolve links - $0 could be a link to btrace's home + PRG="$0" + progname=$(basename "$0") + PRG=$(readlink "$PRG") + + if [ -z "$PRG" ]; then + PRG=$0 + fi + BTRACE_HOME=$(dirname "$PRG")/.. + BTRACE_HOME=$(cd "$BTRACE_HOME" && pwd) +fi +JAVA_ARGS="-XX:+IgnoreUnrecognizedVMOptions" +if [ -d "${JAVA_HOME}/jmods" ]; then + JAVA_ARGS="$JAVA_ARGS --add-exports jdk.internal.jvmstat/sun.jvmstat.monitor=ALL-UNNAMED" +fi +if [ -f "${BTRACE_HOME}/libs/btrace-client.jar" ]; then + if [ -d "${JAVA_HOME}" ]; then + if [ "$1" = "--version" ]; then + ${JAVA_HOME}/bin/java ${JAVA_ARGS} -cp ${BTRACE_HOME}/libs/btrace-client.jar org.openjdk.btrace.client.Main --version + exit 0 + fi + TOOLS_JAR="$JAVA_HOME/lib/tools.jar" + + if [ ! -f "$TOOLS_JAR" ]; then + # probably running on JRE - try to localize JDK + TOOLS_JAR="$JAVA_HOME/../lib/tools.jar" + fi + + if [ ! -f "${TOOLS_JAR}" ] && [ ! -d "${JAVA_HOME}/jmods" ]; then + # old Java versions on MacOS don't have tools.jar at all + case "$(uname)" in + Darwin*) + if [ -f /System/Library/Frameworks/JavaVM.framework/Versions/${JAVA_VERSION}/Classes/classes.jar ]; then + TOOLS_JAR="/System/Library/Frameworks/JavaVM.framework/Versions/${JAVA_VERSION}/Classes/classes.jar" + else + TOOLS_JAR="${JAVA_HOME}/lib/tools.jar" + fi + ;; + *) + TOOLS_JAR="${JAVA_HOME}/lib/tools.jar" + ;; + esac + fi + if [ ! -f "${TOOLS_JAR}" ] && [ ! -d "${JAVA_HOME}/jmods" ]; then + # in non-jigsaw world TOOLS_JAR must point to a valid file + echo "Unable to locate tools.jar. Please, make sure JAVA_HOME points to a valid JDK installation" + exit 1 + fi + + ${JAVA_HOME}/bin/java ${JAVA_ARGS} -cp ${BTRACE_HOME}/libs/btrace-client.jar:${TOOLS_JAR} org.openjdk.btrace.compiler.Compiler $* + else + echo "Please set a valid JAVA_HOME before running this script" + exit 1 + fi +else + echo "Please set BTRACE_HOME before running this script" + exit 1 +fi diff --git a/btrace-dist/src/main/resources/bin/btracec.bat b/btrace-dist/src/main/resources/bin/btracec.bat new file mode 100644 index 000000000..24d0fd85e --- /dev/null +++ b/btrace-dist/src/main/resources/bin/btracec.bat @@ -0,0 +1,27 @@ +@echo off + +rem %~dp0 is expanded pathname of the current script under NT +set DEFAULT_BTRACE_HOME=%~dp0.. + +if "%BTRACE_HOME%"=="" set BTRACE_HOME=%DEFAULT_BTRACE_HOME% +set DEFAULT_BTRACE_HOME= + +if not exist "%BTRACE_HOME%\libs\btrace-client.jar" goto noBTraceHome + +if "%JAVA_HOME%" == "" goto noJavaHome + if exist "%JAVA_HOME%/jmods/" ( + set JAVA_ARGS="%JAVA_ARGS% -XX:+AllowRedefinitionToAddDeleteMethods" + set JAVA_ARGS="%JAVA_ARGS% --add-exports jdk.internal.jvmstat/sun.jvmstat.monitor=ALL-UNNAMED" + ) + if "%1" == "--version" ( + %JAVA_HOME%\bin\java "%JAVA_ARGS%" -cp %BTRACE_HOME%/build/btrace-client.jar org.openjdk.btrace.client.Main --version + goto end + ) + "%JAVA_HOME%/bin/java" "%JAVA_ARGS%" -cp "%BTRACE_HOME%/libs/btrace-client.jar;%JAVA_HOME%/lib/tools.jar" org.openjdk.btrace.compiler.Compiler %* + goto end +:noJavaHome + echo Please set JAVA_HOME before running this script + goto end +:noBTraceHome + echo Please set BTRACE_HOME before running this script +:end \ No newline at end of file diff --git a/btrace-dist/src/main/resources/bin/btracep b/btrace-dist/src/main/resources/bin/btracep new file mode 100755 index 000000000..717eb5d8a --- /dev/null +++ b/btrace-dist/src/main/resources/bin/btracep @@ -0,0 +1,64 @@ +#! /bin/sh + +if [ -z "$BTRACE_HOME" -o ! -d "$BTRACE_HOME" ]; then + # resolve links - $0 could be a link to btrace's home + PRG="$0" + progname=$(basename "$0") + PRG=$(readlink "$PRG") + + if [ -z "$PRG" ]; then + PRG=$0 + fi + BTRACE_HOME=$(dirname "$PRG")/.. + BTRACE_HOME=$(cd "$BTRACE_HOME" && pwd) +fi + +JAVA_ARGS="-XX:+IgnoreUnrecognizedVMOptions" +if [ -d "${JAVA_HOME}/jmods" ]; then + JAVA_ARGS="$JAVA_ARGS -XX:+AllowRedefinitionToAddDeleteMethods" + JAVA_ARGS="$JAVA_ARGS --add-exports jdk.internal.jvmstat/sun.jvmstat.monitor=ALL-UNNAMED" +fi + +if [ -f "${BTRACE_HOME}/libs/btrace-client.jar" ]; then + if [ -d "${JAVA_HOME}" ]; then + if [ "$1" = "--version" ]; then + ${JAVA_HOME}/bin/java ${JAVA_ARGS} -cp ${BTRACE_HOME}/libs/btrace-client.jar org.openjdk.btrace.client.Main --version + exit 0 + fi + TOOLS_JAR="$JAVA_HOME/lib/tools.jar" + + if [ ! -f "$TOOLS_JAR" ]; then + # probably running on JRE - try to localize JDK + TOOLS_JAR="$JAVA_HOME/../lib/tools.jar" + fi + + if [ ! -f "${TOOLS_JAR}" ] && [ ! -d "${JAVA_HOME}/jmods" ]; then + # old Java versions on MacOS don't have tools.jar at all + case "$(uname)" in + Darwin*) + if [ -f /System/Library/Frameworks/JavaVM.framework/Versions/${JAVA_VERSION}/Classes/classes.jar ]; then + TOOLS_JAR="/System/Library/Frameworks/JavaVM.framework/Versions/${JAVA_VERSION}/Classes/classes.jar" + else + TOOLS_JAR="${JAVA_HOME}/lib/tools.jar" + fi + ;; + *) + TOOLS_JAR="${JAVA_HOME}/lib/tools.jar" + ;; + esac + fi + if [ ! -f "${TOOLS_JAR}" ] && [ ! -d "${JAVA_HOME}/jmods" ]; then + # in non-jigsaw world TOOLS_JAR must point to a valid file + echo "Unable to locate tools.jar. Please, make sure JAVA_HOME points to a valid JDK installation" + exit 1 + fi + + ${JAVA_HOME}/bin/java ${JAVA_ARGS} -cp ${BTRACE_HOME}/libs/btrace-client.jar:${TOOLS_JAR} org.openjdk.btrace.client.ProbePrinter $* + else + echo "Please set a valid JAVA_HOME before running this script" + exit 1 + fi +else + echo "Please set BTRACE_HOME before running this script" + exit 1 +fi diff --git a/btrace-dist/src/main/resources/bin/btracep.bat b/btrace-dist/src/main/resources/bin/btracep.bat new file mode 100644 index 000000000..ed4ba76d0 --- /dev/null +++ b/btrace-dist/src/main/resources/bin/btracep.bat @@ -0,0 +1,27 @@ +@echo off + +rem %~dp0 is expanded pathname of the current script under NT +set DEFAULT_BTRACE_HOME=%~dp0.. + +if "%BTRACE_HOME%"=="" set BTRACE_HOME=%DEFAULT_BTRACE_HOME% +set DEFAULT_BTRACE_HOME= + +if not exist "%BTRACE_HOME%\libs\btrace-client.jar" goto noBTraceHome + +if "%JAVA_HOME%" == "" goto noJavaHome + if exist "%JAVA_HOME%/jmods/" ( + set JAVA_ARGS="%JAVA_ARGS% -XX:+AllowRedefinitionToAddDeleteMethods" + set JAVA_ARGS="%JAVA_ARGS% --add-exports jdk.internal.jvmstat/sun.jvmstat.monitor=ALL-UNNAMED" + ) + if "%1" == "--version" ( + %JAVA_HOME%\bin\java "%JAVA_ARGS%" -cp %BTRACE_HOME%/build/btrace-client.jar org.openjdk.btrace.client.Main --version + goto end + ) + "%JAVA_HOME%/bin/java" "%JAVA_ARGS%" -cp "%BTRACE_HOME%/libs/btrace-client.jar;%JAVA_HOME%/lib/tools.jar" org.openjdk.btrace.client.ProbePrinter $* + goto end +:noJavaHome + echo Please set JAVA_HOME before running this script + goto end +:noBTraceHome + echo Please set BTRACE_HOME before running this script +:end \ No newline at end of file diff --git a/btrace-dist/src/main/resources/bin/btracer b/btrace-dist/src/main/resources/bin/btracer new file mode 100755 index 000000000..aaffe6196 --- /dev/null +++ b/btrace-dist/src/main/resources/bin/btracer @@ -0,0 +1,111 @@ +#! /bin/sh + +if [ -z "$BTRACE_HOME" ] || [ ! -d "$BTRACE_HOME" ]; then + # resolve links - $0 could be a link to btrace's home + PRG="$0" + progname=$(basename "$0") + PRG=$(readlink "$PRG") + + if [ -z "$PRG" ]; then + PRG=$0 + fi + BTRACE_HOME=$(dirname "$PRG")/.. + BTRACE_HOME=$(cd "$BTRACE_HOME" && pwd) +fi + +usage() { + echo "btracer " + echo "Options:" + echo " --version\t\t\tShow BTrace version" + echo " -v\t\t\t\tRun in verbose mode" + echo " -u\t\t\t\tRun in unsafe mode" + echo " -p \t\t\tBTrace agent server port" + echo " -statsd [:]\tUse this StatsD server" + echo " -o \t\t\tThe path to a file the btrace agent will store its output" + echo " -d \t\t\tDump modified classes to the provided location" + echo " -pd \t\t\tSearch for the probe XML descriptors here" + echo " --noserver\t\t\tDon't start the socket server" + echo " --stdout\t\t\tRedirect the btrace output to stdout instead of writing it to an arbitrary file" + echo " -bcp \t\t\tAppend to bootstrap class path" + echo " -scp \t\t\tAppend to system class path" + echo " -h\t\t\t\tThis message" + exit 0 +} + +if [ $# -eq 0 ]; then + usage +fi + +JAVA_ARGS="-XX:+IgnoreUnrecognizedVMOptions" +if [ -d "${JAVA_HOME}/jmods" ]; then + JAVA_ARGS="$JAVA_ARGS -XX:+AllowRedefinitionToAddDeleteMethods" + JAVA_ARGS="$JAVA_ARGS --add-exports jdk.internal.jvmstat/sun.jvmstat.monitor=ALL-UNNAMED" +fi + +OPTIONS= + +if [ -d "${JAVA_HOME}" ]; then + while [ true ]; do + case $1 in + -v) + OPTIONS="debug=true,$OPTIONS" + ;; + -u) + OPTIONS="unsafe=true,$OPTIONS" + ;; + -p) + OPTIONS="port=$2,$OPTIONS" + shift + ;; + -d) + OPTIONS="dumpClasses=true,dumpDir=$2,$OPTIONS" + shift + ;; + -o) + OPTIONS="scriptOutputFile=$2,$OPTIONS" + shift + ;; + -pd) + OPTIONS="probeDescPath=$2,$OPTIONS" + shift + ;; + -bcp) + OPTIONS="bootClassPath=$2,$OPTIONS" + shift + ;; + -scp) + OPTIONS="systemClassPath=$2,$OPTIONS" + shift + ;; + -statsd) + OPTIONS="statsd=$2,$OPTIONS" + ;; + --noserver) + OPTIONS="noServer=true,$OPTIONS" + ;; + --stdout) + OPTIONS="stdout=true,$OPTIONS" + ;; + --version) + $JAVA_HOME/bin/java ${JAVA_ARGS} -cp ${BTRACE_HOME}/libs/btrace-client.jar org.openjdk.btrace.client.Main --version + exit 0 + ;; + -h) + usage + ;; + *) + break + ;; + esac + shift + done + + if [ -f "${BTRACE_HOME}/libs/btrace-agent.jar" ]; then + ${JAVA_HOME}/bin/java -Xshare:off ${JAVA_ARGS} -javaagent:${BTRACE_HOME}/libs/btrace-agent.jar=$OPTIONS,script="$@" + else + echo "Please set BTRACE_HOME before running this script" + fi +else + echo "Please set a valid JAVA_HOME before running this script" + exit 1 +fi diff --git a/bin/btracer.bat b/btrace-dist/src/main/resources/bin/btracer.bat similarity index 81% rename from bin/btracer.bat rename to btrace-dist/src/main/resources/bin/btracer.bat index 017d8985a..1c76e4d20 100644 --- a/bin/btracer.bat +++ b/btrace-dist/src/main/resources/bin/btracer.bat @@ -1,114 +1,118 @@ -@echo off - -rem %~dp0 is expanded pathname of the current script under NT -set DEFAULT_BTRACE_HOME=%~dp0.. - -if "%BTRACE_HOME%"=="" set BTRACE_HOME=%DEFAULT_BTRACE_HOME% -set DEFAULT_BTRACE_HOME= - -if not exist "%BTRACE_HOME%\build\btrace-agent.jar" goto noBTraceHome - -set OPTIONS= - -if "%1"=="" ( - call:usage - goto end -) -if "%JAVA_HOME%" == "" goto noJavaHome - -if "%1" == "--version" ( - %JAVA_HOME%\bin\java -jar %BTRACE_HOME%/build/btrace-client.jar com.sun.btrace.Main --version - goto end -) - -set inloop=1 -:loop - IF "%1"=="-v" ( - set OPTIONS="debug=true,%OPTIONS" - goto next - ) - IF "%1"=="-u" ( - set OPTIONS="unsafe=true,%OPTIONS" - goto next - ) - if "%1"=="-p" ( - set OPTIONS="port=%2,%OPTIONS" - shift - goto next - ) - if "%1"=="-d" ( - set OPTIONS="dumpClasses=true,dumpDir=%2,%OPTIONS" - shift - goto next - ) - if "%1"=="-o" ( - set OPTIONS="scriptOutputFile=%2,%OPTIONS" - shift - goto next - ) - if "%1"=="-pd" ( - set OPTIONS="probeDescPath=%2,%OPTIONS" - shift - goto next - ) - if "%1"=="-bcp" ( - OPTIONS="bootClassPath=%2,%OPTIONS" - shift - goto next - ) - if "%1"=="-scp" ( - set OPTIONS="systemClassPath=%2,%OPTIONS" - shift - goto next - ) - if "%1"=="--noserver" ( - set OPTIONS="noServer=true,%OPTIONS" - goto next - ) - if "%1"=="--stdout" ( - set OPTIONS="stdout=true,%OPTIONS" - goto next - ) - if "%1"=="-statsd" ( - set OPTIONS="statsd=%2,%OPTIONS" - goto next - ) - if "%1"=="-h" ( - call :usage - goto end - ) - set inloop=0 - - :next - if %inloop==1 ( - shift - goto loop - ) - -%JAVA_HOME%\bin\java -Xshare:off "-javaagent:%BTRACE_HOME%/build/btrace-agent.jar=%OPTIONS,script=%~1" %2 %3 %4 %5 %6 %7 %8 %9 -goto end - -:noJavaHome - echo Please set JAVA_HOME before running this script - goto end -:noBTraceHome - echo Please set BTRACE_HOME before running this script - goto end - -:usage - echo btracer ^ ^ ^ - echo Options: - echo -v Run in verbose mode - echo -u Run in unsafe mode - echo -p ^ BTrace agent server port - echo -statsd ^ Use this StatsD server - echo -o ^ The path to a file the btrace agent will store its output - echo -d ^ Dump modified classes to the provided location - echo -pd ^ Search for the probe XML descriptors here - echo --noserver Don't start the socket server - echo --stdout Redirect the btrace output to stdout instead of writing it to an arbitrary file - echo -bcp ^ Append to bootstrap class path - echo -scp ^ Append to system class path - echo -h This message - -:end +@echo off + +rem %~dp0 is expanded pathname of the current script under NT +set DEFAULT_BTRACE_HOME=%~dp0.. + +if "%BTRACE_HOME%"=="" set BTRACE_HOME=%DEFAULT_BTRACE_HOME% +set DEFAULT_BTRACE_HOME= + +if not exist "%BTRACE_HOME%\libs \btrace-agent.jar" goto noBTraceHome + +set OPTIONS= + +if "%1"=="" ( + call:usage + goto end +) +if "%JAVA_HOME%" == "" goto noJavaHome + +if exist "%JAVA_HOME%/jmods/" ( + set JAVA_ARGS="%JAVA_ARGS% -XX:+AllowRedefinitionToAddDeleteMethods" + set JAVA_ARGS="%JAVA_ARGS% --add-exports jdk.internal.jvmstat/sun.jvmstat.monitor=ALL-UNNAMED" +) + +if "%1" == "--version" ( + %JAVA_HOME%\bin\java "%JAVA_ARGS%" -cp %BTRACE_HOME%/build/btrace-client.jar org.openjdk.btrace.client.Main --version + goto end +) + +set inloop=1 +:loop + IF "%1"=="-v" ( + set OPTIONS="debug=true,%OPTIONS" + goto next + ) + IF "%1"=="-u" ( + set OPTIONS="unsafe=true,%OPTIONS" + goto next + ) + if "%1"=="-p" ( + set OPTIONS="port=%2,%OPTIONS" + shift + goto next + ) + if "%1"=="-d" ( + set OPTIONS="dumpClasses=true,dumpDir=%2,%OPTIONS" + shift + goto next + ) + if "%1"=="-o" ( + set OPTIONS="scriptOutputFile=%2,%OPTIONS" + shift + goto next + ) + if "%1"=="-pd" ( + set OPTIONS="probeDescPath=%2,%OPTIONS" + shift + goto next + ) + if "%1"=="-bcp" ( + OPTIONS="bootClassPath=%2,%OPTIONS" + shift + goto next + ) + if "%1"=="-scp" ( + set OPTIONS="systemClassPath=%2,%OPTIONS" + shift + goto next + ) + if "%1"=="--noserver" ( + set OPTIONS="noServer=true,%OPTIONS" + goto next + ) + if "%1"=="--stdout" ( + set OPTIONS="stdout=true,%OPTIONS" + goto next + ) + if "%1"=="-statsd" ( + set OPTIONS="statsd=%2,%OPTIONS" + goto next + ) + call :usage + goto end + + set inloop=0 + + :next + if %inloop==1 ( + shift + goto loop + ) + +%JAVA_HOME%\bin\java -Xshare:off "%JAVA_ARGS%" "-javaagent:%BTRACE_HOME%/libs/btrace-agent.jar=%OPTIONS,script=%~1" %2 %3 %4 %5 %6 %7 %8 %9 +goto end + +:noJavaHome + echo Please set JAVA_HOME before running this script + goto end +:noBTraceHome + echo Please set BTRACE_HOME before running this script + goto end + +:usage + echo btracer ^ ^ ^ + echo Options: + echo -v Run in verbose mode + echo -u Run in unsafe mode + echo -p ^ BTrace agent server port + echo -statsd ^ Use this StatsD server + echo -o ^ The path to a file the btrace agent will store its output + echo -d ^ Dump modified classes to the provided location + echo -pd ^ Search for the probe XML descriptors here + echo --noserver Don't start the socket server + echo --stdout Redirect the btrace output to stdout instead of writing it to an arbitrary file + echo -bcp ^ Append to bootstrap class path + echo -scp ^ Append to system class path + echo -h This message + +:end diff --git a/btrace-dist/src/main/resources/libs/amd64/libbtrace.so b/btrace-dist/src/main/resources/libs/amd64/libbtrace.so new file mode 100755 index 000000000..45e2e91ac Binary files /dev/null and b/btrace-dist/src/main/resources/libs/amd64/libbtrace.so differ diff --git a/btrace-dist/src/main/resources/samples/AWTEventTracer.java b/btrace-dist/src/main/resources/samples/AWTEventTracer.java new file mode 100644 index 000000000..4111d7e23 --- /dev/null +++ b/btrace-dist/src/main/resources/samples/AWTEventTracer.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.Self; + +import java.awt.*; +import java.awt.event.FocusEvent; + +import static org.openjdk.btrace.core.BTraceUtils.println; + +/** + * This simple script traces every AWT focus event in + * the target process. + */ +@BTrace +public class AWTEventTracer { + @OnMethod( + clazz = "java.awt.EventQueue", + method = "dispatchEvent" + ) + public static void onevent(@Self EventQueue queue, AWTEvent event) { + if (event instanceof FocusEvent) { + println(event); + println(); + } + } +} + diff --git a/btrace-dist/src/main/resources/samples/AllCalls1.java b/btrace-dist/src/main/resources/samples/AllCalls1.java new file mode 100644 index 000000000..37bbebe33 --- /dev/null +++ b/btrace-dist/src/main/resources/samples/AllCalls1.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.Kind; +import org.openjdk.btrace.core.annotations.Location; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.ProbeMethodName; +import org.openjdk.btrace.core.annotations.Self; +import org.openjdk.btrace.core.annotations.TargetMethodOrField; + +import static org.openjdk.btrace.core.BTraceUtils.println; + +/** + * This script demonstrates the possibility to intercept + * method calls that are about to be executed from the body of + * a certain method. This is achieved by using the {@linkplain Kind#CALL} + * location value. + */ +@BTrace +public class AllCalls1 { + @OnMethod(clazz = "javax.swing.JTextField", method = "/.*/", + location = @Location(value = Kind.CALL, clazz = "/.*/", method = "/.*/")) + public static void m(@Self Object self, @TargetMethodOrField String method, @ProbeMethodName String probeMethod) { // all calls to the methods with signature "()" + println(method + " in " + probeMethod); + } +} diff --git a/btrace-dist/src/main/resources/samples/AllCalls1Sampled.java b/btrace-dist/src/main/resources/samples/AllCalls1Sampled.java new file mode 100644 index 000000000..bc04872d1 --- /dev/null +++ b/btrace-dist/src/main/resources/samples/AllCalls1Sampled.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.Kind; +import org.openjdk.btrace.core.annotations.Location; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.ProbeMethodName; +import org.openjdk.btrace.core.annotations.Sampled; +import org.openjdk.btrace.core.annotations.Self; +import org.openjdk.btrace.core.annotations.TargetMethodOrField; + +import static org.openjdk.btrace.core.BTraceUtils.println; + +/** + * This script demonstrates the possibility to intercept + * method calls that are about to be executed from the body of + * a certain method. This is achieved by using the {@linkplain Kind#CALL} + * location value. + *

+ * Not all instances of the method call are intercepted, however. Adaptive sampling + * is used to balance the captured data and incurred overhead. + */ +@BTrace +public class AllCalls1Sampled { + @OnMethod(clazz = "javax.swing.JTextField", method = "/.*/", + location = @Location(value = Kind.CALL, clazz = "/.*/", method = "/.*/")) + @Sampled(kind = Sampled.Sampler.Adaptive) + public static void m(@Self Object self, @TargetMethodOrField String method, @ProbeMethodName String probeMethod) { // all calls to the methods with signature "()" + println(method + " in " + probeMethod); + } +} diff --git a/btrace-dist/src/main/resources/samples/AllCalls2.java b/btrace-dist/src/main/resources/samples/AllCalls2.java new file mode 100644 index 000000000..b5d8adb4e --- /dev/null +++ b/btrace-dist/src/main/resources/samples/AllCalls2.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.Kind; +import org.openjdk.btrace.core.annotations.Location; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.ProbeClassName; +import org.openjdk.btrace.core.annotations.ProbeMethodName; +import org.openjdk.btrace.core.annotations.Self; +import org.openjdk.btrace.core.annotations.TargetInstance; +import org.openjdk.btrace.core.annotations.TargetMethodOrField; + +import static org.openjdk.btrace.core.BTraceUtils.println; + +/** + * This script demonstrates the possibility to intercept + * method calls that are about to be executed from the body of + * a certain method. This is achieved by using the {@linkplain Kind#CALL} + * location value. + */ +@BTrace +public class AllCalls2 { + @OnMethod(clazz = "/javax\\.swing\\..*/", method = "/.*/", + location = @Location(value = Kind.CALL, clazz = "/.*/", method = "/.*/")) + public static void n(@Self Object self, @ProbeClassName String pcm, @ProbeMethodName String pmn, + @TargetInstance Object instance, @TargetMethodOrField String method, String text) { // all calls to the methods with signature "(String)" + println("Context: " + pcm + "#" + pmn + method + " " + text); + } +} diff --git a/btrace-dist/src/main/resources/samples/AllCalls2Sampled.java b/btrace-dist/src/main/resources/samples/AllCalls2Sampled.java new file mode 100644 index 000000000..1e5c61f09 --- /dev/null +++ b/btrace-dist/src/main/resources/samples/AllCalls2Sampled.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.Kind; +import org.openjdk.btrace.core.annotations.Location; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.ProbeClassName; +import org.openjdk.btrace.core.annotations.ProbeMethodName; +import org.openjdk.btrace.core.annotations.Sampled; +import org.openjdk.btrace.core.annotations.Self; +import org.openjdk.btrace.core.annotations.TargetInstance; +import org.openjdk.btrace.core.annotations.TargetMethodOrField; + +import static org.openjdk.btrace.core.BTraceUtils.println; + +/** + * This script demonstrates the possibility to intercept + * method calls that are about to be executed from the body of + * a certain method. This is achieved by using the {@linkplain Kind#CALL} + * location value. + *

+ * Not all instances of the method call are intercepted, however. Sampling + * is used to pick only statistically representative ones. + */ +@BTrace +public class AllCalls2Sampled { + @OnMethod(clazz = "/javax\\.swing\\..*/", method = "/.*/", + location = @Location(value = Kind.CALL, clazz = "/.*/", method = "/.*/")) + @Sampled + public static void n(@Self Object self, @ProbeClassName String pcm, @ProbeMethodName String pmn, + @TargetInstance Object instance, @TargetMethodOrField String method, String text) { // all calls to the methods with signature "(String)" + println("Context: " + pcm + "#" + pmn + method + " " + text); + } +} diff --git a/btrace-dist/src/main/resources/samples/AllCalls3.java b/btrace-dist/src/main/resources/samples/AllCalls3.java new file mode 100644 index 000000000..caeb292cb --- /dev/null +++ b/btrace-dist/src/main/resources/samples/AllCalls3.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +import org.openjdk.btrace.core.types.AnyType; +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.Kind; +import org.openjdk.btrace.core.annotations.Location; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.ProbeMethodName; +import org.openjdk.btrace.core.annotations.Self; + +import static org.openjdk.btrace.core.BTraceUtils.printArray; + +/** + * This script demonstrates the possibility to intercept + * method calls that are about to be executed from the body of + * a certain method. This is achieved by using the {@linkplain Kind#CALL} + * location value. + */ +@BTrace +public class AllCalls3 { + @OnMethod(clazz = "javax.swing.JButton", method = "/.*/", + location = @Location(value = Kind.CALL, clazz = "/.*/", method = "/.*/")) + public static void o(@Self Object self, @ProbeMethodName String pmn, AnyType[] args) { // all calls to methods + // self - this for the method call + // pmn - textual representation of the method + // contents of args array: + // [0]..[n] - original method call arguments + printArray(args); + } +} diff --git a/btrace-dist/src/main/resources/samples/AllCalls3Sampled.java b/btrace-dist/src/main/resources/samples/AllCalls3Sampled.java new file mode 100644 index 000000000..9ae345457 --- /dev/null +++ b/btrace-dist/src/main/resources/samples/AllCalls3Sampled.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +import org.openjdk.btrace.core.types.AnyType; +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.Kind; +import org.openjdk.btrace.core.annotations.Location; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.ProbeMethodName; +import org.openjdk.btrace.core.annotations.Sampled; +import org.openjdk.btrace.core.annotations.Self; + +import static org.openjdk.btrace.core.BTraceUtils.printArray; + +/** + * This script demonstrates the possibility to intercept + * method calls that are about to be executed from the body of + * a certain method. This is achieved by using the {@linkplain Kind#CALL} + * location value. + *

+ * Not all instances of the method call are intercepted, however. Adaptive sampling + * is used to balance the captured data and incurred overhead. + */ +@BTrace +public class AllCalls3Sampled { + @OnMethod(clazz = "javax.swing.JButton", method = "/.*/", + location = @Location(value = Kind.CALL, clazz = "/.*/", method = "/.*/")) + @Sampled(kind = Sampled.Sampler.Adaptive) + public static void o(@Self Object self, @ProbeMethodName String pmn, AnyType[] args) { // all calls to methods + // self - this for the method call + // pmn - textual representation of the method + // contents of args array: + // [0]..[n] - original method call arguments + printArray(args); + } +} diff --git a/btrace-dist/src/main/resources/samples/AllLines.java b/btrace-dist/src/main/resources/samples/AllLines.java new file mode 100644 index 000000000..d62efc411 --- /dev/null +++ b/btrace-dist/src/main/resources/samples/AllLines.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.Kind; +import org.openjdk.btrace.core.annotations.Location; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.ProbeClassName; +import org.openjdk.btrace.core.annotations.ProbeMethodName; + +import static org.openjdk.btrace.core.BTraceUtils.print; + +/* + * This sample prints a line every time any line + * of code of java.lang.Thread class is reached. + * The line param may be set to any particular + * value so that the probe fires only when that line + * is reached. But, the value -1 means all line numbers. + */ +@BTrace +public class AllLines { + @OnMethod( + clazz = "java.lang.Thread", + location = @Location(value = Kind.LINE, line = -1) + ) + public static void online(@ProbeClassName String pcn, @ProbeMethodName String pmn, int line) { + print(pcn + "." + pmn + ":" + line); + } +} diff --git a/samples/AllMethods.java b/btrace-dist/src/main/resources/samples/AllMethods.java similarity index 76% rename from samples/AllMethods.java rename to btrace-dist/src/main/resources/samples/AllMethods.java index 195b33ae7..8c5a992c2 100644 --- a/samples/AllMethods.java +++ b/btrace-dist/src/main/resources/samples/AllMethods.java @@ -23,23 +23,29 @@ * questions. */ -package com.sun.btrace.samples; -import com.sun.btrace.annotations.*; -import com.sun.btrace.services.impl.Printer; +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.Injected; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.ProbeClassName; +import org.openjdk.btrace.core.annotations.ProbeMethodName; +import org.openjdk.btrace.core.annotations.Self; +import org.openjdk.btrace.core.annotations.ServiceType; +import org.openjdk.btrace.services.impl.Printer; /** * This script traces method entry into every method of * every class in javax.swing package! Think before using * this script -- this will slow down your app significantly!! */ -@BTrace public class AllMethods { +@BTrace +public class AllMethods { @Injected(ServiceType.RUNTIME) private static Printer printer; @OnMethod( - clazz="/javax\\.swing\\..*/", - method="${m}" + clazz = "/javax\\.swing\\..*/", + method = "${m}" ) public static void m(@Self Object o, @ProbeClassName String probeClass, @ProbeMethodName String probeMethod) { printer.println("this = " + o); diff --git a/btrace-dist/src/main/resources/samples/AllMethods1.java b/btrace-dist/src/main/resources/samples/AllMethods1.java new file mode 100644 index 000000000..e50fa6dba --- /dev/null +++ b/btrace-dist/src/main/resources/samples/AllMethods1.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.ProbeClassName; +import org.openjdk.btrace.core.annotations.ProbeMethodName; +import org.openjdk.btrace.core.annotations.Self; + +import static org.openjdk.btrace.core.BTraceUtils.*; + +/** + * This script traces method entry into every method of + * every class in javax.swing package! Think before using + * this script -- this will slow down your app significantly!! + */ +@BTrace +public class AllMethods1 { + @OnMethod( + clazz = "/javax\\.swing\\..*/", + method = "/.*/" + ) + public static void m(@Self Object o, @ProbeClassName String probeClass, @ProbeMethodName String probeMethod) { + println("this = " + o); + print("entered " + probeClass); + println("." + probeMethod); + } +} diff --git a/samples/AllMethodsLevels.java b/btrace-dist/src/main/resources/samples/AllMethodsLevels.java similarity index 77% rename from samples/AllMethodsLevels.java rename to btrace-dist/src/main/resources/samples/AllMethodsLevels.java index 59fb18165..f2fcb9013 100644 --- a/samples/AllMethodsLevels.java +++ b/btrace-dist/src/main/resources/samples/AllMethodsLevels.java @@ -23,25 +23,30 @@ * questions. */ -package com.sun.btrace.samples; -import com.sun.btrace.BTraceUtils; -import com.sun.btrace.annotations.*; -import static com.sun.btrace.BTraceUtils.*; +import org.openjdk.btrace.core.BTraceUtils; +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.Level; +import org.openjdk.btrace.core.annotations.OnEvent; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.ProbeMethodName; + +import static org.openjdk.btrace.core.BTraceUtils.println; /** * This script traces method entry into every method of * every class in javax.swing package! Think before using * this script -- this will slow down your app significantly!! */ -@BTrace public class AllMethodsLevels { +@BTrace +public class AllMethodsLevels { /** * Capturing only methods invoked from javax.swing.JComponent class. */ @OnMethod( - clazz="javax.swing.JComponent", - method="/.*/", - enableAt=@Level("=0") + clazz = "javax.swing.JComponent", + method = "/.*/", + enableAt = @Level("=0") ) public static void l0(@ProbeMethodName(fqn = true) String probeMethod) { println("# " + probeMethod); @@ -51,9 +56,9 @@ public static void l0(@ProbeMethodName(fqn = true) String probeMethod) { * This will intercept all the methods from javax.swing.* classes. */ @OnMethod( - clazz="/javax\\.swing\\.*/", - method="/.*/", - enableAt=@Level(">=1") + clazz = "/javax\\.swing\\.*/", + method = "/.*/", + enableAt = @Level(">=1") ) public static void l1(@ProbeMethodName(fqn = true) String probeMethod) { println("## " + probeMethod); diff --git a/samples/AllMethodsSampled.java b/btrace-dist/src/main/resources/samples/AllMethodsSampled.java similarity index 75% rename from samples/AllMethodsSampled.java rename to btrace-dist/src/main/resources/samples/AllMethodsSampled.java index 03a990c6c..375849ea1 100644 --- a/samples/AllMethodsSampled.java +++ b/btrace-dist/src/main/resources/samples/AllMethodsSampled.java @@ -23,23 +23,30 @@ * questions. */ -package com.sun.btrace.samples; -import com.sun.btrace.annotations.*; -import static com.sun.btrace.BTraceUtils.*; +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.ProbeClassName; +import org.openjdk.btrace.core.annotations.ProbeMethodName; +import org.openjdk.btrace.core.annotations.Sampled; +import org.openjdk.btrace.core.annotations.Self; + +import static org.openjdk.btrace.core.BTraceUtils.print; +import static org.openjdk.btrace.core.BTraceUtils.println; /** * This script traces method entry into every method of * every class in javax.swing package. Think before using * this script -- this will slow down your app significantly!! - * + *

* Not all calls are intercepted, however. Sampling * is used to pick only statistically representative ones. */ -@BTrace public class AllMethodsSampled { +@BTrace +public class AllMethodsSampled { @OnMethod( - clazz="/javax\\.swing\\..*/", - method="/.*/" + clazz = "/javax\\.swing\\..*/", + method = "/.*/" ) @Sampled public static void m(@Self Object o, @ProbeClassName String probeClass, @ProbeMethodName String probeMethod) { diff --git a/samples/AllSync.java b/btrace-dist/src/main/resources/samples/AllSync.java similarity index 75% rename from samples/AllSync.java rename to btrace-dist/src/main/resources/samples/AllSync.java index cfb4049bc..8c953f6db 100644 --- a/samples/AllSync.java +++ b/btrace-dist/src/main/resources/samples/AllSync.java @@ -23,10 +23,15 @@ * questions. */ -package com.sun.btrace.samples; -import com.sun.btrace.annotations.*; -import static com.sun.btrace.BTraceUtils.*; +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.Kind; +import org.openjdk.btrace.core.annotations.Location; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.Where; + +import static org.openjdk.btrace.core.BTraceUtils.identityStr; +import static org.openjdk.btrace.core.BTraceUtils.println; /** * This script traces method/block entry into every method of @@ -38,20 +43,21 @@ * synchronized method. By making the probe point Where.AFTER for SYNC_ENTER, * we probe after monitorenter bytecode or synchronized method entry. */ -@BTrace public class AllSync { +@BTrace +public class AllSync { @OnMethod( - clazz="/javax\\.swing\\..*/", - method="/.*/", - location=@Location(value=Kind.SYNC_ENTRY, where=Where.AFTER) + clazz = "/javax\\.swing\\..*/", + method = "/.*/", + location = @Location(value = Kind.SYNC_ENTRY, where = Where.AFTER) ) public static void onSyncEntry(Object obj) { println("after synchronized entry: " + identityStr(obj)); } @OnMethod( - clazz="/javax\\.swing\\..*/", - method="/.*/", - location=@Location(Kind.SYNC_EXIT) + clazz = "/javax\\.swing\\..*/", + method = "/.*/", + location = @Location(Kind.SYNC_EXIT) ) public static void onSyncExit(Object obj) { println("before synchronized exit: " + identityStr(obj)); diff --git a/samples/ArgArray.java b/btrace-dist/src/main/resources/samples/ArgArray.java similarity index 77% rename from samples/ArgArray.java rename to btrace-dist/src/main/resources/samples/ArgArray.java index 04232a313..509826d5b 100644 --- a/samples/ArgArray.java +++ b/btrace-dist/src/main/resources/samples/ArgArray.java @@ -23,11 +23,15 @@ * questions. */ -package com.sun.btrace.samples; -import com.sun.btrace.annotations.*; -import com.sun.btrace.AnyType; -import static com.sun.btrace.BTraceUtils.*; +import org.openjdk.btrace.core.types.AnyType; +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.ProbeClassName; +import org.openjdk.btrace.core.annotations.ProbeMethodName; + +import static org.openjdk.btrace.core.BTraceUtils.printArray; +import static org.openjdk.btrace.core.BTraceUtils.println; /** * This sample demonstrates regular expression @@ -38,10 +42,11 @@ * java.io package. Probed class, method and arg * array is printed in the action. */ -@BTrace public class ArgArray { +@BTrace +public class ArgArray { @OnMethod( - clazz="/java\\.io\\..*/", - method="/read.*/" + clazz = "/java\\.io\\..*/", + method = "/read.*/" ) public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, AnyType[] args) { println(pcn); diff --git a/btrace-dist/src/main/resources/samples/Classload.java b/btrace-dist/src/main/resources/samples/Classload.java new file mode 100644 index 000000000..6955a5b2e --- /dev/null +++ b/btrace-dist/src/main/resources/samples/Classload.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.Kind; +import org.openjdk.btrace.core.annotations.Location; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.Return; + +import static org.openjdk.btrace.core.BTraceUtils.*; + +/** + * A simple BTrace program that prints stack trace + * whenever a class is loaded by a user-defined + * class loader. We insert a return point probe in + * ClassLoader.defineClass method to detect successful + * class load. + */ +@BTrace +public class Classload { + @OnMethod( + clazz = "+java.lang.ClassLoader", + method = "defineClass", + location = @Location(Kind.RETURN) + ) + public static void defineclass(@Return Class cl) { + println("loaded " + Reflective.name(cl)); + Threads.jstack(); + println("=========================="); + } +} diff --git a/samples/CommandArg.java b/btrace-dist/src/main/resources/samples/CommandArg.java similarity index 86% rename from samples/CommandArg.java rename to btrace-dist/src/main/resources/samples/CommandArg.java index 1550db969..8af0493f0 100644 --- a/samples/CommandArg.java +++ b/btrace-dist/src/main/resources/samples/CommandArg.java @@ -23,10 +23,11 @@ * questions. */ -package com.sun.btrace.samples; -import com.sun.btrace.annotations.*; -import static com.sun.btrace.BTraceUtils.*; +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.OnMethod; + +import static org.openjdk.btrace.core.BTraceUtils.*; /** * This BTrace program demonstrates command line @@ -34,10 +35,11 @@ * arguments. In this example, desired thread name is * passed from the command line (of the BTrace client). */ -@BTrace public class CommandArg { +@BTrace +public class CommandArg { @OnMethod( - clazz="java.lang.Thread", - method="run" + clazz = "java.lang.Thread", + method = "run" ) public static void started() { if (Strings.strcmp(Threads.name(Threads.currentThread()), Sys.$(2)) == 0) { diff --git a/samples/DTraceInline.java b/btrace-dist/src/main/resources/samples/DTraceInline.java similarity index 81% rename from samples/DTraceInline.java rename to btrace-dist/src/main/resources/samples/DTraceInline.java index 4c1129d6d..7bea58c33 100644 --- a/samples/DTraceInline.java +++ b/btrace-dist/src/main/resources/samples/DTraceInline.java @@ -23,15 +23,17 @@ * questions. */ -package com.sun.btrace.samples; -import com.sun.btrace.annotations.*; -import static com.sun.btrace.BTraceUtils.*; +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.DTrace; +import org.openjdk.btrace.core.annotations.OnMethod; + +import static org.openjdk.btrace.core.BTraceUtils.*; /* * This sample demonstrates DTrace/BTrace integration. - * A one-liner D-script is started by BTrace client - * because of @DTrace annotation. In this example + * A one-liner D-script is started by BTrace client + * because of @DTrace annotation. In this example * on new Java Thread starts, BTrace action raises a * DTrace probe. The D-script prints mixed mode stack * trace on receiving this probe. @@ -40,11 +42,11 @@ @BTrace public class DTraceInline { @OnMethod( - clazz="java.lang.Thread", - method="start" + clazz = "java.lang.Thread", + method = "start" ) public static void newThread(Thread th) { println(Threads.name(th)); D.probe("mstack", ""); - } + } } diff --git a/btrace-dist/src/main/resources/samples/DTraceRefDemo.java b/btrace-dist/src/main/resources/samples/DTraceRefDemo.java new file mode 100644 index 000000000..410b4ba37 --- /dev/null +++ b/btrace-dist/src/main/resources/samples/DTraceRefDemo.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.DTraceRef; +import org.openjdk.btrace.core.annotations.Kind; +import org.openjdk.btrace.core.annotations.Location; +import org.openjdk.btrace.core.annotations.OnMethod; + +import static org.openjdk.btrace.core.BTraceUtils.*; + +/* + * This sample demonstrates associating a D-script + * with a BTrace program using @DTraceRef annotation. + * BTrace client looks for absolute or relative path for + * the D-script and submits it to kernel *before* submitting + * BTrace program to BTrace agent. + */ +@DTraceRef("classload.d") +@BTrace +public class DTraceRefDemo { + @OnMethod( + clazz = "java.lang.ClassLoader", + method = "defineClass" + ) + public static void defineClass() { + println("user defined loader load start"); + } + + @OnMethod( + clazz = "java.lang.ClassLoader", + method = "defineClass", + location = @Location(Kind.RETURN) + ) + public static void defineclass(Class cl) { + println("loaded " + Reflective.name(cl)); + Threads.jstack(); + println("=========================="); + } +} diff --git a/samples/Deadlock.java b/btrace-dist/src/main/resources/samples/Deadlock.java similarity index 83% rename from samples/Deadlock.java rename to btrace-dist/src/main/resources/samples/Deadlock.java index 3fe91edb0..5821379bf 100644 --- a/samples/Deadlock.java +++ b/btrace-dist/src/main/resources/samples/Deadlock.java @@ -23,19 +23,21 @@ * questions. */ -package com.sun.btrace.samples; -import com.sun.btrace.annotations.*; -import static com.sun.btrace.BTraceUtils.Threads.*; +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.OnTimer; + +import static org.openjdk.btrace.core.BTraceUtils.Threads.deadlocks; /** * This BTrace program demonstrates deadlocks - * built-in function. This example prints + * built-in function. This example prints * deadlocks (if any) once every 4 seconds. - */ -@BTrace public class Deadlock { + */ +@BTrace +public class Deadlock { @OnTimer(4000) public static void print() { - deadlocks(); + deadlocks(); } } diff --git a/btrace-dist/src/main/resources/samples/FileTracker.java b/btrace-dist/src/main/resources/samples/FileTracker.java new file mode 100644 index 000000000..618cfe6e1 --- /dev/null +++ b/btrace-dist/src/main/resources/samples/FileTracker.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.Kind; +import org.openjdk.btrace.core.annotations.Location; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.Self; +import org.openjdk.btrace.core.annotations.TLS; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; + +import static org.openjdk.btrace.core.BTraceUtils.*; + +/** + * This sample prints all files opened for read/write + * by a Java process. Note that if you pass FileDescriptor + * to File{Input/Output}Stream or File{Reader/Writer}, + * that is not tracked by this script. + */ +@BTrace +public class FileTracker { + @TLS + private static String name; + + @OnMethod( + clazz = "java.io.FileInputStream", + method = "" + ) + public static void onNewFileInputStream(@Self FileInputStream self, File f) { + name = Strings.str(f); + } + + @OnMethod( + clazz = "java.io.FileInputStream", + method = "", + type = "void (java.io.File)", + location = @Location(Kind.RETURN) + ) + public static void onNewFileInputStreamReturn() { + if (name != null) { + println("opened for read " + name); + name = null; + } + } + + @OnMethod( + clazz = "java.io.FileOutputStream", + method = "" + ) + public static void onNewFileOutputStream(@Self FileOutputStream self, File f, boolean b) { + name = str(f); + } + + @OnMethod( + clazz = "java.io.FileOutputStream", + method = "", + type = "void (java.io.File, boolean)", + location = @Location(Kind.RETURN) + ) + public static void OnNewFileOutputStreamReturn() { + if (name != null) { + println("opened for write " + name); + name = null; + } + } +} diff --git a/btrace-dist/src/main/resources/samples/FileTrackerJfr.java b/btrace-dist/src/main/resources/samples/FileTrackerJfr.java new file mode 100644 index 000000000..4122a4ef0 --- /dev/null +++ b/btrace-dist/src/main/resources/samples/FileTrackerJfr.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.Event; +import org.openjdk.btrace.core.annotations.Kind; +import org.openjdk.btrace.core.annotations.Location; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.Self; +import org.openjdk.btrace.core.annotations.TLS; +import org.openjdk.btrace.core.jfr.JfrEvent; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; + +import static org.openjdk.btrace.core.BTraceUtils.*; +import static org.openjdk.btrace.core.BTraceUtils.Jfr.*; + +/** + * This sample prints all files opened for read/write + * by a Java process. Note that if you pass FileDescriptor + * to File{Input/Output}Stream or File{Reader/Writer}, + * that is not tracked by this script. + */ +@BTrace +public class FileTrackerJfr { + @Event( + name = "fileEvent", + label = "BTrace File Event", + description = "Sample BTrace file tracker", + category = {"btrace", "samples"}, + fields = { + @Event.Field(type = Event.FieldType.STRING, name = "fileName"), + @Event.Field(type = Event.FieldType.STRING, name = "operation") + } + ) + private static JfrEvent.Factory eventFactory; + + @TLS + private static String name; + + @OnMethod( + clazz = "java.io.FileInputStream", + method = "" + ) + public static void onNewFileInputStream(@Self FileInputStream self, File f) { + name = Strings.str(f); + } + + @OnMethod( + clazz = "java.io.FileInputStream", + method = "", + type = "void (java.io.File)", + location = @Location(Kind.RETURN) + ) + public static void onNewFileInputStreamReturn() { + if (name != null) { + JfrEvent event = prepareEvent(eventFactory); + setEventField(event, "fileName", name); + setEventField(event, "operation", "read"); + commit(event); + name = null; + } + } + + @OnMethod( + clazz = "java.io.FileOutputStream", + method = "" + ) + public static void onNewFileOutputStream(@Self FileOutputStream self, File f, boolean b) { + name = str(f); + } + + @OnMethod( + clazz = "java.io.FileOutputStream", + method = "", + type = "void (java.io.File, boolean)", + location = @Location(Kind.RETURN) + ) + public static void OnNewFileOutputStreamReturn() { + if (name != null) { + JfrEvent event = prepareEvent(eventFactory); + setEventField(event, "fileName", name); + setEventField(event, "operation", "write"); + commit(event); + name = null; + } + } +} diff --git a/btrace-dist/src/main/resources/samples/FinalizeTracker.java b/btrace-dist/src/main/resources/samples/FinalizeTracker.java new file mode 100644 index 000000000..1228c9b81 --- /dev/null +++ b/btrace-dist/src/main/resources/samples/FinalizeTracker.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.OnTimer; +import org.openjdk.btrace.core.annotations.Self; + +import java.lang.reflect.Field; + +import static org.openjdk.btrace.core.BTraceUtils.*; + +@BTrace +public class FinalizeTracker { + private static Field fdField = + field("java.io.FileInputStream", "fd"); + + @OnTimer(4000) + public static void ontimer() { + runFinalization(); + } + + @OnMethod( + clazz = "java.io.FileInputStream", + method = "finalize" + ) + public static void onfinalize(@Self Object me) { + println(concat("finalizing ", str(me))); + printFields(me); + printFields(get(fdField, me)); + println("=========="); + } + + @OnMethod( + clazz = "java.io.FileInputStream", + method = "close" + ) + public static void onclose(@Self Object me) { + println(concat("closing ", str(me))); + println(concat("thread: ", str(currentThread()))); + printFields(me); + printFields(get(fdField, me)); + jstack(); + println("============="); + } +} diff --git a/samples/HistoOnEvent.java b/btrace-dist/src/main/resources/samples/HistoOnEvent.java similarity index 80% rename from samples/HistoOnEvent.java rename to btrace-dist/src/main/resources/samples/HistoOnEvent.java index b23c4e7c1..54d9017eb 100644 --- a/samples/HistoOnEvent.java +++ b/btrace-dist/src/main/resources/samples/HistoOnEvent.java @@ -23,25 +23,30 @@ * questions. */ -package com.sun.btrace.samples; -import com.sun.btrace.annotations.*; -import static com.sun.btrace.BTraceUtils.*; +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.OnEvent; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.Self; + import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; +import static org.openjdk.btrace.core.BTraceUtils.*; + /** * This sample collects histogram of javax.swing.JComponets * created by traced app. But, the histogram is printed only * on event (from client). */ -@BTrace public class HistoOnEvent { - private static Map histo = newHashMap(); +@BTrace +public class HistoOnEvent { + private static Map histo = newHashMap(); @OnMethod( - clazz="javax.swing.JComponent", - method="" - ) + clazz = "javax.swing.JComponent", + method = "" + ) public static void onnewObject(@Self Object obj) { String cn = name(classOf(obj)); AtomicInteger ai = get(histo, cn); @@ -50,7 +55,7 @@ public static void onnewObject(@Self Object obj) { put(histo, cn, ai); } else { incrementAndGet(ai); - } + } } @OnEvent diff --git a/samples/Histogram.java b/btrace-dist/src/main/resources/samples/Histogram.java similarity index 80% rename from samples/Histogram.java rename to btrace-dist/src/main/resources/samples/Histogram.java index 879c8c030..cc850bcd4 100644 --- a/samples/Histogram.java +++ b/btrace-dist/src/main/resources/samples/Histogram.java @@ -23,25 +23,30 @@ * questions. */ -package com.sun.btrace.samples; -import com.sun.btrace.annotations.*; -import static com.sun.btrace.BTraceUtils.*; +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.OnTimer; +import org.openjdk.btrace.core.annotations.Self; + import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; +import static org.openjdk.btrace.core.BTraceUtils.*; + /** * This sample collects histogram of javax.swing.JComponets * created by traced app. The histogram is printed once * every 4 seconds. */ -@BTrace public class Histogram { - private static Map histo = Collections.newHashMap(); +@BTrace +public class Histogram { + private static Map histo = Collections.newHashMap(); @OnMethod( - clazz="javax.swing.JComponent", - method="" - ) + clazz = "javax.swing.JComponent", + method = "" + ) public static void onnewObject(@Self Object obj) { String cn = Reflective.name(classOf(obj)); AtomicInteger ai = Collections.get(histo, cn); @@ -50,10 +55,10 @@ public static void onnewObject(@Self Object obj) { Collections.put(histo, cn, ai); } else { Atomic.incrementAndGet(ai); - } + } } - @OnTimer(4000) + @OnTimer(4000) public static void print() { if (Collections.size(histo) != 0) { printNumberMap("Component Histogram", histo); diff --git a/btrace-dist/src/main/resources/samples/HistogramBean.java b/btrace-dist/src/main/resources/samples/HistogramBean.java new file mode 100644 index 000000000..aea65a74d --- /dev/null +++ b/btrace-dist/src/main/resources/samples/HistogramBean.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.OnTimer; +import org.openjdk.btrace.core.annotations.Property; +import org.openjdk.btrace.core.annotations.Self; + +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.openjdk.btrace.core.BTraceUtils.*; + +/** + * This sample collects histogram of javax.swing.JComponets + * created by traced app. The histogram is printed once + * every 4 seconds. Also, this example exposes the trace class + * as a JMX bean. After connecting BTrace to the target + * application, connect VisualVM or jconsole or any other + * JMX client to the same application. + */ +@BTrace +public class HistogramBean { + // @Property exposes this field as MBean attribute + @Property + private static Map histo = newHashMap(); + + @OnMethod( + clazz = "javax.swing.JComponent", + method = "" + ) + public static void onnewObject(@Self Object obj) { + String cn = name(classOf(obj)); + AtomicInteger ai = get(histo, cn); + if (ai == null) { + ai = newAtomicInteger(1); + put(histo, cn, ai); + } else { + incrementAndGet(ai); + } + } + + @OnTimer(4000) + public static void print() { + if (size(histo) != 0) { + printNumberMap("Component Histogram", histo); + } + } +} diff --git a/samples/JInfo.java b/btrace-dist/src/main/resources/samples/JInfo.java similarity index 93% rename from samples/JInfo.java rename to btrace-dist/src/main/resources/samples/JInfo.java index 9ebdcc393..ed87979d3 100644 --- a/samples/JInfo.java +++ b/btrace-dist/src/main/resources/samples/JInfo.java @@ -23,10 +23,10 @@ * questions. */ -package com.sun.btrace.samples; -import com.sun.btrace.annotations.*; -import static com.sun.btrace.BTraceUtils.*; +import org.openjdk.btrace.core.annotations.BTrace; + +import static org.openjdk.btrace.core.BTraceUtils.*; /* * A simple sample that prints system properties, flags and exits. diff --git a/samples/JMap.java b/btrace-dist/src/main/resources/samples/JMap.java similarity index 90% rename from samples/JMap.java rename to btrace-dist/src/main/resources/samples/JMap.java index 5b862d09a..ce7baef39 100644 --- a/samples/JMap.java +++ b/btrace-dist/src/main/resources/samples/JMap.java @@ -23,10 +23,11 @@ * questions. */ -package com.sun.btrace.samples; -import com.sun.btrace.annotations.*; -import static com.sun.btrace.BTraceUtils.*; +import org.openjdk.btrace.core.annotations.BTrace; + +import static org.openjdk.btrace.core.BTraceUtils.Sys; +import static org.openjdk.btrace.core.BTraceUtils.println; /* * A simple sample that dumps heap of the target at start and exits. diff --git a/samples/JStack.java b/btrace-dist/src/main/resources/samples/JStack.java similarity index 85% rename from samples/JStack.java rename to btrace-dist/src/main/resources/samples/JStack.java index 44d8101d6..9681026f4 100644 --- a/samples/JStack.java +++ b/btrace-dist/src/main/resources/samples/JStack.java @@ -23,11 +23,12 @@ * questions. */ -package com.sun.btrace.samples; -import com.sun.btrace.annotations.*; -import static com.sun.btrace.BTraceUtils.Sys.*; -import static com.sun.btrace.BTraceUtils.Threads.*; +import org.openjdk.btrace.core.annotations.BTrace; + +import static org.openjdk.btrace.core.BTraceUtils.Sys.exit; +import static org.openjdk.btrace.core.BTraceUtils.Threads.deadlocks; +import static org.openjdk.btrace.core.BTraceUtils.Threads.jstackAll; /* * A simple sample prints stack traces and exits. This diff --git a/samples/JdbcQueries.java b/btrace-dist/src/main/resources/samples/JdbcQueries.java similarity index 87% rename from samples/JdbcQueries.java rename to btrace-dist/src/main/resources/samples/JdbcQueries.java index e9cb5edf6..834895562 100644 --- a/samples/JdbcQueries.java +++ b/btrace-dist/src/main/resources/samples/JdbcQueries.java @@ -23,18 +23,25 @@ * questions. */ -package com.sun.btrace.samples; -import static com.sun.btrace.BTraceUtils.*; +import org.openjdk.btrace.core.types.AnyType; +import org.openjdk.btrace.core.aggregation.Aggregation; +import org.openjdk.btrace.core.aggregation.AggregationFunction; +import org.openjdk.btrace.core.aggregation.AggregationKey; +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.Duration; +import org.openjdk.btrace.core.annotations.Kind; +import org.openjdk.btrace.core.annotations.Location; +import org.openjdk.btrace.core.annotations.OnEvent; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.Return; +import org.openjdk.btrace.core.annotations.Self; +import org.openjdk.btrace.core.annotations.TLS; import java.sql.Statement; import java.util.Map; -import com.sun.btrace.AnyType; -import com.sun.btrace.aggregation.Aggregation; -import com.sun.btrace.aggregation.AggregationFunction; -import com.sun.btrace.aggregation.AggregationKey; -import com.sun.btrace.annotations.*; +import static org.openjdk.btrace.core.BTraceUtils.*; /** * BTrace script to print timings for all executed JDBC statements on an event. Demonstrates @@ -70,7 +77,7 @@ public class JdbcQueries { /** * If "--stack" is passed on command line, print the Java stack trace of the JDBC statement. - * + *

* Otherwise we print the SQL. */ private static boolean useStackTrace = Sys.$(2) != null && Strings.strcmp("--stack", Sys.$(2)) == 0; @@ -81,8 +88,7 @@ public class JdbcQueries { /** * Capture SQL used to create prepared statements. * - * @param args - * the list of method parameters. args[1] is the SQL. + * @param args the list of method parameters. args[1] is the SQL. */ @OnMethod(clazz = "+java.sql.Connection", method = "/prepare(Call|Statement)/") public static void onPrepare(AnyType[] args) { @@ -92,8 +98,7 @@ public static void onPrepare(AnyType[] args) { /** * Cache SQL associated with a prepared statement. * - * @param arg - * the return value from the prepareXxx() method. + * @param arg the return value from the prepareXxx() method. */ @OnMethod(clazz = "+java.sql.Connection", method = "/prepare(Call|Statement)/", location = @Location(Kind.RETURN)) public static void onPrepareReturn(@Return Statement preparedStatement) { diff --git a/samples/LogTracer.java b/btrace-dist/src/main/resources/samples/LogTracer.java similarity index 78% rename from samples/LogTracer.java rename to btrace-dist/src/main/resources/samples/LogTracer.java index 61da6a120..50e914c66 100644 --- a/samples/LogTracer.java +++ b/btrace-dist/src/main/resources/samples/LogTracer.java @@ -23,12 +23,17 @@ * questions. */ -package com.sun.btrace.samples; -import com.sun.btrace.annotations.*; -import static com.sun.btrace.BTraceUtils.*; -import java.util.logging.*; +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.Self; + import java.lang.reflect.Field; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +import static org.openjdk.btrace.core.BTraceUtils.Reflective; +import static org.openjdk.btrace.core.BTraceUtils.println; /** * Simple log message tracer class. This class @@ -36,12 +41,13 @@ * Note that we read LogRecord's private "message" * field using "field()" and "objectValue()" built-ins. */ -@BTrace public class LogTracer { +@BTrace +public class LogTracer { private static Field msgField = Reflective.field("java.util.logging.LogRecord", "message"); @OnMethod( - clazz="+java.util.logging.Logger", - method="log" + clazz = "+java.util.logging.Logger", + method = "log" ) public static void onLog(@Self Logger self, LogRecord record) { println(Reflective.get(msgField, record)); diff --git a/samples/MemAlerter.java b/btrace-dist/src/main/resources/samples/MemAlerter.java similarity index 80% rename from samples/MemAlerter.java rename to btrace-dist/src/main/resources/samples/MemAlerter.java index 5c589b676..4d6f6487a 100644 --- a/samples/MemAlerter.java +++ b/btrace-dist/src/main/resources/samples/MemAlerter.java @@ -23,28 +23,30 @@ * questions. */ -package com.sun.btrace.samples; -import com.sun.btrace.annotations.*; -import static com.sun.btrace.BTraceUtils.*; +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.OnLowMemory; + import java.lang.management.MemoryUsage; +import static org.openjdk.btrace.core.BTraceUtils.println; + /** * This sample traces memory threshold exceeds. * You need to specify the memory pool to watch - * out and the usage threshold. You can write + * out and the usage threshold. You can write * script that dumps heap by dumpHeap on crossing * the threshold instead of just printing a message. * Note that the name of the old gen is dependent on * GC algorithm. With ParallelGC, the name is "PS Old Gen". */ -@BTrace +@BTrace public class MemAlerter { - @OnLowMemory( - pool = "Tenured Gen", - threshold=6000000 - ) - public static void onLowMem(MemoryUsage mu) { - println(mu); - } + @OnLowMemory( + pool = "Tenured Gen", + threshold = 6000000 + ) + public static void onLowMem(MemoryUsage mu) { + println(mu); + } } diff --git a/samples/Memory.java b/btrace-dist/src/main/resources/samples/Memory.java similarity index 84% rename from samples/Memory.java rename to btrace-dist/src/main/resources/samples/Memory.java index f11eb66fd..79ad6642b 100644 --- a/samples/Memory.java +++ b/btrace-dist/src/main/resources/samples/Memory.java @@ -23,19 +23,22 @@ * questions. */ -package com.sun.btrace.samples; -import com.sun.btrace.annotations.*; -import static com.sun.btrace.BTraceUtils.*; +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.OnTimer; + +import static org.openjdk.btrace.core.BTraceUtils.Sys; +import static org.openjdk.btrace.core.BTraceUtils.println; /** * Simple BTrace program that prints memory * usage once every 4 seconds. It is possible - * to modify this to dump heap depending on + * to modify this to dump heap depending on * used memory crossing a threshold or some other * such condition. [dumpHeap is a built-in function]. */ -@BTrace public class Memory { +@BTrace +public class Memory { @OnTimer(4000) public static void printMem() { println("Heap:"); diff --git a/samples/MultiClass.java b/btrace-dist/src/main/resources/samples/MultiClass.java similarity index 82% rename from samples/MultiClass.java rename to btrace-dist/src/main/resources/samples/MultiClass.java index d6b51c052..692fac9e8 100644 --- a/samples/MultiClass.java +++ b/btrace-dist/src/main/resources/samples/MultiClass.java @@ -23,10 +23,12 @@ * questions. */ -package com.sun.btrace.samples; -import com.sun.btrace.annotations.*; -import static com.sun.btrace.BTraceUtils.*; +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.ProbeClassName; + +import static org.openjdk.btrace.core.BTraceUtils.println; /** * This BTrace class demonstrates that we can @@ -36,10 +38,11 @@ * given below. In the example, we put probe into * all readXXX methods of all InputStream classes. */ -@BTrace public class MultiClass { +@BTrace +public class MultiClass { @OnMethod( - clazz="/java\\.io\\..*Input.*/", - method="/read.*/" + clazz = "/java\\.io\\..*Input.*/", + method = "/read.*/" ) public static void onread(@ProbeClassName String pcn) { println("read on " + pcn); diff --git a/btrace-dist/src/main/resources/samples/NewArray.java b/btrace-dist/src/main/resources/samples/NewArray.java new file mode 100644 index 000000000..f5fbede8a --- /dev/null +++ b/btrace-dist/src/main/resources/samples/NewArray.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.Kind; +import org.openjdk.btrace.core.annotations.Location; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.OnTimer; +import org.openjdk.btrace.core.annotations.ProbeClassName; +import org.openjdk.btrace.core.annotations.ProbeMethodName; + +import static org.openjdk.btrace.core.BTraceUtils.println; + +/** + * This script demonstrates the possibility to intercept + * array creations that are about to be executed from the body of + * a certain method. This is achieved by using the {@linkplain Kind#NEWARRAY} + * location value. + */ +@BTrace +public class NewArray { + // component count + private static volatile long count; + + @OnMethod( + clazz = "/.*/", // tracking in all classes; can be restricted to specific user classes + method = "/.*/", // tracking in all methods; can be restricted to specific user methods + location = @Location(value = Kind.NEWARRAY, clazz = "char") + ) + public static void onnew(@ProbeClassName String pcn, @ProbeMethodName String pmn, String arrType, int dim) { + // pcn - allocation place class name + // pmn - allocation place method name + // **** following two parameters MUST always be in this order + // arrType - the actual array type + // dim - the array dimension + + // increment counter on new array + count++; + } + + @OnTimer(2000) + public static void print() { + // print the counter + println("char[] count = " + count); + } +} diff --git a/samples/NewComponent.java b/btrace-dist/src/main/resources/samples/NewComponent.java similarity index 81% rename from samples/NewComponent.java rename to btrace-dist/src/main/resources/samples/NewComponent.java index f05096d70..f1d5bf661 100644 --- a/samples/NewComponent.java +++ b/btrace-dist/src/main/resources/samples/NewComponent.java @@ -23,11 +23,15 @@ * questions. */ -package com.sun.btrace.samples; -import com.sun.btrace.annotations.*; -import static com.sun.btrace.BTraceUtils.*; -import java.awt.Component; +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.OnTimer; +import org.openjdk.btrace.core.annotations.Self; + +import java.awt.*; + +import static org.openjdk.btrace.core.BTraceUtils.println; /** * A BTrace program that can be run against a GUI @@ -36,13 +40,14 @@ * 2 seconds (2000 milliseconds). */ -@BTrace public class NewComponent { +@BTrace +public class NewComponent { // component count private static volatile long count; @OnMethod( - clazz="java.awt.Component", - method="" + clazz = "java.awt.Component", + method = "" ) public static void onnew(@Self Component c) { // increment counter on constructor entry diff --git a/samples/OnThrow.java b/btrace-dist/src/main/resources/samples/OnThrow.java similarity index 75% rename from samples/OnThrow.java rename to btrace-dist/src/main/resources/samples/OnThrow.java index c4ff41ae0..bd5890cb8 100644 --- a/samples/OnThrow.java +++ b/btrace-dist/src/main/resources/samples/OnThrow.java @@ -23,10 +23,16 @@ * questions. */ -package com.sun.btrace.samples; -import com.sun.btrace.annotations.*; -import static com.sun.btrace.BTraceUtils.*; +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.Kind; +import org.openjdk.btrace.core.annotations.Location; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.Self; +import org.openjdk.btrace.core.annotations.TLS; + +import static org.openjdk.btrace.core.BTraceUtils.Threads; +import static org.openjdk.btrace.core.BTraceUtils.println; /** * This example demonstrates printing stack trace @@ -39,41 +45,43 @@ * creation [like in "throw new FooException();"] rather * that be stored and thrown later. */ -@BTrace public class OnThrow { +@BTrace +public class OnThrow { // store current exception in a thread local // variable (@TLS annotation). Note that we can't // store it in a global variable! - @TLS static Throwable currentException; + @TLS + static Throwable currentException; // introduce probe into every constructor of java.lang.Throwable // class and store "this" in the thread local variable. @OnMethod( - clazz="java.lang.Throwable", - method="" + clazz = "java.lang.Throwable", + method = "" ) public static void onthrow(@Self Throwable self) { currentException = self; } @OnMethod( - clazz="java.lang.Throwable", - method="" + clazz = "java.lang.Throwable", + method = "" ) public static void onthrow1(@Self Throwable self, String s) { currentException = self; } @OnMethod( - clazz="java.lang.Throwable", - method="" + clazz = "java.lang.Throwable", + method = "" ) public static void onthrow1(@Self Throwable self, String s, Throwable cause) { currentException = self; } @OnMethod( - clazz="java.lang.Throwable", - method="" + clazz = "java.lang.Throwable", + method = "" ) public static void onthrow2(@Self Throwable self, Throwable cause) { currentException = self; @@ -82,9 +90,9 @@ public static void onthrow2(@Self Throwable self, Throwable cause) { // when any constructor of java.lang.Throwable returns // print the currentException's stack trace. @OnMethod( - clazz="java.lang.Throwable", - method="", - location=@Location(Kind.RETURN) + clazz = "java.lang.Throwable", + method = "", + location = @Location(Kind.RETURN) ) public static void onthrowreturn() { if (currentException != null) { diff --git a/samples/ProbeArgs.java b/btrace-dist/src/main/resources/samples/ProbeArgs.java similarity index 85% rename from samples/ProbeArgs.java rename to btrace-dist/src/main/resources/samples/ProbeArgs.java index b54ff7536..9909df9d0 100644 --- a/samples/ProbeArgs.java +++ b/btrace-dist/src/main/resources/samples/ProbeArgs.java @@ -20,12 +20,14 @@ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ -package com.sun.btrace.samples; -import com.sun.btrace.annotations.*; -import static com.sun.btrace.BTraceUtils.*; +import org.openjdk.btrace.core.annotations.BTrace; -@BTrace(trusted = true) public class ProbeArgs { +import static org.openjdk.btrace.core.BTraceUtils.Sys; +import static org.openjdk.btrace.core.BTraceUtils.println; + +@BTrace(trusted = true) +public class ProbeArgs { static { println("arg#=" + Sys.$length()); for (int i = 0; i < Sys.$length(); i++) { diff --git a/samples/ProbeExit.java b/btrace-dist/src/main/resources/samples/ProbeExit.java similarity index 87% rename from samples/ProbeExit.java rename to btrace-dist/src/main/resources/samples/ProbeExit.java index 1bb34777f..72e52aabb 100644 --- a/samples/ProbeExit.java +++ b/btrace-dist/src/main/resources/samples/ProbeExit.java @@ -23,10 +23,13 @@ * questions. */ -package com.sun.btrace.samples; -import com.sun.btrace.annotations.*; -import static com.sun.btrace.BTraceUtils.*; +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.OnExit; +import org.openjdk.btrace.core.annotations.OnTimer; + +import static org.openjdk.btrace.core.BTraceUtils.Sys; +import static org.openjdk.btrace.core.BTraceUtils.println; /** * This program demonstrates OnExit probe. @@ -36,7 +39,8 @@ * print summary information of tracing and/or do clean-up. */ -@BTrace public class ProbeExit { +@BTrace +public class ProbeExit { private static volatile int i; // @OnExit is called when some BTrace method diff --git a/btrace-dist/src/main/resources/samples/Profiling.java b/btrace-dist/src/main/resources/samples/Profiling.java new file mode 100644 index 000000000..066cac2cf --- /dev/null +++ b/btrace-dist/src/main/resources/samples/Profiling.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +import org.openjdk.btrace.core.Profiler; +import org.openjdk.btrace.core.BTraceUtils; +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.Duration; +import org.openjdk.btrace.core.annotations.Kind; +import org.openjdk.btrace.core.annotations.Location; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.OnTimer; +import org.openjdk.btrace.core.annotations.ProbeMethodName; +import org.openjdk.btrace.core.annotations.Property; + +/** + * This script demonstrates new capabilities built into BTrace 1.2 + *

    + *
  1. Shortened syntax - when omitting "public" identifier in the class + * definition one can safely omit all other modifiers when declaring methods + * and variables
  2. + *
  3. Extended syntax for @ProbeMethodName annotation - you can use + * fqn parameter to request a fully qualified method name instead of + * the short one
  4. + *
  5. Profiling support - you can use {@linkplain Profiler} instance to gather + * performance data with the smallest overhead possible + *
+ * + * @since 1.2 + */ +@BTrace +class Profiling { + @Property + Profiler swingProfiler = BTraceUtils.Profiling.newProfiler(); + + @OnMethod(clazz = "/javax\\.swing\\..*/", method = "/.*/") + void entry(@ProbeMethodName(fqn = true) String probeMethod) { + BTraceUtils.Profiling.recordEntry(swingProfiler, probeMethod); + } + + @OnMethod(clazz = "/javax\\.swing\\..*/", method = "/.*/", location = @Location(value = Kind.RETURN)) + void exit(@ProbeMethodName(fqn = true) String probeMethod, @Duration long duration) { + BTraceUtils.Profiling.recordExit(swingProfiler, probeMethod, duration); + } + + @OnTimer(5000) + void timer() { + BTraceUtils.Profiling.printSnapshot("Swing performance profile", swingProfiler); + } +} diff --git a/samples/Sizeof.java b/btrace-dist/src/main/resources/samples/Sizeof.java similarity index 81% rename from samples/Sizeof.java rename to btrace-dist/src/main/resources/samples/Sizeof.java index dad60a9d0..a9965546b 100644 --- a/samples/Sizeof.java +++ b/btrace-dist/src/main/resources/samples/Sizeof.java @@ -23,16 +23,19 @@ * questions. */ -package com.sun.btrace.samples; -import com.sun.btrace.annotations.*; -import static com.sun.btrace.BTraceUtils.*; +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.Self; -@BTrace public class Sizeof { +import static org.openjdk.btrace.core.BTraceUtils.*; + +@BTrace +public class Sizeof { @OnMethod( - clazz="javax.swing.JComponent", - method="" - ) + clazz = "javax.swing.JComponent", + method = "" + ) public static void onnew(@Self Object obj) { println(Strings.concat("object of: ", Reflective.name(Reflective.classOf(obj)))); println(Strings.concat("size: ", Strings.str(sizeof(obj)))); diff --git a/btrace-dist/src/main/resources/samples/SocketTracker.java b/btrace-dist/src/main/resources/samples/SocketTracker.java new file mode 100644 index 000000000..561c76a79 --- /dev/null +++ b/btrace-dist/src/main/resources/samples/SocketTracker.java @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.Kind; +import org.openjdk.btrace.core.annotations.Location; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.Return; +import org.openjdk.btrace.core.annotations.Self; +import org.openjdk.btrace.core.annotations.TLS; + +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketAddress; +import java.nio.channels.SocketChannel; + +import static org.openjdk.btrace.core.BTraceUtils.Strings; +import static org.openjdk.btrace.core.BTraceUtils.println; + +/** + * This example tracks all server socket creations + * and client socket accepts. + *
+ * Also, it shows how to use shared methods. + */ +@BTrace +public class SocketTracker { + @TLS + private static int port = -1; + @TLS + private static InetAddress inetAddr; + @TLS + private static SocketAddress sockAddr; + + @OnMethod( + clazz = "java.net.ServerSocket", + method = "" + ) + public static void onServerSocket(@Self ServerSocket self, + int p, int backlog, InetAddress bindAddr) { + port = p; + inetAddr = bindAddr; + } + + @OnMethod( + clazz = "java.net.ServerSocket", + method = "", + type = "void (int, int, java.net.InetAddress)", + location = @Location(Kind.RETURN) + ) + public static void onSockReturn() { + if (port != -1) { + println("server socket at " + port); + port = -1; + } + if (inetAddr != null) { + println("server socket at " + inetAddr); + inetAddr = null; + } + } + + @OnMethod( + clazz = "java.net.ServerSocket", + method = "bind" + ) + public static void onBind(@Self ServerSocket self, SocketAddress addr, int backlog) { + sockAddr = addr; + } + + @OnMethod( + clazz = "java.net.ServerSocket", + method = "bind", + type = "void (java.net.SocketAddress, int)", + location = @Location(Kind.RETURN) + ) + public static void onBindReturn() { + socketBound(); + } + + @OnMethod( + clazz = "sun.nio.ch.ServerSocketChannelImpl", + method = "bind" + ) + public static void onBind(@Self Object self, SocketAddress addr, int backlog) { + sockAddr = addr; + } + + @OnMethod( + clazz = "sun.nio.ch.ServerSocketChannelImpl", + method = "bind", + type = "void (java.net.SocketAddress, int)", + location = @Location(Kind.RETURN) + ) + public static void onBindReturn2() { + socketBound(); + } + + @OnMethod( + clazz = "java.net.ServerSocket", + method = "accept", + location = @Location(Kind.RETURN) + ) + public static void onAcceptReturn(@Return Socket sock) { + clientSocketAcc(sock); + } + + @OnMethod( + clazz = "sun.nio.ch.ServerSocketChannelImpl", + method = "socket", + location = @Location(Kind.RETURN) + ) + public static void onSocket(@Return ServerSocket ssock) { + println(Strings.strcat("server socket at ", Strings.str(ssock))); + } + + @OnMethod( + clazz = "sun.nio.ch.ServerSocketChannelImpl", + method = "accept", + location = @Location(Kind.RETURN) + ) + public static void onAcceptReturn(@Return SocketChannel sockChan) { + clientSocketAcc(sockChan); + } + + private static void socketBound() { + if (sockAddr != null) { + println("server socket bind " + sockAddr); + sockAddr = null; + } + } + + private static void clientSocketAcc(Object obj) { + if (obj != null) { + println("client socket accept " + obj); + } + } +} diff --git a/btrace-dist/src/main/resources/samples/SocketTracker1.java b/btrace-dist/src/main/resources/samples/SocketTracker1.java new file mode 100644 index 000000000..b2e7bbfc5 --- /dev/null +++ b/btrace-dist/src/main/resources/samples/SocketTracker1.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +import org.openjdk.btrace.core.types.AnyType; +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.Kind; +import org.openjdk.btrace.core.annotations.Location; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.OnProbe; +import org.openjdk.btrace.core.annotations.Return; +import org.openjdk.btrace.core.annotations.Self; +import org.openjdk.btrace.core.annotations.TLS; + +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.SocketAddress; + +import static org.openjdk.btrace.core.BTraceUtils.println; + +/** + * This example tracks all server socket creations + * and client socket accepts. Unlike SockerTracker.java, + * this script uses only public API classes and @OnProbe + * probes - which would be mapped to internal implementation + * classes by a XML descriptor at BTrace agent. For this + * sample, XML probe descriptor is "java.net.socket.xml". + */ +@BTrace +public class SocketTracker1 { + @TLS + private static int port = -1; + @TLS + private static InetAddress inetAddr; + @TLS + private static SocketAddress sockAddr; + + @OnMethod( + clazz = "java.net.ServerSocket", + method = "" + ) + public static void onServerSocket(@Self ServerSocket self, + int p, int backlog, InetAddress bindAddr) { + port = p; + inetAddr = bindAddr; + } + + @OnMethod( + clazz = "java.net.ServerSocket", + method = "", + type = "void (int, int, java.net.InetAddress)", + location = @Location(Kind.RETURN) + ) + public static void onSockReturn() { + if (port != -1) { + println("server socket at " + port); + port = -1; + } + if (inetAddr != null) { + println("server socket at " + inetAddr); + inetAddr = null; + } + } + + @OnProbe( + namespace = "java.net.socket", + name = "server-socket-creator" + ) + public static void onSocket(@Return ServerSocket ssock) { + println("server socket at " + ssock); + } + + @OnProbe( + namespace = "java.net.socket", + name = "bind" + ) + public static void onBind(@Self Object self, SocketAddress addr, int backlog) { + sockAddr = addr; + } + + @OnProbe( + namespace = "java.net.socket", + name = "bind-return" + ) + public static void onBindReturn() { + if (sockAddr != null) { + println("server socket bind " + sockAddr); + sockAddr = null; + } + } + + @OnProbe( + namespace = "java.net.socket", + name = "accept-return" + ) + public static void onAcceptReturn(AnyType sock) { + if (sock != null) { + println("client socket accept " + sock); + } + } +} diff --git a/samples/SubtypeTracer.java b/btrace-dist/src/main/resources/samples/SubtypeTracer.java similarity index 79% rename from samples/SubtypeTracer.java rename to btrace-dist/src/main/resources/samples/SubtypeTracer.java index 1d97c042a..6209374d8 100644 --- a/samples/SubtypeTracer.java +++ b/btrace-dist/src/main/resources/samples/SubtypeTracer.java @@ -23,10 +23,14 @@ * questions. */ -package com.sun.btrace.samples; -import com.sun.btrace.annotations.*; -import static com.sun.btrace.BTraceUtils.*; +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.ProbeClassName; +import org.openjdk.btrace.core.annotations.ProbeMethodName; + +import static org.openjdk.btrace.core.BTraceUtils.print; +import static org.openjdk.btrace.core.BTraceUtils.println; /** * A simple example that demonstrates subtype matching by +foo pattern @@ -35,8 +39,8 @@ @BTrace public class SubtypeTracer { @OnMethod( - clazz="+java.lang.Runnable", - method="run" + clazz = "+java.lang.Runnable", + method = "run" ) public static void onRun(@ProbeClassName String pcn, @ProbeMethodName String pmn) { // on every Runnable.run() method entry print class.method diff --git a/samples/SysProp.java b/btrace-dist/src/main/resources/samples/SysProp.java similarity index 76% rename from samples/SysProp.java rename to btrace-dist/src/main/resources/samples/SysProp.java index 31c4325da..78b47c4c4 100644 --- a/samples/SysProp.java +++ b/btrace-dist/src/main/resources/samples/SysProp.java @@ -23,28 +23,31 @@ * questions. */ -package com.sun.btrace.samples; -import com.sun.btrace.annotations.*; -import static com.sun.btrace.BTraceUtils.*; +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.OnMethod; + +import static org.openjdk.btrace.core.BTraceUtils.Sys; +import static org.openjdk.btrace.core.BTraceUtils.println; /** * This BTrace script demonstrates that it is okay - * to trace bootstrap classes and call the same + * to trace bootstrap classes and call the same * inside the trace actions. In this example, we insert - * a probe into System.getProperty() method and call + * a probe into System.getProperty() method and call * System.getProperty [through property() built-in function] - * without getting into infinite recursion. A thread local + * without getting into infinite recursion. A thread local * flag is used by BTrace to avoid infinite recursion here. */ -@BTrace public class SysProp { +@BTrace +public class SysProp { @OnMethod( - clazz="java.lang.System", - method="getProperty" + clazz = "java.lang.System", + method = "getProperty" ) public static void onGetProperty(String name) { println(name); // call property safely here. println(Sys.Env.property(name)); - } + } } diff --git a/samples/Test.java b/btrace-dist/src/main/resources/samples/Test.java similarity index 91% rename from samples/Test.java rename to btrace-dist/src/main/resources/samples/Test.java index 8fec91e57..56e6b13e4 100644 --- a/samples/Test.java +++ b/btrace-dist/src/main/resources/samples/Test.java @@ -23,10 +23,9 @@ * questions. */ -package com.sun.btrace.samples; -import com.sun.btrace.annotations.*; -import static com.sun.btrace.BTraceUtils.*; +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.DTraceRef; @DTraceRef("syscalls.d") @BTrace diff --git a/samples/ThreadBean.java b/btrace-dist/src/main/resources/samples/ThreadBean.java similarity index 75% rename from samples/ThreadBean.java rename to btrace-dist/src/main/resources/samples/ThreadBean.java index 72b88f55f..169fd8ffd 100644 --- a/samples/ThreadBean.java +++ b/btrace-dist/src/main/resources/samples/ThreadBean.java @@ -23,12 +23,12 @@ * questions. */ -package com.sun.btrace.samples; + /* - * This sample demonstrates simple preprocessor in BTrace. + * This sample demonstrates simple preprocessor in BTrace. * When you run this sample against a Java process, you have - * to specify -I . option so that the preprocessor can find + * to specify -I . option so that the preprocessor can find * the "btracedefs.h" file: * * btrace -I . ThreadBean.java @@ -36,32 +36,32 @@ * Without -I option in command, BTrace skips preprocessor * invocation. */ -#include "btracedefs.h" +#include"btracedefs.h" -BTRACE_IMPORT + BTRACE_IMPORT /** * This sample demonstrates that you can expose a BTrace * class as a JMX MBean. After connecting BTrace to the * target application, connect VisualVM or jconsole or * any other JMX client to the same application. - */ -BTRACE ThreadBean { + */ + BTRACE ThreadBean{ - // PROPERTY makes the count field to be exposed - // as an attribute of this MBean. - PROPERTY long count; + // PROPERTY makes the count field to be exposed + // as an attribute of this MBean. + PROPERTY long count; - @OnMethod( - clazz="java.lang.Thread", - method="start" - ) - ACTION onnewThread(@Self Thread t) { +@OnMethod( + clazz = "java.lang.Thread", + method = "start" +) + ACTION onnewThread(@Self Thread t){ count++; - } + } - @OnTimer(2000) - ACTION ontimer() { - println(count); - } -} +@OnTimer(2000) + ACTION ontimer(){ + println(count); + } + } diff --git a/btrace-dist/src/main/resources/samples/ThreadCounter.java b/btrace-dist/src/main/resources/samples/ThreadCounter.java new file mode 100644 index 000000000..297dd952c --- /dev/null +++ b/btrace-dist/src/main/resources/samples/ThreadCounter.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.Export; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.OnTimer; +import org.openjdk.btrace.core.annotations.Self; + +import static org.openjdk.btrace.core.BTraceUtils.Counters; +import static org.openjdk.btrace.core.BTraceUtils.println; + +/** + * This sample creates a jvmstat counter and + * increments it everytime Thread.start() is + * called. This thread count may be accessed + * from outside the process. The @Export annotated + * fields are mapped to jvmstat counters. The counter + * name is "btrace." + + "." + + */ +@BTrace +public class ThreadCounter { + + // create a jvmstat counter using @Export + @Export + private static long count; + + @OnMethod( + clazz = "java.lang.Thread", + method = "start" + ) + public static void onnewThread(@Self Thread t) { + // updating counter is easy. Just assign to + // the static field! + count++; + } + + @OnTimer(2000) + public static void ontimer() { + // we can access counter as "count" as well + // as from jvmstat counter directly. + println(count); + // or equivalently ... + println(Counters.perfLong("btrace.org.openjdk.btrace.samples.ThreadCounter.count")); + } +} diff --git a/samples/ThreadCounterBean.java b/btrace-dist/src/main/resources/samples/ThreadCounterBean.java similarity index 76% rename from samples/ThreadCounterBean.java rename to btrace-dist/src/main/resources/samples/ThreadCounterBean.java index 4f42dbdb1..8958b0f73 100644 --- a/samples/ThreadCounterBean.java +++ b/btrace-dist/src/main/resources/samples/ThreadCounterBean.java @@ -23,18 +23,23 @@ * questions. */ -package com.sun.btrace.samples; -import com.sun.btrace.annotations.*; -import static com.sun.btrace.BTraceUtils.*; +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.OnTimer; +import org.openjdk.btrace.core.annotations.Property; +import org.openjdk.btrace.core.annotations.Self; + +import static org.openjdk.btrace.core.BTraceUtils.println; /** * This sample demonstrates that you can expose a BTrace * class as a JMX MBean. After connecting BTrace to the - * target application, connect VisualVM or jconsole or + * target application, connect VisualVM or jconsole or * any other JMX client to the same application. - */ -@BTrace public class ThreadCounterBean { + */ +@BTrace +public class ThreadCounterBean { // @Property makes the count field to be exposed // as an attribute of this MBean. @@ -42,14 +47,14 @@ private static long count; @OnMethod( - clazz="java.lang.Thread", - method="start" - ) + clazz = "java.lang.Thread", + method = "start" + ) public static void onnewThread(@Self Thread t) { count++; } - @OnTimer(2000) + @OnTimer(2000) public static void ontimer() { println(count); } diff --git a/samples/ThreadStart.java b/btrace-dist/src/main/resources/samples/ThreadStart.java similarity index 84% rename from samples/ThreadStart.java rename to btrace-dist/src/main/resources/samples/ThreadStart.java index cb1a2c825..d2baab8ad 100644 --- a/samples/ThreadStart.java +++ b/btrace-dist/src/main/resources/samples/ThreadStart.java @@ -23,10 +23,12 @@ * questions. */ -package com.sun.btrace.samples; -import com.sun.btrace.annotations.*; -import static com.sun.btrace.BTraceUtils.*; +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.Self; + +import static org.openjdk.btrace.core.BTraceUtils.*; /* * This BTrace script inserts a probe into @@ -36,10 +38,11 @@ * A D-script like jthread.d may be used to get the * associated DTrace probe events. */ -@BTrace public class ThreadStart { +@BTrace +public class ThreadStart { @OnMethod( - clazz="java.lang.Thread", - method="start" + clazz = "java.lang.Thread", + method = "start" ) public static void onnewThread(@Self Thread t) { D.probe("jthreadstart", Threads.name(t)); diff --git a/samples/Timers.java b/btrace-dist/src/main/resources/samples/Timers.java similarity index 91% rename from samples/Timers.java rename to btrace-dist/src/main/resources/samples/Timers.java index ac1a103a0..3fe5d1ff7 100644 --- a/samples/Timers.java +++ b/btrace-dist/src/main/resources/samples/Timers.java @@ -22,10 +22,12 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.sun.btrace.samples; -import com.sun.btrace.annotations.*; -import static com.sun.btrace.BTraceUtils.*; + +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.OnTimer; + +import static org.openjdk.btrace.core.BTraceUtils.*; /** * Demonstrates multiple timer probes with different periods to fire. diff --git a/samples/URLTracker.java b/btrace-dist/src/main/resources/samples/URLTracker.java similarity index 75% rename from samples/URLTracker.java rename to btrace-dist/src/main/resources/samples/URLTracker.java index fd6fe0ad7..3a68cff42 100644 --- a/samples/URLTracker.java +++ b/btrace-dist/src/main/resources/samples/URLTracker.java @@ -23,12 +23,18 @@ * questions. */ -package com.sun.btrace.samples; -import com.sun.btrace.annotations.*; -import static com.sun.btrace.BTraceUtils.*; -import static com.sun.btrace.BTraceUtils.Strings.*; -import java.net.*; +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.DTraceRef; +import org.openjdk.btrace.core.annotations.Kind; +import org.openjdk.btrace.core.annotations.Location; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.TLS; + +import java.net.Proxy; +import java.net.URL; + +import static org.openjdk.btrace.core.BTraceUtils.*; /* * This sample prints every Java URL openURL and @@ -44,29 +50,31 @@ * that are not explicitly printed by DTrace printa call). */ @DTraceRef("jurls.d") -@BTrace public class URLTracker { - @TLS private static URL url; +@BTrace +public class URLTracker { + @TLS + private static URL url; @OnMethod( - clazz="java.net.URL", - method="openConnection" + clazz = "java.net.URL", + method = "openConnection" ) public static void openURL(URL self) { url = self; } @OnMethod( - clazz="java.net.URL", - method="openConnection" + clazz = "java.net.URL", + method = "openConnection" ) public static void openURL(URL self, Proxy p) { url = self; } @OnMethod( - clazz="java.net.URL", - method="openConnection", - location=@Location(Kind.RETURN) + clazz = "java.net.URL", + method = "openConnection", + location = @Location(Kind.RETURN) ) public static void openURL() { if (url != null) { diff --git a/btrace-dist/src/main/resources/samples/WebServiceTracker.java b/btrace-dist/src/main/resources/samples/WebServiceTracker.java new file mode 100644 index 000000000..aafd93110 --- /dev/null +++ b/btrace-dist/src/main/resources/samples/WebServiceTracker.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.Duration; +import org.openjdk.btrace.core.annotations.Kind; +import org.openjdk.btrace.core.annotations.Location; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.ProbeClassName; +import org.openjdk.btrace.core.annotations.ProbeMethodName; + +import static org.openjdk.btrace.core.BTraceUtils.*; + +/** + * A simple BTrace program that prints a class name + * and method name whenever a webservice is called and + * also prints time taken by service method. WebService + * entry points are annotated javax.jws.WebService and + * javax.jws.WebMethod. We insert tracing actions into + * every class and method annotated by these annotations. + * This way we don't need to know actual webservice + * implementor class name. + */ +@BTrace +public class WebServiceTracker { + @OnMethod( + clazz = "@javax.jws.WebService", + method = "@javax.jws.WebMethod" + ) + public static void onWebserviceEntry(@ProbeClassName String pcn, @ProbeMethodName String pmn) { + print("entering webservice "); + println(Strings.strcat(Strings.strcat(pcn, "."), pmn)); + } + + @OnMethod( + clazz = "@javax.jws.WebService", + method = "@javax.jws.WebMethod", + location = @Location(Kind.RETURN) + ) + public static void onWebserviceReturn(@ProbeClassName String pcn, @ProbeMethodName String pmn, @Duration long d) { + print("leaving web service "); + println(Strings.strcat(Strings.strcat(pcn, "."), pmn)); + println(Strings.strcat("Time taken (msec) ", Strings.str(d / 1000))); + println("=========================="); + } + +} diff --git a/samples/btracedefs.h b/btrace-dist/src/main/resources/samples/btracedefs.h similarity index 91% rename from samples/btracedefs.h rename to btrace-dist/src/main/resources/samples/btracedefs.h index 19449cfc0..fc8203f60 100644 --- a/samples/btracedefs.h +++ b/btrace-dist/src/main/resources/samples/btracedefs.h @@ -24,9 +24,9 @@ */ -#define BTRACE_UTILS import static com.sun.btrace.BTraceUtils.*; +#define BTRACE_UTILS import static org.openjdk.btrace.core.BTraceUtils.*; -#define BTRACE_ANNO import com.sun.btrace.annotations.*; +#define BTRACE_ANNO import org.openjdk.btrace.core.annotations.*; #define BTRACE_IMPORT BTRACE_UTILS \ BTRACE_ANNO diff --git a/samples/classload.d b/btrace-dist/src/main/resources/samples/classload.d similarity index 95% rename from samples/classload.d rename to btrace-dist/src/main/resources/samples/classload.d index 6c19829e9..60858bce4 100644 --- a/samples/classload.d +++ b/btrace-dist/src/main/resources/samples/classload.d @@ -1,17 +1,17 @@ -/* - * This D-script prints one line on each Java class - * load and unload. To mark DTrace message begin, this - * script prints a message on DTrace session start. - */ - -BEGIN { - printf("dtrace start\n"); -} - -hotspot$1:::class-loaded { - printf("loaded %s\n", copyinstr(arg0, arg1)); -} - -hotspot$1:::class-unloaded { - printf("unloaded %s\n", copyinstr(arg0, arg1)); -} +/* + * This D-script prints one line on each Java class + * load and unload. To mark DTrace message begin, this + * script prints a message on DTrace session start. + */ + +BEGIN { + printf("dtrace start\n"); +} + +hotspot$1:::class-loaded { + printf("loaded %s\n", copyinstr(arg0, arg1)); +} + +hotspot$1:::class-unloaded { + printf("unloaded %s\n", copyinstr(arg0, arg1)); +} diff --git a/btrace-dist/src/main/resources/samples/java.net.socket.xml b/btrace-dist/src/main/resources/samples/java.net.socket.xml new file mode 100644 index 000000000..b38e9c30b --- /dev/null +++ b/btrace-dist/src/main/resources/samples/java.net.socket.xml @@ -0,0 +1,88 @@ + + + + + + sun.nio.ch.ServerSocketChannelImpl + socket + + RETURN + + + + + + + sun.nio.ch.ServerSocketChannelImpl + bind + void (java.net.SocketAddress, int) + + + java.net.ServerSocket + bind + void (java.net.SocketAddress, int) + + + + + + sun.nio.ch.ServerSocketChannelImpl + bind + void (java.net.SocketAddress, int) + + RETURN + + + + java.net.ServerSocket + bind + void (java.net.SocketAddress, int) + + RETURN + + + + + + + java.net.ServerSocket + accept + + RETURN + + + + sun.nio.ch.ServerSocketChannelImpl + accept + + RETURN + + + + + diff --git a/samples/jthread.d b/btrace-dist/src/main/resources/samples/jthread.d similarity index 94% rename from samples/jthread.d rename to btrace-dist/src/main/resources/samples/jthread.d index 548134092..b70fcd95b 100644 --- a/samples/jthread.d +++ b/btrace-dist/src/main/resources/samples/jthread.d @@ -1,10 +1,10 @@ -#!/usr/sbin/dtrace -s - -btrace$1:::event -/ - copyinstr(arg0) == "jthreadstart" && - arg1 != NULL -/ -{ - printf("From DTrace: Java Thread '%s' started\n", copyinstr(arg1)); -} +#!/usr/sbin/dtrace -s + +btrace$1:::event +/ + copyinstr(arg0) == "jthreadstart" && + arg1 != NULL +/ +{ + printf("From DTrace: Java Thread '%s' started\n", copyinstr(arg1)); +} diff --git a/samples/jurls.d b/btrace-dist/src/main/resources/samples/jurls.d similarity index 95% rename from samples/jurls.d rename to btrace-dist/src/main/resources/samples/jurls.d index 1a79b88d5..83d531533 100644 --- a/samples/jurls.d +++ b/btrace-dist/src/main/resources/samples/jurls.d @@ -1,12 +1,12 @@ - -/* - * This D-script maintains a aggregation whenever - * btrace:::event probe is raised with "java-url-open" - * as first argument. - */ -btrace$target:::event -/ copyinstr(arg0) == "java-url-open" / -{ - @[copyinstr(arg1)] = count(); -} - + +/* + * This D-script maintains a aggregation whenever + * btrace:::event probe is raised with "java-url-open" + * as first argument. + */ +btrace$target:::event +/ copyinstr(arg0) == "java-url-open" / +{ + @[copyinstr(arg1)] = count(); +} + diff --git a/samples/syscalls.d b/btrace-dist/src/main/resources/samples/syscalls.d similarity index 93% rename from samples/syscalls.d rename to btrace-dist/src/main/resources/samples/syscalls.d index e129d2cde..fe1890bb1 100644 --- a/samples/syscalls.d +++ b/btrace-dist/src/main/resources/samples/syscalls.d @@ -1,5 +1,5 @@ -syscall:::entry -/ pid == $target / -{ - @[probefunc] = count(); -} +syscall:::entry +/ pid == $target / +{ + @[probefunc] = count(); +} diff --git a/btrace-dtrace/build.gradle b/btrace-dtrace/build.gradle new file mode 100644 index 000000000..4dd322ed9 --- /dev/null +++ b/btrace-dtrace/build.gradle @@ -0,0 +1,68 @@ +import org.gradle.internal.os.OperatingSystem +import org.gradle.internal.jvm.Jvm + +plugins { + id 'java' + alias(libs.plugins.shadow) +} + +def nativeHeadersDir = "${buildDir}/generated/native/include" +def dtraceLibDir = "${buildDir}/dtrace" +def javaHome = Jvm.current().javaHome +def isSolaris = OperatingSystem.current() == OperatingSystem.SOLARIS +def osArch = System.getProperty("os.arch") + +dependencies { + implementation project(':btrace-core') +} + +task nativeHeaders(type:Exec) { + def classpath = sourceSets.main.runtimeClasspath.asPath + commandLine "javah", "-d", nativeHeadersDir, "-classpath", classpath, "org.openjdk.btrace.core.BTraceRuntime" + + dependsOn classes +} + +if (isSolaris) { + task makedirs(type: Exec) { + commandLine "mkdir", "-p", "${dtraceLibDir}", "${dtraceLibDir}/libs/${osArch}" + } + task compileC(type: Exec) { + commandLine "gcc", "-c", "-I${javaHome}/include", "-I${javaHome}/include/solaris", "-I${nativeHeadersDir}", "-o${dtraceLibDir}/btrace.o", "${projectDir}/src/main/native/btrace.c" + dependsOn nativeHeaders, makedirs + } + + task dtrace(type: Exec) { + commandLine "/usr/sbin/dtrace", "-G", "-o", "${dtraceLibDir}/btraced.o", "-s", "${projectDir}/src/main/native/btraced.d", "${dtraceLibDir}/btrace.o" + dependsOn compileC, makedirs + } + + task linkDTraceLib(type: Exec) { + commandLine "gcc", "-G", "${dtraceLibDir}/btrace.o", "${dtraceLibDir}/btraced.o", "-o", "${dtraceLibDir}/libs/${osArch}/libbtrace.so" + dependsOn dtrace, makedirs + } +} + +sourceSets { + main { + java { + srcDirs "src/main/java" + srcDirs "src/mock/java" + } + } +} + +shadowJar { + include 'org/openjdk/btrace/dtrace/**/*' + archiveClassifier = null +} + +sourcesJar { + duplicatesStrategy DuplicatesStrategy.EXCLUDE +} + +build.dependsOn shadowJar + +if (isSolaris) { + jar.dependsOn linkDTraceLib +} diff --git a/btrace-dtrace/src/main/java/org/openjdk/btrace/dtrace/DTrace.java b/btrace-dtrace/src/main/java/org/openjdk/btrace/dtrace/DTrace.java new file mode 100644 index 000000000..950970cec --- /dev/null +++ b/btrace-dtrace/src/main/java/org/openjdk/btrace/dtrace/DTrace.java @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.dtrace; + +import java.io.File; +import java.io.IOException; +import org.openjdk.btrace.core.comm.Command; +import org.openjdk.btrace.core.comm.CommandListener; +import org.openjdk.btrace.core.comm.ErrorCommand; +import org.openjdk.btrace.core.comm.MessageCommand; +import org.opensolaris.os.dtrace.*; + +/** + * Simple wrapper class around DTrace/Java API. This wrapper accepts a BTrace command listener, + * DTrace file or string and DTrace macro arguments. The events from DTrace are wrapped as BTrace + * command instances and given to the listener. + * + * @author A. Sundararajan + */ +@SuppressWarnings("RedundantThrows") +public class DTrace { + + /** + * Submits a D-script from given file and passes given argument array as DTrace macro arguments. + * The events from DTrace are wrapped as BTrace commands and listener given is notified. + * + * @param file D-script file to submit + * @param args DTrace macro arguments + * @param listener BTrace command listener that is notified + */ + public static void submit(File file, String[] args, CommandListener listener) + throws DTraceException, IOException { + Consumer cons = newConsumer(args, listener); + cons.compile(file, args); + start(cons, listener); + } + + /** + * Submits a D-script string and passes the given argument array as DTrace macro arguments. The + * events from DTrace are wrapped as BTrace commands and listener given is notified. + * + * @param program D-script as a string + * @param args DTrace macro arguments + * @param listener BTrace command listener that is notified + */ + public static void submit(String program, String[] args, CommandListener listener) + throws DTraceException { + Consumer cons = newConsumer(args, listener); + cons.compile(program, args); + start(cons, listener); + } + + private static void start(Consumer cons, CommandListener listener) throws DTraceException { + cons.enable(); + cons.go( + th -> { + try { + listener.onCommand(new ErrorCommand(th)); + } catch (IOException ioexp) { + ioexp.printStackTrace(); + } + }); + } + + private static Consumer newConsumer(String[] args, CommandListener listener) + throws DTraceException { + Consumer cons = new LocalConsumer(); + cons.addConsumerListener( + new ConsumerAdapter() { + private void notify(Command cmd) { + try { + listener.onCommand(cmd); + } catch (IOException ioexp) { + ioexp.printStackTrace(); + } + } + + @Override + public void consumerStarted(ConsumerEvent ce) { + notify(new DTraceStartCommand(ce)); + } + + @Override + public void consumerStopped(ConsumerEvent ce) { + Consumer cons = ce.getSource(); + Aggregate ag = null; + try { + ag = cons.getAggregate(); + } catch (DTraceException dexp) { + notify(new ErrorCommand(dexp)); + } + StringBuilder buf = new StringBuilder(); + if (ag != null) { + for (Aggregation agg : ag.asMap().values()) { + String name = agg.getName(); + if (name != null && name.length() > 0) { + buf.append(name); + buf.append('\n'); + } + for (AggregationRecord rec : agg.asMap().values()) { + buf.append('\t'); + buf.append(rec.getTuple()); + buf.append(" "); + buf.append(rec.getValue()); + buf.append('\n'); + } + } + } + String msg = buf.toString(); + if (msg.length() > 0) { + notify(new MessageCommand(msg)); + } + notify(new DTraceStopCommand(ce)); + cons.close(); + } + + @Override + public void dataReceived(DataEvent de) { + notify(new DTraceDataCommand(de)); + } + + @Override + public void dataDropped(DropEvent de) { + notify(new DTraceDropCommand(de)); + } + + @Override + public void errorEncountered(ErrorEvent ee) throws ConsumerException { + try { + super.errorEncountered(ee); + } catch (ConsumerException ce) { + notify(new DTraceErrorCommand(ce, ee)); + throw ce; + } + } + }); + + // open DTrace Consumer + cons.open(); + + // unused macro arguments are fine + cons.setOption(Option.argref, ""); + // if no macro arg passed use "" or NULL + cons.setOption(Option.defaultargs, ""); + // allow empty D-scripts + cons.setOption(Option.empty, ""); + // be quiet! equivalent to DTrace's -q + cons.setOption(Option.quiet, ""); + // undefined user land symbols are fine + cons.setOption(Option.unodefs, ""); + // allow zero matching of probes (needed for late loading) + cons.setOption(Option.zdefs, ""); + try { + int pid = Integer.parseInt(args[0]); + cons.grabProcess(pid); + } catch (Exception ignored) { + } + return cons; + } +} diff --git a/src/solaris/classes/com/sun/btrace/dtrace/DTraceCommand.java b/btrace-dtrace/src/main/java/org/openjdk/btrace/dtrace/DTraceCommand.java similarity index 88% rename from src/solaris/classes/com/sun/btrace/dtrace/DTraceCommand.java rename to btrace-dtrace/src/main/java/org/openjdk/btrace/dtrace/DTraceCommand.java index 6dc9a5896..b245fe7ca 100644 --- a/src/solaris/classes/com/sun/btrace/dtrace/DTraceCommand.java +++ b/btrace-dtrace/src/main/java/org/openjdk/btrace/dtrace/DTraceCommand.java @@ -23,13 +23,11 @@ * questions. */ -package com.sun.btrace.dtrace; +package org.openjdk.btrace.dtrace; /** - * A marker interface to tell whether a given - * BTrace command is a wrapper of a DTrace event. + * A marker interface to tell whether a given BTrace command is a wrapper of a DTrace event. * * @author A. Sundararajan */ -public interface DTraceCommand { -} +public interface DTraceCommand {} diff --git a/btrace-dtrace/src/main/java/org/openjdk/btrace/dtrace/DTraceConsumerCommand.java b/btrace-dtrace/src/main/java/org/openjdk/btrace/dtrace/DTraceConsumerCommand.java new file mode 100644 index 000000000..9e441d203 --- /dev/null +++ b/btrace-dtrace/src/main/java/org/openjdk/btrace/dtrace/DTraceConsumerCommand.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.dtrace; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import org.openjdk.btrace.core.comm.EventCommand; +import org.opensolaris.os.dtrace.Consumer; +import org.opensolaris.os.dtrace.ConsumerEvent; + +/** + * Abstract command to represent consumer event from DTrace. + * + * @author A. Sundararajan + */ +public abstract class DTraceConsumerCommand extends EventCommand implements DTraceCommand { + private ConsumerEvent ce; + + public DTraceConsumerCommand(String type, ConsumerEvent ce) { + super(type); + this.ce = ce; + } + + /** Returns the underlying DTrace ConsumerEvent. */ + public ConsumerEvent getConsumerEvent() { + return ce; + } + + /** Returns the Consumer object. */ + public Consumer getConsumer() { + return ce.getSource(); + } + + public void write(ObjectOutput out) throws IOException { + out.writeObject(ce); + } + + public void read(ObjectInput in) throws ClassNotFoundException, IOException { + ce = (ConsumerEvent) in.readObject(); + } +} diff --git a/btrace-dtrace/src/main/java/org/openjdk/btrace/dtrace/DTraceDataCommand.java b/btrace-dtrace/src/main/java/org/openjdk/btrace/dtrace/DTraceDataCommand.java new file mode 100644 index 000000000..a375393dc --- /dev/null +++ b/btrace-dtrace/src/main/java/org/openjdk/btrace/dtrace/DTraceDataCommand.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.dtrace; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.util.List; +import org.openjdk.btrace.core.comm.MessageCommand; +import org.opensolaris.os.dtrace.DataEvent; +import org.opensolaris.os.dtrace.ProbeData; +import org.opensolaris.os.dtrace.Record; + +/** + * Command to represent data event from DTrace. + * + * @author A. Sundararajan + */ +public class DTraceDataCommand extends MessageCommand implements DTraceCommand { + private DataEvent de; + + public DTraceDataCommand(DataEvent de) { + super(asString(de), true); + this.de = de; + } + + /** Returns the underlying DTrace DataEvent. */ + public DataEvent getDataEvent() { + return de; + } + + public void write(ObjectOutput out) throws IOException { + super.write(out); + out.writeObject(de); + } + + public void read(ObjectInput in) throws ClassNotFoundException, IOException { + super.read(in); + de = (DataEvent) in.readObject(); + } + + private static String asString(DataEvent de) { + ProbeData pd = de.getProbeData(); + List records = pd.getRecords(); + StringBuilder buf = new StringBuilder(); + for (Record rec : records) { + buf.append(rec); + } + return buf.toString(); + } +} diff --git a/btrace-dtrace/src/main/java/org/openjdk/btrace/dtrace/DTraceDropCommand.java b/btrace-dtrace/src/main/java/org/openjdk/btrace/dtrace/DTraceDropCommand.java new file mode 100644 index 000000000..0134b6a42 --- /dev/null +++ b/btrace-dtrace/src/main/java/org/openjdk/btrace/dtrace/DTraceDropCommand.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.dtrace; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import org.openjdk.btrace.core.comm.MessageCommand; +import org.opensolaris.os.dtrace.DropEvent; + +/** + * Command class that represents DTrace drop event. + * + * @author A. Sundararajan + */ +public class DTraceDropCommand extends MessageCommand implements DTraceCommand { + private DropEvent de; + + public DTraceDropCommand(DropEvent de) { + super(asString(de), true); + this.de = de; + } + + /** Returns the underlying DTrace drop event */ + public DropEvent getDropEvent() { + return de; + } + + public void write(ObjectOutput out) throws IOException { + super.write(out); + out.writeObject(out); + } + + public void read(ObjectInput in) throws ClassNotFoundException, IOException { + super.read(in); + de = (DropEvent) in.readObject(); + } + + private static String asString(DropEvent de) { + return de.getDrop().getDefaultMessage(); + } +} diff --git a/btrace-dtrace/src/main/java/org/openjdk/btrace/dtrace/DTraceErrorCommand.java b/btrace-dtrace/src/main/java/org/openjdk/btrace/dtrace/DTraceErrorCommand.java new file mode 100644 index 000000000..43400920f --- /dev/null +++ b/btrace-dtrace/src/main/java/org/openjdk/btrace/dtrace/DTraceErrorCommand.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.dtrace; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import org.openjdk.btrace.core.comm.ErrorCommand; +import org.opensolaris.os.dtrace.ErrorEvent; + +/** + * Command that represents error message from DTrace. + * + * @author A. Sundararajan + */ +public class DTraceErrorCommand extends ErrorCommand implements DTraceCommand { + private ErrorEvent ee; + + public DTraceErrorCommand(Exception exp, ErrorEvent ee) { + super(exp); + this.ee = ee; + } + + /** Returns the underlying DTrace error event. */ + public ErrorEvent getErrorEvent() { + return ee; + } + + public void write(ObjectOutput out) throws IOException { + super.write(out); + out.writeObject(ee); + } + + public void read(ObjectInput in) throws ClassNotFoundException, IOException { + super.read(in); + ee = (ErrorEvent) in.readObject(); + } +} diff --git a/src/solaris/classes/com/sun/btrace/dtrace/DTraceStartCommand.java b/btrace-dtrace/src/main/java/org/openjdk/btrace/dtrace/DTraceStartCommand.java similarity index 92% rename from src/solaris/classes/com/sun/btrace/dtrace/DTraceStartCommand.java rename to btrace-dtrace/src/main/java/org/openjdk/btrace/dtrace/DTraceStartCommand.java index 5b33b0160..9768abce5 100644 --- a/src/solaris/classes/com/sun/btrace/dtrace/DTraceStartCommand.java +++ b/btrace-dtrace/src/main/java/org/openjdk/btrace/dtrace/DTraceStartCommand.java @@ -23,7 +23,7 @@ * questions. */ -package com.sun.btrace.dtrace; +package org.openjdk.btrace.dtrace; import org.opensolaris.os.dtrace.ConsumerEvent; @@ -33,7 +33,7 @@ * @author A. Sundararajan */ public class DTraceStartCommand extends DTraceConsumerCommand { - public DTraceStartCommand(ConsumerEvent ce) { - super("dtrace-start", ce); - } + public DTraceStartCommand(ConsumerEvent ce) { + super("dtrace-start", ce); + } } diff --git a/src/solaris/classes/com/sun/btrace/dtrace/DTraceStopCommand.java b/btrace-dtrace/src/main/java/org/openjdk/btrace/dtrace/DTraceStopCommand.java similarity index 92% rename from src/solaris/classes/com/sun/btrace/dtrace/DTraceStopCommand.java rename to btrace-dtrace/src/main/java/org/openjdk/btrace/dtrace/DTraceStopCommand.java index 5d9cc4971..85d741237 100644 --- a/src/solaris/classes/com/sun/btrace/dtrace/DTraceStopCommand.java +++ b/btrace-dtrace/src/main/java/org/openjdk/btrace/dtrace/DTraceStopCommand.java @@ -23,7 +23,7 @@ * questions. */ -package com.sun.btrace.dtrace; +package org.openjdk.btrace.dtrace; import org.opensolaris.os.dtrace.ConsumerEvent; @@ -33,7 +33,7 @@ * @author A. Sundararajan */ public class DTraceStopCommand extends DTraceConsumerCommand { - public DTraceStopCommand(ConsumerEvent ce) { - super("dtrace-stop", ce); - } + public DTraceStopCommand(ConsumerEvent ce) { + super("dtrace-stop", ce); + } } diff --git a/src/solaris/native/btrace.c b/btrace-dtrace/src/main/native/btrace.c similarity index 91% rename from src/solaris/native/btrace.c rename to btrace-dtrace/src/main/native/btrace.c index 6b7049390..1779e6eae 100644 --- a/src/solaris/native/btrace.c +++ b/btrace-dtrace/src/main/native/btrace.c @@ -23,15 +23,15 @@ * questions. */ -#include "com_sun_btrace_BTraceRuntime.h" +#include "org_openjdk_btrace_core_BTraceRuntime.h" #include /* - * Class: com_sun_btrace_BTraceRuntime + * Class: org_openjdk_btrace_core_BTraceRuntime * Method: dtraceProbe0 * Signature: (Ljava/lang/String;Ljava/lang/String;II)I */ -JNIEXPORT jint JNICALL Java_com_sun_btrace_BTraceRuntime_dtraceProbe0 +JNIEXPORT jint JNICALL Java_org_openjdk_btrace_core_BTraceRuntime_dtraceProbe0 (JNIEnv *env, jclass cls, jstring str1, jstring str2, jint i1, jint i2) { int result = 0; const char* cstr1; diff --git a/src/solaris/native/btraced.d b/btrace-dtrace/src/main/native/btraced.d similarity index 96% rename from src/solaris/native/btraced.d rename to btrace-dtrace/src/main/native/btraced.d index 8416c83b8..8dcb3def2 100644 --- a/src/solaris/native/btraced.d +++ b/btrace-dtrace/src/main/native/btraced.d @@ -1,3 +1,3 @@ -provider btrace { - probe event(char* c1, char* c2, int i1, int i2, int* i3); -}; +provider btrace { + probe event(char* c1, char* c2, int i1, int i2, int* i3); +}; diff --git a/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/Aggregate.java b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/Aggregate.java new file mode 100644 index 000000000..265ea13d4 --- /dev/null +++ b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/Aggregate.java @@ -0,0 +1,10 @@ +package org.opensolaris.os.dtrace; + +import java.util.Map; + +@SuppressWarnings("SameReturnValue") +public class Aggregate { + public Map asMap() { + return null; + } +} diff --git a/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/Aggregation.java b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/Aggregation.java new file mode 100644 index 000000000..0f6366252 --- /dev/null +++ b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/Aggregation.java @@ -0,0 +1,14 @@ +package org.opensolaris.os.dtrace; + +import java.util.Map; + +@SuppressWarnings("SameReturnValue") +public class Aggregation { + public String getName() { + return null; + } + + public Map asMap() { + return null; + } +} diff --git a/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/AggregationRecord.java b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/AggregationRecord.java new file mode 100644 index 000000000..b8b36356f --- /dev/null +++ b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/AggregationRecord.java @@ -0,0 +1,12 @@ +package org.opensolaris.os.dtrace; + +@SuppressWarnings("SameReturnValue") +public class AggregationRecord { + public Tuple getTuple() { + return null; + } + + public AggregationValue getValue() { + return null; + } +} diff --git a/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/AggregationValue.java b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/AggregationValue.java new file mode 100644 index 000000000..010ecb140 --- /dev/null +++ b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/AggregationValue.java @@ -0,0 +1,3 @@ +package org.opensolaris.os.dtrace; + +public class AggregationValue {} diff --git a/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/Consumer.java b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/Consumer.java new file mode 100644 index 000000000..b2ee126d9 --- /dev/null +++ b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/Consumer.java @@ -0,0 +1,62 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + * + * ident "%Z%%M% %I% %E% SMI" + */ +package org.opensolaris.os.dtrace; + +import java.io.File; + +public interface Consumer { + @SuppressWarnings("EmptyMethod") + void open(); + + @SuppressWarnings("EmptyMethod") + void setOption(String argref, String s); + + @SuppressWarnings("EmptyMethod") + void grabProcess(int pid); + + @SuppressWarnings("EmptyMethod") + void close(); + + @SuppressWarnings({"SameReturnValue", "RedundantThrows"}) + Aggregate getAggregate() throws DTraceException; + + @SuppressWarnings("EmptyMethod") + void addConsumerListener(ConsumerListener consumerListener); + + @SuppressWarnings("EmptyMethod") + void go(ExceptionHandler exceptionHandler); + + @SuppressWarnings("EmptyMethod") + void enable(); + + @SuppressWarnings("EmptyMethod") + void compile(File program, String[] args); + + @SuppressWarnings("EmptyMethod") + void compile(String program, String[] args); +} diff --git a/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/ConsumerAdapter.java b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/ConsumerAdapter.java new file mode 100644 index 000000000..15a73ed95 --- /dev/null +++ b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/ConsumerAdapter.java @@ -0,0 +1,13 @@ +package org.opensolaris.os.dtrace; + +public abstract class ConsumerAdapter implements ConsumerListener { + public abstract void consumerStarted(ConsumerEvent ce); + + public abstract void consumerStopped(ConsumerEvent ce); + + public abstract void dataReceived(DataEvent de); + + public abstract void dataDropped(DropEvent de); + + public void errorEncountered(ErrorEvent ee) throws ConsumerException {} +} diff --git a/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/ConsumerEvent.java b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/ConsumerEvent.java new file mode 100644 index 000000000..561653d5c --- /dev/null +++ b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/ConsumerEvent.java @@ -0,0 +1,8 @@ +package org.opensolaris.os.dtrace; + +@SuppressWarnings("SameReturnValue") +public class ConsumerEvent { + public Consumer getSource() { + return null; + } +} diff --git a/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/ConsumerException.java b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/ConsumerException.java new file mode 100644 index 000000000..53fff674b --- /dev/null +++ b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/ConsumerException.java @@ -0,0 +1,3 @@ +package org.opensolaris.os.dtrace; + +public class ConsumerException extends Exception {} diff --git a/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/ConsumerListener.java b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/ConsumerListener.java new file mode 100644 index 000000000..e1a20baf9 --- /dev/null +++ b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/ConsumerListener.java @@ -0,0 +1,3 @@ +package org.opensolaris.os.dtrace; + +public interface ConsumerListener {} diff --git a/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/DTraceException.java b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/DTraceException.java new file mode 100644 index 000000000..8c62bd6c3 --- /dev/null +++ b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/DTraceException.java @@ -0,0 +1,22 @@ +package org.opensolaris.os.dtrace; + +public class DTraceException extends Exception { + public DTraceException() {} + + public DTraceException(String message) { + super(message); + } + + public DTraceException(String message, Throwable cause) { + super(message, cause); + } + + public DTraceException(Throwable cause) { + super(cause); + } + + public DTraceException( + String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/DataEvent.java b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/DataEvent.java new file mode 100644 index 000000000..5e804582e --- /dev/null +++ b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/DataEvent.java @@ -0,0 +1,14 @@ +package org.opensolaris.os.dtrace; + +import java.util.EventObject; + +@SuppressWarnings("SameReturnValue") +public class DataEvent extends EventObject { + public DataEvent(Object source) { + super(source); + } + + public ProbeData getProbeData() { + return null; + } +} diff --git a/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/Drop.java b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/Drop.java new file mode 100644 index 000000000..cfa4f7cd0 --- /dev/null +++ b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/Drop.java @@ -0,0 +1,8 @@ +package org.opensolaris.os.dtrace; + +@SuppressWarnings("SameReturnValue") +public class Drop { + public String getDefaultMessage() { + return null; + } +} diff --git a/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/DropEvent.java b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/DropEvent.java new file mode 100644 index 000000000..3db363d01 --- /dev/null +++ b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/DropEvent.java @@ -0,0 +1,14 @@ +package org.opensolaris.os.dtrace; + +import java.util.EventObject; + +@SuppressWarnings("SameReturnValue") +public class DropEvent extends EventObject { + public DropEvent(Object source) { + super(source); + } + + public Drop getDrop() { + return null; + } +} diff --git a/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/ErrorEvent.java b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/ErrorEvent.java new file mode 100644 index 000000000..fa96b4bfe --- /dev/null +++ b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/ErrorEvent.java @@ -0,0 +1,3 @@ +package org.opensolaris.os.dtrace; + +public class ErrorEvent {} diff --git a/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/ExceptionHandler.java b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/ExceptionHandler.java new file mode 100644 index 000000000..1bf5769e0 --- /dev/null +++ b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/ExceptionHandler.java @@ -0,0 +1,5 @@ +package org.opensolaris.os.dtrace; + +public interface ExceptionHandler { + void handleException(Throwable t); +} diff --git a/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/LocalConsumer.java b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/LocalConsumer.java new file mode 100644 index 000000000..4195c96d6 --- /dev/null +++ b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/LocalConsumer.java @@ -0,0 +1,38 @@ +package org.opensolaris.os.dtrace; + +import java.io.File; + +public class LocalConsumer implements Consumer { + @Override + public void open() {} + + @Override + public void setOption(String argref, String s) {} + + @Override + public void grabProcess(int pid) {} + + @Override + public void close() {} + + @SuppressWarnings("RedundantThrows") + @Override + public Aggregate getAggregate() throws DTraceException { + return null; + } + + @Override + public void addConsumerListener(ConsumerListener consumerListener) {} + + @Override + public void go(ExceptionHandler exceptionHandler) {} + + @Override + public void enable() {} + + @Override + public void compile(File program, String[] args) {} + + @Override + public void compile(String program, String[] args) {} +} diff --git a/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/Option.java b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/Option.java new file mode 100644 index 000000000..0c14c8601 --- /dev/null +++ b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/Option.java @@ -0,0 +1,10 @@ +package org.opensolaris.os.dtrace; + +public class Option { + public static String argref; + public static String defaultargs; + public static String empty; + public static String quiet; + public static String unodefs; + public static String zdefs; +} diff --git a/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/Probe.java b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/Probe.java new file mode 100644 index 000000000..5a3216567 --- /dev/null +++ b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/Probe.java @@ -0,0 +1,3 @@ +package org.opensolaris.os.dtrace; + +public class Probe {} diff --git a/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/ProbeData.java b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/ProbeData.java new file mode 100644 index 000000000..839b88923 --- /dev/null +++ b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/ProbeData.java @@ -0,0 +1,10 @@ +package org.opensolaris.os.dtrace; + +import java.util.List; + +@SuppressWarnings("SameReturnValue") +public class ProbeData { + public List getRecords() { + return null; + } +} diff --git a/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/ProbeDescription.java b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/ProbeDescription.java new file mode 100644 index 000000000..8337dc6cb --- /dev/null +++ b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/ProbeDescription.java @@ -0,0 +1,3 @@ +package org.opensolaris.os.dtrace; + +public class ProbeDescription {} diff --git a/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/Program.java b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/Program.java new file mode 100644 index 000000000..17df57ee2 --- /dev/null +++ b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/Program.java @@ -0,0 +1,3 @@ +package org.opensolaris.os.dtrace; + +public class Program {} diff --git a/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/Record.java b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/Record.java new file mode 100644 index 000000000..cd1be9c40 --- /dev/null +++ b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/Record.java @@ -0,0 +1,3 @@ +package org.opensolaris.os.dtrace; + +public class Record {} diff --git a/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/Tuple.java b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/Tuple.java new file mode 100644 index 000000000..934593ffe --- /dev/null +++ b/btrace-dtrace/src/mock/java/org/opensolaris/os/dtrace/Tuple.java @@ -0,0 +1,3 @@ +package org.opensolaris.os.dtrace; + +public class Tuple {} diff --git a/btrace-instr/build.gradle b/btrace-instr/build.gradle new file mode 100644 index 000000000..f27997413 --- /dev/null +++ b/btrace-instr/build.gradle @@ -0,0 +1,70 @@ +import java.nio.file.Files +import java.nio.file.Paths + +plugins { + id 'java' +} + +dependencies { + implementation libs.slf4j + implementation libs.asm + implementation libs.asm.tree + implementation libs.autoService + implementation libs.jctools + implementation project(':btrace-core') + implementation project(':btrace-services-api') + implementation project(':btrace-runtime') + implementation project(':btrace-compiler') + implementation project(':btrace-statsd') + + testImplementation libs.asm.util + testImplementation libs.slf4j.simple + testImplementation libs.junit.jupiter + testImplementation project(':btrace-client') +} + +compileJava { + javaCompiler = javaToolchains.compilerFor { + languageVersion.set(JavaLanguageVersion.of(8)) + } +} + +compileTestJava { + options.fork = true + options.forkOptions.executable = "${getJavac(8)}" +} + +task compileTestProbes { + dependsOn compileTestJava + doLast { + def path = project(':btrace-instr').sourceSets.main.runtimeClasspath + + def loader = new URLClassLoader(path.collect { f -> f.toURL() } as URL[]) + def compiler = loader.loadClass('org.openjdk.btrace.compiler.Compiler') + def rtCp = sourceSets.main.runtimeClasspath + + def args = ["-cp", rtCp.plus(files(buildDir.toPath().resolve("classes/java/test"), buildDir.toPath().resolve("classes/java/java11_dummy"))).getAsPath(), "-d", buildDir.toPath().resolve("classes")] + + def files = fileTree(dir: "src/test/btrace", include: '**/*.java', exclude: 'verifier/**/*.java').findAll { + it != null + }.collect { it } + + args.addAll(files) + + compiler.main(args as String[]) + } +} + +test { + dependsOn cleanTest + inputs.files compileTestProbes.outputs + testLogging.showStandardStreams = true + + def props = new Properties() + props.load(Files.newInputStream(Paths.get(System.getenv("JAVA_HOME"), "release"))) + if (props.getProperty("JAVA_VERSION")?.contains("1.8")) { + jvmArgs "-Dproject.version=${project.version}" + } else { + jvmArgs '-XX:+IgnoreUnrecognizedVMOptions', '--add-opens', 'java.base/jdk.internal.reflect=ALL-UNNAMED', '--add-opens', 'java.base/java.lang=ALL-UNNAMED', '--add-exports', 'java.base/jdk.internal.reflect=ALL-UNNAMED', "-Dproject.version=${project.version}" + } +} \ No newline at end of file diff --git a/btrace-instr/src/main/java/org/openjdk/btrace/instr/ArrayAccessInstrumentor.java b/btrace-instr/src/main/java/org/openjdk/btrace/instr/ArrayAccessInstrumentor.java new file mode 100644 index 000000000..bcc5151fb --- /dev/null +++ b/btrace-instr/src/main/java/org/openjdk/btrace/instr/ArrayAccessInstrumentor.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.instr; + +import static org.objectweb.asm.Opcodes.*; + +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Type; + +/** + * This visitor helps in inserting code whenever an array access is done. Code to insert on array + * access may be decided by derived class. By default, this class inserts code to print array + * access. + * + * @author A. Sundararajan + */ +public class ArrayAccessInstrumentor extends MethodInstrumentor { + public ArrayAccessInstrumentor( + ClassLoader cl, + MethodVisitor mv, + MethodInstrumentorHelper mHelper, + String parentClz, + String superClz, + int access, + String name, + String desc) { + super(cl, mv, mHelper, parentClz, superClz, access, name, desc); + } + + @Override + public void visitInsn(int opcode) { + boolean arrayload = false; + boolean arraystore = false; + switch (opcode) { + case IALOAD: + case LALOAD: + case FALOAD: + case DALOAD: + case AALOAD: + case BALOAD: + case CALOAD: + case SALOAD: + arrayload = true; + break; + + case IASTORE: + case LASTORE: + case FASTORE: + case DASTORE: + case AASTORE: + case BASTORE: + case CASTORE: + case SASTORE: + arraystore = true; + break; + } + if (arrayload) { + onBeforeArrayLoad(opcode); + } else if (arraystore) { + onBeforeArrayStore(opcode); + } + super.visitInsn(opcode); + if (arrayload) { + onAfterArrayLoad(opcode); + } else if (arraystore) { + onAfterArrayStore(opcode); + } + } + + protected final boolean locationTypeMismatch(Location loc, Type arrtype, Type itemType) { + return !loc.getType().isEmpty() + && (!loc.getType().equals(arrtype.getClassName()) + && !loc.getType().equals(itemType.getClassName())); + } + + protected void onBeforeArrayLoad(int opcode) {} + + protected void onAfterArrayLoad(int opcode) {} + + protected void onBeforeArrayStore(int opcode) {} + + protected void onAfterArrayStore(int opcode) {} +} diff --git a/btrace-instr/src/main/java/org/openjdk/btrace/instr/ArrayAllocInstrumentor.java b/btrace-instr/src/main/java/org/openjdk/btrace/instr/ArrayAllocInstrumentor.java new file mode 100644 index 000000000..093be4f82 --- /dev/null +++ b/btrace-instr/src/main/java/org/openjdk/btrace/instr/ArrayAllocInstrumentor.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.instr; + +import static org.objectweb.asm.Opcodes.ANEWARRAY; +import static org.objectweb.asm.Opcodes.NEWARRAY; + +import org.objectweb.asm.MethodVisitor; + +/** + * This visitor helps in inserting code whenever an array is allocated. The code to insert on method + * entry may be decided by derived class. By default, this class inserts code to print allocated + * array objects. + * + * @author A. Sundararajan + */ +public class ArrayAllocInstrumentor extends MethodInstrumentor { + public ArrayAllocInstrumentor( + ClassLoader cl, + MethodVisitor mv, + MethodInstrumentorHelper mHelper, + String parentClz, + String superClz, + int access, + String name, + String desc) { + super(cl, mv, mHelper, parentClz, superClz, access, name, desc); + } + + @Override + public void visitIntInsn(int opcode, int operand) { + String desc = null; + if (opcode == NEWARRAY) { + desc = InstrumentUtils.arrayDescriptorFor(operand); + onBeforeArrayNew(getPlainType(desc), 1); + } + super.visitIntInsn(opcode, operand); + if (opcode == NEWARRAY) { + onAfterArrayNew(getPlainType(desc), 1); + } + } + + @Override + public void visitTypeInsn(int opcode, String desc) { + if (opcode == ANEWARRAY) { + onBeforeArrayNew("L" + desc + ";", 1); + } + super.visitTypeInsn(opcode, desc); + if (opcode == ANEWARRAY) { + onAfterArrayNew("L" + desc + ";", 1); + } + } + + @Override + public void visitMultiANewArrayInsn(String desc, int dims) { + String type = getPlainType(desc); + onBeforeArrayNew(type, dims); + super.visitMultiANewArrayInsn(desc, dims); + onAfterArrayNew(type, dims); + } + + protected void onBeforeArrayNew(String desc, int dims) { + asm.println("before allocating " + desc); + } + + protected void onAfterArrayNew(String desc, int dims) { + asm.dup().printObject(); + } + + private String getPlainType(String desc) { + int index = desc.lastIndexOf('[') + 1; + if (index > 0) { + return desc.substring(index); + } + return desc; + } +} diff --git a/btrace-instr/src/main/java/org/openjdk/btrace/instr/Assembler.java b/btrace-instr/src/main/java/org/openjdk/btrace/instr/Assembler.java new file mode 100644 index 000000000..c1a0ea830 --- /dev/null +++ b/btrace-instr/src/main/java/org/openjdk/btrace/instr/Assembler.java @@ -0,0 +1,589 @@ +/* + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.btrace.instr; + +import static org.objectweb.asm.Opcodes.*; +import static org.openjdk.btrace.instr.Constants.*; + +import org.objectweb.asm.Handle; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Type; +import org.openjdk.btrace.runtime.Interval; + +/** + * Convenient fluent wrapper over the ASM method visitor + * + * @author Jaroslav Bachorik + */ +@SuppressWarnings("UnusedReturnValue") +public final class Assembler { + private final MethodVisitor mv; + private final MethodInstrumentorHelper mHelper; + + public Assembler(MethodVisitor mv, MethodInstrumentorHelper mHelper) { + this.mv = mv; + this.mHelper = mHelper; + } + + public Assembler push(int value) { + if (value >= -1 && value <= 5) { + mv.visitInsn(ICONST_0 + value); + } else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) { + mv.visitIntInsn(BIPUSH, value); + } else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) { + mv.visitIntInsn(SIPUSH, value); + } else { + mv.visitLdcInsn(value); + } + return this; + } + + public Assembler arrayLoad(Type type) { + mv.visitInsn(type.getOpcode(IALOAD)); + return this; + } + + public Assembler arrayStore(Type type) { + mv.visitInsn(type.getOpcode(IASTORE)); + return this; + } + + public Assembler jump(int opcode, Label l) { + mv.visitJumpInsn(opcode, l); + return this; + } + + public Assembler ldc(Object o) { + if (o == null) { + return loadNull(); + } + if (o instanceof Integer) { + int i = (int) o; + if (i >= -1 && i <= 5) { + int opcode = -1; + switch (i) { + case 0: + { + opcode = ICONST_0; + break; + } + case 1: + { + opcode = ICONST_1; + break; + } + case 2: + { + opcode = ICONST_2; + break; + } + case 3: + { + opcode = ICONST_3; + break; + } + case 4: + { + opcode = ICONST_4; + break; + } + case 5: + { + opcode = ICONST_5; + break; + } + case -1: + { + opcode = ICONST_M1; + break; + } + } + mv.visitInsn(opcode); + return this; + } + } + if (o instanceof Long) { + long l = (long) o; + if (l >= 0 && l <= 1) { + int opcode = -1; + switch ((int) l) { + case 0: + { + opcode = LCONST_0; + break; + } + case 1: + { + opcode = LCONST_1; + break; + } + } + mv.visitInsn(opcode); + return this; + } + } + mv.visitLdcInsn(o); + return this; + } + + public Assembler sub(Type t) { + int opcode = -1; + switch (t.getSort()) { + case Type.SHORT: + case Type.BYTE: + case Type.INT: + { + opcode = ISUB; + break; + } + case Type.LONG: + { + opcode = LSUB; + break; + } + case Type.FLOAT: + { + opcode = FSUB; + break; + } + case Type.DOUBLE: + { + opcode = DSUB; + break; + } + } + if (opcode != -1) { + mv.visitInsn(opcode); + } + return this; + } + + public Assembler loadNull() { + mv.visitInsn(ACONST_NULL); + return this; + } + + public Assembler loadLocal(Type type, int index) { + mv.visitVarInsn(type.getOpcode(ILOAD), index); + return this; + } + + public Assembler storeLocal(Type type, int index) { + mv.visitVarInsn(type.getOpcode(ISTORE), index); + return this; + } + + public Assembler storeField(Type owner, String name, Type t) { + mv.visitFieldInsn(PUTFIELD, owner.getInternalName(), name, t.getDescriptor()); + return this; + } + + public Assembler storeStaticField(Type owner, String name, Type t) { + mv.visitFieldInsn(PUTSTATIC, owner.getInternalName(), name, t.getDescriptor()); + return this; + } + + public Assembler loadField(Type owner, String name, Type t) { + mv.visitFieldInsn(GETFIELD, owner.getInternalName(), name, t.getDescriptor()); + return this; + } + + public Assembler loadStaticField(Type owner, String name, Type t) { + mv.visitFieldInsn(GETSTATIC, owner.getInternalName(), name, t.getDescriptor()); + return this; + } + + public Assembler pop() { + mv.visitInsn(POP); + return this; + } + + public Assembler dup() { + mv.visitInsn(DUP); + return this; + } + + public Assembler dup_x1() { + mv.visitInsn(DUP_X1); + return this; + } + + public Assembler dup_x2() { + mv.visitInsn(DUP_X2); + return this; + } + + public Assembler dup2() { + mv.visitInsn(DUP2); + return this; + } + + public Assembler dup2_x1() { + mv.visitInsn(DUP2_X1); + return this; + } + + public Assembler dup2_x2() { + mv.visitInsn(DUP2_X2); + return this; + } + + public Assembler swap() { + mv.visitInsn(SWAP); + return this; + } + + public Assembler newInstance(Type t) { + mv.visitTypeInsn(NEW, t.getInternalName()); + return this; + } + + public Assembler newArray(Type t) { + mv.visitTypeInsn(ANEWARRAY, t.getInternalName()); + return this; + } + + public Assembler dupArrayValue(int arrayOpcode) { + switch (arrayOpcode) { + case IALOAD: + case FALOAD: + case AALOAD: + case BALOAD: + case CALOAD: + case SALOAD: + case IASTORE: + case FASTORE: + case AASTORE: + case BASTORE: + case CASTORE: + case SASTORE: + dup(); + break; + + case LALOAD: + case DALOAD: + case LASTORE: + case DASTORE: + dup2(); + break; + } + return this; + } + + public Assembler dupReturnValue(int returnOpcode) { + switch (returnOpcode) { + case IRETURN: + case FRETURN: + case ARETURN: + mv.visitInsn(DUP); + break; + case LRETURN: + case DRETURN: + mv.visitInsn(DUP2); + break; + case RETURN: + break; + default: + throw new IllegalArgumentException("not return"); + } + return this; + } + + public Assembler dupValue(Type type) { + switch (type.getSize()) { + case 1: + dup(); + break; + case 2: + dup2(); + break; + } + return this; + } + + public Assembler dupValue(String desc) { + int typeCode = desc.charAt(0); + switch (typeCode) { + case '[': + case 'L': + case 'Z': + case 'C': + case 'B': + case 'S': + case 'I': + mv.visitInsn(DUP); + break; + case 'J': + case 'D': + mv.visitInsn(DUP2); + break; + default: + throw new RuntimeException("invalid signature"); + } + return this; + } + + public Assembler box(Type type) { + return box(type.getDescriptor()); + } + + public Assembler box(String desc) { + int typeCode = desc.charAt(0); + switch (typeCode) { + case '[': + case 'L': + break; + case 'Z': + invokeStatic(BOOLEAN_BOXED_INTERNAL, BOX_VALUEOF, BOX_BOOLEAN_DESC); + break; + case 'C': + invokeStatic(CHARACTER_BOXED_INTERNAL, BOX_VALUEOF, BOX_CHARACTER_DESC); + break; + case 'B': + invokeStatic(BYTE_BOXED_INTERNAL, BOX_VALUEOF, BOX_BYTE_DESC); + break; + case 'S': + invokeStatic(SHORT_BOXED_INTERNAL, BOX_VALUEOF, BOX_SHORT_DESC); + break; + case 'I': + invokeStatic(INTEGER_BOXED_INTERNAL, BOX_VALUEOF, BOX_INTEGER_DESC); + break; + case 'J': + invokeStatic(LONG_BOXED_INTERNAL, BOX_VALUEOF, BOX_LONG_DESC); + break; + case 'F': + invokeStatic(FLOAT_BOXED_INTERNAL, BOX_VALUEOF, BOX_FLOAT_DESC); + break; + case 'D': + invokeStatic(DOUBLE_BOXED_INTERNAL, BOX_VALUEOF, BOX_DOUBLE_DESC); + break; + } + return this; + } + + public Assembler unbox(Type type) { + return unbox(type.getDescriptor()); + } + + public Assembler unbox(String desc) { + int typeCode = desc.charAt(0); + switch (typeCode) { + case '[': + case 'L': + mv.visitTypeInsn(CHECKCAST, Type.getType(desc).getInternalName()); + break; + case 'Z': + mv.visitTypeInsn(CHECKCAST, BOOLEAN_BOXED_INTERNAL); + invokeVirtual(BOOLEAN_BOXED_INTERNAL, BOOLEAN_VALUE, BOOLEAN_VALUE_DESC); + break; + case 'C': + mv.visitTypeInsn(CHECKCAST, CHARACTER_BOXED_INTERNAL); + invokeVirtual(CHARACTER_BOXED_INTERNAL, CHAR_VALUE, CHAR_VALUE_DESC); + break; + case 'B': + mv.visitTypeInsn(CHECKCAST, NUMBER_INTERNAL); + invokeVirtual(NUMBER_INTERNAL, BYTE_VALUE, BYTE_VALUE_DESC); + break; + case 'S': + mv.visitTypeInsn(CHECKCAST, NUMBER_INTERNAL); + invokeVirtual(NUMBER_INTERNAL, SHORT_VALUE, SHORT_VALUE_DESC); + break; + case 'I': + mv.visitTypeInsn(CHECKCAST, NUMBER_INTERNAL); + invokeVirtual(NUMBER_INTERNAL, INT_VALUE, INT_VALUE_DESC); + break; + case 'J': + mv.visitTypeInsn(CHECKCAST, NUMBER_INTERNAL); + invokeVirtual(NUMBER_INTERNAL, LONG_VALUE, LONG_VALUE_DESC); + break; + case 'F': + mv.visitTypeInsn(CHECKCAST, NUMBER_INTERNAL); + invokeVirtual(NUMBER_INTERNAL, FLOAT_VALUE, FLOAT_VALUE_DESC); + break; + case 'D': + mv.visitTypeInsn(CHECKCAST, NUMBER_INTERNAL); + invokeVirtual(NUMBER_INTERNAL, DOUBLE_VALUE, DOUBLE_VALUE_DESC); + break; + } + return this; + } + + public Assembler defaultValue(String desc) { + int typeCode = desc.charAt(0); + switch (typeCode) { + case '[': + case 'L': + mv.visitInsn(ACONST_NULL); + break; + case 'Z': + case 'C': + case 'B': + case 'S': + case 'I': + mv.visitInsn(ICONST_0); + break; + case 'J': + mv.visitInsn(LCONST_0); + break; + case 'F': + mv.visitInsn(FCONST_0); + break; + case 'D': + mv.visitInsn(DCONST_0); + break; + } + return this; + } + + public Assembler println(String msg) { + mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); + mv.visitLdcInsn(msg); + invokeVirtual("java/io/PrintStream", "println", "(Ljava/lang/String;)V"); + return this; + } + + // print the object on the top of the stack + public Assembler printObject() { + mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); + mv.visitInsn(SWAP); + mv.visitMethodInsn( + INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false); + return this; + } + + public Assembler invokeVirtual(String owner, String method, String desc) { + mv.visitMethodInsn(INVOKEVIRTUAL, owner, method, desc, false); + return this; + } + + public Assembler invokeSpecial(String owner, String method, String desc) { + mv.visitMethodInsn(INVOKESPECIAL, owner, method, desc, false); + return this; + } + + public Assembler invokeStatic(String owner, String method, String desc) { + mv.visitMethodInsn(INVOKESTATIC, owner, method, desc, false); + return this; + } + + public Assembler invokeDynamic( + String name, String descriptor, Handle bootstrap, Object... bootstrapArguments) { + mv.visitInvokeDynamicInsn(name, descriptor, bootstrap, bootstrapArguments); + return this; + } + + public Assembler invokeInterface(String owner, String method, String desc) { + mv.visitMethodInsn(INVOKEVIRTUAL, owner, method, desc, true); + return this; + } + + public Assembler getStatic(String owner, String name, String desc) { + mv.visitFieldInsn(GETSTATIC, owner, name, desc); + return this; + } + + public Assembler putStatic(String owner, String name, String desc) { + mv.visitFieldInsn(PUTSTATIC, owner, name, desc); + return this; + } + + public Assembler label(Label l) { + mv.visitLabel(l); + return this; + } + + public Assembler addLevelCheck(String clsName, Level level, Label jmp) { + return addLevelCheck(clsName, level.getValue(), jmp); + } + + public Assembler addLevelCheck(String clsName, Interval itv, Label jmp) { + getStatic(clsName, "$btrace$$level", INT_DESC); + if (itv.getA() <= 0) { + if (itv.getB() != Integer.MAX_VALUE) { + ldc(itv.getB()); + jump(IF_ICMPGT, jmp); + } + } else if (itv.getA() < itv.getB()) { + if (itv.getB() == Integer.MAX_VALUE) { + ldc(itv.getA()); + jump(IF_ICMPLT, jmp); + } else { + ldc(itv.getA()); + jump(IF_ICMPLT, jmp); + getStatic(clsName, "$btrace$$level", INT_DESC); + ldc(itv.getB()); + jump(IF_ICMPGT, jmp); + } + } + return this; + } + + /** + * Compares the instrumentation level interval against the runtime value. + * + *

If the runtime value is fitting the level interval there will be 0 on stack upon return from + * this method. Otherwise there will be -1. + * + * @param clsName The probe class name + * @param level The probe instrumentation level + * @return itself + */ + public Assembler compareLevel(String clsName, Level level) { + Interval itv = level.getValue(); + if (itv.getA() <= 0) { + if (itv.getB() != Integer.MAX_VALUE) { + ldc(itv.getB()); + getStatic(clsName, BTRACE_LEVEL_FLD, INT_DESC); + sub(Type.INT_TYPE); + } + } else if (itv.getA() < itv.getB()) { + if (itv.getB() == Integer.MAX_VALUE) { + getStatic(clsName, BTRACE_LEVEL_FLD, INT_DESC); + ldc(itv.getA()); + sub(Type.INT_TYPE); + } else { + Label l1 = new Label(); + Label l2 = new Label(); + ldc(itv.getA()); + jump(IF_ICMPLT, l1); + getStatic(clsName, BTRACE_LEVEL_FLD, INT_DESC); + ldc(itv.getB()); + jump(IF_ICMPGT, l1); + ldc(0); + Label l3 = new Label(); + label(l3); + mHelper.insertFrameSameStack(l3); + jump(GOTO, l2); + label(l1); + mHelper.insertFrameSameStack(l1); + ldc(-1); + label(l2); + mHelper.insertFrameSameStack(l2); + } + } + return this; + } +} diff --git a/btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceBCPClassLoader.java b/btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceBCPClassLoader.java new file mode 100644 index 000000000..0f3809d2f --- /dev/null +++ b/btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceBCPClassLoader.java @@ -0,0 +1,51 @@ +package org.openjdk.btrace.instr; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.List; +import org.openjdk.btrace.core.SharedSettings; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +final class BTraceBCPClassLoader extends URLClassLoader { + private static final Logger log = LoggerFactory.getLogger(BTraceBCPClassLoader.class); + + BTraceBCPClassLoader(SharedSettings settings) { + super(getBCPUrls(settings), null); + } + + private static URL[] getBCPUrls(SharedSettings settings) { + String bcp = settings.getBootClassPath(); + if (bcp != null && !bcp.isEmpty()) { + List urls = new ArrayList<>(); + for (String cpElement : bcp.split(File.pathSeparator)) { + try { + urls.add(new File(cpElement).toURI().toURL()); + } catch (MalformedURLException e) { + log.debug("Invalid classpath definition: {}", cpElement, e); + } + } + return urls.toArray(new URL[0]); + } + return new URL[0]; + } + + @Override + public Class loadClass(String name) throws ClassNotFoundException { + // delegate class loading to parent directly + ClassLoader parent = getParent(); + if (parent == null) { + parent = ClassLoader.getSystemClassLoader(); + } + return parent.loadClass(name); + } + + @Override + public URL getResource(String name) { + // follow the standard process to load resources + return super.getResource(name); + } +} diff --git a/btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceClassReader.java b/btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceClassReader.java new file mode 100644 index 000000000..daa7cbd32 --- /dev/null +++ b/btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceClassReader.java @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.btrace.instr; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +/** + * A hacked version of ClassReader + * allowing fast access to class name, class version, super type, interfaces and annotations. + * + * @author Jaroslav Bachorik + */ +final class BTraceClassReader extends ClassReader { + private static final Method getAttributesMthd; + private static final Method readAnnotationValuesMthd; + private static final Field itemsFld; + private static final Field mslFld; + + static { + Method m1 = null, m2 = null; + Field f1 = null, f2 = null; + + try { + m1 = ClassReader.class.getDeclaredMethod("getFirstAttributeOffset"); + m1.setAccessible(true); + + m2 = + ClassReader.class.getDeclaredMethod( + "readElementValue", AnnotationVisitor.class, int.class, String.class, char[].class); + m2.setAccessible(true); + + f1 = ClassReader.class.getDeclaredField("cpInfoOffsets"); + f1.setAccessible(true); + + f2 = ClassReader.class.getDeclaredField("maxStringLength"); + f2.setAccessible(true); + } catch (Exception e) { + e.printStackTrace(); + } + getAttributesMthd = m1; + readAnnotationValuesMthd = m2; + itemsFld = f1; + mslFld = f2; + } + + private final ClassLoader cl; + + BTraceClassReader(ClassLoader cl, byte[] bytes) { + super(bytes); + this.cl = cl; + } + + BTraceClassReader(ClassLoader cl, InputStream in) throws IOException { + super(in); + this.cl = cl; + } + + public static void bailout() { + throw BailoutException.INSTANCE; + } + + public ClassLoader getClassLoader() { + return cl; + } + + /** + * The associated Java class name ('.' is the package delimiter) + * + * @return + */ + public String getJavaClassName() { + return getClassName().replace('/', '.'); + } + + public String[] readClassSupers() { + String[] ifaces = getInterfaces(); + String[] supers = Arrays.copyOf(ifaces, ifaces.length + 1); + supers[supers.length - 1] = getSuperName(); + return supers; + } + + public boolean isInterface() { + return (getAccess() & Opcodes.ACC_INTERFACE) != 0; + } + + public boolean isBTrace() { + return getAnnotationTypes().contains(Constants.BTRACE_DESC); + } + + public Collection getAnnotationTypes() { + Collection types = new HashSet<>(); + + char[] c = new char[getMaxStringLength()]; // buffer used to read strings + int anns = getAnnotationsOffset(c); + if (anns != -1) { + for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) { + types.add(Type.getType(readUTF8(v, c)).getClassName()); + v = skipAnnotationValues(v + 2, c); + if (v == -1) break; + } + } + return types; + } + + public int getClassVersion() { + try { + if (itemsFld != null) { + int[] items = (int[]) itemsFld.get(this); + return readInt(items[1] - 7); + } + } catch (Exception e) { + e.printStackTrace(); + } + return 51; // default to Java 7 + } + + @Override + public void accept(ClassVisitor cv, Attribute[] atrbts, int i) { + try { + super.accept(cv, atrbts, i); + } catch (BailoutException e) { + // expected; ignore + } + } + + @Override + public void accept(ClassVisitor cv, int i) { + try { + super.accept(cv, i); + } catch (BailoutException e) { + // expected; ignore + } + } + + private int getAttributes() { + try { + if (getAttributesMthd != null) { + return (int) getAttributesMthd.invoke(this); + } + } catch (Exception e) { + e.printStackTrace(); + } + return -1; + } + + private int getAnnotationsOffset(char[] buf) { + int u = getAttributes(); + if (u == -1) { + return -1; + } + for (int i = readUnsignedShort(u - 2); i > 0; --i) { + String attrName = readUTF8(u, buf); + int attributeLength = readInt(u + 2); + u += 6; + if ("RuntimeVisibleAnnotations".equals(attrName)) { + return u; + } + u += attributeLength; + } + return -1; + } + + private int skipAnnotationValues(int off, char[] buf) { + try { + if (readAnnotationValuesMthd != null) { + return (int) readAnnotationValuesMthd.invoke(this, null, off, null, buf); + } + } catch (Exception e) { + e.printStackTrace(); + } + return -1; + } +} diff --git a/btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceClassWriter.java b/btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceClassWriter.java new file mode 100644 index 000000000..862be8cd6 --- /dev/null +++ b/btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceClassWriter.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.btrace.instr; + +import java.util.ArrayDeque; +import java.util.Collection; +import java.util.Deque; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.MethodNode; + +/** + * A hacked version of ClassWriter + * allowing to plug-in instrumentation providers and instrument class in single invocation. Also, it + * provides a smart and lightweight common supertype resolution method for computing frames. + * + *

The class is not thread-safe but since there is exactly one instance per instrumented class + * there is no chance of parallel access ever happening. + * + * @author Jaroslav Bachorik + */ +final class BTraceClassWriter extends ClassWriter { + private final Deque instrumentors = new ArrayDeque<>(); + private final ClassLoader targetCL; + private final BTraceClassReader cr; + private final Collection cushionMethods = new HashSet<>(); + + BTraceClassWriter(ClassLoader cl, int flags) { + super(flags); + targetCL = cl != null ? cl : ClassLoader.getSystemClassLoader(); + cr = null; + } + + BTraceClassWriter(ClassLoader cl, BTraceClassReader reader, int flags) { + super(reader, flags); + targetCL = cl != null ? cl : ClassLoader.getSystemClassLoader(); + cr = reader; + } + + public void addInstrumentor(BTraceProbe bp) { + addInstrumentor(bp, null); + } + + public void addInstrumentor(BTraceProbe bp, ClassLoader cl) { + if (cr != null && bp != null) { + Instrumentor top = instrumentors.peekLast(); + ClassVisitor parent = top != null ? top : this; + Instrumentor i = Instrumentor.create(cr, bp, parent, cl); + if (i != null) { + instrumentors.add(i); + } + } + } + + public byte[] instrument() { + boolean hit = false; + if (instrumentors.isEmpty()) return null; + + Instrumentor top = instrumentors.peekLast(); + ClassVisitor cv = + new ClassVisitor(Opcodes.ASM9, top != null ? top : this) { + @Override + public void visitEnd() { + if (top != null && top.hasCushionMethods()) { + for (MethodNode m : cushionMethods) { + m.accept(this); + } + } + super.visitEnd(); + } + }; + InstrumentUtils.accept(cr, cv); + for (Instrumentor i : instrumentors) { + hit |= i.hasMatch(); + } + return hit ? toByteArray() : null; + } + + @Override + protected String getCommonSuperClass(String type1, String type2) { + // Using type closures resolved via the associate classloader + LinkedHashSet type1Closure = new LinkedHashSet<>(); + LinkedHashSet type2Closure = new LinkedHashSet<>(); + InstrumentUtils.collectHierarchyClosure(targetCL, type1, type1Closure, true); + InstrumentUtils.collectHierarchyClosure(targetCL, type2, type2Closure, true); + // basically, do intersection + type1Closure.retainAll(type2Closure); + + // if the intersection is not empty the first element is the closest common ancestor + Iterator iter = type1Closure.iterator(); + if (iter.hasNext()) { + return iter.next(); + } + return Constants.OBJECT_INTERNAL; + } + + /** + * Add dummy cushion methods to account for an instrumented code in hot loop still running even + * once the instrumentation was removed (code can not be hotswapped as long as the affected method + * is on stack so it may happen that an instrumentation from a disconnected BTrace client will be + * running for a long time) + * + * @param pillowMethods the methods to create cushions for + */ + public void addCushionMethods(Collection pillowMethods) { + cushionMethods.addAll(pillowMethods); + } +} diff --git a/btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceMethodNode.java b/btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceMethodNode.java new file mode 100644 index 000000000..896527784 --- /dev/null +++ b/btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceMethodNode.java @@ -0,0 +1,419 @@ +/* + * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.btrace.instr; + +import java.util.Comparator; +import java.util.Set; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.MethodNode; +import org.openjdk.btrace.core.annotations.Kind; +import org.openjdk.btrace.core.annotations.Sampled; +import org.openjdk.btrace.core.annotations.Where; + +/** @author Jaroslav Bachorik */ +public class BTraceMethodNode extends MethodNode { + public static final Comparator COMPARATOR = + (o1, o2) -> (o1.name + "#" + o1.desc).compareTo(o2.name + "#" + o2.desc); + private final BTraceProbeNode cn; + private final CallGraph graph; + private final String methodId; + private OnMethod om; + private OnProbe op; + private Location loc; + private boolean sampled; + private boolean isBTraceHandler; + + BTraceMethodNode(MethodNode from, BTraceProbeNode cn) { + this(from, cn, false); + } + + BTraceMethodNode(MethodNode from, BTraceProbeNode cn, boolean initBTraceHandler) { + super( + Opcodes.ASM9, + from.access, + from.name, + from.desc, + from.signature, + from.exceptions.toArray(new String[0])); + this.cn = cn; + graph = cn.getGraph(); + methodId = CallGraph.methodId(name, desc); + isBTraceHandler = initBTraceHandler; + } + + @Override + public void visitEnd() { + if (om != null) { + verifySpecialParameters(om); + cn.addOnMethod(om); + } + if (op != null) { + cn.addOnProbe(op); + } + if (isBTraceHandler) { + graph.addStarting(new CallGraph.Node(methodId)); + } + super.visitEnd(); + } + + @Override + public AnnotationVisitor visitAnnotation(String type, boolean visible) { + AnnotationVisitor av = super.visitAnnotation(type, visible); + + if (type.startsWith("Lorg/openjdk/btrace/core/annotations/")) { + isBTraceHandler = true; + } + if (type.equals(Constants.ONMETHOD_DESC)) { + om = new OnMethod(this); + om.setTargetName(name); + om.setTargetDescriptor(desc); + return new AnnotationVisitor(Opcodes.ASM9, av) { + @Override + public void visit(String name, Object value) { + super.visit(name, value); + + switch (name) { + case "clazz": + om.setClazz((String) value); + break; + case "method": + om.setMethod((String) value); + break; + case "type": + om.setType((String) value); + break; + case "exactTypeMatch": + { + om.setExactTypeMatch((boolean) value); + break; + } + default: + System.err.println("btrace WARNING: Unsupported @OnMethod attribute: " + name); + } + } + + @Override + public AnnotationVisitor visitAnnotation(String name, String desc) { + AnnotationVisitor av1 = super.visitAnnotation(name, desc); + if (desc.equals(Constants.LOCATION_DESC)) { + loc = new Location(); + return new AnnotationVisitor(Opcodes.ASM9, av1) { + @Override + public void visitEnum(String name, String desc, String value) { + super.visitEnum(name, desc, value); + + if (desc.equals(Constants.WHERE_DESC)) { + loc.setWhere(Enum.valueOf(Where.class, value)); + } else if (desc.equals(Constants.KIND_DESC)) { + loc.setValue(Enum.valueOf(Kind.class, value)); + } + } + + @Override + public void visit(String name, Object value) { + super.visit(name, value); + + switch (name) { + case "clazz": + loc.setClazz((String) value); + break; + case "method": + loc.setMethod((String) value); + break; + case "type": + loc.setType((String) value); + break; + case "field": + loc.setField((String) value); + break; + case "line": + loc.setLine(((Number) value).intValue()); + break; + } + } + + @Override + public void visitEnd() { + if (loc != null) { + om.setLocation(loc); + } + super.visitEnd(); + } + }; + } else if (desc.equals(Constants.LEVEL_DESC)) { + return new AnnotationVisitor(Opcodes.ASM9, av1) { + @Override + public void visit(String name, Object value) { + super.visit(name, value); + + if ("value".equals(name)) { + om.setLevel(Level.fromString((String) value)); + } + } + }; + } + return av1; + } + }; + } else if (type.equals(Constants.ONPROBE_DESC)) { + op = new OnProbe(this); + op.setTargetName(name); + op.setTargetDescriptor(desc); + return new AnnotationVisitor(Opcodes.ASM9, av) { + @Override + public void visit(String name, Object value) { + super.visit(name, value); + + switch (name) { + case "namespace": + op.setNamespace((String) value); + break; + case "name": + op.setName((String) value); + break; + } + } + }; + } else if (type.equals(Constants.SAMPLED_DESC)) { + if (om != null) { + om.setSamplerKind(Sampled.Sampler.Adaptive); + return new AnnotationVisitor(Opcodes.ASM9, av) { + private boolean meanSet = false; + + @Override + public void visit(String name, Object value) { + super.visit(name, value); + + if (name.equals("mean")) { + om.setSamplerMean((Integer) value); + meanSet = true; + } + } + + @Override + public void visitEnum(String name, String desc, String value) { + super.visitEnum(name, desc, value); + + if (name.equals("kind") && desc.equals(Constants.SAMPLER_DESC)) { + om.setSamplerKind(Sampled.Sampler.valueOf(value)); + } + } + + @Override + public void visitEnd() { + if (!meanSet) { + if (om.getSamplerKind() == Sampled.Sampler.Adaptive) { + om.setSamplerMean(500); + } else if (om.getSamplerKind() == Sampled.Sampler.Const) { + om.setSamplerMean(Sampled.MEAN_DEFAULT); + } + } + if (om.getSamplerKind() == Sampled.Sampler.Adaptive) { + // the time frame for adaptive sampling + // should be at least 180ns - + // (80ns timestamps + 15ns stub) * 2 safety margin + if (om.getSamplerMean() < 180) { + System.err.println( + "Setting the adaptive sampler time windows to the default of 180ns"); + om.setSamplerMean(180); + } + } + super.visitEnd(); + } + }; + } + sampled = true; + } + return av; + } + + @Override + public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) { + AnnotationVisitor av = super.visitParameterAnnotation(parameter, desc, visible); + + if (om != null) { + av = setSpecialParameters(om, desc, parameter, av); + } else if (op != null) { + av = setSpecialParameters(op, desc, parameter, av); + } + + return av; + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + owner = cn.translateOwner(owner); + if (opcode == Opcodes.INVOKESTATIC) { + graph.addEdge(methodId, CallGraph.methodId(name, desc)); + } + super.visitMethodInsn(opcode, owner, name, desc, itf); + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + super.visitFieldInsn(opcode, cn.translateOwner(owner), name, desc); + } + + public boolean isBcpRequired() { + return isBTraceHandler && om == null && op == null; + } + + public boolean isBTraceHandler() { + return isBTraceHandler; + } + + public OnMethod getOnMethod() { + return om; + } + + public boolean isSampled() { + return sampled; + } + + public Set getCallees() { + return cn.callees(name, desc); + } + + public Set getCallers() { + return cn.callers(name, desc); + } + + boolean isFieldInjected(String name) { + return cn.isFieldInjected(name); + } + + OnProbe getOnProbe() { + return op; + } + + private AnnotationVisitor setSpecialParameters( + SpecialParameterHolder ph, String desc, int parameter, AnnotationVisitor av) { + // for OnProbe the 'loc' variable will be null; we will need to verfiy the placement later on + if (desc.equals(Constants.SELF_DESC)) { + ph.setSelfParameter(parameter); + } else if (desc.equals(Constants.BTRACE_PROBECLASSNAME_DESC)) { + ph.setClassNameParameter(parameter); + } else if (desc.equals(Constants.BTRACE_PROBEMETHODNAME_DESC)) { + ph.setMethodParameter(parameter); + av = + new AnnotationVisitor(Opcodes.ASM9, av) { + @Override + public void visit(String name, Object val) { + if (name.equals("fqn")) { + ph.setMethodFqn((Boolean) val); + } + super.visit(name, val); + } + }; + } else if (desc.equals(Constants.RETURN_DESC)) { + ph.setReturnParameter(parameter); + } else if (desc.equals(Constants.TARGETMETHOD_DESC)) { + ph.setTargetMethodOrFieldParameter(parameter); + + av = + new AnnotationVisitor(Opcodes.ASM9, av) { + @Override + public void visit(String name, Object val) { + if (name.equals("fqn")) { + ph.setTargetMethodOrFieldFqn((Boolean) val); + } + super.visit(name, val); + } + }; + } else if (desc.equals(Constants.TARGETINSTANCE_DESC)) { + ph.setTargetInstanceParameter(parameter); + } else if (desc.equals(Constants.DURATION_DESC)) { + ph.setDurationParameter(parameter); + } + return av; + } + + private void verifySpecialParameters(OnMethod om) { + Location loc = om.getLocation(); + if (om.getReturnParameter() != -1) { + if (!(loc.getValue() == Kind.RETURN + || (loc.getValue() == Kind.CALL && loc.getWhere() == Where.AFTER) + || (loc.getValue() == Kind.ARRAY_GET && loc.getWhere() == Where.AFTER) + || (loc.getValue() == Kind.FIELD_GET && loc.getWhere() == Where.AFTER) + || (loc.getValue() == Kind.NEW && loc.getWhere() == Where.AFTER) + || (loc.getValue() == Kind.NEWARRAY && loc.getWhere() == Where.AFTER))) { + Verifier.reportError( + "return.desc.invalid", + om.getTargetName() + om.getTargetDescriptor() + "(" + om.getReturnParameter() + ")"); + } + } + if (om.getTargetMethodOrFieldParameter() != -1) { + if (!(loc.getValue() == Kind.CALL + || loc.getValue() == Kind.FIELD_GET + || loc.getValue() == Kind.FIELD_SET + || loc.getValue() == Kind.ARRAY_GET + || loc.getValue() == Kind.ARRAY_SET)) { + Verifier.reportError( + "target-method.desc.invalid", + om.getTargetName() + + om.getTargetDescriptor() + + "(" + + om.getTargetMethodOrFieldParameter() + + ")"); + } + } + if (om.getTargetInstanceParameter() != -1) { + if (!(loc.getValue() == Kind.CALL + || loc.getValue() == Kind.FIELD_GET + || loc.getValue() == Kind.FIELD_SET + || loc.getValue() == Kind.ARRAY_GET + || loc.getValue() == Kind.ARRAY_SET + || loc.getValue() == Kind.INSTANCEOF + || loc.getValue() == Kind.CHECKCAST + || loc.getValue() == Kind.ERROR + || loc.getValue() == Kind.THROW + || loc.getValue() == Kind.CATCH + || loc.getValue() == Kind.SYNC_ENTRY + || loc.getValue() == Kind.SYNC_EXIT)) { + Verifier.reportError( + "target-instance.desc.invalid", + om.getTargetName() + + om.getTargetDescriptor() + + "(" + + om.getTargetInstanceParameter() + + ")"); + } + } + if (om.getDurationParameter() != -1) { + if (!((loc.getValue() == Kind.RETURN || loc.getValue() == Kind.ERROR) + || (loc.getValue() == Kind.CALL && loc.getWhere() == Where.AFTER))) { + Verifier.reportError( + "duration.desc.invalid", + om.getTargetName() + om.getTargetDescriptor() + "(" + om.getDurationParameter() + ")"); + } + } + } + + @Override + public String toString() { + return "BTraceMethodNode{name = " + name + ", desc=" + desc + '}'; + } +} diff --git a/btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceMethodVisitor.java b/btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceMethodVisitor.java new file mode 100644 index 000000000..469cc4c84 --- /dev/null +++ b/btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceMethodVisitor.java @@ -0,0 +1,41 @@ +package org.openjdk.btrace.instr; + +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +public class BTraceMethodVisitor extends MethodVisitor { + private final MethodInstrumentorHelper mHelper; + + public BTraceMethodVisitor(MethodVisitor mv, MethodInstrumentorHelper mHelper) { + super(Opcodes.ASM9, mv); + this.mHelper = mHelper; + } + + public final int storeAsNew() { + return mHelper.storeAsNew(); + } + + public final int storeNewLocal(Type t) { + int index = mHelper.newVar(t); + super.visitVarInsn(t.getOpcode(Opcodes.ISTORE), index); + return index; + } + + public final void addTryCatchHandler(Label start, Label handler) { + mHelper.addTryCatchHandler(start, handler); + } + + public void insertFrameReplaceStack(Label l, Type... stack) { + mHelper.insertFrameReplaceStack(l, stack); + } + + public void insertFrameAppendStack(Label l, Type... stack) { + mHelper.insertFrameAppendStack(l, stack); + } + + public void insertFrameSameStack(Label l) { + mHelper.insertFrameSameStack(l); + } +} diff --git a/btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceProbe.java b/btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceProbe.java new file mode 100644 index 000000000..8103979c4 --- /dev/null +++ b/btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceProbe.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + * Copyright (c) 2017, 2018, Jaroslav Bachorik . + * All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Copyright owner designates + * this particular file as subject to the "Classpath" exception as provided + * by the owner in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ +package org.openjdk.btrace.instr; + +import java.util.Collection; +import org.objectweb.asm.ClassVisitor; +import org.openjdk.btrace.core.ArgsMap; +import org.openjdk.btrace.core.BTraceRuntime; + +public interface BTraceProbe { + Collection getApplicableHandlers(BTraceClassReader cr); + + byte[] getFullBytecode(); + + byte[] getDataHolderBytecode(); + + String getClassName(); + + String getClassName(boolean internal); + + boolean isClassRenamed(); + + boolean isTransforming(); + + boolean isVerified(); + + void notifyTransform(String className); + + Iterable onmethods(); + + Iterable onprobes(); + + Class register(BTraceRuntime.Impl rt, BTraceTransformer t); + + void unregister(); + + boolean willInstrument(Class clz); + + void checkVerified(); + + void copyHandlers(ClassVisitor copyingVisitor); + + void applyArgs(ArgsMap argsMap); + + BTraceRuntime.Impl getRuntime(); +} diff --git a/btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceProbeFactory.java b/btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceProbeFactory.java new file mode 100644 index 000000000..db2286046 --- /dev/null +++ b/btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceProbeFactory.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + * Copyright (c) 2017, 2018, Jaroslav Bachorik . + * All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Copyright owner designates + * this particular file as subject to the "Classpath" exception as provided + * by the owner in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ +package org.openjdk.btrace.instr; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import org.openjdk.btrace.core.ArgsMap; +import org.openjdk.btrace.core.SharedSettings; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A factory class for {@link BTraceProbeNode} instances + * + * @author Jaroslav Bachorik + */ +public final class BTraceProbeFactory { + private static final Logger log = LoggerFactory.getLogger(BTraceProbeFactory.class); + + private static final int CLASS_MAGIC = 0xCAFEBABE; + + private final SharedSettings settings; + + public BTraceProbeFactory(SharedSettings settings) { + this.settings = settings; + } + + private static void applyArgs(BTraceProbe bp, ArgsMap argsMap) { + if (bp != null && argsMap != null && !argsMap.isEmpty()) { + bp.applyArgs(argsMap); + } + } + + /** + * Check if a particular file can be loaded as a BTrace probe. Currently only the plain class file + * and BTrace probe pack are supported. + * + * @param filePath the file path + * @return {@literal true} if a BTrace probe can be reconstructed from data in the given file + */ + public static boolean canLoad(String filePath) { + return canLoad(filePath, null); + } + + public static boolean canLoad(String filePath, ClassLoader cl) { + if (filePath == null) { + return false; + } + Path path = Paths.get(filePath); + InputStream is = null; + try { + if (!Files.exists(path)) { + // try to load from the classpath + if (cl == null) { + cl = ClassLoader.getSystemClassLoader(); + } + is = cl.getResourceAsStream("META-INF/btrace/" + filePath); + } else { + is = Files.newInputStream(path); + } + if (is != null) { + try { + try (DataInputStream dis = new DataInputStream(is)) { + int magic = dis.readInt(); + return magic == CLASS_MAGIC || magic == BTraceProbePersisted.MAGIC; + } + } catch (IOException ignored) { + is = null; + } + } + } catch (IOException ignored) { + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException ignored) { + } + } + } + return false; + } + + SharedSettings getSettings() { + return settings; + } + + public BTraceProbe createProbe(byte[] code) { + return createProbe(code, null); + } + + public BTraceProbe createProbe(byte[] code, ArgsMap argsMap) { + BTraceProbe bp = null; + + int mgc = + ((code[0] & 0xff) << 24) + | ((code[1] & 0xff) << 16) + | ((code[2] & 0xff) << 8) + | ((code[3] & 0xff)); + if (mgc == BTraceProbePersisted.MAGIC) { + BTraceProbePersisted bpp = new BTraceProbePersisted(this); + try (DataInputStream dis = + new DataInputStream(new ByteArrayInputStream(Arrays.copyOfRange(code, 4, code.length)))) { + bpp.read(dis); + bp = bpp; + } catch (IOException e) { + log.debug("Failed to read BTrace pack", e); + } + } else { + bp = new BTraceProbeNode(this, code); + } + + applyArgs(bp, argsMap); + HandlerRepositoryImpl.registerProbe(bp); + return bp; + } + + public BTraceProbe createProbe(InputStream code) { + return createProbe(code, null); + } + + public BTraceProbe createProbe(InputStream code, ArgsMap argsMap) { + BTraceProbe bp = null; + try (DataInputStream dis = new DataInputStream(code)) { + dis.mark(0); + int mgc = dis.readInt(); + if (mgc == BTraceProbePersisted.MAGIC) { + BTraceProbePersisted bpp = new BTraceProbePersisted(this); + bpp.read(dis); + bp = bpp; + } else { + code.reset(); + bp = new BTraceProbeNode(this, code); + } + } catch (IOException e) { + log.debug("Failed to create a probe", e); + } + + applyArgs(bp, argsMap); + HandlerRepositoryImpl.registerProbe(bp); + return bp; + } +} diff --git a/btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceProbeNode.java b/btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceProbeNode.java new file mode 100644 index 000000000..47bff8c0b --- /dev/null +++ b/btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceProbeNode.java @@ -0,0 +1,519 @@ +/* + * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.instr; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; +import org.openjdk.btrace.core.ArgsMap; +import org.openjdk.btrace.core.BTraceRuntime; +import org.openjdk.btrace.core.DebugSupport; +import org.openjdk.btrace.core.VerifierException; +import org.openjdk.btrace.core.comm.RetransformClassNotification; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** @author Jaroslav Bachorik */ +public final class BTraceProbeNode extends ClassNode implements BTraceProbe { + private static final Logger log = LoggerFactory.getLogger(BTraceProbeNode.class); + + final BTraceProbeSupport delegate; + + final BTraceProbeFactory factory; + + final DebugSupport debug; + + private final CallGraph graph; + + private final Map idmap; + private final Set jfrHandlers = new HashSet<>(); + private final Preprocessor prep; + private final BTraceBCPClassLoader bcpResourceClassLoader; + + private volatile BTraceRuntime.Impl rt = null; + + private BTraceTransformer transformer; + private VerifierException verifierException = null; + + private BTraceProbeNode(BTraceProbeFactory factory) { + super(Opcodes.ASM9); + this.factory = factory; + bcpResourceClassLoader = new BTraceBCPClassLoader(factory.getSettings()); + debug = new DebugSupport(factory.getSettings()); + delegate = new BTraceProbeSupport(); + idmap = new HashMap<>(); + graph = new CallGraph(); + prep = new Preprocessor(); + } + + BTraceProbeNode(BTraceProbeFactory factory, byte[] code) { + this(factory); + initialize(code); + } + + BTraceProbeNode(BTraceProbeFactory factory, InputStream code) throws IOException { + this(factory); + initialize(code); + } + + @Override + public boolean isTransforming() { + return delegate.isTransforming(); + } + + @Override + public void visit( + int version, int access, String name, String sig, String superType, String[] itfcs) { + delegate.setClassName(name); + super.visit(version, access, delegate.getClassName(true), sig, superType, itfcs); + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String sig, String[] exceptions) { + super.visitMethod(access, name, desc, sig, exceptions); + MethodNode mn = methods.remove(methods.size() - 1); + BTraceMethodNode bmn = new BTraceMethodNode(mn, this, jfrHandlers.contains(name)); + methods.add(bmn); + idmap.put(CallGraph.methodId(name, desc), bmn); + return isTrusted() + ? bmn + : new MethodVerifier( + bmn, access, delegate.getOrigName(), name, desc, bcpResourceClassLoader); + } + + @Override + public FieldVisitor visitField( + int access, String name, String desc, String signature, Object value) { + return new FieldVisitor(Opcodes.ASM9, super.visitField(access, name, desc, signature, value)) { + @Override + public AnnotationVisitor visitAnnotation(String type, boolean aVisible) { + AnnotationVisitor av = super.visitAnnotation(type, aVisible); + if (type.equals(Constants.INJECTED_DESC)) { + delegate.addServiceField(name, Type.getType(desc).getInternalName()); + } + if (type.equals("Lorg/openjdk/btrace/core/annotations/Event;")) { + av = + new AnnotationVisitor(Opcodes.ASM8, av) { + @Override + public void visit(String name, Object value) { + if (name.equals("handler") && value instanceof String) { + jfrHandlers.add((String) value); + } + super.visit(name, value); + } + }; + } + return av; + } + }; + } + + @Override + public Collection getApplicableHandlers(BTraceClassReader cr) { + return delegate.getApplicableHandlers(cr); + } + + @Override + public Iterable onmethods() { + return delegate.onmethods(); + } + + public Collection getOnMethods() { + return delegate.getOnMethods(); + } + + @Override + public Iterable onprobes() { + return delegate.onprobes(); + } + + @Override + public String getClassName() { + return getClassName(false); + } + + @Override + public String getClassName(boolean internal) { + return delegate.getClassName(internal); + } + + String translateOwner(String owner) { + return delegate.translateOwner(owner); + } + + @Override + public Class register(BTraceRuntime.Impl rt, BTraceTransformer t) { + byte[] code = getBytecode(true); + if (debug.isDumpClasses()) { + debug.dumpClass(name + "_bcp", code); + } + Class clz = delegate.defineClass(rt, code); + t.register(this); + transformer = t; + this.rt = rt; + return clz; + } + + @Override + public void unregister() { + if (transformer != null && isTransforming()) { + if (log.isDebugEnabled()) { + log.debug("onExit: removing transformer for {}", getClassName()); + } + transformer.unregister(this); + } + rt = null; + } + + @Override + public byte[] getFullBytecode() { + return getBytecode(false); + } + + @Override + public byte[] getDataHolderBytecode() { + return getBytecode(true); + } + + @Override + public BTraceRuntime.Impl getRuntime() { + return rt; + } + + private byte[] getBytecode(boolean onlyBcpMethods) { + ClassWriter cw = InstrumentUtils.newClassWriter(true); + ClassVisitor cv = cw; + if (onlyBcpMethods) { + cv = + new ClassVisitor(Opcodes.ASM9, cw) { + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String sig, String[] exceptions) { + if (name.startsWith("<")) { + // never check constructor and static initializer + return super.visitMethod(access, name, desc, sig, exceptions); + } + BTraceMethodNode bmn = idmap.get(CallGraph.methodId(name, desc)); + if (bmn != null) { + if (bmn.isBcpRequired()) { + return super.visitMethod(access, name, desc, sig, exceptions); + } + for (BTraceMethodNode c : bmn.getCallers()) { + if (c.isBcpRequired()) { + return super.visitMethod(access, name, desc, sig, exceptions); + } + } + return null; + } + return super.visitMethod(access, name, desc, sig, exceptions); + } + }; + } + accept(cv); + return cw.toByteArray(); + } + + /** + * Collects all the methods reachable from this particular method + * + * @param name the method name + * @param desc the method descriptor + * @return the callee reachability closure + */ + Set callees(String name, String desc) { + Set closure = new HashSet<>(); + graph.callees(name, desc, closure); + return fromIdSet(closure); + } + + /** + * Collects all the methods from which this particular method is reachable + * + * @param name the method name + * @param desc the method descriptor + * @return the caller reachability closure + */ + Set callers(String name, String desc) { + Set closure = new HashSet<>(); + graph.callers(name, desc, closure); + return fromIdSet(closure); + } + + @Override + public boolean willInstrument(Class clz) { + return delegate.willInstrument(clz); + } + + @Override + public boolean isClassRenamed() { + return delegate.isClassRenamed(); + } + + @Override + public boolean isVerified() { + return verifierException == null; + } + + private VerifierException getVerifierException() { + return verifierException; + } + + boolean isFieldInjected(String name) { + return delegate.isFieldInjected(name); + } + + void addOnMethod(OnMethod om) { + delegate.addOnMethod(om); + } + + void addOnProbe(OnProbe op) { + delegate.addOnProbe(op); + } + + void setTrusted() { + delegate.setTrusted(); + } + + boolean isTrusted() { + return delegate.isTrusted(); + } + + CallGraph getGraph() { + return graph; + } + + @Override + public void notifyTransform(String className) { + if (rt != null && factory.getSettings().isTrackRetransforms()) { + rt.send(new RetransformClassNotification(className.replace('/', '.'))); + } + } + + @Override + public void checkVerified() { + if (!isVerified()) { + throw getVerifierException(); + } + } + + @Override + public void copyHandlers(ClassVisitor copyingVisitor) { + Set copyNodes = new TreeSet<>(BTraceMethodNode.COMPARATOR); + + for (OnMethod om : onmethods()) { + if (!om.isCalled()) { + continue; + } + + BTraceMethodNode bmn = om.getMethodNode(); + + MethodNode mn = copy(bmn); + + copyNodes.add(mn); + for (BTraceMethodNode c : bmn.getCallees()) { + copyNodes.add(copy(c)); + } + } + copyingVisitor.visit( + Opcodes.V1_7, + Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, + getClassName(true), + null, + "java/lang/Object", + null); + for (MethodNode mn : copyNodes) { + mn.accept(copyingVisitor); + } + copyingVisitor.visitEnd(); + } + + @Override + public void applyArgs(ArgsMap argsMap) { + delegate.applyArgs(argsMap); + } + + /** Maps a list of @OnProbe's to a list @OnMethod's using probe descriptor XML files. */ + private void mapOnProbes() { + ProbeDescriptorLoader pdl = getProbeDescriptorLoader(); + + for (OnProbe op : delegate.onprobes()) { + String ns = op.getNamespace(); + if (log.isDebugEnabled()) { + log.debug("about to load probe descriptor for namespace {}", ns); + } + // load probe descriptor for this namespace + ProbeDescriptor probeDesc = pdl.load(ns); + if (probeDesc == null) { + if (log.isDebugEnabled()) { + log.debug("failed to find probe descriptor for namespace {}", ns); + } + continue; + } + // find particular probe mappings using "local" name + OnProbe foundProbe = probeDesc.findProbe(op.getName()); + if (foundProbe == null) { + if (log.isDebugEnabled()) { + log.debug("no probe mappings for {}", op.getName()); + } + continue; + } + if (log.isDebugEnabled()) { + log.debug("found probe mappings for {}", op.getName()); + } + Collection omColl = foundProbe.getOnMethods(); + for (OnMethod om : omColl) { + // copy the info in a new OnMethod so that + // we can set target method name and descriptor + // Note that the probe descriptor cache is used + // across BTrace sessions. So, we should not update + // cached OnProbes (and their OnMethods). + OnMethod omn = new OnMethod(op.getMethodNode()); + omn.copyFrom(om); + omn.setTargetName(op.getTargetName()); + omn.setTargetDescriptor(op.getTargetDescriptor()); + omn.setClassNameParameter(op.getClassNameParameter()); + omn.setMethodParameter(op.getMethodParameter()); + omn.setDurationParameter(op.getDurationParameter()); + omn.setMethodFqn(op.isMethodFqn()); + omn.setReturnParameter(op.getReturnParameter()); + omn.setSelfParameter(op.getSelfParameter()); + omn.setTargetInstanceParameter(op.getTargetInstanceParameter()); + omn.setTargetMethodOrFieldFqn(op.isTargetMethodOrFieldFqn()); + omn.setTargetMethodOrFieldParameter(op.getTargetMethodOrFieldParameter()); + addOnMethod(omn); + } + } + } + + private ProbeDescriptorLoader getProbeDescriptorLoader() { + String path = factory.getSettings().getProbeDescPath(); + return new ProbeDescriptorLoader(path); + } + + private void initialize(byte[] code) { + ClassReader cr = new ClassReader(code); + if (debug.isDumpClasses()) { + debug.dumpClass(cr.getClassName() + "_orig", code); + } + initialize(cr); + } + + private void initialize(InputStream code) throws IOException { + initialize(readFully(code)); + } + + private void initialize(ClassReader cr) { + try { + Verifier v = new Verifier(this, factory.getSettings().isTrusted()); + log.debug("verifying BTrace class ..."); + cr.accept(v, ClassReader.SKIP_DEBUG); + if (log.isDebugEnabled()) { + String clzName = getClassName(); + log.debug("BTrace class {} verified", clzName); + log.debug("preprocessing BTrace class {} ...", clzName); + } + prep.process(this); + log.debug("... preprocessed"); + try { + Class.forName("javax.xml.bind.JAXBException"); + mapOnProbes(); + } catch (ClassNotFoundException e) { + log.debug("XML bindings are missing. @OnProbe support is disabled."); + } + } catch (VerifierException e) { + verifierException = e; + } finally { + if (debug.isDumpClasses()) { + debug.dumpClass(name, getBytecode(false)); + } + } + } + + private Set fromIdSet(Set ids) { + Set methods = new HashSet<>(); + for (String id : ids) { + BTraceMethodNode mn = idmap.get(id); + if (mn != null) { + methods.add(mn); + } + } + return methods; + } + + private MethodNode copy(MethodNode n) { + String[] exceptions = n.exceptions != null ? n.exceptions.toArray(new String[0]) : null; + MethodNode mn = new MethodNode(Opcodes.ASM9, n.access, n.name, n.desc, n.signature, exceptions); + n.accept(mn); + mn.access = Opcodes.ACC_STATIC | Opcodes.ACC_PRIVATE; + mn.desc = mn.desc.replace(Constants.ANYTYPE_DESC, Constants.OBJECT_DESC); + mn.signature = + mn.signature != null + ? mn.signature.replace(Constants.ANYTYPE_DESC, Constants.OBJECT_DESC) + : null; + mn.name = InstrumentUtils.getActionPrefix(getClassName(true)) + mn.name; + return mn; + } + + private byte[] readFully(InputStream is) throws IOException { + int bufSize = 512; + int pos = 0; + byte[] finArr = new byte[1024]; + byte[] buff = new byte[bufSize]; + + int read = 0; + while ((read = is.read(buff, 0, bufSize)) > 0) { + int newpos = pos + read; + if (newpos >= finArr.length) { + finArr = Arrays.copyOf(finArr, finArr.length * 2); + } + System.arraycopy(buff, 0, finArr, pos, read); + pos = newpos; + } + return Arrays.copyOfRange(finArr, 0, pos); + } + + @Override + public String toString() { + return "BTraceProbe{" + "delegate=" + delegate + '}'; + } +} diff --git a/btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceProbePersisted.java b/btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceProbePersisted.java new file mode 100644 index 000000000..97e4f6cd3 --- /dev/null +++ b/btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceProbePersisted.java @@ -0,0 +1,836 @@ +/* + * Copyright (c) 2017, 2018, Jaroslav Bachorik . + * All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Copyright owner designates + * this particular file as subject to the "Classpath" exception as provided + * by the owner in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ +package org.openjdk.btrace.instr; + +import static org.objectweb.asm.Opcodes.*; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.openjdk.btrace.core.ArgsMap; +import org.openjdk.btrace.core.BTraceRuntime; +import org.openjdk.btrace.core.DebugSupport; +import org.openjdk.btrace.core.VerifierException; +import org.openjdk.btrace.core.annotations.Kind; +import org.openjdk.btrace.core.annotations.Sampled; +import org.openjdk.btrace.core.annotations.Where; +import org.openjdk.btrace.core.comm.RetransformClassNotification; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BTraceProbePersisted implements BTraceProbe { + private static final Logger log = LoggerFactory.getLogger(BTraceProbePersisted.class); + + static final int MAGIC = 0xbacecaca; + + private static final int VERSION = 2; + + final BTraceProbeSupport delegate; + private final BTraceProbeFactory factory; + private final DebugSupport debug; + private final AtomicBoolean triedVerify = new AtomicBoolean(false); + private final Map> calleeMap = new HashMap<>(); + private volatile BTraceRuntime.Impl rt = null; + private BTraceTransformer transformer; + private byte[] fullData = null; + private byte[] dataHolder = null; + private boolean preverified; + + BTraceProbePersisted(BTraceProbeFactory f) { + this(f, null); + } + + private BTraceProbePersisted(BTraceProbeFactory f, BTraceProbeSupport delegate) { + this.debug = new DebugSupport(f.getSettings()); + this.delegate = delegate != null ? delegate : new BTraceProbeSupport(); + factory = f; + preverified = false; + } + + private BTraceProbePersisted(BTraceProbeNode bpn) { + this(bpn.factory, bpn.delegate); + fullData = bpn.getFullBytecode(); + dataHolder = bpn.getDataHolderBytecode(); + loadCalleeMap(bpn, calleeMap); + preverified = true; + } + + public static BTraceProbePersisted from(BTraceProbe bp) { + return bp instanceof BTraceProbePersisted + ? (BTraceProbePersisted) bp + : new BTraceProbePersisted((BTraceProbeNode) bp); + } + + private static void loadCalleeMap(BTraceProbeNode bpn, Map> cMap) { + Set roots = new HashSet<>(); + for (OnMethod om : bpn.onmethods()) { + roots.add(new Handler(om.getTargetName(), om.getTargetDescriptor())); + } + for (OnProbe op : bpn.onprobes()) { + roots.add(new Handler(op.getTargetName(), op.getTargetDescriptor())); + } + for (Handler h : roots) { + String rootKey = CallGraph.methodId(h.name, h.desc); + Set cs = cMap.computeIfAbsent(rootKey, k -> new HashSet<>()); + for (BTraceMethodNode bmn : bpn.callees(h.name, h.desc)) { + cs.add(CallGraph.methodId(bmn.name, bmn.desc)); + } + } + } + + private static String getClazz(OnMethod om) { + String clzName = om.getClazz(); + + if (om.isSubtypeMatcher()) { + return "+" + om.getClazz(); + } else { + if (om.isClassRegexMatcher()) { + clzName = "/" + clzName + "/"; + } + if (om.isClassAnnotationMatcher()) { + clzName = "@" + clzName; + } + } + + return clzName; + } + + private static String getMethod(OnMethod om) { + String mName = om.getMethod(); + + if (om.isMethodRegexMatcher()) { + mName = "/" + mName + "/"; + } + + if (om.isMethodAnnotationMatcher()) { + mName = "@" + mName; + } + + return mName; + } + + public void read(DataInputStream dis) throws IOException { + int version = dis.readInt(); + switch (version) { + case 1: + { + read_1(dis); + break; + } + case 2: + { + read_2(dis); + break; + } + default: + { + throw new IOException("Unsupported version for persisted probe: " + version); + } + } + } + + /** + * Read in the structure for version 1 + * + * @param dis data input stream + * @throws IOException + */ + private void read_1(DataInputStream dis) throws IOException { + delegate.setClassName(dis.readUTF()); + readServices(dis); + readOnMethods(dis); + readOnProbes(dis); + readCallees(dis); + readDataHolderClass(dis); + readFullData(dis); + upgradeBytecode(); + } + + /** + * Read in the structure for version 2 + * + * @param dis data input stream + * @throws IOException + */ + private void read_2(DataInputStream dis) throws IOException { + delegate.setClassName(dis.readUTF()); + readServices(dis); + readOnMethods(dis); + readOnProbes(dis); + readCallees(dis); + readDataHolderClass(dis); + readFullData(dis); + } + + public void write(DataOutputStream dos) { + try { + dos.writeInt(MAGIC); + dos.writeInt(VERSION); + dos.writeUTF(getClassName(true)); + writeServices(dos); + writeOnMethods(dos); + writeOnProbes(dos); + writeCallees(dos); + writeDataHolderClass(dos); + writeFullData(dos); + } catch (IOException e) { + log.debug("Failed to write probe {}", getClassName(), e); + } + } + + private void readServices(DataInputStream dis) throws IOException { + int num = dis.readInt(); + for (int i = 0; i < num; i++) { + delegate.addServiceField(dis.readUTF(), dis.readUTF()); + } + } + + private void readOnMethods(DataInputStream dis) throws IOException { + int num = dis.readInt(); + for (int i = 0; i < num; i++) { + OnMethod om = new OnMethod(); + om.setClazz(dis.readUTF()); + om.setMethod(dis.readUTF()); + om.setExactTypeMatch(dis.readBoolean()); + om.setTargetDescriptor(dis.readUTF()); + om.setTargetName(dis.readUTF()); + om.setType(dis.readUTF()); + om.setClassNameParameter(dis.readInt()); + om.setDurationParameter(dis.readInt()); + om.setMethodParameter(dis.readInt()); + om.setReturnParameter(dis.readInt()); + om.setSelfParameter(dis.readInt()); + om.setTargetInstanceParameter(dis.readInt()); + om.setTargetMethodOrFieldParameter(dis.readInt()); + om.setMethodFqn(dis.readBoolean()); + om.setTargetMethodOrFieldFqn(dis.readBoolean()); + om.setSamplerKind(Sampled.Sampler.valueOf(dis.readUTF())); + om.setSamplerMean(dis.readInt()); + om.setLevel(dis.readBoolean() ? Level.fromString(dis.readUTF()) : null); + Location loc = new Location(); + loc.setValue(Kind.valueOf(dis.readUTF())); + loc.setWhere(Where.valueOf(dis.readUTF())); + loc.setClazz(dis.readBoolean() ? dis.readUTF() : null); + loc.setField(dis.readBoolean() ? dis.readUTF() : null); + loc.setMethod(dis.readBoolean() ? dis.readUTF() : null); + loc.setType(dis.readBoolean() ? dis.readUTF() : null); + loc.setLine(dis.readInt()); + om.setLocation(loc); + delegate.addOnMethod(om); + } + } + + private void readOnProbes(DataInputStream dis) throws IOException { + int num = dis.readInt(); + for (int i = 0; i < num; i++) { + OnProbe op = new OnProbe(); + op.setNamespace(dis.readUTF()); + op.setName(dis.readUTF()); + op.setTargetDescriptor(dis.readUTF()); + op.setTargetName(dis.readUTF()); + op.setClassNameParameter(dis.readInt()); + op.setDurationParameter(dis.readInt()); + op.setMethodParameter(dis.readInt()); + op.setReturnParameter(dis.readInt()); + op.setSelfParameter(dis.readInt()); + op.setTargetInstanceParameter(dis.readInt()); + op.setTargetMethodOrFieldParameter(dis.readInt()); + op.setMethodFqn(dis.readBoolean()); + op.setTargetMethodOrFieldFqn(dis.readBoolean()); + delegate.addOnProbe(op); + } + } + + private void readFullData(DataInputStream dis) throws IOException { + int fullDataLen = dis.readInt(); + fullData = new byte[fullDataLen]; + dis.readFully(fullData); + } + + private void readDataHolderClass(DataInputStream dis) throws IOException { + int holderLen = dis.readInt(); + dataHolder = new byte[holderLen]; + dis.readFully(dataHolder); + if (dataHolder.length > 0 && isClassRenamed()) { + dataHolder = ProbeRenameVisitor.rename(getClassName(), dataHolder); + } + } + + private void readCallees(DataInputStream dis) throws IOException { + int cnt = dis.readInt(); + for (int i = 0; i < cnt; i++) { + String from = dis.readUTF(); + Set calleeSet = calleeMap.computeIfAbsent(from, k -> new HashSet<>()); + int callees = dis.readInt(); + for (int j = 0; j < callees; j++) { + String to = dis.readUTF(); + calleeSet.add(to); + } + } + } + + private void writeServices(DataOutputStream dos) throws IOException { + Map svcFields = delegate.serviceFields(); + dos.writeInt(svcFields.size()); + for (Map.Entry e : svcFields.entrySet()) { + dos.writeUTF(e.getKey()); + dos.writeUTF(e.getValue()); + } + } + + private void writeOnMethods(DataOutputStream dos) throws IOException { + Collection onMethods = delegate.getOnMethods(); + int cnt = onMethods.size(); + dos.writeInt(cnt); + for (OnMethod om : onMethods) { + dos.writeUTF(getClazz(om)); + dos.writeUTF(getMethod(om)); + dos.writeBoolean(om.isExactTypeMatch()); + dos.writeUTF(om.getTargetDescriptor()); + dos.writeUTF(om.getTargetName()); + dos.writeUTF(om.getType()); + dos.writeInt(om.getClassNameParameter()); + dos.writeInt(om.getDurationParameter()); + dos.writeInt(om.getMethodParameter()); + dos.writeInt(om.getReturnParameter()); + dos.writeInt(om.getSelfParameter()); + dos.writeInt(om.getTargetInstanceParameter()); + dos.writeInt(om.getTargetMethodOrFieldParameter()); + dos.writeBoolean(om.isMethodFqn()); + dos.writeBoolean(om.isTargetMethodOrFieldFqn()); + dos.writeUTF(om.getSamplerKind().name()); + dos.writeInt(om.getSamplerMean()); + dos.writeBoolean(om.getLevel() != null); + if (om.getLevel() != null) { + dos.writeUTF(om.getLevel().getValue().toString()); + } + Location loc = om.getLocation(); + dos.writeUTF(loc.getValue().name()); + dos.writeUTF(loc.getWhere().name()); + dos.writeBoolean(loc.getClazz() != null); + if (loc.getClazz() != null) { + dos.writeUTF(loc.getClazz()); + } + dos.writeBoolean(loc.getField() != null); + if (loc.getField() != null) { + dos.writeUTF(loc.getField()); + } + dos.writeBoolean(loc.getMethod() != null); + if (loc.getMethod() != null) { + dos.writeUTF(loc.getMethod()); + } + dos.writeBoolean(loc.getType() != null); + if (loc.getType() != null) { + dos.writeUTF(loc.getType()); + } + dos.writeInt(loc.getLine()); + } + } + + private void writeOnProbes(DataOutputStream dos) throws IOException { + Collection onProbes = delegate.getOnProbes(); + int cnt = onProbes.size(); + dos.writeInt(cnt); + for (OnProbe op : onProbes) { + dos.writeUTF(op.getNamespace()); + dos.writeUTF(op.getName()); + dos.writeUTF(op.getTargetDescriptor()); + dos.writeUTF(op.getTargetName()); + dos.writeInt(op.getClassNameParameter()); + dos.writeInt(op.getDurationParameter()); + dos.writeInt(op.getMethodParameter()); + dos.writeInt(op.getReturnParameter()); + dos.writeInt(op.getSelfParameter()); + dos.writeInt(op.getTargetInstanceParameter()); + dos.writeInt(op.getTargetMethodOrFieldParameter()); + dos.writeBoolean(op.isMethodFqn()); + dos.writeBoolean(op.isTargetMethodOrFieldFqn()); + } + } + + private void writeFullData(DataOutputStream dos) throws IOException { + dos.writeInt(fullData.length); + dos.write(fullData); + } + + private void writeDataHolderClass(DataOutputStream dos) throws IOException { + dos.writeInt(dataHolder.length); + dos.write(dataHolder); + } + + private void writeCallees(DataOutputStream dos) throws IOException { + int cnt = 0; + for (Set callees : calleeMap.values()) { + if (!callees.isEmpty()) { + cnt++; + } + } + dos.writeInt(cnt); + for (Map.Entry> e : calleeMap.entrySet()) { + if (!e.getValue().isEmpty()) { + dos.writeUTF(e.getKey()); + dos.writeInt(e.getValue().size()); + for (String c : e.getValue()) { + dos.writeUTF(c); + } + } + } + } + + @Override + public Collection getApplicableHandlers(BTraceClassReader cr) { + return delegate.getApplicableHandlers(cr); + } + + @Override + public byte[] getFullBytecode() { + return fullData; + } + + @Override + public byte[] getDataHolderBytecode() { + return dataHolder; + } + + @Override + public String getClassName() { + return delegate.getClassName(false); + } + + @Override + public String getClassName(boolean internal) { + return delegate.getClassName(internal); + } + + @Override + public boolean isClassRenamed() { + return delegate.isClassRenamed(); + } + + @Override + public boolean isTransforming() { + return delegate.isTransforming(); + } + + @Override + public boolean isVerified() { + if (factory.getSettings().isTrusted()) { + return true; + } + if (triedVerify.compareAndSet(false, true)) { + try { + verifyBytecode(); + return true; + } catch (VerifierException e) { + log.debug("Class '{}' verification failed", getClassName(), e); + } + } + return false; + } + + @Override + public void notifyTransform(String className) { + if (rt != null && factory.getSettings().isTrackRetransforms()) { + rt.send(new RetransformClassNotification(className.replace('/', '.'))); + } + } + + @Override + public Iterable onmethods() { + return delegate.onmethods(); + } + + public Collection getOnMethods() { + return delegate.getOnMethods(); + } + + @Override + public Iterable onprobes() { + return delegate.onprobes(); + } + + @Override + public Class register(BTraceRuntime.Impl rt, BTraceTransformer t) { + byte[] code = dataHolder; + if (debug.isDumpClasses()) { + debug.dumpClass(delegate.getClassName(true) + "_bcp", code); + } + Class clz = delegate.defineClass(rt, code); + t.register(this); + transformer = t; + this.rt = rt; + return clz; + } + + @Override + public void unregister() { + if (transformer != null && isTransforming()) { + if (log.isDebugEnabled()) { + log.debug("onExit: removing transformer for {}", getClassName()); + } + transformer.unregister(this); + } + rt = null; + } + + @Override + public boolean willInstrument(Class clz) { + return delegate.willInstrument(clz); + } + + @Override + public void checkVerified() { + if (!preverified) { + isVerified(); + } + } + + @Override + public void copyHandlers(ClassVisitor copyingVisitor) { + ClassReader cr = new ClassReader(fullData); + Set copiedMethods = new HashSet<>(); + for (OnMethod om : onmethods()) { + if (om.isCalled()) { + String mid = CallGraph.methodId(om.getTargetName(), om.getTargetDescriptor()); + copiedMethods.add(mid); + Set callees = calleeMap.get(mid); + if (callees != null) { + copiedMethods.addAll(calleeMap.get(mid)); + } + } + } + cr.accept( + new ClassVisitor(ASM9) { + @Override + public void visit( + int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + copyingVisitor.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String signature, String[] exceptions) { + String mid = CallGraph.methodId(name, desc); + if (copiedMethods.contains(mid)) { + return copyingVisitor.visitMethod( + ACC_PRIVATE | ACC_STATIC, + InstrumentUtils.getActionPrefix(getClassName(true)) + name, + desc.replace(Constants.ANYTYPE_DESC, Constants.OBJECT_DESC), + signature != null + ? signature.replace(Constants.ANYTYPE_DESC, Constants.OBJECT_DESC) + : null, + exceptions); + } + return super.visitMethod(access, name, desc, signature, exceptions); + } + }, + 0); + } + + @Override + public void applyArgs(ArgsMap argsMap) { + delegate.applyArgs(argsMap); + } + + @Override + public BTraceRuntime.Impl getRuntime() { + return rt; + } + + private void upgradeBytecode() { + fullData = ProbeUpgradeVisitor_1_2.upgrade(new ClassReader(fullData)); + dataHolder = ProbeUpgradeVisitor_1_2.upgrade(new ClassReader(dataHolder)); + } + + private void verifyBytecode() throws VerifierException { + ClassReader cr = new ClassReader(fullData); + cr.accept( + new ClassVisitor(ASM9) { + private String className; + + @Override + public void visit( + int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + if ((access & ACC_INTERFACE) != 0 || (access & ACC_ENUM) != 0) { + Verifier.reportError("btrace.program.should.be.class"); + } + if ((access & ACC_PUBLIC) == 0) { + Verifier.reportError("class.should.be.public", name); + } + + if (!superName.equals(Constants.OBJECT_INTERNAL)) { + Verifier.reportError("object.superclass.required", superName); + } + if (interfaces != null && interfaces.length > 0) { + Verifier.reportError("no.interface.implementation"); + } + className = name; + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public void visitInnerClass(String name, String outerName, String innerName, int access) { + if (className.equals(outerName)) { + Verifier.reportError("no.nested.class"); + } + } + + @Override + public void visitOuterClass(String s, String s1, String s2) { + Verifier.reportError("no.outer.class"); + } + + @Override + public FieldVisitor visitField( + int access, String name, String desc, String sig, Object dflt) { + if ((access & ACC_STATIC) == 0) { + Verifier.reportError("agent.no.instance.variables", name); + } + return super.visitField(access, name, desc, sig, dflt); + } + + @Override + public MethodVisitor visitMethod( + int access, String methodName, String desc, String sig, String[] exceptions) { + if ((access & ACC_SYNCHRONIZED) != 0) { + Verifier.reportError( + "no.synchronized.methods", + TypeUtils.descriptorToSimplified(desc, className, methodName)); + } + + if (!methodName.equals(Constants.CONSTRUCTOR)) { + if ((access & ACC_STATIC) == 0) { + Verifier.reportError( + "no.instance.method", + TypeUtils.descriptorToSimplified(desc, className, methodName)); + } + } + + if (methodName.equals(Constants.CLASS_INITIALIZER)) { + return super.visitMethod(access, methodName, desc, sig, exceptions); + } + + return new MethodVisitor( + ASM9, super.visitMethod(access, methodName, desc, sig, exceptions)) { + private final Map labels = new HashMap<>(); + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + if (opcode == PUTFIELD) { + Verifier.reportError("no.assignment"); + } + + if (opcode == PUTSTATIC) { + if (!owner.equals(className)) { + Verifier.reportError("no.assignment"); + } + } + super.visitFieldInsn(opcode, owner, name, desc); + } + + @Override + public void visitInsn(int opcode) { + switch (opcode) { + case IASTORE: + case LASTORE: + case FASTORE: + case DASTORE: + case AASTORE: + case BASTORE: + case CASTORE: + case SASTORE: + Verifier.reportError("no.assignment"); + break; + case ATHROW: + Verifier.reportError("no.throw"); + break; + case MONITORENTER: + case MONITOREXIT: + Verifier.reportError("no.synchronized.blocks"); + break; + } + super.visitInsn(opcode); + } + + @Override + public void visitIntInsn(int opcode, int operand) { + if (opcode == NEWARRAY) { + Verifier.reportError("no.array.creation"); + } + super.visitIntInsn(opcode, operand); + } + + @Override + public void visitJumpInsn(int opcode, Label label) { + if (labels.get(label) != null) { + Verifier.reportError("no.loops"); + } + super.visitJumpInsn(opcode, label); + } + + @Override + public void visitMethodInsn( + int opcode, String owner, String name, String desc, boolean itfc) { + switch (opcode) { + case INVOKEVIRTUAL: + if (MethodVerifier.isPrimitiveWrapper(owner) + && MethodVerifier.isUnboxMethod(name)) { + // allow primitive type unbox methods. + // These calls are generated by javac for auto-unboxing ` + // and can't be caught by source AST analyzer as well. + } else if (owner.equals(Constants.STRING_BUILDER_INTERNAL)) { + // allow string concatenation via StringBuilder + } else if (owner.equals(Constants.THREAD_LOCAL_INTERNAL)) { + // allow ThreadLocal methods + } else if (owner.equals(Constants.BTRACERTACCESS_INTERNAL)) { + // allow BTraceRuntimeAccess methods + } else if (owner.equals(Constants.BTRACERTBASE_INTERNAL)) { + // allow BTraceRuntimeImplBase methods + } else { + if (!delegate.isServiceType(owner)) { + Verifier.reportError("no.method.calls", owner + "." + name + desc); + } + } + break; + case INVOKEINTERFACE: + Verifier.reportError("no.method.calls", owner + "." + name + desc); + break; + case INVOKESPECIAL: + if (owner.equals(Constants.OBJECT_INTERNAL) + && name.equals(Constants.CONSTRUCTOR)) { + // allow object initializer + } else if (owner.equals(Constants.STRING_BUILDER_INTERNAL)) { + // allow string concatenation via StringBuilder + } else if (owner.equals(Constants.THREAD_LOCAL_INTERNAL)) { + // allow ThreadLocal methods + } else if (delegate.isServiceType(owner)) { + // allow services invocations + } else { + Verifier.reportError("no.method.calls", owner + "." + name + desc); + } + break; + case INVOKESTATIC: + if (!owner.startsWith("org/openjdk/btrace/") && !owner.equals(className)) { + if ("valueOf".equals(name) && MethodVerifier.isPrimitiveWrapper(owner)) { + // allow primitive wrapper boxing methods. + // These calls are generated by javac for autoboxing + // and can't be caught sourc AST analyzer as well. + } else { + Verifier.reportError("no.method.calls", owner + "." + name + desc); + } + } + break; + } + super.visitMethodInsn(opcode, owner, name, desc, itfc); + } + + @Override + public void visitMultiANewArrayInsn(String desc, int dims) { + Verifier.reportError("no.array.creation"); + } + + @Override + public void visitTypeInsn(int opcode, String desc) { + if (opcode == ANEWARRAY) { + Verifier.reportError("no.array.creation", desc); + } + if (opcode == NEW) { + // allow StringBuilder creation for string concatenation + if (!desc.equals(Constants.STRING_BUILDER_INTERNAL) + && !delegate.isServiceType(desc)) { + Verifier.reportError("no.new.object", desc); + } + } + super.visitTypeInsn(opcode, desc); + } + + @Override + public void visitVarInsn(int opcode, int var) { + if (opcode == RET) { + Verifier.reportError("no.try"); + } + super.visitVarInsn(opcode, var); + } + }; + } + }, + ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + } + + private static final class Handler { + private final String name; + private final String desc; + + public Handler(String name, String desc) { + this.name = name; + this.desc = desc; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 29 * hash + Objects.hashCode(name); + hash = 29 * hash + Objects.hashCode(desc); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Handler other = (Handler) obj; + if (!Objects.equals(name, other.name)) { + return false; + } + return Objects.equals(desc, other.desc); + } + } +} diff --git a/btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceProbeSupport.java b/btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceProbeSupport.java new file mode 100644 index 000000000..6e5190e64 --- /dev/null +++ b/btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceProbeSupport.java @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2017,2018, Jaroslav Bachorik . + * All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Copyright owner designates + * this particular file as subject to the "Classpath" exception as provided + * by the owner in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ +package org.openjdk.btrace.instr; + +import static org.openjdk.btrace.instr.ClassFilter.isSubTypeOf; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; +import org.openjdk.btrace.core.ArgsMap; +import org.openjdk.btrace.core.BTraceRuntime; +import org.openjdk.btrace.runtime.BTraceRuntimeAccess; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class BTraceProbeSupport { + private static final Logger log = LoggerFactory.getLogger(BTraceProbeSupport.class); + + private final List onMethods; + private final List onProbes; + private final Map serviceFields; + + private final Object filterLock = new Object(); + private volatile ClassFilter filter; + private boolean trustedScript = false; + private boolean classRenamed = false; + private String className, origName; + + BTraceProbeSupport() { + onMethods = new ArrayList<>(); + onProbes = new ArrayList<>(); + serviceFields = new HashMap<>(); + } + + void setClassName(String name) { + origName = name; + String clientName = BTraceRuntimeAccess.getClientName(name); + className = clientName != null ? clientName : name; + classRenamed = !className.equals(name); + } + + String getClassName(boolean internal) { + return internal ? className : className.replace("/", "."); + } + + String getOrigName() { + return origName; + } + + boolean isClassRenamed() { + return classRenamed; + } + + String translateOwner(String owner) { + if (owner.equals(origName)) { + return getClassName(true); + } + return owner; + } + + boolean isTransforming() { + return !onMethods.isEmpty(); + } + + Collection getApplicableHandlers(BTraceClassReader cr) { + Collection applicables = new ArrayList<>(onMethods.size()); + String targetName = cr.getJavaClassName(); + + outer: + for (OnMethod om : onMethods) { + String probeClass = om.getClazz(); + if (probeClass == null || probeClass.isEmpty()) continue; + + if (probeClass.equals(targetName)) { + applicables.add(om); + continue; + } + // Check regex match + if (om.isClassRegexMatcher() && !om.isClassAnnotationMatcher()) { + Pattern p = Pattern.compile(probeClass); + if (p.matcher(targetName).matches()) { + applicables.add(om); + continue; + } + } + if (om.isClassAnnotationMatcher()) { + Collection annoTypes = cr.getAnnotationTypes(); + if (om.isClassRegexMatcher()) { + Pattern p = Pattern.compile(probeClass); + for (String annoType : annoTypes) { + if (p.matcher(annoType).matches()) { + applicables.add(om); + continue outer; + } + } + } else { + if (annoTypes.contains(probeClass)) { + applicables.add(om); + continue; + } + } + } + // And, finally, check the class hierarchy + if (om.isSubtypeMatcher()) { + // internal name of super type. + if (isSubTypeOf(cr.getClassName(), cr.getClassLoader(), probeClass)) { + applicables.add(om); + } + } + } + return applicables; + } + + Collection getOnMethods() { + return Collections.unmodifiableCollection(onMethods); + } + + Collection getOnProbes() { + return Collections.unmodifiableCollection(onProbes); + } + + Iterable onmethods() { + return () -> Collections.unmodifiableCollection(onMethods).iterator(); + } + + Iterable onprobes() { + return () -> onProbes.iterator(); + } + + Map serviceFields() { + return Collections.unmodifiableMap(serviceFields); + } + + boolean isServiceType(String typeName) { + return serviceFields.containsValue(typeName); + } + + boolean isFieldInjected(String name) { + return serviceFields.containsKey(name); + } + + void addOnMethod(OnMethod om) { + onMethods.add(om); + } + + void addOnProbe(OnProbe op) { + onProbes.add(op); + } + + void addServiceField(String fldName, String svcType) { + serviceFields.put(fldName, svcType); + } + + boolean willInstrument(Class clz) { + return getClassFilter().isCandidate(clz); + } + + private ClassFilter getClassFilter() { + synchronized (filterLock) { + if (filter == null) { + filter = new ClassFilter(onmethods()); + } + return filter; + } + } + + void setTrusted() { + trustedScript = true; + } + + boolean isTrusted() { + return trustedScript; + } + + Class defineClass(BTraceRuntime.Impl rt, byte[] code) { + // This extra BTraceRuntime.enter is needed to + // check whether we have already entered before. + boolean enteredHere = BTraceRuntime.enter(); + try { + // The trace class static initializer needs to be run + // without BTraceRuntime.enter(). Please look at the + // static initializer code of trace class. + BTraceRuntime.leave(); + if (log.isDebugEnabled()) { + log.debug("about to defineClass {}", getClassName(false)); + } + Class clz = rt.defineClass(code, isTransforming()); + if (log.isDebugEnabled()) { + log.debug("defineClass succeeded for {}", getClassName(false)); + } + return clz; + } finally { + // leave BTraceRuntime enter state as it was before + // we started executing this method. + if (!enteredHere) BTraceRuntime.enter(); + } + } + + void applyArgs(ArgsMap argsMap) { + for (OnMethod om : onMethods) { + om.applyArgs(argsMap); + } + } + + @Override + public String toString() { + return "BTraceProbeSupport{" + + "onMethods=" + + onMethods + + ", onProbes=" + + onProbes + + ", trustedScript=" + + trustedScript + + ", serviceFields=" + + serviceFields + + '}'; + } +} diff --git a/btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceTransformer.java b/btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceTransformer.java new file mode 100644 index 000000000..75653662e --- /dev/null +++ b/btrace-instr/src/main/java/org/openjdk/btrace/instr/BTraceTransformer.java @@ -0,0 +1,271 @@ +/* + * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.btrace.instr; + +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.IllegalClassFormatException; +import java.security.ProtectionDomain; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.regex.Pattern; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.InsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.openjdk.btrace.core.BTraceRuntime; +import org.openjdk.btrace.core.DebugSupport; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The single entry point for class transformation. + * + *

When a class is to be transformed all the registered {@linkplain BTraceProbe} instances are + * asked for the appropriate instrumentation. When there are no registered probes or none of the + * registered probes is able to instrument the class it will not be transformed. + * + * @author Jaroslav Bachorik + * @since 1.3.5 + */ +@SuppressWarnings("RedundantThrows") +public final class BTraceTransformer implements ClassFileTransformer { + private static final Logger log = LoggerFactory.getLogger(BTraceTransformer.class); + + private final DebugSupport debug; + private final ReentrantReadWriteLock setupLock = new ReentrantReadWriteLock(); + private final Collection probes = new ArrayList<>(3); + private final Filter filter = new Filter(); + private final Collection cushionMethods = new HashSet<>(); + + public BTraceTransformer(DebugSupport d) { + debug = d; + } + + /* + * Certain classes like java.lang.ThreadLocal and it's + * inner classes, java.lang.Object cannot be safely + * instrumented with BTrace. This is because BTrace uses + * ThreadLocal class to check recursive entries due to + * BTrace's own functions. But this leads to infinite recursions + * if BTrace instruments java.lang.ThreadLocal for example. + * For now, we avoid such classes till we find a solution. + */ + private static boolean isSensitiveClass(String name) { + return ClassFilter.isSensitiveClass(name); + } + + public void register(BTraceProbe p) { + try { + setupLock.writeLock().lock(); + probes.add(p); + for (OnMethod om : p.onmethods()) { + filter.add(om); + } + } finally { + setupLock.writeLock().unlock(); + } + } + + public final void unregister(BTraceProbe p) { + try { + setupLock.writeLock().lock(); + probes.remove(p); + for (OnMethod om : p.onmethods()) { + filter.remove(om); + MethodNode cushionMethod = + new MethodNode( + Opcodes.ASM9, + Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, + Instrumentor.getActionMethodName(p, om.getTargetName()), + om.getTargetDescriptor(), + null, + null); + InsnList code = new InsnList(); + code.add(new InsnNode(Opcodes.RETURN)); + cushionMethod.instructions = code; + int localSize = 0; + for (Type t : Type.getArgumentTypes(om.getTargetDescriptor())) { + localSize += t.getSize(); + } + cushionMethod.maxLocals = localSize; + cushionMethods.add(cushionMethod); + } + + } finally { + setupLock.writeLock().unlock(); + } + } + + @Override + public byte[] transform( + ClassLoader loader, + String className, + Class classBeingRedefined, + ProtectionDomain protectionDomain, + byte[] classfileBuffer) + throws IllegalClassFormatException { + try { + setupLock.readLock().lock(); + if (probes.isEmpty()) return null; + + className = className != null ? className : ""; + + if ((loader == null || loader.equals(ClassLoader.getSystemClassLoader())) + && isSensitiveClass(className)) { + if (log.isDebugEnabled()) { + log.debug("skipping transform for BTrace class {}", className); // NOI18N + } + return null; + } + + if (filter.matchClass(className) == Filter.Result.FALSE) return null; + + boolean entered = BTraceRuntime.enter(); + try { + if (debug.isDumpClasses()) { + debug.dumpClass(className.replace('.', '/') + "_orig", classfileBuffer); + } + BTraceClassReader cr = InstrumentUtils.newClassReader(loader, classfileBuffer); + BTraceClassWriter cw = InstrumentUtils.newClassWriter(cr); + cw.addCushionMethods(cushionMethods); + for (BTraceProbe p : probes) { + p.notifyTransform(className); + cw.addInstrumentor(p, loader); + } + byte[] transformed = cw.instrument(); + if (transformed == null) { + // no instrumentation necessary + if (log.isDebugEnabled()) { + log.debug("skipping class {}", cr.getJavaClassName()); + } + return classfileBuffer; + } else { + if (log.isDebugEnabled()) { + log.error("transformed class {}", cr.getJavaClassName()); + } + if (debug.isDumpClasses()) { + debug.dumpClass(className.replace('.', '/'), transformed); + } + } + return transformed; + } catch (Throwable th) { + log.debug("Failed to transform class {}", className, th); + throw th; + } finally { + if (entered) { + BTraceRuntime.leave(); + } + } + } finally { + setupLock.readLock().unlock(); + } + } + + static class Filter { + private final Map nameMap = new HashMap<>(); + private final Map nameRegexMap = new HashMap<>(); + private boolean isFast = true; + private boolean isRegex = false; + + @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") + private static void addToMap(Map map, K name) { + synchronized (map) { + map.merge(name, 1, Integer::sum); + } + } + + @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") + private static void removeFromMap(Map map, K name) { + synchronized (map) { + Integer i = map.get(name); + if (i == null) { + return; + } + int freq = i - 1; + if (freq == 0) { + map.remove(name); + } + } + } + + void add(OnMethod om) { + if (om.isSubtypeMatcher() || om.isClassAnnotationMatcher()) { + isFast = false; + } else { + if (om.isClassRegexMatcher()) { + isRegex = true; + String name = om.getClazz().replace("\\.", "/"); + addToMap(nameRegexMap, Pattern.compile(name)); + } else { + String name = om.getClazz().replace('.', '/'); + addToMap(nameMap, name); + } + } + } + + void remove(OnMethod om) { + String name = om.getClazz().replace('.', '/'); + if (!(om.isSubtypeMatcher() || om.isClassAnnotationMatcher())) { + if (om.isClassRegexMatcher()) { + removeFromMap(nameRegexMap, Pattern.compile(name)); + } else { + removeFromMap(nameMap, name); + } + } + } + + public Result matchClass(String className) { + if (isFast) { + synchronized (nameMap) { + if (nameMap.containsKey(className)) { + return Result.TRUE; + } + } + if (isRegex) { + synchronized (nameRegexMap) { + for (Pattern p : nameRegexMap.keySet()) { + if (p.matcher(className).matches()) { + return Result.TRUE; + } + } + } + } + return Result.FALSE; + } + return Result.MAYBE; + } + + enum Result { + TRUE, + FALSE, + MAYBE + } + } +} diff --git a/btrace-instr/src/main/java/org/openjdk/btrace/instr/BailoutException.java b/btrace-instr/src/main/java/org/openjdk/btrace/instr/BailoutException.java new file mode 100644 index 000000000..27fb2cd99 --- /dev/null +++ b/btrace-instr/src/main/java/org/openjdk/btrace/instr/BailoutException.java @@ -0,0 +1,19 @@ +package org.openjdk.btrace.instr; + +/** + * Dummy, non-stack-collecting runtime exception. It is used for execution control in ClassReader + * instances in order to avoid processing the complete class file when the relevant info is + * available right at the beginning of parsing. + */ +final class BailoutException extends RuntimeException { + /** Shared instance to optimize the cost of throwing */ + static final BailoutException INSTANCE = new BailoutException(); + + private BailoutException() {} + + @Override + public synchronized Throwable fillInStackTrace() { + // we don't need the stack here + return this; + } +} diff --git a/btrace-instr/src/main/java/org/openjdk/btrace/instr/CallGraph.java b/btrace-instr/src/main/java/org/openjdk/btrace/instr/CallGraph.java new file mode 100644 index 000000000..35f178154 --- /dev/null +++ b/btrace-instr/src/main/java/org/openjdk/btrace/instr/CallGraph.java @@ -0,0 +1,327 @@ +/* + * Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.instr; + +import java.util.ArrayDeque; +import java.util.Collections; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.regex.Pattern; + +/** + * This class allows building an arbitrary graph caller-callee relationship + * + * @author Jaroslav Bachorik + */ +public final class CallGraph { + private static final Pattern MID_SPLIT_PTN = Pattern.compile("::"); + private final Set nodes = new HashSet<>(); + private final Set startingNodes = new HashSet<>(); + + public static String methodId(String name, String desc) { + return name + "::" + desc; + } + + public static String[] method(String methodId) { + if (methodId.contains("::")) { + return MID_SPLIT_PTN.split(methodId); + } + return new String[0]; + } + + public void addEdge(String fromId, String toId) { + Node fromNode = null; + Node toNode = null; + for (Node n : nodes) { + if (n.id.equals(fromId)) { + fromNode = n; + } + if (n.id.equals(toId)) { + toNode = n; + } + if (fromNode != null && toNode != null) break; + } + if (fromNode == null) { + fromNode = new Node(fromId); + nodes.add(fromNode); + } + if (toNode == null) { + toNode = new Node(toId); + nodes.add(toNode); + } + Edge e = new Edge(fromNode, toNode); + fromNode.addOutgoing(e); + toNode.addIncoming(e); + } + + public void addStarting(Node n) { + for (Node orig : nodes) { + if (orig.equals(n)) { + startingNodes.add(orig); + return; + } + } + startingNodes.add(n); + nodes.add(n); + } + + public boolean hasCycle() { + Set looped = findCycles(); + if (looped.isEmpty()) { + return false; + } + + Set checkingSet = new HashSet<>(looped); + + checkingSet.retainAll(startingNodes); + if (!checkingSet.isEmpty()) { + // a starting node is part of the loop + return true; + } + + Deque processingQueue = new ArrayDeque<>(); + for (Node n : startingNodes) { + processingQueue.push(n); + do { + Node current = processingQueue.pop(); + if (looped.contains(current)) { + // there is a path leading from a starting node to the detected loop + return true; + } + for (Edge e : current.outgoing) { + processingQueue.push(e.to); + } + } while (!processingQueue.isEmpty()); + } + return false; + } + + void callees(String name, String desc, Set closure) { + collectOutgoings(methodId(name, desc), closure); + } + + void callers(String name, String desc, Set closure) { + collectIncomings(methodId(name, desc), closure); + } + + private void collectOutgoings(String methodId, Set closure) { + for (Node n : nodes) { + if (n.id.equals(methodId)) { + for (Edge e : n.outgoing) { + String id = e.to.id; + if (!closure.contains(id)) { + closure.add(id); + collectOutgoings(id, closure); + } + } + } + } + } + + private void collectIncomings(String methodId, Set closure) { + for (Node n : nodes) { + if (n.id.equals(methodId)) { + for (Edge e : n.incoming) { + String id = e.from.id; + if (!closure.contains(id)) { + closure.add(id); + collectIncomings(id, closure); + } + } + } + } + } + + private Set findCycles() { + if (nodes.size() < 2) return Collections.emptySet(); + + Map checkingNodes = new HashMap<>(); + for (Node n : nodes) { + Node newN = checkingNodes.get(n.id); + if (newN == null) { + newN = new Node(n.id); + checkingNodes.put(n.id, newN); + } + for (Edge e : n.incoming) { + Node fromN = checkingNodes.get(e.from.id); + if (fromN == null) { + fromN = new Node(e.from.id); + checkingNodes.put(e.from.id, fromN); + } + Edge ee = new Edge(fromN, newN); + newN.addIncoming(ee); + fromN.addOutgoing(ee); + } + for (Edge e : n.outgoing) { + Node toN = checkingNodes.get(e.to.id); + if (toN == null) { + toN = new Node(e.to.id); + checkingNodes.put(e.to.id, toN); + } + Edge ee = new Edge(newN, toN); + newN.addOutgoing(ee); + toN.addIncoming(ee); + } + } + + Set sortedNodes = new HashSet<>(checkingNodes.values()); + // collect all terminal nodes + Deque terminalNodes = new ArrayDeque<>(); + for (Node node : sortedNodes) { + if ((node.incoming.isEmpty() && !startingNodes.contains(node)) || node.outgoing.isEmpty()) { + terminalNodes.addLast(node); + } + } + + // remove each terminal node from the graph and if the removal creates more terminal nodes + // add them all for processing + while (!terminalNodes.isEmpty()) { + Node n = terminalNodes.removeFirst(); + sortedNodes.remove(n); + for (Edge e : new HashSet<>(n.incoming)) { + e.delete(); + if (e.from.outgoing.isEmpty()) { + terminalNodes.addLast(e.from); + } + } + for (Edge e : new HashSet<>(n.outgoing)) { + e.delete(); + if (e.to.incoming.isEmpty() && !startingNodes.contains(e.to)) { + terminalNodes.addLast(e.to); + } + } + } + return sortedNodes; + } + + public static class Node { + private final String id; + private final Set incoming = new HashSet<>(); + private final Set outgoing = new HashSet<>(); + + public Node(String id) { + this.id = id; + } + + public void addIncoming(Edge e) { + incoming.add(e); + } + + public void addOutgoing(Edge e) { + outgoing.add(e); + } + + public void removeIncoming(Edge e) { + incoming.remove(e); + } + + public void removeOutgoing(Edge e) { + outgoing.remove(e); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Node other = (Node) obj; + return Objects.equals(id, other.id); + } + + @Override + public int hashCode() { + int hash = 7; + hash = 11 * hash + (id != null ? id.hashCode() : 0); + return hash; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Node{id='").append(id).append("'}"); + sb.append("\n"); + sb.append("incomming:\n"); + sb.append("=============================\n"); + for (Edge e : incoming) { + sb.append(e.from.id).append("\n"); + } + sb.append("=============================\n"); + sb.append("outgoing:\n"); + for (Edge e : outgoing) { + sb.append(e.to.id).append("\n"); + } + sb.append("=============================\n"); + + return sb.toString(); + } + } + + public static class Edge { + private final Node from; + private final Node to; + + public Edge(Node from, Node to) { + this.from = from; + this.to = to; + } + + public void delete() { + from.removeOutgoing(this); + to.removeIncoming(this); + } + + @Override + @SuppressWarnings("ReferenceEquality") + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Edge other = (Edge) obj; + if (!Objects.equals(from, other.from)) { + return false; + } + return Objects.equals(to, other.to); + } + + @Override + public int hashCode() { + int hash = 5; + hash = 37 * hash + (from != null ? from.hashCode() : 0); + hash = 37 * hash + (to != null ? to.hashCode() : 0); + return hash; + } + } +} diff --git a/btrace-instr/src/main/java/org/openjdk/btrace/instr/CatchInstrumentor.java b/btrace-instr/src/main/java/org/openjdk/btrace/instr/CatchInstrumentor.java new file mode 100644 index 000000000..2cfe997b8 --- /dev/null +++ b/btrace-instr/src/main/java/org/openjdk/btrace/instr/CatchInstrumentor.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.instr; + +import java.util.HashMap; +import java.util.Map; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Type; + +/** + * This visitor helps in inserting code whenever an exception is caught or finally block is reached. + * The code to insert on exception catch or finally may be decided by derived class. By default, + * this class inserts code to print a message. + * + * @author A. Sundararajan + */ +public class CatchInstrumentor extends MethodInstrumentor { + private final Map handlers = new HashMap<>(); + + public CatchInstrumentor( + ClassLoader cl, + MethodVisitor mv, + MethodInstrumentorHelper mHelper, + String parentClz, + String superClz, + int access, + String name, + String desc) { + super(cl, mv, mHelper, parentClz, superClz, access, name, desc); + } + + @Override + public void visitLabel(Label label) { + super.visitLabel(label); + String catchType = handlers.get(label); + if (catchType != null) { + insertFrameReplaceStack(label, Type.getObjectType(catchType)); + onCatch(catchType); + } + } + + @Override + public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { + if (type != null) { + handlers.put(handler, type); + } + super.visitTryCatchBlock(start, end, handler, type); + } + + protected void onCatch(String type) { + asm.println("catching " + type); + } +} diff --git a/btrace-instr/src/main/java/org/openjdk/btrace/instr/ClassCache.java b/btrace-instr/src/main/java/org/openjdk/btrace/instr/ClassCache.java new file mode 100644 index 000000000..2ab9ed3c8 --- /dev/null +++ b/btrace-instr/src/main/java/org/openjdk/btrace/instr/ClassCache.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.btrace.instr; + +import java.lang.ref.PhantomReference; +import java.lang.ref.ReferenceQueue; +import java.util.Map; +import java.util.Objects; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.ConcurrentMap; +import org.jctools.maps.NonBlockingHashMap; +import org.jctools.maps.NonBlockingIdentityHashMap; +import org.openjdk.btrace.instr.ClassInfo.ClassName; + +/** + * A simple class cache holding {@linkplain ClassInfo} instances and being searchable either by + * {@linkplain Class} or a tuple of {@code (className, classLoader)} + * + * @author Jaroslav Bachorik + */ +public final class ClassCache { + private static final class CacheKey { + public final String name; + public final int id; + + private final int hashCode; + + public CacheKey(String name, int id) { + this.name = name; + this.id = id; + this.hashCode = Objects.hash(name, id); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CacheKey cacheKey = (CacheKey) o; + return id == cacheKey.id && name.equals(cacheKey.name); + } + + @Override + public int hashCode() { + return hashCode; + } + } + + private static final class ClassLoaderReference extends PhantomReference { + final CacheKey key; + + public ClassLoaderReference(ClassLoader referent, ReferenceQueue q) { + super(referent, q); + this.key = getCacheKey(referent); + } + } + + private final Map loaderRefs = + new NonBlockingIdentityHashMap<>(); + private final ReferenceQueue cleanupQueue = new ReferenceQueue<>(); + private final ConcurrentMap> cacheMap = + new NonBlockingHashMap<>(); + private final ConcurrentMap bootstrapInfos = new NonBlockingHashMap<>(500); + + private final Timer cleanupTimer = new Timer(true); + + public static ClassCache getInstance() { + return Singleton.INSTANCE; + } + + ClassCache(long cleanupPeriod) { + cleanupTimer.schedule( + new TimerTask() { + @Override + public void run() { + ClassLoaderReference ref = null; + while ((ref = (ClassLoaderReference) cleanupQueue.poll()) != null) { + cacheMap.remove(ref.key); + loaderRefs.remove(ref); + } + } + }, + cleanupPeriod, + cleanupPeriod); + } + + public ClassInfo get(Class clz) { + return get(clz.getClassLoader(), clz.getName()); + } + + /** + * Returns a cached {@linkplain ClassInfo} value. If the corresponding value has not been cached + * yet then it is created and put into the cache. + * + * @param cl The associated {@linkplain ClassLoader} + * @param className The Java class name or internal class name + */ + public ClassInfo get(ClassLoader cl, String className) { + return get(cl, new ClassName(className)); + } + + ClassInfo get(ClassLoader cl, ClassName className) { + ConcurrentMap infos = getInfos(cl); + + return infos.computeIfAbsent(className, k -> new ClassInfo(ClassCache.this, cl, k)); + } + + ConcurrentMap getInfos(ClassLoader cl) { + if (cl == null) { + return bootstrapInfos; + } + boolean[] rslt = new boolean[] {false}; + ConcurrentMap infos = + cacheMap.computeIfAbsent( + getCacheKey(cl), + k -> { + rslt[0] = true; + return new NonBlockingHashMap<>(500); + }); + if (rslt[0]) { + ClassLoaderReference ref = new ClassLoaderReference(cl, cleanupQueue); + loaderRefs.put(ref, ref); + } + return infos; + } + + private static CacheKey getCacheKey(ClassLoader cl) { + return new CacheKey(cl.getClass().getName(), System.identityHashCode(cl)); + } + + int getSize() { + return cacheMap.size(); + } + + private static final class Singleton { + private static final ClassCache INSTANCE = new ClassCache(5000); + } +} diff --git a/btrace-instr/src/main/java/org/openjdk/btrace/instr/ClassFilter.java b/btrace-instr/src/main/java/org/openjdk/btrace/instr/ClassFilter.java new file mode 100644 index 000000000..1377bd210 --- /dev/null +++ b/btrace-instr/src/main/java/org/openjdk/btrace/instr/ClassFilter.java @@ -0,0 +1,321 @@ +/* + * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.btrace.instr; + +import java.lang.annotation.Annotation; +import java.lang.ref.Reference; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.openjdk.btrace.core.PrefixMap; +import org.openjdk.btrace.core.annotations.BTrace; + +/** + * This class checks whether a given target class matches at least one probe specified in a BTrace + * class. + * + * @author A. Sundararajan + */ +public class ClassFilter { + private static final Class REFERENCE_CLASS = Reference.class; + private static final PrefixMap SENSITIVE_CLASSES = new PrefixMap(); + + static { + ClassReader.class.getClassLoader(); + AnnotationVisitor.class.getClassLoader(); + FieldVisitor.class.getClassLoader(); + MethodVisitor.class.getClassLoader(); + Attribute.class.getClassLoader(); + + SENSITIVE_CLASSES.add("java/lang/Object"); + SENSITIVE_CLASSES.add("java/lang/String"); + SENSITIVE_CLASSES.add("java/lang/Shutdown"); + SENSITIVE_CLASSES.add("java/lang/ThreadLocal"); + SENSITIVE_CLASSES.add("java/lang/VerifyError"); + SENSITIVE_CLASSES.add("java/lang/instrument/"); + SENSITIVE_CLASSES.add("java/lang/invoke/"); + SENSITIVE_CLASSES.add("java/lang/ref/"); + SENSITIVE_CLASSES.add("java/lang/concurrent/"); + SENSITIVE_CLASSES.add("sun/reflect"); + SENSITIVE_CLASSES.add("sun/misc/Unsafe"); + SENSITIVE_CLASSES.add("sun/security/"); + SENSITIVE_CLASSES.add("org/openjdk/btrace/"); + SENSITIVE_CLASSES.add("com/sun/proxy/"); + SENSITIVE_CLASSES.add("sun/instrument/"); + SENSITIVE_CLASSES.add("java/lang/ClassValue"); + SENSITIVE_CLASSES.add("java/lang/Throwable$PrintStreamOrWriter"); + SENSITIVE_CLASSES.add("java/lang/Throwable$WrappedPrintStream"); + SENSITIVE_CLASSES.add("java/lang/StackTraceElement"); + SENSITIVE_CLASSES.add("java/lang/StackWalker"); + SENSITIVE_CLASSES.add("java/lang/WeakPairMap$Pair$Weak"); + SENSITIVE_CLASSES.add("java/util/concurrent/locks/"); + SENSITIVE_CLASSES.add("java/nio/charset/"); + SENSITIVE_CLASSES.add("java/nio/HeapCharBuffer"); + SENSITIVE_CLASSES.add("java/nio/CharBuffer"); + SENSITIVE_CLASSES.add("java/nio/Buffer"); + SENSITIVE_CLASSES.add("java/nio/ByteBuffer"); + SENSITIVE_CLASSES.add("java/nio/HeapByteBuffer"); + SENSITIVE_CLASSES.add("jdk/internal/misc/"); + SENSITIVE_CLASSES.add("jdk/internal/reflect/GeneratedConstructorAccessor1"); + SENSITIVE_CLASSES.add("sun/invoke/"); + SENSITIVE_CLASSES.add("java/lang/"); + SENSITIVE_CLASSES.add("java/util/"); + SENSITIVE_CLASSES.add("com/sun/"); + } + + private final List onMethods; + private Set sourceClasses; + private Pattern[] sourceClassPatterns; + private String[] annotationClasses; + private Pattern[] annotationClassPatterns; + // +foo type class pattern in any @OnMethod. + private String[] superTypes; + // same as above but stored in internal name form ('/' instead of '.') + private String[] superTypesInternal; + + public ClassFilter(Iterable onMethods) { + this.onMethods = new ArrayList<>(); + for (OnMethod om : onMethods) { + this.onMethods.add(om); + } + init(); + } + + /* + * return whether given Class is subtype of given type name + * Note that we can not use Class.isAssignableFrom because the other + * type is specified by just name and not by Class object. + */ + public static boolean isSubTypeOf(Class clazz, String typeName) { + if (clazz == null) { + return false; + } else if (clazz.getName().equals(typeName)) { + return true; + } else { + for (Class iface : clazz.getInterfaces()) { + if (isSubTypeOf(iface, typeName)) { + return true; + } + } + return isSubTypeOf(clazz.getSuperclass(), typeName); + } + } + + /** + * Return whether given Class typeA is subtype of any of the given type names. + * + * @param typeA the type to check + * @param loader the classloader for loading the type (my be null) + * @param types any requested supertypes + */ + public static boolean isSubTypeOf(String typeA, ClassLoader loader, String... types) { + if (typeA == null || typeA.equals(Constants.OBJECT_INTERNAL)) { + return false; + } + if (types.length == 0) { + return false; + } + + boolean internal = types[0].contains("/"); + + loader = (loader != null ? loader : ClassLoader.getSystemClassLoader()); + + if (internal) { + typeA = typeA.replace('.', '/'); + } else { + typeA = typeA.replace('/', '.'); + } + + Set typeSet = new HashSet<>(Arrays.asList(types)); + if (typeSet.contains(typeA)) { + return true; + } + ClassInfo ci = ClassCache.getInstance().get(loader, typeA); + Collection sTypesInfo = ci.getSupertypes(false); + if (sTypesInfo != null) { + Collection sTypes = new ArrayList<>(sTypesInfo.size()); + for (ClassInfo sCi : sTypesInfo) { + sTypes.add(internal ? sCi.getClassName() : sCi.getJavaClassName()); + } + sTypes.retainAll(typeSet); + return !sTypes.isEmpty(); + } else { + return false; + } + } + + /* + * Certain classes like java.lang.ThreadLocal and it's + * inner classes, java.lang.Object cannot be safely + * instrumented with BTrace. This is because BTrace uses + * ThreadLocal class to check recursive entries due to + * BTrace's own functions. But this leads to infinite recursions + * if BTrace instruments java.lang.ThreadLocal for example. + * For now, we avoid such classes till we find a solution. + */ + public static boolean isSensitiveClass(String name) { + return SENSITIVE_CLASSES.contains(name); + } + + public boolean isCandidate(Class target) { + if (target.isInterface() || target.isPrimitive() || target.isArray()) { + return false; + } + + if (REFERENCE_CLASS.equals(target)) { + // instrumenting the java.lang.ref.Reference class will lead + // to StackOverflowError in java.lang.ThreadLocal.get() + return false; + } + + try { + // ignore classes annotated with @BTrace - + // We don't want to instrument tracing classes! + if (target.getAnnotation(BTrace.class) != null) { + return false; + } + } catch (Throwable t) { + // thrown from java.lang.Class.initAnnotationsIfNecessary() + // seems to be a case when trying to access non-existing annotations + // on a superclass + // * messed up situation - ignore the class * + return false; + } + + String className = target.getName(); + if (isNameMatching(className)) { + return true; + } + + for (Pattern pat : sourceClassPatterns) { + if (pat.matcher(className).matches()) { + return true; + } + } + + for (String st : superTypes) { + if (isSubTypeOf(target, st)) { + return true; + } + } + + Annotation[] annotations = target.getAnnotations(); + String[] annoTypes = new String[annotations.length]; + for (int i = 0; i < annotations.length; i++) { + annoTypes[i] = annotations[i].annotationType().getName(); + } + + for (String name : annotationClasses) { + for (String annoType : annoTypes) { + if (name.equals(annoType)) { + return true; + } + } + } + + for (Pattern pat : annotationClassPatterns) { + for (String annoType : annoTypes) { + if (pat.matcher(annoType).matches()) { + return true; + } + } + } + + return false; + } + + public boolean isNameMatching(String clzName) { + if (sourceClasses.contains(clzName)) { + return true; + } + + for (Pattern pat : sourceClassPatterns) { + if (pat.matcher(clzName).matches()) { + return true; + } + } + return false; + } + + private void init() { + List strSrcList = new ArrayList<>(); + List patSrcList = new ArrayList<>(); + List superTypesList = new ArrayList<>(); + List superTypesInternalList = new ArrayList<>(); + List strAnoList = new ArrayList<>(); + List patAnoList = new ArrayList<>(); + + for (OnMethod om : onMethods) { + String className = om.getClazz(); + if (className.length() == 0) { + continue; + } + if (om.isClassRegexMatcher()) { + try { + Pattern p = Pattern.compile(className); + if (om.isClassAnnotationMatcher()) { + patAnoList.add(p); + } else { + patSrcList.add(p); + } + } catch (PatternSyntaxException pse) { + System.err.println( + "btrace ERROR: invalid regex pattern - " + + className.substring(1, className.length() - 1)); + } + } else if (om.isClassAnnotationMatcher()) { + strAnoList.add(className); + } else if (om.isSubtypeMatcher()) { + superTypesList.add(className); + superTypesInternalList.add(className.replace('.', '/')); + } else { + strSrcList.add(className); + } + } + + sourceClasses = new HashSet<>(strSrcList.size()); + sourceClasses.addAll(strSrcList); + sourceClassPatterns = new Pattern[patSrcList.size()]; + patSrcList.toArray(sourceClassPatterns); + superTypes = new String[superTypesList.size()]; + superTypesList.toArray(superTypes); + superTypesInternal = new String[superTypesInternalList.size()]; + superTypesInternalList.toArray(superTypesInternal); + annotationClasses = new String[strAnoList.size()]; + strAnoList.toArray(annotationClasses); + annotationClassPatterns = new Pattern[patAnoList.size()]; + patAnoList.toArray(annotationClassPatterns); + } +} diff --git a/btrace-instr/src/main/java/org/openjdk/btrace/instr/ClassInfo.java b/btrace-instr/src/main/java/org/openjdk/btrace/instr/ClassInfo.java new file mode 100644 index 000000000..de3ad1eb1 --- /dev/null +++ b/btrace-instr/src/main/java/org/openjdk/btrace/instr/ClassInfo.java @@ -0,0 +1,386 @@ +/* + * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.btrace.instr; + +import org.openjdk.btrace.core.BTraceRuntime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.Objects; +import java.util.Set; + +/** + * Arbitrary class info type allowing access to supertype information also for not-already-loaded + * classes. + * + * @author Jaroslav Bachorik + */ +public final class ClassInfo { + private static final Logger log = LoggerFactory.getLogger(ClassInfo.class); + + private static final ClassLoader SYS_CL = ClassLoader.getSystemClassLoader(); + private static volatile MethodHandle BSTRP_CHECK_MTD; + private final String cLoaderId; + private final ClassName classId; + + // @ThreadSafe + private final Collection supertypes = new ArrayList<>(); + private final ClassCache cache; + private boolean isInterface = false; + private boolean isAvailable = false; + + ClassInfo(ClassCache cache, Class clz) { + this.cache = cache; + ClassLoader cl = clz.getClassLoader(); + cLoaderId = (cl != null ? cl.toString() : ""); + classId = new ClassName(clz.getName()); + Class supr = clz.getSuperclass(); + if (supr != null) { + supertypes.add(cache.get(supr)); + } + for (Class itfc : clz.getInterfaces()) { + if (itfc != null) { + supertypes.add(cache.get(itfc)); + } + } + isInterface = clz.isInterface(); + isAvailable = true; + } + + ClassInfo(ClassCache cache, ClassLoader cl, ClassName cName) { + this.cache = cache; + cLoaderId = (cl != null ? cl.toString() : ""); + classId = cName; + loadExternalClass(cl, cName); + } + + private static ClassLoader inferClassLoader(ClassLoader initiating, ClassName className) { + if (className == null) { + return initiating; + } + + String jClassName = className.getJavaClassName().toString(); + if (initiating == null || isBootstrap(jClassName)) { + return null; + } else { + String rsrcName = className.getResourcePath(); + ClassLoader cl = initiating; + ClassLoader prev = initiating; + while (cl != null) { + try { + if (cl.getResource(rsrcName) == null) { + return prev; + } + } catch (Throwable t) { + // some containers can impose additional restrictions on loading resources and error on + // unexpected state + log.warn("Failed to get resource {}", rsrcName, t); + } + prev = cl; + cl = cl.getParent(); + } + return initiating; + } + } + + // package private only for testing purposes + static boolean isBootstrap(String className) { + return BTraceRuntime.isBootstrapClass(className); + } + + /** + * Retrieves supertypes (including interfaces) + * + * @param onlyDirect only immediate supertype and implemented interfaces + * @return supertypes (including interfaces) + */ + public Collection getSupertypes(boolean onlyDirect) { + if (onlyDirect) { + return supertypes; + } + Set supers = new LinkedHashSet<>(supertypes); + for (ClassInfo ci : supertypes) { + supers.addAll(ci.getSupertypes(onlyDirect)); + } + return supers; + } + + /** + * Associated class loader string representation as returned by {@code cl.toString()} or {@code + * ""} + * + * @return associated class loader id + */ + public String getLoaderId() { + return cLoaderId; + } + + /** + * Class ID = internal class name + * + * @return internal class name + */ + public String getClassName() { + return classId.getInternalClassName().toString(); + } + + public String getJavaClassName() { + return classId.getJavaClassName().toString(); + } + + public boolean isInterface() { + return isInterface; + } + + public boolean isAvailable() { + return isAvailable; + } + + // not thread safe - must be called only from the constructor + private void loadExternalClass(ClassLoader cl, ClassName className) { + String resourcePath = className.getResourcePath(); + + try { + InputStream typeIs = + cl == null + ? SYS_CL.getResourceAsStream(resourcePath) + : cl.getResourceAsStream(resourcePath); + if (typeIs != null) { + try { + BTraceClassReader cr = new BTraceClassReader(cl, typeIs); + + isInterface = cr.isInterface(); + String[] info = cr.readClassSupers(); + String superName = info[0]; + if (superName != null) { + ClassName superClassName = new ClassName(superName); + supertypes.add(cache.get(inferClassLoader(cl, superClassName), superClassName)); + } + if (info.length > 1) { + for (int i = 1; i < info.length; i++) { + String ifc = info[i]; + if (ifc != null) { + ClassName ifcClassName = new ClassName(ifc); + supertypes.add(cache.get(inferClassLoader(cl, ifcClassName), ifcClassName)); + } + } + } + isAvailable = true; + } catch (IllegalArgumentException | IOException e) { + log.warn("Unable to load class: {}", className, e); + } + } + } catch (Throwable t) { + // some containers can impose additional restrictions on classloaders throwing exceptions when + // not in expected state + log.warn("Failed to load class {}", className, t); + } + } + + @Override + public int hashCode() { + int hash = 5; + hash = 37 * hash + Objects.hashCode(cLoaderId); + hash = 37 * hash + Objects.hashCode(classId); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + ClassInfo other = (ClassInfo) obj; + if (!Objects.equals(cLoaderId, other.cLoaderId)) { + return false; + } + return Objects.equals(classId, other.classId); + } + + @Override + public String toString() { + return "ClassInfo{" + + "cLoaderId=" + + cLoaderId + + ", classId=" + + classId + + ", supertypes=" + + supertypes + + '}'; + } + + private abstract static class BaseClassName implements CharSequence { + protected final CharSequence wrapped; + private String str = null; + + protected BaseClassName(CharSequence wrapped) { + this.wrapped = wrapped; + } + + @Override + public int length() { + return wrapped.length(); + } + + @Override + public CharSequence subSequence(int start, int end) { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + if (str == null) { + char[] val = new char[wrapped.length()]; + for (int i = 0; i < wrapped.length(); i++) { + val[i] = charAt(i); + } + str = new String(val); + } + return str; + } + } + + private static final class JavaClassName extends BaseClassName { + public JavaClassName(CharSequence wrapped) { + super(wrapped); + } + + @Override + public char charAt(int index) { + char c = wrapped.charAt(index); + return (c == '/' ? '.' : c); + } + } + + private static final class InternalClassName extends BaseClassName { + public InternalClassName(CharSequence wrapped) { + super(wrapped); + } + + @Override + public char charAt(int index) { + char c = wrapped.charAt(index); + return (c == '.' ? '/' : c); + } + } + + static final class ClassName { + private final CharSequence cName; + private final JavaClassName jcName; + private final InternalClassName icName; + private String rsrcName = null; + + public ClassName(CharSequence cName) { + this.cName = cName; + jcName = new JavaClassName(cName); + icName = new InternalClassName(cName); + } + + public CharSequence getJavaClassName() { + return jcName; + } + + public CharSequence getInternalClassName() { + return icName; + } + + public String getResourcePath() { + if (rsrcName == null) { + rsrcName = icName + ".class"; + } + return rsrcName; + } + + @Override + public String toString() { + return String.valueOf(cName); + } + + @Override + public int hashCode() { + int h = 7; + int len = cName.length(); + for (int i = 0; i < len; i++) { + char c = cName.charAt(i); + h = 31 * h + (c == '.' ? '/' : c); + } + + return h; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + ClassName other = (ClassName) obj; + if (cName.length() != other.cName.length()) { + return false; + } + for (int i = 0; i < cName.length(); i++) { + char c1 = cName.charAt(i); + char c2 = other.cName.charAt(i); + switch (c1) { + case '.': + case '/': + { + if (c2 != '.' && c2 != '/') { + return false; + } + break; + } + default: + { + if (c1 != c2) { + return false; + } + } + } + } + return true; + } + } +} diff --git a/btrace-instr/src/main/java/org/openjdk/btrace/instr/Constants.java b/btrace-instr/src/main/java/org/openjdk/btrace/instr/Constants.java new file mode 100644 index 000000000..372e4062b --- /dev/null +++ b/btrace-instr/src/main/java/org/openjdk/btrace/instr/Constants.java @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.instr; + +import java.util.regex.Pattern; +import org.objectweb.asm.Type; +import org.openjdk.btrace.core.ArgsMap; +import org.openjdk.btrace.core.BTraceUtils; +import org.openjdk.btrace.core.annotations.BTrace; +import org.openjdk.btrace.core.annotations.Duration; +import org.openjdk.btrace.core.annotations.Injected; +import org.openjdk.btrace.core.annotations.Kind; +import org.openjdk.btrace.core.annotations.Level; +import org.openjdk.btrace.core.annotations.Location; +import org.openjdk.btrace.core.annotations.OnError; +import org.openjdk.btrace.core.annotations.OnEvent; +import org.openjdk.btrace.core.annotations.OnExit; +import org.openjdk.btrace.core.annotations.OnLowMemory; +import org.openjdk.btrace.core.annotations.OnMethod; +import org.openjdk.btrace.core.annotations.OnProbe; +import org.openjdk.btrace.core.annotations.OnTimer; +import org.openjdk.btrace.core.annotations.PeriodicEvent; +import org.openjdk.btrace.core.annotations.ProbeClassName; +import org.openjdk.btrace.core.annotations.ProbeMethodName; +import org.openjdk.btrace.core.annotations.Return; +import org.openjdk.btrace.core.annotations.Sampled; +import org.openjdk.btrace.core.annotations.Self; +import org.openjdk.btrace.core.annotations.TargetInstance; +import org.openjdk.btrace.core.annotations.TargetMethodOrField; +import org.openjdk.btrace.core.annotations.Where; +import org.openjdk.btrace.core.jfr.JfrEvent; +import org.openjdk.btrace.services.api.Service; + +/** + * Constants shared by few classes. + * + * @author A. Sundararajan + */ +public abstract class Constants { + public static final String BTRACE_METHOD_PREFIX = "$btrace$"; + + public static final String CONSTRUCTOR = ""; + public static final String CLASS_INITIALIZER = ""; + + public static final Type NULL_TYPE = Type.getType("L$$null"); + public static final Type TOP_TYPE = Type.getType("L$$top"); + + public static final Type VOIDREF_TYPE = Type.getType("Ljava/lang/Void;"); + + public static final String OBJECT_INTERNAL = "java/lang/Object"; + public static final String OBJECT_DESC = "L" + OBJECT_INTERNAL + ";"; + public static final Type OBJECT_TYPE = Type.getType(OBJECT_DESC); + + public static final String ANYTYPE_INTERNAL = "org/openjdk/btrace/core/types/AnyType"; + public static final String ANYTYPE_DESC = "L" + ANYTYPE_INTERNAL + ";"; + public static final Type ANYTYPE_TYPE = Type.getType(ANYTYPE_DESC); + + public static final String CLASS_DESC = "Ljava/lang/Class;"; + public static final Type CLASS_TYPE = Type.getType(CLASS_DESC); + + public static final String STRING_INTERNAL = "java/lang/String"; + public static final String STRING_DESC = "L" + STRING_INTERNAL + ";"; + public static final Type STRING_TYPE = Type.getType(STRING_DESC); + + public static final String STRING_BUILDER_INTERNAL = "java/lang/StringBuilder"; + public static final String STRING_BUILDER_DESC = "L" + STRING_BUILDER_INTERNAL + ";"; + public static final Type STRING_BUILDER_TYPE = Type.getType(STRING_BUILDER_DESC); + + public static final String VOID_DESC = "V"; + public static final String BOOLEAN_DESC = "Z"; + public static final String INT_DESC = "I"; + + public static final String THROWABLE_INTERNAL = "java/lang/Throwable"; + public static final String THROWABLE_DESC = "L" + THROWABLE_INTERNAL + ";"; + public static final Type THROWABLE_TYPE = Type.getType(THROWABLE_DESC); + + public static final String BTRACERTACCESS_INTERNAL = + "org/openjdk/btrace/runtime/BTraceRuntimeAccess"; + public static final String BTRACERTACCESS_DESC = "L" + BTRACERTACCESS_INTERNAL + ";"; + public static final String BTRACERT_INTERNAL = "org/openjdk/btrace/core/BTraceRuntime"; + public static final String BTRACERT_DESC = "L" + BTRACERT_INTERNAL + ";"; + public static final String BTRACERTIMPL_INTERNAL = "org/openjdk/btrace/core/BTraceRuntime$Impl"; + public static final String BTRACERTIMPL_DESC = "L" + BTRACERTIMPL_INTERNAL + ";"; + public static final String BTRACERTBASE_INTERNAL = + "org/openjdk/btrace/runtime/BTraceRuntimeImplBase"; + public static final String BTRACERTBASE_DESC = "L" + BTRACERTBASE_INTERNAL + ";"; + public static final Type BTRACERT_TYPE = Type.getType(BTRACERT_DESC); + + public static final String THREAD_LOCAL_INTERNAL = "java/lang/ThreadLocal"; + public static final String THREAD_LOCAL_DESC = "L" + THREAD_LOCAL_INTERNAL + ";"; + public static final Type THREAD_LOCAL_TYPE = Type.getType(ThreadLocal.class); + + // BTrace specific stuff + public static final String BTRACE_UTILS = Type.getInternalName(BTraceUtils.class); + + public static final String SERVICE = Type.getInternalName(Service.class); + + public static final String BTRACE_DESC = Type.getDescriptor(BTrace.class); + + public static final String ONMETHOD_DESC = Type.getDescriptor(OnMethod.class); + + public static final String JFRPERIODIC_DESC = Type.getDescriptor(PeriodicEvent.class); + + public static final String JFREVENTFACTORY_DESC = Type.getDescriptor(JfrEvent.Factory.class); + + public static final String BTRACE_PROBECLASSNAME_DESC = Type.getDescriptor(ProbeClassName.class); + + public static final String BTRACE_PROBEMETHODNAME_DESC = + Type.getDescriptor(ProbeMethodName.class); + + public static final String ONTIMER_DESC = Type.getDescriptor(OnTimer.class); + public static final String ONEVENT_DESC = Type.getDescriptor(OnEvent.class); + public static final String ONEXIT_DESC = Type.getDescriptor(OnExit.class); + public static final String ONERROR_DESC = Type.getDescriptor(OnError.class); + public static final String ONLOWMEMORY_DESC = Type.getDescriptor(OnLowMemory.class); + + public static final String SAMPLED_DESC = Type.getDescriptor(Sampled.class); + + public static final String SAMPLER_DESC = Type.getDescriptor(Sampled.Sampler.class); + + public static final String ONPROBE_DESC = Type.getDescriptor(OnProbe.class); + + public static final String LOCATION_DESC = Type.getDescriptor(Location.class); + + public static final String LEVEL_DESC = Type.getDescriptor(Level.class); + + public static final String WHERE_DESC = Type.getDescriptor(Where.class); + + public static final String KIND_DESC = Type.getDescriptor(Kind.class); + + public static final String INJECTED_DESC = Type.getDescriptor(Injected.class); + + public static final String RETURN_DESC = Type.getDescriptor(Return.class); + + public static final String SELF_DESC = Type.getDescriptor(Self.class); + + public static final String TARGETMETHOD_DESC = Type.getDescriptor(TargetMethodOrField.class); + + public static final String TARGETINSTANCE_DESC = Type.getDescriptor(TargetInstance.class); + + public static final String DURATION_DESC = Type.getDescriptor(Duration.class); + + public static final String ARGSMAP_DESC = Type.getDescriptor(ArgsMap.class); + + // class name pattern is specified with this pattern + public static final Pattern REGEX_SPECIFIER = Pattern.compile("/.+/"); + + public static final String JAVA_LANG_THREAD_LOCAL = Type.getInternalName(ThreadLocal.class); + public static final String JAVA_LANG_THREAD_LOCAL_GET = "get"; + public static final String JAVA_LANG_THREAD_LOCAL_GET_DESC = "()Ljava/lang/Object;"; + public static final String JAVA_LANG_THREAD_LOCAL_SET = "set"; + public static final String JAVA_LANG_THREAD_LOCAL_SET_DESC = "(Ljava/lang/Object;)V"; + + public static final String NUMBER_INTERNAL = "java/lang/Number"; + public static final String INTEGER_BOXED_INTERNAL = "java/lang/Integer"; + public static final String INTEGER_BOXED_DESC = "L" + INTEGER_BOXED_INTERNAL + ";"; + public static final String SHORT_BOXED_INTERNAL = "java/lang/Short"; + public static final String SHORT_BOXED_DESC = "L" + SHORT_BOXED_INTERNAL + ";"; + public static final String LONG_BOXED_INTERNAL = "java/lang/Long"; + public static final String LONG_BOXED_DESC = "L" + LONG_BOXED_INTERNAL + ";"; + public static final String FLOAT_BOXED_INTERNAL = "java/lang/Float"; + public static final String FLOAT_BOXED_DESC = "L" + FLOAT_BOXED_INTERNAL + ";"; + public static final String DOUBLE_BOXED_INTERNAL = "java/lang/Double"; + public static final String DOUBLE_BOXED_DESC = "L" + DOUBLE_BOXED_INTERNAL + ";"; + public static final String BYTE_BOXED_INTERNAL = "java/lang/Byte"; + public static final String BYTE_BOXED_DESC = "L" + BYTE_BOXED_INTERNAL + ";"; + public static final String BOOLEAN_BOXED_INTERNAL = "java/lang/Boolean"; + public static final String BOOLEAN_BOXED_DESC = "L" + BOOLEAN_BOXED_INTERNAL + ";"; + public static final String CHARACTER_BOXED_INTERNAL = "java/lang/Character"; + public static final String CHARACTER_BOXED_DESC = "L" + CHARACTER_BOXED_INTERNAL + ";"; + + public static final String BOX_VALUEOF = "valueOf"; + public static final String BOX_BOOLEAN_DESC = "(" + BOOLEAN_DESC + ")" + BOOLEAN_BOXED_DESC; + public static final String BOX_CHARACTER_DESC = "(C)Ljava/lang/Character;"; + public static final String BOX_BYTE_DESC = "(B)Ljava/lang/Byte;"; + public static final String BOX_SHORT_DESC = "(S)Ljava/lang/Short;"; + public static final String BOX_INTEGER_DESC = "(I)Ljava/lang/Integer;"; + public static final String BOX_LONG_DESC = "(J)Ljava/lang/Long;"; + public static final String BOX_FLOAT_DESC = "(F)Ljava/lang/Float;"; + public static final String BOX_DOUBLE_DESC = "(D)Ljava/lang/Double;"; + + public static final String BOOLEAN_VALUE = "booleanValue"; + public static final String CHAR_VALUE = "charValue"; + public static final String BYTE_VALUE = "byteValue"; + public static final String SHORT_VALUE = "shortValue"; + public static final String INT_VALUE = "intValue"; + public static final String LONG_VALUE = "longValue"; + public static final String FLOAT_VALUE = "floatValue"; + public static final String DOUBLE_VALUE = "doubleValue"; + + public static final String BOOLEAN_VALUE_DESC = "()Z"; + public static final String CHAR_VALUE_DESC = "()C"; + public static final String BYTE_VALUE_DESC = "()B"; + public static final String SHORT_VALUE_DESC = "()S"; + public static final String INT_VALUE_DESC = "()I"; + public static final String LONG_VALUE_DESC = "()J"; + public static final String FLOAT_VALUE_DESC = "()F"; + public static final String DOUBLE_VALUE_DESC = "()D"; + public static final String EMBEDDED_BTRACE_SECTION_HEADER = "META-INF/btrace/"; + + public static final String BTRACE_LEVEL_FLD = "$btrace$$level"; +} diff --git a/btrace-instr/src/main/java/org/openjdk/btrace/instr/CopyingVisitor.java b/btrace-instr/src/main/java/org/openjdk/btrace/instr/CopyingVisitor.java new file mode 100644 index 000000000..1e08291f9 --- /dev/null +++ b/btrace-instr/src/main/java/org/openjdk/btrace/instr/CopyingVisitor.java @@ -0,0 +1,61 @@ +package org.openjdk.btrace.instr; + +import static org.objectweb.asm.Opcodes.ASM9; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +class CopyingVisitor extends ClassVisitor { + private final boolean renameParent; + private final String targetClassName; + + private String origClassName; + + public CopyingVisitor(String targetClassName, boolean renameParent, ClassVisitor parent) { + super(Opcodes.ASM8, parent); + this.targetClassName = targetClassName; + this.renameParent = renameParent; + } + + @Override + public void visit( + int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + if (renameParent) { + super.visit(version, access, targetClassName, signature, superName, interfaces); + } + origClassName = name; + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String sig, String[] exceptions) { + return new MethodVisitor( + ASM9, + super.visitMethod( + Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, getMethodName(name), desc, sig, exceptions)) { + @Override + public void visitMethodInsn( + int opcode, String owner, String name, String desc, boolean itfc) { + if (owner.equals(origClassName)) { + owner = targetClassName; + name = getActionMethodName(name); + } + super.visitMethodInsn(opcode, owner, name, desc, itfc); + } + }; + } + + protected String getActionMethodName(String name) { + return name; + } + + protected String getMethodName(String name) { + return name; + } +} diff --git a/btrace-instr/src/main/java/org/openjdk/btrace/instr/ErrorReturnInstrumentor.java b/btrace-instr/src/main/java/org/openjdk/btrace/instr/ErrorReturnInstrumentor.java new file mode 100644 index 000000000..f3724b2ed --- /dev/null +++ b/btrace-instr/src/main/java/org/openjdk/btrace/instr/ErrorReturnInstrumentor.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.instr; + +import static org.objectweb.asm.Opcodes.ATHROW; +import static org.openjdk.btrace.instr.Constants.THROWABLE_INTERNAL; +import static org.openjdk.btrace.instr.Constants.THROWABLE_TYPE; + +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; + +/** + * This visitor helps in inserting code whenever a method "returns" because of an exception (i.e., + * no exception handler in the method and so it's frame poped). The code to insert on method error + * return may be decided by derived class. By default, this class inserts code to print message to + * say "no handler here". + * + * @author A. Sundararajan + */ +public class ErrorReturnInstrumentor extends MethodReturnInstrumentor { + private final Label start = new Label(); + private final Label end = new Label(); + + public ErrorReturnInstrumentor( + ClassLoader cl, + MethodVisitor mv, + MethodInstrumentorHelper mHelper, + String parentClz, + String superClz, + int access, + String name, + String desc) { + super(cl, mv, mHelper, parentClz, superClz, access, name, desc); + } + + @Override + protected void visitMethodPrologue() { + addTryCatchHandler(start, end); + visitLabel(start); + super.visitMethodPrologue(); + } + + @Override + public void visitMaxs(int maxStack, int maxLocals) { + visitTryCatchBlock(start, end, end, THROWABLE_INTERNAL); + visitLabel(end); + insertFrameReplaceStack(end, THROWABLE_TYPE); + onErrorReturn(); + visitInsn(ATHROW); + super.visitMaxs(maxStack, maxLocals); + } + + @Override + protected void onMethodEntry() {} + + @Override + protected void onMethodReturn(int opcode) {} + + protected void onErrorReturn() { + asm.println("error return from " + getName() + getDescriptor()); + } +} diff --git a/btrace-instr/src/main/java/org/openjdk/btrace/instr/FieldAccessInstrumentor.java b/btrace-instr/src/main/java/org/openjdk/btrace/instr/FieldAccessInstrumentor.java new file mode 100644 index 000000000..b2674c8b8 --- /dev/null +++ b/btrace-instr/src/main/java/org/openjdk/btrace/instr/FieldAccessInstrumentor.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the Classpath exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.instr; + +import static org.objectweb.asm.Opcodes.*; + +import org.objectweb.asm.MethodVisitor; + +/** + * This visitor helps in inserting code whenever an field access is done. The code to insert on + * field access may be decided by derived class. By default, this class inserts code to print the + * field access. + * + * @author A. Sundararajan + */ +public class FieldAccessInstrumentor extends MethodInstrumentor { + protected boolean isStaticAccess = false; + + public FieldAccessInstrumentor( + ClassLoader cl, + MethodVisitor mv, + MethodInstrumentorHelper mHelper, + String parentClz, + String superClz, + int access, + String name, + String desc) { + super(cl, mv, mHelper, parentClz, superClz, access, name, desc); + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + boolean get; + // ignore any internal BTrace fields + if (name.contains("$btrace$")) { + super.visitFieldInsn(opcode, owner, name, desc); + return; + } + + get = opcode == GETFIELD || opcode == GETSTATIC; + isStaticAccess = (opcode == GETSTATIC || opcode == PUTSTATIC); + + if (get) { + onBeforeGetField(opcode, owner, name, desc); + } else { + onBeforePutField(opcode, owner, name, desc); + } + super.visitFieldInsn(opcode, owner, name, desc); + if (get) { + onAfterGetField(opcode, owner, name, desc); + } else { + onAfterPutField(opcode, owner, name, desc); + } + } + + protected void onBeforeGetField(int opcode, String owner, String name, String desc) {} + + protected void onAfterGetField(int opcode, String owner, String name, String desc) {} + + protected void onBeforePutField(int opcode, String owner, String name, String desc) {} + + protected void onAfterPutField(int opcode, String owner, String name, String desc) {} +} diff --git a/btrace-instr/src/main/java/org/openjdk/btrace/instr/HandlerRepositoryImpl.java b/btrace-instr/src/main/java/org/openjdk/btrace/instr/HandlerRepositoryImpl.java new file mode 100644 index 000000000..8543ed8ce --- /dev/null +++ b/btrace-instr/src/main/java/org/openjdk/btrace/instr/HandlerRepositoryImpl.java @@ -0,0 +1,78 @@ +package org.openjdk.btrace.instr; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.openjdk.btrace.core.DebugSupport; +import org.openjdk.btrace.core.HandlerRepository; +import org.openjdk.btrace.core.SharedSettings; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public final class HandlerRepositoryImpl { + private static final Logger log = LoggerFactory.getLogger(HandlerRepositoryImpl.class); + + private static final Map probeMap = new ConcurrentHashMap<>(); + + static { + try { + Class indyClz = Class.forName("org.openjdk.btrace.runtime.Indy"); + HandlerRepository hook = HandlerRepositoryImpl::getProbeHandler; + indyClz.getField("repository").set(null, hook); + } catch (UnsupportedClassVersionError ignored) { + // expected for pre Java 15 runtimes + } catch (Throwable t) { + log.warn("Unable to initialize BTrace Indy support", t); + } + } + + public static void registerProbe(BTraceProbe probe) { + probeMap.put(probe.getClassName(true), probe); + } + + public static void unregisterProbe(BTraceProbe probe) { + probeMap.remove(probe.getClassName(true)); + } + + public static byte[] getProbeHandler( + String callerName, String probeName, String handlerName, String handlerDesc) { + DebugSupport debugSupport = new DebugSupport(SharedSettings.GLOBAL); + BTraceProbe probe = probeMap.get(probeName); + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); + + String handlerClassName = callerName.replace('.', '/') + "$" + probeName.replace('/', '_'); + ClassVisitor visitor = + new CopyingVisitor(handlerClassName, true, writer) { + @Override + protected String getMethodName(String name) { + int idx = name.lastIndexOf("$"); + if (idx > -1) { + return name.substring(idx + 1); + } + return name; + } + }; + + probe.copyHandlers(visitor); + byte[] data = writer.toByteArray(); + + if (debugSupport.isDumpClasses()) { + try { + String handlerPath = + debugSupport.getDumpClassDir() + "/" + handlerClassName.replace('/', '_') + ".class"; + log.debug("BTrace INDY handler dumped: {}", handlerPath); + Files.write(Paths.get(handlerPath), data, StandardOpenOption.CREATE); + } catch (IOException e) { + e.printStackTrace(); + } + } + + return data; + } +} diff --git a/btrace-instr/src/main/java/org/openjdk/btrace/instr/InstrPackGenerator.java b/btrace-instr/src/main/java/org/openjdk/btrace/instr/InstrPackGenerator.java new file mode 100644 index 000000000..b9fc8eab1 --- /dev/null +++ b/btrace-instr/src/main/java/org/openjdk/btrace/instr/InstrPackGenerator.java @@ -0,0 +1,28 @@ +package org.openjdk.btrace.instr; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import org.openjdk.btrace.compiler.PackGenerator; +import org.openjdk.btrace.core.SharedSettings; + +public final class InstrPackGenerator implements PackGenerator { + @Override + public byte[] generateProbePack(byte[] classData) throws IOException { + BTraceProbeNode bpn = + (BTraceProbeNode) new BTraceProbeFactory(SharedSettings.GLOBAL).createProbe(classData); + // force bytecode verification before creating the persisted representation + bpn.checkVerified(); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + try (DataOutputStream dos = new DataOutputStream(bos)) { + BTraceProbePersisted bpp = BTraceProbePersisted.from(bpn); + if (!bpp.isVerified()) { + throw new Error(); + } + bpp.write(dos); + } + + return bos.toByteArray(); + } +} diff --git a/btrace-instr/src/main/java/org/openjdk/btrace/instr/InstrumentUtils.java b/btrace-instr/src/main/java/org/openjdk/btrace/instr/InstrumentUtils.java new file mode 100644 index 000000000..175e27dd3 --- /dev/null +++ b/btrace-instr/src/main/java/org/openjdk/btrace/instr/InstrumentUtils.java @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.btrace.instr; + +import static org.objectweb.asm.Opcodes.*; +import static org.openjdk.btrace.instr.TypeUtils.isAnyType; +import static org.openjdk.btrace.instr.TypeUtils.isPrimitive; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Type; + +/** + * @author A. Sundararajan + * @author J. Bachorik + */ +public final class InstrumentUtils { + private static final int CW_FLAGS = 0; // ClassWriter.COMPUTE_MAXS; + + /** + * Collects the type hierarchy into the provided list, sorted from the actual type to root. Common + * superclasses may be present multiple times (eg. {@code java.lang.Object}) It will use the + * associated classloader to locate the class file resources. + * + * @param cl the associated classloader + * @param type the type to compute the hierarchy closure for (either Java or internal name format) + * @param closure the ordered set to store the closure in + * @param useInternal should internal types names be used in the closure + */ + public static void collectHierarchyClosure( + ClassLoader cl, String type, Set closure, boolean useInternal) { + collectHierarchyClosure(cl, type, closure, useInternal, false); + } + + /** + * Collects the type hierarchy into the provided list, sorted from the actual type to root. Common + * superclasses may be present multiple times (eg. {@code java.lang.Object}) It will use the + * associated classloader to locate the class file resources. + * + * @param cl the associated classloader + * @param type the type to compute the hierarchy closure for (either Java or internal name format) + * @param closure the ordered set to store the closure in + * @param useInternal should internal types names be used in the closure + */ + public static void collectHierarchyClosure( + ClassLoader cl, String type, Set closure, boolean useInternal, boolean ifcs) { + if (type == null || type.equals(Constants.OBJECT_INTERNAL)) { + return; + } + ClassInfo ci = ClassCache.getInstance().get(cl, type); + + Set ciSet = new LinkedHashSet<>(); + + // add self + ciSet.add(ci); + for (ClassInfo sci : ci.getSupertypes(false)) { + if ((ifcs || !sci.isInterface()) && !sci.getClassName().equals(Constants.OBJECT_INTERNAL)) { + ciSet.add(sci); + } + } + + for (ClassInfo sci : ciSet) { + closure.add(useInternal ? sci.getClassName() : sci.getJavaClassName()); + } + } + + public static boolean isAssignable( + Type left, Type right, ClassLoader cl, boolean exactTypeCheck) { + boolean isSame = left.equals(right); + + if (isSame) { + return true; + } + + if (TypeUtils.isVoid(left)) { + return TypeUtils.isVoid(right); + } + + if (TypeUtils.isAnyType(left)) { + return true; + } + + if (exactTypeCheck) { + return false; + } + + if (TypeUtils.isObject(left)) { + return true; + } + + Set closure = new HashSet<>(); + collectHierarchyClosure(cl, right.getInternalName(), closure, true, true); + return closure.contains(left.getInternalName()); + } + + public static boolean isAssignable( + Type[] args1, Type[] args2, ClassLoader cl, boolean exactTypeCheck) { + if (args1.length != args2.length) { + return false; + } + for (int i = 0; i < args1.length; i++) { + if (!args1[i].equals(args2[i])) { + int sort2 = args2[i].getSort(); + /* + * if destination is AnyType and right side is + * Object or Array (i.e., any reference type) + * then we allow it - because AnyType is mapped to + * java.lang.Object. + */ + if (!(isAnyType(args1[i]) + && (sort2 == Type.OBJECT || sort2 == Type.ARRAY || isPrimitive(args2[i])))) { + if (!isAssignable(args1[i], args2[i], cl, exactTypeCheck)) { + return false; + } + } + } + } + return true; + } + + public static String arrayDescriptorFor(int typeCode) { + switch (typeCode) { + case T_BOOLEAN: + return "[Z"; + case T_CHAR: + return "[C"; + case T_FLOAT: + return "[F"; + case T_DOUBLE: + return "[D"; + case T_BYTE: + return "[B"; + case T_SHORT: + return "[S"; + case T_INT: + return "[I"; + case T_LONG: + return "[J"; + default: + throw new IllegalArgumentException(); + } + } + + public static void accept(BTraceClassReader reader, ClassVisitor visitor) { + accept(reader, visitor, 0); + } + + public static void accept(BTraceClassReader reader, ClassVisitor visitor, int flags) { + if (reader == null || visitor == null) return; + + reader.accept(visitor, flags); + } + + private static boolean isJDK16OrAbove(byte[] code) { + return isJDK16OrAbove(getMajor(code)); + } + + private static boolean isJDK16OrAbove(BTraceClassReader cr) { + return isJDK16OrAbove(getMajor(cr)); + } + + private static boolean isJDK16OrAbove(int major) { + return major >= 50; + } + + private static int getMajor(BTraceClassReader cr) { + return cr.getClassVersion(); + } + + private static int getMajor(byte[] code) { + // skip 0xCAFEBABE magic and minor version + int majorOffset = 4 + 2; + return (((code[majorOffset] << 8) & 0xFF00) | ((code[majorOffset + 1]) & 0xFF)); + } + + public static ClassWriter newClassWriter() { + return newClassWriter(false); + } + + public static ClassWriter newClassWriter(boolean computeFrames) { + return newClassWriter(computeFrames, false); + } + + public static ClassWriter newClassWriter(boolean computeFrames, boolean computeMaxs) { + int flags = CW_FLAGS; + if (computeFrames) { + flags |= ClassWriter.COMPUTE_FRAMES; + } + if (computeMaxs) { + flags |= ClassWriter.COMPUTE_MAXS; + } + return newClassWriter(null, flags); + } + + static BTraceClassWriter newClassWriter(BTraceClassReader cr) { + return newClassWriter(cr, CW_FLAGS); + } + + static BTraceClassWriter newClassWriter(BTraceClassReader reader, int flags) { + BTraceClassWriter cw; + cw = + reader != null + ? new BTraceClassWriter(reader.getClassLoader(), reader, flags) + : new BTraceClassWriter(null, flags); + + return cw; + } + + static BTraceClassReader newClassReader(byte[] code) { + return new BTraceClassReader(ClassLoader.getSystemClassLoader(), code); + } + + static BTraceClassReader newClassReader(ClassLoader cl, byte[] code) { + return new BTraceClassReader(cl, code); + } + + static BTraceClassReader newClassReader(InputStream is) throws IOException { + return new BTraceClassReader(ClassLoader.getSystemClassLoader(), is); + } + + static BTraceClassReader newClassReader(ClassLoader cl, InputStream is) throws IOException { + return new BTraceClassReader(cl, is); + } + + static String getActionPrefix(String className) { + return Constants.BTRACE_METHOD_PREFIX + className.replace('/', '$') + "$"; + } +} diff --git a/btrace-instr/src/main/java/org/openjdk/btrace/instr/InstrumentingMethodVisitor.java b/btrace-instr/src/main/java/org/openjdk/btrace/instr/InstrumentingMethodVisitor.java new file mode 100644 index 000000000..1d233ff24 --- /dev/null +++ b/btrace-instr/src/main/java/org/openjdk/btrace/instr/InstrumentingMethodVisitor.java @@ -0,0 +1,1662 @@ +/* + * Copyright (c) 2017, Jaroslav Bachorik . + * All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Copyright owner designates + * this particular file as subject to the "Classpath" exception as provided + * by the owner in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +package org.openjdk.btrace.instr; + +import static org.objectweb.asm.Opcodes.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Handle; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.TypePath; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A method visitor providing support for introducing new local variables in bytecode recomputing + * stackmap frames as necessary. It also provides an API for downstream visitors to hint insertion + * of stackmap frames at required locations. + */ +public final class InstrumentingMethodVisitor extends MethodVisitor + implements MethodInstrumentorHelper { + private static final Logger log = LoggerFactory.getLogger(InstrumentingMethodVisitor.class); + + static final Object TOP_EXT = -2; + private final VariableMapper variableMapper; + private final SimulatedStack stack = new SimulatedStack(); + private final List locals = new ArrayList<>(); + private final Set newLocals = new HashSet<>(3); + private final LocalVarTypes localTypes = new LocalVarTypes(); + private final Set frameOffsets = new HashSet<>(); + private final Map jumpTargetStates = new HashMap<>(); + private final Map> tryCatchHandlerMap = new HashMap<>(); + private final String owner; + private final String desc; + private int argsSize = 0; + private int localsTailPtr = 0; + private int pc = 0, lastFramePc = Integer.MIN_VALUE; + + public InstrumentingMethodVisitor( + int access, String owner, String name, String desc, MethodVisitor mv) { + super(ASM9, mv); + this.owner = owner; + this.desc = desc; + + initLocals((access & ACC_STATIC) == 0, "".equals(name)); + variableMapper = new VariableMapper(argsSize); + } + + private static Object toSlotType(Type t) { + if (t == null) { + return null; + } + switch (t.getSort()) { + case Type.BOOLEAN: + case Type.CHAR: + case Type.BYTE: + case Type.SHORT: + case Type.INT: + { + return INTEGER; + } + case Type.FLOAT: + { + return FLOAT; + } + case Type.LONG: + { + return LONG; + } + case Type.DOUBLE: + { + return DOUBLE; + } + default: + { + return t == Constants.NULL_TYPE + ? NULL + : (t == Constants.TOP_TYPE ? TOP : t.getInternalName()); + } + } + } + + @Override + public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { + if (lastFramePc == pc) { + return; + } + lastFramePc = pc; + + switch (type) { + case F_NEW: // fallthrough + case F_FULL: + { + locals.clear(); + this.stack.reset(); + + locals.addAll(Arrays.asList(local).subList(0, nLocal)); + localsTailPtr = nLocal; + + for (int i = 0; i < nStack; i++) { + Object e = stack[i]; + this.stack.push(e); + } + break; + } + case F_SAME: + { + this.stack.reset(); + break; + } + case F_SAME1: + { + this.stack.reset(); + Object e = stack[0]; + this.stack.push(e); + break; + } + case F_APPEND: + { + this.stack.reset(); + int top = locals.size(); + for (int i = 0; i < nLocal; i++) { + Object e = local[i]; + if (localsTailPtr < top) { + locals.set(localsTailPtr, e); + } else { + locals.add(e); + } + localsTailPtr++; + } + break; + } + case F_CHOP: + { + this.stack.reset(); + for (int i = 0; i < nLocal; i++) { + locals.remove(--localsTailPtr); + } + break; + } + } + + Object[] localsArr = computeFrameLocals(); + localTypes.replaceWith(localsArr); + + int off = 0; + for (int i = 0; i < localsArr.length; i++) { + Object val = localsArr[i]; + if (val == TOP_EXT) { + off++; + continue; + } + if (off > 0) { + localsArr[i - off] = localsArr[i]; + } + } + localsArr = Arrays.copyOf(localsArr, localsArr.length - off); + Object[] tmpStack = this.stack.toArray(true); + + super.visitFrame(F_NEW, localsArr.length, localsArr, tmpStack.length, tmpStack); + } + + @Override + public void visitMultiANewArrayInsn(String arrayTypeName, int dims) { + Type arrayType = Type.getObjectType(arrayTypeName); + for (int i = 0; i < dims; i++) { + stack.pop(); + } + stack.push(arrayType.getDescriptor()); + super.visitMultiANewArrayInsn(arrayTypeName, dims); + pc++; + } + + @Override + public void visitLookupSwitchInsn(Label label, int[] ints, Label[] labels) { + stack.pop(); + SavedState state = + new SavedState(localTypes, stack, newLocals, SavedState.CONDITIONAL); + jumpTargetStates.put(label, state); + for (Label l : labels) { + jumpTargetStates.put(l, state); + } + super.visitLookupSwitchInsn(label, ints, labels); + pc++; + } + + @Override + public void visitTableSwitchInsn(int i, int i1, Label label, Label... labels) { + stack.pop(); + SavedState state = + new SavedState(localTypes, stack, newLocals, SavedState.CONDITIONAL); + jumpTargetStates.put(label, state); + for (Label l : labels) { + jumpTargetStates.put(l, state); + } + super.visitTableSwitchInsn(i, i1, label, labels); + pc++; + } + + @Override + public void visitLdcInsn(Object o) { + Type t = Type.getType(o.getClass()); + switch (t.getInternalName()) { + case "java/lang/Integer": + { + pushToStack(Type.INT_TYPE); + break; + } + case "java/lang/Long": + { + pushToStack(Type.LONG_TYPE); + break; + } + case "java/lang/Byte": + { + pushToStack(Type.BYTE_TYPE); + break; + } + case "java/lang/Short": + { + pushToStack(Type.SHORT_TYPE); + break; + } + case "java/lang/Character": + { + pushToStack(Type.CHAR_TYPE); + break; + } + case "java/lang/Boolean": + { + pushToStack(Type.BOOLEAN_TYPE); + break; + } + case "java/lang/Float": + { + pushToStack(Type.FLOAT_TYPE); + break; + } + case "java/lang/Double": + { + pushToStack(Type.DOUBLE_TYPE); + break; + } + default: + { + pushToStack(t); + } + } + super.visitLdcInsn(o); + pc++; + } + + @Override + public void visitJumpInsn(int opcode, Label label) { + super.visitJumpInsn(opcode, label); + pc++; + switch (opcode) { + case IFEQ: + case IFGE: + case IFGT: + case IFLE: + case IFLT: + case IFNE: + case IFNONNULL: + case IFNULL: + { + stack.pop(); + break; + } + case IF_ACMPEQ: + case IF_ACMPNE: + case IF_ICMPEQ: + case IF_ICMPGE: + case IF_ICMPGT: + case IF_ICMPLE: + case IF_ICMPLT: + case IF_ICMPNE: + { + stack.pop(); + stack.pop(); + break; + } + } + jumpTargetStates.put( + label, + new SavedState( + localTypes, + stack, + newLocals, + opcode == GOTO || opcode == JSR ? SavedState.UNCONDITIONAL : SavedState.CONDITIONAL)); + } + + @Override + public void visitInvokeDynamicInsn(String name, String desc, Handle handle, Object... bsmArgs) { + Type[] args = Type.getArgumentTypes(desc); + Type ret = Type.getReturnType(desc); + + for (int i = args.length - 1; i >= 0; i--) { + if (!args[i].equals(Type.VOID_TYPE)) { + popFromStack(args[i]); + } + } + super.visitInvokeDynamicInsn(name, desc, handle, bsmArgs); + pc++; + + if (!ret.equals(Type.VOID_TYPE)) { + pushToStack(ret); + } + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itfc) { + Type[] args = Type.getArgumentTypes(desc); + Type ret = Type.getReturnType(desc); + + for (int i = args.length - 1; i >= 0; i--) { + if (!args[i].equals(Type.VOID_TYPE)) { + popFromStack(args[i]); + } + } + + if (opcode != INVOKESTATIC) { + stack.pop(); + } + super.visitMethodInsn(opcode, owner, name, desc, itfc); + pc++; + + if (!ret.equals(Type.VOID_TYPE)) { + pushToStack(ret); + } + if (opcode == INVOKESPECIAL && name.equals("")) { + if (stack.peek() instanceof Label) { + stack.pop(); + pushToStack(Type.getObjectType(owner)); + } + } + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + Type t = Type.getType(desc); + super.visitFieldInsn(opcode, owner, name, desc); + pc++; + + if (opcode == PUTFIELD || opcode == PUTSTATIC) { + popFromStack(t); + } + if (opcode == GETFIELD || opcode == PUTFIELD) { + stack.pop(); // pop 'this' + } + if (opcode == GETFIELD || opcode == GETSTATIC) { + pushToStack(t); + } + } + + @Override + public void visitTypeInsn(int opcode, String type) { + super.visitTypeInsn(opcode, type); + pc++; + + switch (opcode) { + case NEW: + { + pushToStack(Type.getObjectType(type)); + break; + } + case ANEWARRAY: + { + stack.pop(); + Type elementType = type.endsWith(";") ? Type.getType(type) : Type.getObjectType(type); + Type arrayType = Type.getType("[" + elementType); + pushToStack(arrayType); + break; + } + case INSTANCEOF: + { + stack.pop(); + pushToStack(Type.BOOLEAN_TYPE); + break; + } + case CHECKCAST: + { + stack.pop(); + pushToStack(Type.getObjectType(type)); + break; + } + } + } + + @Override + public void visitVarInsn(int opcode, int var) { + int size = 1; + + switch (opcode) { + case DLOAD: + case LLOAD: + case DSTORE: + case LSTORE: + { + size++; + break; + } + } + var = variableMapper.remap(var, size); + + boolean isPush = false; + Type opType = null; + switch (opcode) { + case ILOAD: + { + opType = Type.INT_TYPE; + isPush = true; + break; + } + case LLOAD: + { + opType = Type.LONG_TYPE; + isPush = true; + break; + } + case FLOAD: + { + opType = Type.FLOAT_TYPE; + isPush = true; + break; + } + case DLOAD: + { + opType = Type.DOUBLE_TYPE; + isPush = true; + break; + } + case ALOAD: + { + Object o = localTypes.getType(var); + opType = fromSlotType(o); + isPush = true; + break; + } + case ISTORE: + { + opType = Type.INT_TYPE; + break; + } + case LSTORE: + { + opType = Type.LONG_TYPE; + break; + } + case FSTORE: + { + opType = Type.FLOAT_TYPE; + break; + } + case DSTORE: + { + opType = Type.DOUBLE_TYPE; + break; + } + case ASTORE: + { + opType = fromSlotType(stack.peek()); + break; + } + } + + assert opType != null; + + if (isPush) { + pushToStack(opType); + } else { + popFromStack(opType); + localTypes.setType(var, opType); + } + + super.visitVarInsn(opcode, var); + pc++; + } + + @Override + public void visitIntInsn(int opcode, int operand) { + super.visitIntInsn(opcode, operand); + pc++; + + switch (opcode) { + case BIPUSH: + case SIPUSH: + { + stack.push(INTEGER); + break; + } + case NEWARRAY: + { + popFromStack(Type.INT_TYPE); // size + switch (operand) { + case T_BOOLEAN: + { + pushToStack(Type.getObjectType("[Z")); + break; + } + case T_CHAR: + { + pushToStack(Type.getObjectType("[C")); + break; + } + case T_FLOAT: + { + pushToStack(Type.getObjectType("[F")); + break; + } + case T_DOUBLE: + { + pushToStack(Type.getObjectType("[D")); + break; + } + case T_BYTE: + { + pushToStack(Type.getObjectType("[B")); + break; + } + case T_SHORT: + { + pushToStack(Type.getObjectType("[S")); + break; + } + case T_INT: + { + pushToStack(Type.getObjectType("[I")); + break; + } + case T_LONG: + { + pushToStack(Type.getObjectType("[J")); + break; + } + } + break; + } + } + } + + @Override + public void visitInsn(int opcode) { + super.visitInsn(opcode); + pc++; + + switch (opcode) { + case ACONST_NULL: + { + stack.push(NULL); + break; + } + case ICONST_0: + case ICONST_1: + case ICONST_2: + case ICONST_3: + case ICONST_4: + case ICONST_5: + case ICONST_M1: + { + pushToStack(Type.INT_TYPE); + break; + } + case FCONST_0: + case FCONST_1: + case FCONST_2: + { + pushToStack(Type.FLOAT_TYPE); + break; + } + case LCONST_0: + case LCONST_1: + { + pushToStack(Type.LONG_TYPE); + break; + } + case DCONST_0: + case DCONST_1: + { + pushToStack(Type.DOUBLE_TYPE); + break; + } + case AALOAD: + { + stack.pop(); // index + Object target = stack.pop(); + + if (target instanceof String) { + Type t; + String typeStr = (String) target; + if (typeStr.startsWith("[")) { + if (typeStr.contains("/") && !typeStr.endsWith(";")) { + typeStr += ";"; + } + // Type.getElementType() will give the non-array type which we don't want here + // For a multi-dimensional array the element type is the lower dimension array + typeStr = typeStr.substring(1); + t = Type.getType(typeStr); + } else { + t = Type.getObjectType(typeStr); + } + pushToStack(t); + } else if (target == NULL) { + pushToStack(Constants.NULL_TYPE); + } else { + pushToStack(Constants.OBJECT_TYPE); + } + break; + } + case IALOAD: + { + stack.pop(); + stack.pop(); + + pushToStack(Type.INT_TYPE); + break; + } + case FALOAD: + { + stack.pop(); + stack.pop(); + + pushToStack(Type.FLOAT_TYPE); + break; + } + case BALOAD: + { + stack.pop(); + stack.pop(); + + pushToStack(Type.BYTE_TYPE); + break; + } + case CALOAD: + { + stack.pop(); + stack.pop(); + + pushToStack(Type.CHAR_TYPE); + break; + } + case SALOAD: + { + stack.pop(); + stack.pop(); + + pushToStack(Type.SHORT_TYPE); + break; + } + case LALOAD: + { + stack.pop(); + stack.pop(); + + pushToStack(Type.LONG_TYPE); + break; + } + case DALOAD: + { + stack.pop(); + stack.pop(); + + pushToStack(Type.DOUBLE_TYPE); + break; + } + case AASTORE: + case IASTORE: + case FASTORE: + case BASTORE: + case CASTORE: + case SASTORE: + case LASTORE: + case DASTORE: + { + stack.pop(); // val + stack.pop(); // index + stack.pop(); // arrayref + + break; + } + case POP: + { + stack.pop1(); + break; + } + case POP2: + { + stack.pop1(); + stack.pop1(); + break; + } + case DUP: + { + stack.push1(stack.peek()); + break; + } + case DUP_X1: + { + Object x = stack.pop1(); + Object y = stack.pop1(); + stack.push1(x); + stack.push1(y); + stack.push1(x); + break; + } + case DUP_X2: + { + Object x = stack.pop1(); + Object y = stack.pop1(); + Object z = stack.pop1(); + stack.push1(x); + stack.push1(z); + stack.push1(y); + stack.push1(x); + break; + } + case DUP2: + { + Object x = stack.pop1(); + Object y = stack.peek(); + stack.push1(x); + stack.push1(y); + stack.push1(x); + break; + } + case DUP2_X1: + { + Object x2 = stack.pop1(); + Object x1 = stack.pop1(); + Object y = stack.pop1(); + stack.push1(x1); + stack.push1(x2); + stack.push1(y); + stack.push1(x1); + stack.push1(x2); + break; + } + case DUP2_X2: + { + Object x2 = stack.pop1(); + Object x1 = stack.pop1(); + Object y2 = stack.pop1(); + Object y1 = stack.pop1(); + stack.push1(x1); + stack.push1(x2); + stack.push1(y1); + stack.push1(y2); + stack.push1(x1); + stack.push1(x2); + break; + } + case SWAP: + { + Object x = stack.pop1(); + Object y = stack.pop1(); + stack.push1(x); + stack.push1(y); + break; + } + case IADD: + case ISUB: + case IMUL: + case IDIV: + case IREM: + case IAND: + case IOR: + case IXOR: + case ISHR: + case ISHL: + case IUSHR: + { + popFromStack(Type.INT_TYPE); + popFromStack(Type.INT_TYPE); + pushToStack(Type.INT_TYPE); + break; + } + case FADD: + case FSUB: + case FMUL: + case FDIV: + case FREM: + { + popFromStack(Type.FLOAT_TYPE); + popFromStack(Type.FLOAT_TYPE); + pushToStack(Type.FLOAT_TYPE); + break; + } + case LADD: + case LSUB: + case LMUL: + case LDIV: + case LREM: + case LAND: + case LOR: + case LXOR: + case LSHR: + case LSHL: + case LUSHR: + { + popFromStack(Type.LONG_TYPE); + popFromStack(Type.LONG_TYPE); + pushToStack(Type.LONG_TYPE); + break; + } + case DADD: + case DSUB: + case DMUL: + case DDIV: + case DREM: + { + popFromStack(Type.DOUBLE_TYPE); + popFromStack(Type.DOUBLE_TYPE); + pushToStack(Type.DOUBLE_TYPE); + break; + } + case I2L: + { + popFromStack(Type.INT_TYPE); + pushToStack(Type.LONG_TYPE); + break; + } + case I2F: + { + popFromStack(Type.INT_TYPE); + pushToStack(Type.FLOAT_TYPE); + break; + } + case I2B: + { + popFromStack(Type.INT_TYPE); + pushToStack(Type.BYTE_TYPE); + break; + } + case I2C: + { + popFromStack(Type.INT_TYPE); + pushToStack(Type.CHAR_TYPE); + break; + } + case I2S: + { + popFromStack(Type.INT_TYPE); + pushToStack(Type.SHORT_TYPE); + break; + } + case I2D: + { + popFromStack(Type.INT_TYPE); + pushToStack(Type.DOUBLE_TYPE); + break; + } + case L2I: + { + popFromStack(Type.LONG_TYPE); + pushToStack(Type.INT_TYPE); + break; + } + case L2F: + { + popFromStack(Type.LONG_TYPE); + pushToStack(Type.FLOAT_TYPE); + break; + } + case L2D: + { + popFromStack(Type.LONG_TYPE); + pushToStack(Type.DOUBLE_TYPE); + break; + } + case F2I: + { + popFromStack(Type.FLOAT_TYPE); + pushToStack(Type.INT_TYPE); + break; + } + case F2L: + { + popFromStack(Type.FLOAT_TYPE); + pushToStack(Type.LONG_TYPE); + break; + } + case F2D: + { + popFromStack(Type.FLOAT_TYPE); + pushToStack(Type.DOUBLE_TYPE); + break; + } + case D2I: + { + popFromStack(Type.DOUBLE_TYPE); + pushToStack(Type.INT_TYPE); + break; + } + case D2F: + { + popFromStack(Type.DOUBLE_TYPE); + pushToStack(Type.FLOAT_TYPE); + break; + } + case D2L: + { + popFromStack(Type.DOUBLE_TYPE); + pushToStack(Type.LONG_TYPE); + break; + } + case LCMP: + { + popFromStack(Type.LONG_TYPE); + popFromStack(Type.LONG_TYPE); + + pushToStack(Type.INT_TYPE); + break; + } + case FCMPL: + case FCMPG: + { + popFromStack(Type.FLOAT_TYPE); + popFromStack(Type.FLOAT_TYPE); + + pushToStack(Type.INT_TYPE); + break; + } + case DCMPL: + case DCMPG: + { + popFromStack(Type.DOUBLE_TYPE); + popFromStack(Type.DOUBLE_TYPE); + + pushToStack(Type.INT_TYPE); + break; + } + case IRETURN: + { + popFromStack(Type.INT_TYPE); + break; + } + case LRETURN: + { + popFromStack(Type.LONG_TYPE); + break; + } + case FRETURN: + { + popFromStack(Type.FLOAT_TYPE); + break; + } + case DRETURN: + { + popFromStack(Type.DOUBLE_TYPE); + break; + } + case ARETURN: + { + popFromStack(Type.getReturnType(desc)); + break; + } + case ATHROW: + { + popFromStack(Constants.THROWABLE_TYPE); + break; + } + case ARRAYLENGTH: + { + stack.pop(); + pushToStack(Type.INT_TYPE); + break; + } + case MONITORENTER: + case MONITOREXIT: + { + stack.pop(); + break; + } + } + } + + @Override + public void visitIincInsn(int var, int increment) { + super.visitIincInsn(variableMapper.remap(var, 1), increment); + pc++; + } + + @Override + public void visitLocalVariable( + String name, String desc, String signature, Label start, Label end, int index) { + int newIndex = variableMapper.map(index, start); + if (newIndex != 0xFFFFFFFF) { + super.visitLocalVariable( + name, desc, signature, start, end, newIndex == Integer.MIN_VALUE ? 0 : newIndex); + } + } + + @Override + public AnnotationVisitor visitLocalVariableAnnotation( + int typeRef, + TypePath typePath, + Label[] start, + Label[] end, + int[] index, + String desc, + boolean visible) { + Type t = Type.getType(desc); + int cnt = 0; + int[] newIndex = new int[index.length]; + for (int i = 0; i < newIndex.length; ++i) { + int idx = variableMapper.map(index[i]); + if (idx != 0xFFFFFFFF) { + newIndex[cnt++] = idx; + } + } + return super.visitLocalVariableAnnotation( + typeRef, typePath, start, end, Arrays.copyOf(newIndex, cnt), desc, visible); + } + + @Override + public void visitTryCatchBlock(Label start, Label end, Label handler, String exception) { + addTryCatchHandler(start, handler); + super.visitTryCatchBlock(start, end, handler, exception); + } + + @Override + public void visitLabel(Label label) { + variableMapper.noteLabel(label); + SavedState ss = jumpTargetStates.get(label); + if (ss != null) { + if (ss.kind != SavedState.CONDITIONAL) { + reset(); + } + localTypes.mergeWith(ss.lvTypes.toArray()); + stack.replaceWith(ss.sStack.toArray()); + if (ss.kind == SavedState.EXCEPTION) { + stack.push(toSlotType(Constants.THROWABLE_TYPE)); + } + for (LocalVarSlot lvs : newLocals) { + if (!ss.newLocals.contains(lvs)) { + lvs.expire(); + } + } + newLocals.clear(); + newLocals.addAll(ss.newLocals); + } + Set