mirror of
https://github.com/async-profiler/async-profiler.git
synced 2026-04-28 19:03:33 +00:00
Compare commits
55 Commits
native-ima
...
release
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9cd3fdd42b | ||
|
|
5930966a92 | ||
|
|
7737df342d | ||
|
|
843f1d9f3e | ||
|
|
733f2a513c | ||
|
|
9824786981 | ||
|
|
5fffdb1eaa | ||
|
|
7bf8528f75 | ||
|
|
80ae8aed19 | ||
|
|
1c1a14c1ec | ||
|
|
83e9bdd9bd | ||
|
|
22ce08f5ef | ||
|
|
7c4385b0b1 | ||
|
|
461a3c1b93 | ||
|
|
5b178bfc5c | ||
|
|
520b897dce | ||
|
|
a70f25e00f | ||
|
|
f79729167a | ||
|
|
f627b3157b | ||
|
|
85fefd2800 | ||
|
|
5091304efd | ||
|
|
c42bf7ad9d | ||
|
|
2b8dffff27 | ||
|
|
09ad6c1663 | ||
|
|
40fd71a8a0 | ||
|
|
557f4adecb | ||
|
|
de54c536dc | ||
|
|
c74107e53f | ||
|
|
b3968f5e38 | ||
|
|
29dd537907 | ||
|
|
0330a6e333 | ||
|
|
9b44c2e99d | ||
|
|
5b4450b85c | ||
|
|
82d13772a5 | ||
|
|
bbca9f1817 | ||
|
|
981619680e | ||
|
|
2b556680dc | ||
|
|
b3f58429f5 | ||
|
|
2844e6c5c1 | ||
|
|
0e1008531b | ||
|
|
19ad42cd23 | ||
|
|
f76833a2c0 | ||
|
|
4b1df29aab | ||
|
|
795da942f7 | ||
|
|
bedffcb080 | ||
|
|
660ffcd5c6 | ||
|
|
60e79e364a | ||
|
|
d89ab7a16c | ||
|
|
d042e0a8db | ||
|
|
3256fde4c1 | ||
|
|
3bbab49e3c | ||
|
|
ed57317281 | ||
|
|
c17de4c220 | ||
|
|
3a9252c677 | ||
|
|
fd8ba8b9ee |
134
.clang-tidy
Normal file
134
.clang-tidy
Normal file
@@ -0,0 +1,134 @@
|
||||
Checks: >
|
||||
-*,
|
||||
google-readability-braces-around-statements,
|
||||
bugprone-assert-side-effect,
|
||||
bugprone-bool-pointer-implicit-conversion,
|
||||
bugprone-chained-comparison,
|
||||
bugprone-copy-constructor-init,
|
||||
bugprone-incorrect-roundings,
|
||||
bugprone-infinite-loop,
|
||||
bugprone-integer-division,
|
||||
bugprone-misplaced-operator-in-strlen-in-alloc,
|
||||
bugprone-misplaced-pointer-arithmetic-in-alloc,
|
||||
bugprone-misplaced-widening-cast,
|
||||
bugprone-non-zero-enum-to-bool-conversion,
|
||||
bugprone-pointer-arithmetic-on-polymorphic-object,
|
||||
bugprone-posix-return,
|
||||
bugprone-redundant-branch-condition,
|
||||
bugprone-return-const-ref-from-parameter,
|
||||
bugprone-sizeof-container,
|
||||
bugprone-standalone-empty,
|
||||
bugprone-string-literal-with-embedded-nul,
|
||||
bugprone-string-integer-assignment,
|
||||
bugprone-suspicious-include,
|
||||
bugprone-suspicious-memset-usage,
|
||||
bugprone-suspicious-missing-comma,
|
||||
bugprone-suspicious-realloc-usage,
|
||||
bugprone-suspicious-semicolon,
|
||||
bugprone-suspicious-string-compare,
|
||||
bugprone-swapped-arguments,
|
||||
bugprone-terminating-continue,
|
||||
bugprone-too-small-loop-variable,
|
||||
bugprone-undefined-memory-manipulation,
|
||||
bugprone-undelegated-constructor,
|
||||
bugprone-unhandled-self-assignment,
|
||||
bugprone-unused-raii,
|
||||
bugprone-unused-return-value,
|
||||
bugprone-use-after-move,
|
||||
bugprone-virtual-near-miss,
|
||||
cppcoreguidelines-misleading-capture-default-by-value,
|
||||
cppcoreguidelines-pro-type-const-cast,
|
||||
cppcoreguidelines-slicing,
|
||||
cert-oop58-cpp,
|
||||
cert-flp30-c,
|
||||
misc-confusable-identifiers,
|
||||
misc-definitions-in-headers,
|
||||
misc-header-include-cycle,
|
||||
misc-misplaced-const,
|
||||
misc-non-copyable-objects,
|
||||
misc-redundant-expression,
|
||||
misc-static-assert,
|
||||
misc-unconventional-assign-operator,
|
||||
misc-unused-alias-decls,
|
||||
performance-avoid-endl,
|
||||
performance-faster-string-find,
|
||||
performance-for-range-copy,
|
||||
performance-implicit-conversion-in-loop,
|
||||
performance-inefficient-algorithm,
|
||||
performance-inefficient-string-concatenation,
|
||||
performance-inefficient-vector-operation,
|
||||
performance-move-const-arg,
|
||||
performance-move-constructor-init,
|
||||
performance-no-automatic-move,
|
||||
performance-noexcept-destructor,
|
||||
performance-noexcept-move-constructor,
|
||||
performance-noexcept-swap,
|
||||
performance-trivially-destructible,
|
||||
performance-type-promotion-in-math-fn,
|
||||
performance-unnecessary-copy-initialization,
|
||||
performance-unnecessary-value-param,
|
||||
readability-avoid-return-with-void-value,
|
||||
readability-avoid-unconditional-preprocessor-if,
|
||||
readability-const-return-type,
|
||||
readability-container-contains,
|
||||
readability-container-data-pointer,
|
||||
readability-container-size-empty,
|
||||
readability-delete-null-pointer,
|
||||
readability-duplicate-include,
|
||||
readability-function-size,
|
||||
readability-identifier-naming,
|
||||
readability-misleading-indentation,
|
||||
readability-misplaced-array-index,
|
||||
readability-named-parameter,
|
||||
readability-operators-representation,
|
||||
readability-qualified-auto,
|
||||
readability-redundant-access-specifiers,
|
||||
readability-redundant-casting,
|
||||
readability-redundant-control-flow,
|
||||
readability-redundant-declaration,
|
||||
readability-redundant-function-ptr-dereference,
|
||||
readability-redundant-preprocessor,
|
||||
readability-redundant-string-cstr,
|
||||
readability-redundant-string-init,
|
||||
readability-reference-to-constructed-temporary,
|
||||
readability-simplify-subscript-expr,
|
||||
readability-static-accessed-through-instance,
|
||||
readability-static-definition-in-anonymous-namespace,
|
||||
readability-string-compare,
|
||||
readability-uniqueptr-delete-release,
|
||||
readability-uppercase-literal-suffix,
|
||||
readability-use-anyofallof,
|
||||
# TODO: Consider these
|
||||
# bugprone-switch-missing-default-case
|
||||
# bugprone-multi-level-implicit-pointer-conversion
|
||||
# bugprone-branch-clone
|
||||
# cert-err33-c
|
||||
# cppcoreguidelines-narrowing-conversions
|
||||
# cppcoreguidelines-init-variables
|
||||
# cppcoreguidelines-explicit-virtual-functions
|
||||
# cppcoreguidelines-special-member-functions
|
||||
# llvm-include-order
|
||||
# misc-const-correctness
|
||||
# modernize-*
|
||||
# performance-enum-size
|
||||
# readability-function-cognitive-complexity
|
||||
# readability-else-after-return
|
||||
# readability-convert-member-functions-to-static
|
||||
# readability-math-missing-parentheses
|
||||
# readability-non-const-parameter
|
||||
# readability-redundant-member-init
|
||||
# readability-simplify-boolean-expr
|
||||
# misc-include-cleaner
|
||||
# google-explicit-constructor
|
||||
# cppcoreguidelines-virtual-class-destructor
|
||||
# readability-make-member-function-const
|
||||
HeaderFilterRegex: "*"
|
||||
CheckOptions:
|
||||
- key: readability-identifier-naming.LocalVariableCase
|
||||
value: lower_case
|
||||
- key: readability-identifier-naming.LocalVariableIgnoredRegexp
|
||||
value: '(KB|Thread|setDaemon|klassOop|nVMs|loadLibrary|getTicksFrequency|counterTime|System|M|R|s_)'
|
||||
- key: readability-identifier-naming.PrivateMemberPrefix
|
||||
value: _
|
||||
- key: google-readability-braces-around-statements.ShortStatementLines
|
||||
value: '1'
|
||||
48
.github/workflows/clang-tidy-review.yml
vendored
Normal file
48
.github/workflows/clang-tidy-review.yml
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
name: clang-tidy-review
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows:
|
||||
- code-check
|
||||
types:
|
||||
- completed
|
||||
|
||||
jobs:
|
||||
clang-tidy-results:
|
||||
if: ${{ github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' }}
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: "public.ecr.aws/async-profiler/asprof-code-check:latest"
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: write
|
||||
actions: read
|
||||
steps:
|
||||
- name: Download code-check artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
run-id: ${{ github.event.workflow_run.id }}
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
name: code-check-artifacts
|
||||
path: /tmp/code-check-artifacts/
|
||||
- name: Read PR information
|
||||
id: pr_info
|
||||
run: |
|
||||
cd /tmp/code-check-artifacts
|
||||
echo "pr_id=$(cat pr-id.txt)" >> "$GITHUB_OUTPUT"
|
||||
echo "pr_head_repo=$(cat pr-head-repo.txt)" >> "$GITHUB_OUTPUT"
|
||||
echo "pr_head_sha=$(cat pr-head-sha.txt)" >> "$GITHUB_OUTPUT"
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: ${{ steps.pr_info.outputs.pr_head_repo }}
|
||||
ref: ${{ steps.pr_info.outputs.pr_head_sha }}
|
||||
persist-credentials: false
|
||||
- name: Run clang-tidy-pr-comments action
|
||||
uses: platisd/clang-tidy-pr-comments@v1
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
clang_tidy_fixes: /tmp/code-check-artifacts/clang-tidy-fixes.yml
|
||||
pull_request_id: ${{ steps.pr_info.outputs.pr_id }}
|
||||
python_path: python
|
||||
auto_resolve_conversations: true
|
||||
suggestions_per_comment: 100
|
||||
46
.github/workflows/code-check.yml
vendored
Normal file
46
.github/workflows/code-check.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
name: code-check
|
||||
|
||||
on:
|
||||
- pull_request
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
cpp-lint:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: "public.ecr.aws/async-profiler/asprof-code-check:latest"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
fetch-depth: 0
|
||||
- name: Mark repo as safe for Git
|
||||
run: git config --global --add safe.directory $GITHUB_WORKSPACE
|
||||
- name: Fetch base branch
|
||||
run: |
|
||||
git remote add upstream "https://github.com/${{ github.event.pull_request.base.repo.full_name }}"
|
||||
git fetch --no-tags --no-recurse-submodules upstream "${{ github.event.pull_request.base.ref }}"
|
||||
- name: Create artifacts directory
|
||||
run: |
|
||||
mkdir code-check-artifacts/
|
||||
echo "${{ github.event.number }}" > code-check-artifacts/pull-request-id.txt
|
||||
- name: Run clang-tidy
|
||||
run: |
|
||||
set pipefail
|
||||
make cpp-lint-diff \
|
||||
DIFF_BASE="$(git merge-base HEAD "upstream/${{ github.event.pull_request.base.ref }}")" \
|
||||
CLANG_TIDY_ARGS_EXTRA="-export-fixes code-check-artifacts/clang-tidy-fixes.yml"
|
||||
shell: bash
|
||||
- name: Save PR information
|
||||
run: |
|
||||
echo "${{ github.event.number }}" > code-check-artifacts/pr-id.txt
|
||||
echo "${{ github.event.pull_request.head.repo.full_name }}" > code-check-artifacts/pr-head-repo.txt
|
||||
echo "${{ github.event.pull_request.head.sha }}" > code-check-artifacts/pr-head-sha.txt
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: code-check-artifacts
|
||||
path: code-check-artifacts/
|
||||
4
.github/workflows/linters.yml
vendored
4
.github/workflows/linters.yml
vendored
@@ -4,6 +4,10 @@ on:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
license-header:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
64
.github/workflows/test-and-publish-nightly.yml
vendored
64
.github/workflows/test-and-publish-nightly.yml
vendored
@@ -4,6 +4,10 @@ on: # We are very liberal in terms of triggering builds. This should be revisit
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
java_default_distribution: corretto
|
||||
java_default_version: 11
|
||||
@@ -61,10 +65,10 @@ jobs:
|
||||
case "${{ matrix.runson.display }}" in
|
||||
macos*)
|
||||
brew install gcovr
|
||||
make COMMIT_TAG=$HASH FAT_BINARY=true release coverage -j
|
||||
make FAT_BINARY=true release coverage -j
|
||||
;;
|
||||
*)
|
||||
make COMMIT_TAG=$HASH CC=/usr/local/musl/bin/musl-gcc release coverage -j
|
||||
make CC=/usr/local/musl/bin/musl-gcc release coverage -j
|
||||
echo "debug_archive=$(find . -type f -name "async-profiler-*-debug*" -exec basename {} \;)" >> $GITHUB_OUTPUT
|
||||
;;
|
||||
esac
|
||||
@@ -105,9 +109,9 @@ jobs:
|
||||
- display: linux-x64
|
||||
# Using "latest" here as the build and test will any ways run inside a container which we control
|
||||
name: ubuntu-latest
|
||||
java-version: [11, 17, 21, 24]
|
||||
java-version: [8, 11, 17, 21, 24]
|
||||
java-distribution: [corretto]
|
||||
image: [public.ecr.aws/async-profiler/asprof-builder-x86:latest]
|
||||
image: ["public.ecr.aws/async-profiler/asprof-builder-x86:latest"]
|
||||
include:
|
||||
- runson:
|
||||
display: macos-arm64
|
||||
@@ -118,10 +122,19 @@ jobs:
|
||||
image: ""
|
||||
# ARM MacOS should take fat binaries built on ARM
|
||||
asprof-binaries-job: macos
|
||||
- runson:
|
||||
display: macos-arm64
|
||||
name: macos-15
|
||||
java-version: 21
|
||||
java-distribution: corretto
|
||||
# Not using container for mac-os as we have images only for linux
|
||||
image: ""
|
||||
# ARM MacOS should take fat binaries built on ARM
|
||||
asprof-binaries-job: macos
|
||||
- runson:
|
||||
display: macos-x64
|
||||
name: macos-13
|
||||
java-version: 11
|
||||
java-version: 17
|
||||
java-distribution: corretto
|
||||
architecture: x64
|
||||
image: ""
|
||||
@@ -209,6 +222,13 @@ jobs:
|
||||
esac
|
||||
echo "jars_directory=jar_artifacts" >> $GITHUB_OUTPUT
|
||||
echo "release_directory=$(basename $(find . -type d -iname "async-profiler-*" ))" >> $GITHUB_OUTPUT
|
||||
- name: Download Protobuf Java runtime
|
||||
run: |
|
||||
mkdir -p test/deps
|
||||
cd test/deps
|
||||
curl -L -O "https://repo1.maven.org/maven2/com/google/protobuf/protobuf-java/$PB_JAVA_VERSION/protobuf-java-$PB_JAVA_VERSION.jar"
|
||||
env:
|
||||
PB_JAVA_VERSION: "4.31.1"
|
||||
- name: Run integration tests
|
||||
run: |
|
||||
mkdir -p build/jar
|
||||
@@ -227,7 +247,7 @@ jobs:
|
||||
build/test/logs/
|
||||
hs_err*.log
|
||||
publish-only-on-push:
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/release'
|
||||
name: publish (nightly)
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build-jars, integration-tests]
|
||||
@@ -247,32 +267,12 @@ jobs:
|
||||
owner: "async-profiler",
|
||||
repo: "async-profiler",
|
||||
};
|
||||
let previousRelease = undefined;
|
||||
try {
|
||||
previousRelease = await github.rest.repos.getReleaseByTag({
|
||||
...commonOptions,
|
||||
tag: "nightly",
|
||||
});
|
||||
} catch (e) {
|
||||
console.log("No previous nightly release");
|
||||
// ignore, there was no previous nightly release
|
||||
}
|
||||
if (previousRelease !== undefined) {
|
||||
// delete previous release and nightly tag
|
||||
await github.rest.repos.deleteRelease({
|
||||
...commonOptions,
|
||||
release_id: previousRelease.data.id,
|
||||
});
|
||||
await github.rest.git.deleteRef({...commonOptions, ref: "tags/nightly"});
|
||||
}
|
||||
// create draft release
|
||||
const newReleaseId = (await github.rest.repos.createRelease({
|
||||
...commonOptions,
|
||||
tag_name: "nightly",
|
||||
target_commitish: "${{ github.sha }}",
|
||||
name: "Nightly builds",
|
||||
body: "Async-profiler binaries published automatically from the latest sources in `master` upon a successful build.",
|
||||
prerelease: true,
|
||||
tag_name: "v4.1",
|
||||
target_commitish: "v4.x",
|
||||
name: "New release",
|
||||
draft: true,
|
||||
})).data.id;
|
||||
// upload binaries and jars to draft release
|
||||
@@ -284,9 +284,3 @@ jobs:
|
||||
data: await fs.readFile(archiveName),
|
||||
});
|
||||
}
|
||||
// publish release
|
||||
await github.rest.repos.updateRelease({
|
||||
...commonOptions,
|
||||
release_id: newReleaseId,
|
||||
draft: false,
|
||||
});
|
||||
|
||||
47
CHANGELOG.md
47
CHANGELOG.md
@@ -1,5 +1,52 @@
|
||||
# Changelog
|
||||
|
||||
## [4.1] - 2025-07-21
|
||||
|
||||
### Features
|
||||
- Experimental support for the OpenTelemetry profiling signal
|
||||
* #1188: OTLP output format and `dumpOtlp` Java API
|
||||
* #1336: JFR to OTLP converter
|
||||
- JDK 25 support
|
||||
* #1222: Update VMStructs for JDK 25
|
||||
- Productize native memory profiling
|
||||
* #1193: Full `nativemem` support on macOS
|
||||
* #1254: Fixed Nativemem tests on Alpine
|
||||
* #1269: Native memory profiling now works with `jemalloc`
|
||||
* #1323: `nativemem` shows allocations inside async-profiler itself
|
||||
|
||||
### Improvements
|
||||
- #1174: Detect JVM in non-Java application and attach to it
|
||||
- #1223: Native API to add custom events in JFR recording
|
||||
- #1259: `--all` option to collect all possible events simultaneously
|
||||
- #1286: Record which CPU a sample was taken on
|
||||
- #1299: Skip last 10% allocations for leak detection
|
||||
- #1300: Allow profiling kprobes/uprobes with `--fdtransfer`
|
||||
- #1366: Rewrite `jfrconv` executable to shell
|
||||
- #1400: Unwind checksum and digest intrinsics on ARM64
|
||||
- #1357, #1389: VMStructs-based stack unwinding for `alloc` and `nativemem` profiling
|
||||
|
||||
### Bug fixes
|
||||
- #1251: `--ttsp` option does not work on Alpine
|
||||
- #1264: Guard hook installation with dlopen/dlclose
|
||||
- #1319: SIGSEGV in PerfEvents::walk
|
||||
- #1350: Disable JFR OldObjectSample event in jfrsync mode
|
||||
- #1358: Do not dereference jmethodIDs on JDK 26
|
||||
- #1374: Correctly check if profiler is preloaded
|
||||
- #1380: Workaround clang type promotion bug
|
||||
- #1387: JFR writer crashes when using cstack=vmx
|
||||
- #1393: Improve stack walking termination logic: no endless `unknown` frames
|
||||
- Stack unwinding fixes for ARM64
|
||||
|
||||
### Project Infrastructure
|
||||
- #1129: Command-line option to filter tests
|
||||
- #1262: Include `asprof.h` in async-profiler release package
|
||||
- #1271: Release additional binaries with debug symbols
|
||||
- #1274: Add Corretto 8 to the test matrix
|
||||
- #1246, #1226: Run tests on Amazon Linux and Alpine Linux
|
||||
- #1360: Auto-generated clang-tidy review comments
|
||||
- #1373: Save all generated test logs for debug purposes
|
||||
- Fixed flaky tests (#1282, #1307, #1376)
|
||||
|
||||
## [4.0] - 2025-04-08
|
||||
|
||||
### Features
|
||||
|
||||
73
Makefile
73
Makefile
@@ -1,4 +1,4 @@
|
||||
PROFILER_VERSION ?= 4.0
|
||||
PROFILER_VERSION ?= 4.1
|
||||
|
||||
ifeq ($(COMMIT_TAG),true)
|
||||
PROFILER_VERSION := $(PROFILER_VERSION)-$(shell git rev-parse --short=8 HEAD)
|
||||
@@ -6,9 +6,10 @@ else ifneq ($(COMMIT_TAG),)
|
||||
PROFILER_VERSION := $(PROFILER_VERSION)-$(COMMIT_TAG)
|
||||
endif
|
||||
|
||||
TMP_DIR=/tmp
|
||||
COMMA=,
|
||||
PACKAGE_NAME=async-profiler-$(PROFILER_VERSION)-$(OS_TAG)-$(ARCH_TAG)
|
||||
PACKAGE_DIR=/tmp/$(PACKAGE_NAME)
|
||||
PACKAGE_DIR=$(TMP_DIR)/$(PACKAGE_NAME)
|
||||
DEBUG_PACKAGE_NAME=$(PACKAGE_NAME)-debug
|
||||
DEBUG_PACKAGE_DIR=$(PACKAGE_DIR)-debug
|
||||
|
||||
@@ -52,6 +53,8 @@ JAVAC_OPTIONS=--release $(JAVA_TARGET) -Xlint:-options
|
||||
|
||||
TEST_LIB_DIR=build/test/lib
|
||||
TEST_BIN_DIR=build/test/bin
|
||||
TEST_DEPS_DIR=test/deps
|
||||
TEST_GEN_DIR=test/gen
|
||||
LOG_DIR=build/test/logs
|
||||
LOG_LEVEL=
|
||||
SKIP=
|
||||
@@ -64,7 +67,7 @@ RESOURCES := $(wildcard src/res/*)
|
||||
JAVA_HELPER_CLASSES := $(wildcard src/helper/one/profiler/*.class)
|
||||
API_SOURCES := $(wildcard src/api/one/profiler/*.java)
|
||||
CONVERTER_SOURCES := $(shell find src/converter -name '*.java')
|
||||
TEST_SOURCES := $(shell find test -name '*.java')
|
||||
TEST_SOURCES := $(shell find test -name '*.java' ! -path 'test/stubs/*')
|
||||
TESTS ?=
|
||||
CPP_TEST_SOURCES := test/native/testRunner.cpp $(shell find test/native -name '*Test.cpp')
|
||||
CPP_TEST_HEADER := test/native/testRunner.hpp
|
||||
@@ -148,11 +151,9 @@ $(PACKAGE_NAME).tar.gz: $(PACKAGE_DIR)
|
||||
rm -r $(DEBUG_PACKAGE_DIR)
|
||||
|
||||
$(PACKAGE_NAME).zip: $(PACKAGE_DIR)
|
||||
truncate -cs -`stat -f "%z" build/$(CONVERTER_JAR)` $(PACKAGE_DIR)/$(JFRCONV)
|
||||
ifneq ($(GITHUB_ACTIONS), true)
|
||||
codesign -s "Developer ID" -o runtime --timestamp -v $(PACKAGE_DIR)/$(ASPROF) $(PACKAGE_DIR)/$(JFRCONV) $(PACKAGE_DIR)/$(LIB_PROFILER)
|
||||
endif
|
||||
cat build/$(CONVERTER_JAR) >> $(PACKAGE_DIR)/$(JFRCONV)
|
||||
ditto -c -k --keepParent $(PACKAGE_DIR) $@
|
||||
rm -r $(PACKAGE_DIR)
|
||||
|
||||
@@ -177,9 +178,9 @@ build/$(ASPROF): src/main/* src/jattach/* src/fdtransfer.h
|
||||
$(CC) $(CPPFLAGS) $(CFLAGS) $(DEFS) -o $@ src/main/*.cpp src/jattach/*.c
|
||||
$(STRIP) $@
|
||||
|
||||
build/$(JFRCONV): src/launcher/* build/$(CONVERTER_JAR)
|
||||
$(CC) $(CPPFLAGS) $(CFLAGS) $(DEFS) -o $@ src/launcher/*.cpp
|
||||
$(STRIP) $@
|
||||
build/$(JFRCONV): src/launcher/launcher.sh build/$(CONVERTER_JAR)
|
||||
sed -e 's/PROFILER_VERSION/$(PROFILER_VERSION)/g' -e 's/BUILD_DATE/$(shell date "+%b %d %Y")/g' src/launcher/launcher.sh > $@
|
||||
chmod +x $@
|
||||
cat build/$(CONVERTER_JAR) >> $@
|
||||
|
||||
build/$(LIB_PROFILER): $(SOURCES) $(HEADERS) $(RESOURCES) $(JAVA_HELPER_CLASSES)
|
||||
@@ -226,13 +227,13 @@ build-test: build-test-cpp build-test-java
|
||||
|
||||
build-test-libs:
|
||||
@mkdir -p $(TEST_LIB_DIR)
|
||||
|
||||
ifeq ($(OS_TAG),linux)
|
||||
$(CC) -shared -fPIC -o $(TEST_LIB_DIR)/libreladyn.$(SOEXT) test/native/libs/reladyn.c
|
||||
$(CC) -shared -fPIC -o $(TEST_LIB_DIR)/libcallsmalloc.$(SOEXT) test/native/libs/callsmalloc.c
|
||||
$(CC) -shared -fPIC $(INCLUDES) -Isrc -o $(TEST_LIB_DIR)/libjnimalloc.$(SOEXT) test/native/libs/jnimalloc.c
|
||||
$(CC) -shared -fPIC -o $(TEST_LIB_DIR)/libmalloc.$(SOEXT) test/native/libs/malloc.c
|
||||
$(CC) -fno-optimize-sibling-calls -shared -fPIC $(INCLUDES) -Isrc -o $(TEST_LIB_DIR)/libjninativestacks.$(SOEXT) test/native/libs/jninativestacks.c
|
||||
|
||||
ifeq ($(OS_TAG),linux)
|
||||
$(CC) -c -shared -fPIC -o $(TEST_LIB_DIR)/vaddrdif.o test/native/libs/vaddrdif.c
|
||||
$(LD) -N -shared -o $(TEST_LIB_DIR)/libvaddrdif.$(SOEXT) $(TEST_LIB_DIR)/vaddrdif.o -T test/native/libs/vaddrdif.ld
|
||||
|
||||
@@ -249,15 +250,16 @@ build-test-bins:
|
||||
$(CC) -o $(TEST_BIN_DIR)/native_api -Isrc test/test/c/native_api.c -ldl
|
||||
$(CC) -o $(TEST_BIN_DIR)/profile_with_dlopen -Isrc test/test/nativemem/profile_with_dlopen.c -ldl
|
||||
$(CC) -o $(TEST_BIN_DIR)/preload_malloc -Isrc test/test/nativemem/preload_malloc.c -ldl
|
||||
$(CXX) -o $(TEST_BIN_DIR)/non_java_app $(INCLUDES) $(CPP_TEST_INCLUDES) test/test/nonjava/non_java_app.cpp $(LIBS)
|
||||
$(CC) -o $(TEST_BIN_DIR)/nativemem_known_lib_crash -Isrc test/test/nativemem/nativemem_known_lib_crash.c -ldl
|
||||
$(CXX) -o $(TEST_BIN_DIR)/non_java_app -std=c++11 $(INCLUDES) $(CPP_TEST_INCLUDES) test/test/nonjava/non_java_app.cpp $(LIBS)
|
||||
|
||||
test-cpp: build-test-cpp
|
||||
echo "Running cpp tests..."
|
||||
LD_LIBRARY_PATH="$(TEST_LIB_DIR)" build/test/cpptests
|
||||
LD_LIBRARY_PATH="$(TEST_LIB_DIR)" DYLD_LIBRARY_PATH="$(TEST_LIB_DIR)" build/test/cpptests
|
||||
|
||||
test-java: build-test-java
|
||||
echo "Running tests against $(LIB_PROFILER)"
|
||||
$(JAVA) "-Djava.library.path=$(TEST_LIB_DIR)" $(TEST_FLAGS) -ea -cp "build/test.jar:build/jar/*:build/lib/*" one.profiler.test.Runner $(subst $(COMMA), ,$(TESTS))
|
||||
$(JAVA) "-Djava.library.path=$(TEST_LIB_DIR)" $(TEST_FLAGS) -ea -cp "build/$(TEST_JAR):build/jar/*:build/lib/*:$(TEST_DEPS_DIR)/*:$(TEST_GEN_DIR)/*" one.profiler.test.Runner $(subst $(COMMA), ,$(TESTS))
|
||||
|
||||
coverage: override FAT_BINARY=false
|
||||
coverage: clean-coverage
|
||||
@@ -268,10 +270,47 @@ coverage: clean-coverage
|
||||
|
||||
test: test-cpp test-java
|
||||
|
||||
build/$(TEST_JAR): $(TEST_SOURCES) build/$(CONVERTER_JAR)
|
||||
mkdir -p build/test
|
||||
$(JAVAC) -source $(JAVA_TARGET) -target $(JAVA_TARGET) -Xlint:-options -cp "build/jar/*:build/converter/*" -d build/test $(TEST_SOURCES)
|
||||
$(JAR) cf $@ -C build/test .
|
||||
$(TEST_DEPS_DIR):
|
||||
mkdir -p $@
|
||||
|
||||
build/$(TEST_JAR): build/$(API_JAR) $(TEST_SOURCES) build/$(CONVERTER_JAR) $(TEST_DEPS_DIR)
|
||||
rm -rf build/test/classes
|
||||
mkdir -p build/test/classes
|
||||
$(JAVAC) -source $(JAVA_TARGET) -target $(JAVA_TARGET) -Xlint:-options -XDignore.symbol.file \
|
||||
-implicit:none \
|
||||
-cp "build/jar/*:$(TEST_DEPS_DIR)/*:$(TEST_GEN_DIR)/*:test/stubs" \
|
||||
-d build/test/classes \
|
||||
$(TEST_SOURCES)
|
||||
$(JAR) cf $@ -C build/test/classes .
|
||||
|
||||
update-otlp-classes-jar:
|
||||
@if [ -z "$(OTEL_PROTO_PATH)" ]; then \
|
||||
echo "'OTEL_PROTO_PATH' is empty"; \
|
||||
exit 1; \
|
||||
fi
|
||||
rm -rf $(TMP_DIR)/gen/java $(TMP_DIR)/build
|
||||
mkdir -p $(TMP_DIR)/gen/java $(TMP_DIR)/build $(TEST_GEN_DIR)
|
||||
cd $(OTEL_PROTO_PATH) && protoc --java_out=$(TMP_DIR)/gen/java $$(find . \
|
||||
-type f \
|
||||
-name '*.proto' \
|
||||
-not \( -name 'logs*.proto' -o -name 'metrics*.proto' -o -name 'trace*.proto' -o -name '*service.proto' \))
|
||||
$(JAVAC) -source $(JAVA_TARGET) \
|
||||
-target $(JAVA_TARGET) \
|
||||
-cp $(TEST_DEPS_DIR)/* \
|
||||
-d $(TMP_DIR)/build \
|
||||
-Xlint:-options \
|
||||
$$(find $(TMP_DIR)/gen/java -name "*.java")
|
||||
$(JAR) cvf $(TEST_GEN_DIR)/opentelemetry-gen-classes.jar -C $(TMP_DIR)/build .
|
||||
|
||||
LINT_SOURCES=`ls -1 src/*.cpp src/*/*.cpp | grep -v rustDemangle.cpp`
|
||||
CLANG_TIDY_ARGS_EXTRA=
|
||||
cpp-lint:
|
||||
clang-tidy $(LINT_SOURCES) $(CLANG_TIDY_ARGS_EXTRA) -- -x c++ $(CXXFLAGS) $(INCLUDES) $(DEFS) $(LIBS)
|
||||
|
||||
DIFF_BASE=
|
||||
cpp-lint-diff:
|
||||
git diff -U0 $(DIFF_BASE) -- 'src/*.cpp' 'src/**/*.cpp' 'src/*.h' 'src/**/*.h' ':!**/rustDemangle.cpp' | \
|
||||
clang-tidy-diff.py -p1 $(CLANG_TIDY_ARGS_EXTRA) -- -x c++ $(CXXFLAGS) $(INCLUDES) $(DEFS) $(LIBS)
|
||||
|
||||
check-md:
|
||||
prettier -c README.md "docs/**/*.md"
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
FROM public.ecr.aws/docker/library/amazoncorretto:11-alpine-jdk
|
||||
|
||||
RUN apk add --no-cache make gcc g++ linux-headers musl-dev util-linux patchelf gcovr bash tar
|
||||
RUN apk add --no-cache make gcc g++ linux-headers musl-dev util-linux patchelf gcovr bash tar curl
|
||||
|
||||
10
docker/code-check.Dockerfile
Normal file
10
docker/code-check.Dockerfile
Normal file
@@ -0,0 +1,10 @@
|
||||
# Image for all tasks related to static code analysis in async-profiler
|
||||
FROM public.ecr.aws/docker/library/amazoncorretto:11-alpine-jdk
|
||||
|
||||
ADD --chmod=555 https://raw.githubusercontent.com/llvm/llvm-project/67be4fe3d5fd986a3149de3806bcf2c92320015e/clang-tools-extra/clang-tidy/tool/clang-tidy-diff.py /usr/bin/
|
||||
RUN apk add --no-cache clang-extra-tools linux-headers make python3 git py3-pip bash
|
||||
# Needed by clang-tidy-diff.py to merge multiple results in one file.
|
||||
# '--break-system-packages' is needed because Alpine does not like other package managers than 'apk' ('pip' in this case) to install
|
||||
# software globally, but it's safe to do in this case.
|
||||
RUN pip install --break-system-packages pyyaml
|
||||
ENV CPLUS_INCLUDE_PATH="/usr/lib/jvm/java-11-amazon-corretto/include:/usr/lib/jvm/java-11-amazon-corretto/include/linux"
|
||||
@@ -6,11 +6,11 @@ as a standalone Java application: [`jfr-converter.jar`](https://github.com/async
|
||||
|
||||
## Supported conversions
|
||||
|
||||
| Source | html | collapsed | pprof | pb.gz | heatmap |
|
||||
| --------- | ---- | --------- | ----- | ----- | ------- |
|
||||
| jfr | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| html | ✅ | ✅ | ❌ | ❌ | ❌ |
|
||||
| collapsed | ✅ | ✅ | ❌ | ❌ | ❌ |
|
||||
| Source | html | collapsed | pprof | pb.gz | heatmap | otlp |
|
||||
| --------- | ---- | --------- | ----- | ----- | ------- | ---- |
|
||||
| jfr | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| html | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
|
||||
| collapsed | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -34,13 +34,15 @@ Conversion options:
|
||||
usage like CPU and memory for the application.
|
||||
|
||||
# pprof: pprof is a profiling visualization and analysis tool from Google. More details on
|
||||
pprof on the official github page https://github.com/google/pprof.
|
||||
pprof on the official github page https://github.com/google/pprof.
|
||||
|
||||
# pb.gz: This is a compressed version of pprof output.
|
||||
|
||||
# heatmap: A single page interactive heatmap that allows to explore profiling events
|
||||
on a timeline.
|
||||
|
||||
# otlp: OpenTelemetry profile format.
|
||||
|
||||
|
||||
JFR options:
|
||||
--cpu Generate only CPU profile during conversion
|
||||
@@ -49,6 +51,7 @@ JFR options:
|
||||
--live Build allocation profile from live objects only during conversion
|
||||
--nativemem Generate native memory allocation profile
|
||||
--leak Only include memory leaks in nativemem
|
||||
--tail RATIO Ignore tail allocations for leak profiling (10% by default)
|
||||
--lock Generate only Lock contention profile during conversion
|
||||
-t --threads Split stack traces by threads
|
||||
-s --state LIST Filter thread states: runnable, sleeping, default. State name is case insensitive
|
||||
|
||||
@@ -58,3 +58,6 @@ async-profiler currently supports the following output formats:
|
||||
about the JVM as well as the Java application running on it. async-profiler can generate output in `jfr` format
|
||||
compatible with tools capable of viewing and analyzing `jfr` files. JDK Mission Control (JMC) and Intellij IDEA are
|
||||
some of many options to visualize `jfr` files. More details [here](JfrVisualization.md).
|
||||
|
||||
- `otlp` - OpenTelemetry protocol format for [profiling data](https://opentelemetry.io/blog/2024/profiling).
|
||||
Experimental feature: backward-incompatible changes may happen in future releases of async-profiler.
|
||||
|
||||
@@ -114,5 +114,6 @@ By default, async-profiler merges stack traces starting from the outermost (e.g.
|
||||
- `flamegraph` - produce Flame Graph in HTML format.
|
||||
- `tree` - produce Call Tree in HTML format.
|
||||
- `--reverse` option will generate backtrace view.
|
||||
- `otlp` - dump events in OpenTelemetry format.
|
||||
|
||||
It is possible to specify multiple dump options at the same time.
|
||||
|
||||
@@ -117,10 +117,19 @@ jfrconv --total --nativemem --leak app.jfr app-leak.html
|
||||
jfrconv --total --nativemem app.jfr app-malloc.html
|
||||
```
|
||||
|
||||
When `--leak` option is used, the generated flame graph will show allocations without matching `free` calls. If `-nofree` is specified, every allocation will be reported as a leak:
|
||||
When `--leak` option is used, the generated flame graph will show allocations without matching `free` calls.
|
||||
|
||||

|
||||
|
||||
To avoid bias towards youngest allocations not freed by the end of the profiling session,
|
||||
leak profiler ignores tail allocations made in the last 10% of the profiling period.
|
||||
Tail length can be altered with `--tail` option that accepts `ratio` or `percent%` as an argument.
|
||||
For example, to ignore allocations in the last 2 minutes of a 10 minutes profile, use
|
||||
|
||||
```
|
||||
jfrconv --nativemem --leak --tail 20% app.jfr app-leak.html
|
||||
```
|
||||
|
||||
The overhead of `nativemem` profiling depends on the number of native allocations,
|
||||
but is usually small enough even for production use. If required, the overhead can be reduced
|
||||
by configuring the profiling interval. E.g. if you add `nativemem=1m` profiler option,
|
||||
@@ -180,9 +189,9 @@ of all compiled methods. The subsequent instrumentation flushes only the _depend
|
||||
|
||||
The massive CodeCache flush doesn't occur if attaching async-profiler as an agent.
|
||||
|
||||
### Java native method profiling
|
||||
## Native function profiling
|
||||
|
||||
Here are some useful native methods to profile:
|
||||
Here are some useful native functions to profile:
|
||||
|
||||
- `G1CollectedHeap::humongous_obj_allocate` - trace _humongous allocations_ of the G1 GC,
|
||||
- `JVM_StartThread` - trace creation of new Java threads,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>tools.profiler</groupId>
|
||||
<artifactId>jfr-converter</artifactId>
|
||||
<version>4.0</version>
|
||||
<version>4.1</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>async-profiler</name>
|
||||
|
||||
2
pom.xml
2
pom.xml
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>tools.profiler</groupId>
|
||||
<artifactId>async-profiler</artifactId>
|
||||
<version>4.0</version>
|
||||
<version>4.1</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>async-profiler</name>
|
||||
|
||||
@@ -70,7 +70,9 @@ void AllocTracer::recordAllocation(void* ucontext, EventType event_type, uintptr
|
||||
}
|
||||
|
||||
Error AllocTracer::check(Arguments& args) {
|
||||
if (args._live) {
|
||||
if (args._live && !args._all) {
|
||||
// This engine is only going to be selected in Profiler::selectAllocEngine
|
||||
// when can_generate_sampled_object_alloc_events is not available, i.e. JDK<11.
|
||||
return Error("'live' option is supported on OpenJDK 11+");
|
||||
}
|
||||
|
||||
|
||||
@@ -231,6 +231,22 @@ public class AsyncProfiler implements AsyncProfilerMXBean {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump collected data in OTLP format.
|
||||
* <p>
|
||||
* This API is UNSTABLE and might change or be removed in the next version of async-profiler.
|
||||
*
|
||||
* @return OTLP representation of the profile
|
||||
*/
|
||||
@Override
|
||||
public byte[] dumpOtlp() {
|
||||
try {
|
||||
return execute1("otlp");
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given thread to the set of profiled threads.
|
||||
* 'filter' option must be enabled to use this method.
|
||||
@@ -271,5 +287,7 @@ public class AsyncProfiler implements AsyncProfilerMXBean {
|
||||
|
||||
private native String execute0(String command) throws IllegalArgumentException, IllegalStateException, IOException;
|
||||
|
||||
private native byte[] execute1(String command) throws IllegalArgumentException, IllegalStateException, IOException;
|
||||
|
||||
private native void filterThread0(Thread thread, boolean enable);
|
||||
}
|
||||
|
||||
@@ -29,4 +29,5 @@ public interface AsyncProfilerMXBean {
|
||||
String dumpCollapsed(Counter counter);
|
||||
String dumpTraces(int maxTraces);
|
||||
String dumpFlat(int maxMethods);
|
||||
byte[] dumpOtlp();
|
||||
}
|
||||
|
||||
12
src/arch.h
12
src/arch.h
@@ -15,8 +15,6 @@
|
||||
# define unlikely(x) (__builtin_expect(!!(x), 0))
|
||||
#endif
|
||||
|
||||
#define callerPC() __builtin_return_address(0)
|
||||
|
||||
#ifdef _LP64
|
||||
# define LP64_ONLY(code) code
|
||||
#else // !_LP64
|
||||
@@ -67,6 +65,7 @@ const int PERF_REG_PC = 8; // PERF_REG_X86_IP
|
||||
#define rmb() asm volatile("lfence" : : : "memory")
|
||||
#define flushCache(addr) asm volatile("mfence; clflush (%0); mfence" : : "r" (addr) : "memory")
|
||||
|
||||
#define callerPC() __builtin_return_address(0)
|
||||
#define callerFP() __builtin_frame_address(1)
|
||||
#define callerSP() ((void**)__builtin_frame_address(0) + 2)
|
||||
|
||||
@@ -88,6 +87,7 @@ const int PERF_REG_PC = 15; // PERF_REG_ARM_PC
|
||||
#define rmb() asm volatile("dmb ish" : : : "memory")
|
||||
#define flushCache(addr) __builtin___clear_cache((char*)(addr), (char*)(addr) + sizeof(instruction_t))
|
||||
|
||||
#define callerPC() __builtin_return_address(0)
|
||||
#define callerFP() __builtin_frame_address(1)
|
||||
#define callerSP() __builtin_frame_address(1)
|
||||
|
||||
@@ -108,8 +108,9 @@ const int PERF_REG_PC = 32; // PERF_REG_ARM64_PC
|
||||
#define rmb() asm volatile("dmb ish" : : : "memory")
|
||||
#define flushCache(addr) __builtin___clear_cache((char*)(addr), (char*)(addr) + sizeof(instruction_t))
|
||||
|
||||
#define callerFP() __builtin_frame_address(1)
|
||||
#define callerSP() __builtin_frame_address(1)
|
||||
#define callerPC() ({ void* pc; asm volatile("adr %0, ." : "=r"(pc)); pc; })
|
||||
#define callerFP() ({ void* fp; asm volatile("mov %0, fp" : "=r"(fp)); fp; })
|
||||
#define callerSP() ({ void* sp; asm volatile("mov %0, sp" : "=r"(sp)); sp; })
|
||||
|
||||
#elif defined(__PPC64__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
|
||||
|
||||
@@ -130,6 +131,7 @@ const int PERF_REG_PC = 32; // PERF_REG_POWERPC_NIP
|
||||
#define rmb() asm volatile ("sync" : : : "memory") // lwsync would do but better safe than sorry
|
||||
#define flushCache(addr) __builtin___clear_cache((char*)(addr), (char*)(addr) + sizeof(instruction_t))
|
||||
|
||||
#define callerPC() __builtin_return_address(0)
|
||||
#define callerFP() __builtin_frame_address(1)
|
||||
#define callerSP() __builtin_frame_address(0)
|
||||
|
||||
@@ -154,6 +156,7 @@ const int PERF_REG_PC = 0; // PERF_REG_RISCV_PC
|
||||
#define rmb() asm volatile ("fence" : : : "memory")
|
||||
#define flushCache(addr) __builtin___clear_cache((char*)(addr), (char*)(addr) + sizeof(instruction_t))
|
||||
|
||||
#define callerPC() __builtin_return_address(0)
|
||||
#define callerFP() __builtin_frame_address(1)
|
||||
#define callerSP() __builtin_frame_address(0)
|
||||
|
||||
@@ -174,6 +177,7 @@ const int PERF_REG_PC = 0; // PERF_REG_LOONGARCH_PC
|
||||
#define rmb() asm volatile("dbar 0x0" : : : "memory")
|
||||
#define flushCache(addr) __builtin___clear_cache((char*)(addr), (char*)(addr) + sizeof(instruction_t))
|
||||
|
||||
#define callerPC() __builtin_return_address(0)
|
||||
#define callerFP() __builtin_frame_address(1)
|
||||
#define callerSP() __builtin_frame_address(0)
|
||||
|
||||
|
||||
@@ -69,6 +69,7 @@ static const Multiplier UNIVERSAL[] = {{'n', 1}, {'u', 1000}, {'m', 1000000}, {'
|
||||
// jfrsync[=CONFIG] - start Java Flight Recording with the given config along with the profiler
|
||||
// traces[=N] - dump top N call traces
|
||||
// flat[=N] - dump top N methods (aka flat profile)
|
||||
// otlp - dump in OpenTelemetry format
|
||||
// samples - count the number of samples (default)
|
||||
// total - count the total value (time, bytes, etc.) instead of samples
|
||||
// chunksize=N - approximate size of JFR chunk in bytes (default: 100 MB)
|
||||
@@ -107,6 +108,7 @@ static const Multiplier UNIVERSAL[] = {{'n', 1}, {'u', 1000}, {'m', 1000000}, {'
|
||||
// begin=FUNCTION - begin profiling when FUNCTION is executed
|
||||
// end=FUNCTION - end profiling when FUNCTION is executed
|
||||
// nostop - do not stop profiling outside --begin/--end window
|
||||
// ttsp - only time-to-safepoint profiling
|
||||
// title=TITLE - FlameGraph title
|
||||
// minwidth=PCT - FlameGraph minimum frame width in percent
|
||||
// reverse - generate stack-reversed FlameGraph / Call tree (defaults to icicle graph)
|
||||
@@ -199,6 +201,9 @@ Error Arguments::parse(const char* args) {
|
||||
_output = OUTPUT_TEXT;
|
||||
_dump_flat = value == NULL ? INT_MAX : atoi(value);
|
||||
|
||||
CASE("otlp")
|
||||
_output = OUTPUT_OTLP;
|
||||
|
||||
CASE("samples")
|
||||
_counter = COUNTER_SAMPLES;
|
||||
|
||||
@@ -444,6 +449,13 @@ Error Arguments::parse(const char* args) {
|
||||
CASE("nostop")
|
||||
_nostop = true;
|
||||
|
||||
CASE("ttsp")
|
||||
if (_begin != NULL || _end != NULL) {
|
||||
msg = "begin and end must both be empty when ttsp is set";
|
||||
}
|
||||
_begin = "SafepointSynchronize::begin";
|
||||
_end = "RuntimeService::record_safepoint_synchronized";
|
||||
|
||||
// FlameGraph options
|
||||
CASE("title")
|
||||
_title = value;
|
||||
|
||||
@@ -76,7 +76,8 @@ enum SHORT_ENUM Output {
|
||||
OUTPUT_COLLAPSED,
|
||||
OUTPUT_FLAMEGRAPH,
|
||||
OUTPUT_TREE,
|
||||
OUTPUT_JFR
|
||||
OUTPUT_JFR,
|
||||
OUTPUT_OTLP
|
||||
};
|
||||
|
||||
enum JfrOption {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <sys/mman.h>
|
||||
#include "codeCache.h"
|
||||
#include "dwarf.h"
|
||||
#include "log.h"
|
||||
#include "os.h"
|
||||
|
||||
|
||||
@@ -28,19 +29,22 @@ size_t NativeFunc::usedMemory(const char* name) {
|
||||
}
|
||||
|
||||
|
||||
CodeCache::CodeCache(const char* name, short lib_index, bool imports_patchable,
|
||||
const void* min_address, const void* max_address) {
|
||||
CodeCache::CodeCache(const char* name, short lib_index,
|
||||
const void* min_address, const void* max_address,
|
||||
const char* image_base) {
|
||||
_name = NativeFunc::create(name, -1);
|
||||
|
||||
_lib_index = lib_index;
|
||||
_min_address = min_address;
|
||||
_max_address = max_address;
|
||||
_text_base = NULL;
|
||||
_image_base = image_base;
|
||||
|
||||
_plt_offset = 0;
|
||||
_plt_size = 0;
|
||||
|
||||
memset(_imports, 0, sizeof(_imports));
|
||||
_imports_patchable = imports_patchable;
|
||||
_imports_patchable = false;
|
||||
_debug_symbols = false;
|
||||
|
||||
_dwarf_table = NULL;
|
||||
@@ -234,15 +238,13 @@ void CodeCache::addImport(void** entry, const char* name) {
|
||||
void** CodeCache::findImport(ImportId id) {
|
||||
if (!_imports_patchable) {
|
||||
makeImportsPatchable();
|
||||
_imports_patchable = true;
|
||||
}
|
||||
return _imports[id][PRIMARY];
|
||||
}
|
||||
|
||||
void CodeCache::patchImport(ImportId id, void* hook_func) {
|
||||
if (!_imports_patchable) {
|
||||
makeImportsPatchable();
|
||||
_imports_patchable = true;
|
||||
if (!_imports_patchable && !makeImportsPatchable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int ty = 0; ty < NUM_IMPORT_TYPES; ty++) {
|
||||
@@ -253,7 +255,7 @@ void CodeCache::patchImport(ImportId id, void* hook_func) {
|
||||
}
|
||||
}
|
||||
|
||||
void CodeCache::makeImportsPatchable() {
|
||||
bool CodeCache::makeImportsPatchable() {
|
||||
void** min_import = (void**)-1;
|
||||
void** max_import = NULL;
|
||||
for (int i = 0; i < NUM_IMPORTS; i++) {
|
||||
@@ -268,8 +270,14 @@ void CodeCache::makeImportsPatchable() {
|
||||
if (max_import != NULL) {
|
||||
uintptr_t patch_start = (uintptr_t)min_import & ~OS::page_mask;
|
||||
uintptr_t patch_end = (uintptr_t)max_import & ~OS::page_mask;
|
||||
mprotect((void*)patch_start, patch_end - patch_start + OS::page_size, PROT_READ | PROT_WRITE);
|
||||
if (OS::mprotect((void*)patch_start, patch_end - patch_start + OS::page_size, PROT_READ | PROT_WRITE) != 0) {
|
||||
Log::warn("Could not patch %s", name());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_imports_patchable = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void CodeCache::setDwarfTable(FrameDesc* table, int length) {
|
||||
|
||||
@@ -107,6 +107,7 @@ class CodeCache {
|
||||
const void* _min_address;
|
||||
const void* _max_address;
|
||||
const char* _text_base;
|
||||
const char* _image_base;
|
||||
|
||||
unsigned int _plt_offset;
|
||||
unsigned int _plt_size;
|
||||
@@ -123,15 +124,15 @@ class CodeCache {
|
||||
CodeBlob* _blobs;
|
||||
|
||||
void expand();
|
||||
void makeImportsPatchable();
|
||||
bool makeImportsPatchable();
|
||||
void saveImport(ImportId id, void** entry);
|
||||
|
||||
public:
|
||||
CodeCache(const char* name,
|
||||
short lib_index = -1,
|
||||
bool imports_patchable = false,
|
||||
const void* min_address = NO_MIN_ADDRESS,
|
||||
const void* max_address = NO_MAX_ADDRESS);
|
||||
const void* max_address = NO_MAX_ADDRESS,
|
||||
const char* image_base = NULL);
|
||||
|
||||
~CodeCache();
|
||||
|
||||
@@ -147,6 +148,10 @@ class CodeCache {
|
||||
return _max_address;
|
||||
}
|
||||
|
||||
const char* imageBase() const {
|
||||
return _image_base;
|
||||
}
|
||||
|
||||
bool contains(const void* address) const {
|
||||
return address >= _min_address && address < _max_address;
|
||||
}
|
||||
@@ -189,7 +194,7 @@ class CodeCache {
|
||||
|
||||
void addImport(void** entry, const char* name);
|
||||
void** findImport(ImportId id);
|
||||
void patchImport(ImportId, void* hook_func);
|
||||
void patchImport(ImportId id, void* hook_func);
|
||||
|
||||
CodeBlob* findBlob(const char* name);
|
||||
CodeBlob* findBlobByAddress(const void* address);
|
||||
@@ -202,6 +207,8 @@ class CodeCache {
|
||||
FrameDesc* findFrameDesc(const void* pc);
|
||||
|
||||
size_t usedMemory();
|
||||
|
||||
friend class UnloadProtection;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -58,6 +58,8 @@ public class Main {
|
||||
JfrToPprof.convert(input, output, args);
|
||||
} else if ("heatmap".equals(args.output)) {
|
||||
JfrToHeatmap.convert(input, output, args);
|
||||
} else if ("otlp".equals(args.output)) {
|
||||
JfrToOtlp.convert(input, output, args);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unrecognized output format: " + args.output);
|
||||
}
|
||||
@@ -93,7 +95,7 @@ public class Main {
|
||||
System.out.print("Usage: jfrconv [options] <input> [<input>...] <output>\n" +
|
||||
"\n" +
|
||||
"Conversion options:\n" +
|
||||
" -o --output FORMAT Output format: html, collapsed, pprof, pb.gz, heatmap\n" +
|
||||
" -o --output FORMAT Output format: html, collapsed, pprof, pb.gz, heatmap, otlp\n" +
|
||||
"\n" +
|
||||
"JFR options:\n" +
|
||||
" --cpu CPU profile\n" +
|
||||
@@ -102,6 +104,7 @@ public class Main {
|
||||
" --live Live object profile\n" +
|
||||
" --nativemem malloc profile\n" +
|
||||
" --leak Only include memory leaks in nativemem\n" +
|
||||
" --tail RATIO Ignore tail allocations for leak profiling (10% by default)\n" +
|
||||
" --lock Lock contention profile\n" +
|
||||
" -t --threads Split stack traces by threads\n" +
|
||||
" -s --state LIST Filter thread states: runnable, sleeping\n" +
|
||||
|
||||
@@ -19,6 +19,7 @@ public class Arguments {
|
||||
public Pattern exclude;
|
||||
public double minwidth;
|
||||
public double grain;
|
||||
public double tail = 0.1;
|
||||
public int skip;
|
||||
public boolean help;
|
||||
public boolean reverse;
|
||||
@@ -69,7 +70,7 @@ public class Arguments {
|
||||
} else if (type == int.class) {
|
||||
f.setInt(this, Integer.parseInt(args[++i]));
|
||||
} else if (type == double.class) {
|
||||
f.setDouble(this, Double.parseDouble(args[++i]));
|
||||
f.setDouble(this, parseRatio(args[++i]));
|
||||
} else if (type == long.class) {
|
||||
f.setLong(this, parseTimestamp(args[++i]));
|
||||
} else if (type == Pattern.class) {
|
||||
@@ -104,6 +105,14 @@ public class Arguments {
|
||||
}
|
||||
}
|
||||
|
||||
// Absolute floating point value or percentage followed by %
|
||||
private static double parseRatio(String value) {
|
||||
if (value.endsWith("%")) {
|
||||
return Double.parseDouble(value.substring(0, value.length() - 1)) / 100;
|
||||
}
|
||||
return Double.parseDouble(value);
|
||||
}
|
||||
|
||||
// Milliseconds or HH:mm:ss.S or yyyy-MM-dd'T'HH:mm:ss.S
|
||||
private static long parseTimestamp(String time) {
|
||||
if (time.indexOf(':') < 0) {
|
||||
|
||||
@@ -8,6 +8,16 @@ package one.convert;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Container which records the index of appearance of the value it holds.
|
||||
* <p>
|
||||
* Allows retrieving the index of a given object in constant time, as well as
|
||||
* an ordered list of all values seen.
|
||||
* <p>
|
||||
* The object at index 0 is always the empty object.
|
||||
*
|
||||
* @param <T> type of the objects held in the container.
|
||||
*/
|
||||
public class Index<T> extends HashMap<T, Integer> {
|
||||
private final Class<T> cls;
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ public abstract class JfrConverter extends Classifier {
|
||||
this.args = args;
|
||||
|
||||
EventCollector collector = createCollector(args);
|
||||
this.collector = args.nativemem && args.leak ? new MallocLeakAggregator(collector) : collector;
|
||||
this.collector = args.nativemem && args.leak ? new MallocLeakAggregator(collector, args.tail) : collector;
|
||||
}
|
||||
|
||||
public void convert() throws IOException {
|
||||
@@ -260,10 +260,30 @@ public abstract class JfrConverter extends Classifier {
|
||||
methodType == TYPE_KERNEL;
|
||||
}
|
||||
|
||||
public String getValueType() {
|
||||
if (args.nativemem) return "malloc";
|
||||
if (args.alloc || args.live) return "allocations";
|
||||
if (args.lock) return "locks";
|
||||
return "cpu";
|
||||
}
|
||||
|
||||
public String getSampleUnits() {
|
||||
return "count";
|
||||
}
|
||||
|
||||
public String getTotalUnits() {
|
||||
if (args.nativemem || args.alloc || args.live) return "bytes";
|
||||
return "nanoseconds";
|
||||
}
|
||||
|
||||
public double counterFactor() {
|
||||
return args.lock ? 1e9 / jfr.ticksPerSec : 1.0;
|
||||
}
|
||||
|
||||
// Select sum(samples) or sum(value) depending on the --total option.
|
||||
// For lock events, convert lock duration from ticks to nanoseconds.
|
||||
protected abstract class AggregatedEventVisitor implements EventCollector.Visitor {
|
||||
final double factor = !args.total ? 0.0 : args.lock ? 1e9 / jfr.ticksPerSec : 1.0;
|
||||
private final double factor = !args.total ? 0.0 : counterFactor();
|
||||
|
||||
@Override
|
||||
public final void visit(Event event, long samples, long value) {
|
||||
|
||||
271
src/converter/one/convert/JfrToOtlp.java
Normal file
271
src/converter/one/convert/JfrToOtlp.java
Normal file
@@ -0,0 +1,271 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package one.convert;
|
||||
|
||||
import static one.convert.OtlpConstants.*;
|
||||
|
||||
import one.jfr.JfrReader;
|
||||
import one.jfr.StackTrace;
|
||||
import one.jfr.event.Event;
|
||||
import one.jfr.event.EventCollector;
|
||||
import one.proto.Proto;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.*;
|
||||
|
||||
/** Converts .jfr output to OpenTelemetry protocol. */
|
||||
public class JfrToOtlp extends JfrConverter {
|
||||
// Size in bytes to be allocated in the buffer to hold the varint containing the length of the message
|
||||
private static final int MSG_LARGE = 5;
|
||||
private static final int MSG_SMALL = 1;
|
||||
|
||||
private final Index<String> stringPool = new Index<>(String.class, "");
|
||||
private final Index<String> functionPool = new Index<>(String.class, "");
|
||||
private final Index<Line> linePool = new Index<>(Line.class, Line.EMPTY);
|
||||
private final Index<KeyValue> attributesPool = new Index<>(KeyValue.class, KeyValue.EMPTY);
|
||||
|
||||
private final Proto proto = new Proto(1024);
|
||||
|
||||
public JfrToOtlp(JfrReader jfr, Arguments args) {
|
||||
super(jfr, args);
|
||||
}
|
||||
|
||||
public void dump(OutputStream out) throws IOException {
|
||||
out.write(proto.buffer(), 0, proto.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void convert() throws IOException {
|
||||
long rpMark = proto.startField(PROFILES_DATA_resource_profiles, MSG_LARGE);
|
||||
long spMark = proto.startField(RESOURCE_PROFILES_scope_profiles, MSG_LARGE);
|
||||
super.convert();
|
||||
proto.commitField(spMark);
|
||||
proto.commitField(rpMark);
|
||||
|
||||
writeProfileDictionary();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void convertChunk() {
|
||||
long pMark = proto.startField(SCOPE_PROFILES_profiles, MSG_LARGE);
|
||||
|
||||
writeSampleTypes();
|
||||
writeTimingInformation();
|
||||
|
||||
List<Integer> locationIndices = new ArrayList<>();
|
||||
collector.forEach(new OtlpEventToSampleVisitor(locationIndices));
|
||||
|
||||
long liMark = proto.startField(PROFILE_location_indices, MSG_LARGE);
|
||||
locationIndices.forEach(proto::writeInt);
|
||||
proto.commitField(liMark);
|
||||
|
||||
proto.commitField(pMark);
|
||||
}
|
||||
|
||||
private void writeSampleTypes() {
|
||||
long stsMark = proto.startField(PROFILE_sample_type, MSG_SMALL);
|
||||
proto.field(VALUE_TYPE_type_strindex, stringPool.index(getValueType()));
|
||||
proto.field(VALUE_TYPE_unit_strindex, stringPool.index(getSampleUnits()));
|
||||
proto.field(VALUE_TYPE_aggregation_temporality, AGGREGATION_TEMPORARALITY_cumulative);
|
||||
proto.commitField(stsMark);
|
||||
|
||||
long sttMark = proto.startField(PROFILE_sample_type, MSG_SMALL);
|
||||
proto.field(VALUE_TYPE_type_strindex, stringPool.index(getValueType()));
|
||||
proto.field(VALUE_TYPE_unit_strindex, stringPool.index(getTotalUnits()));
|
||||
proto.field(VALUE_TYPE_aggregation_temporality, AGGREGATION_TEMPORARALITY_cumulative);
|
||||
proto.commitField(sttMark);
|
||||
}
|
||||
|
||||
private void writeTimingInformation() {
|
||||
proto.field(PROFILE_time_nanos, jfr.chunkStartNanos);
|
||||
proto.field(PROFILE_duration_nanos, jfr.chunkDurationNanos());
|
||||
}
|
||||
|
||||
private void writeProfileDictionary() {
|
||||
long profilesDictionaryMark = proto.startField(PROFILES_DATA_dictionary, MSG_LARGE);
|
||||
|
||||
// Mapping[0] must be a default mapping according to the spec
|
||||
long mMark = proto.startField(PROFILES_DICTIONARY_mapping_table, MSG_SMALL);
|
||||
proto.commitField(mMark);
|
||||
|
||||
// Write function table
|
||||
for (String name : functionPool.keys()) {
|
||||
long fMark = proto.startField(PROFILES_DICTIONARY_function_table, MSG_SMALL);
|
||||
proto.field(FUNCTION_name_strindex, stringPool.index(name));
|
||||
proto.commitField(fMark);
|
||||
}
|
||||
|
||||
// Write location table
|
||||
for (Line line : linePool.keys()) {
|
||||
long locMark = proto.startField(PROFILES_DICTIONARY_location_table, MSG_SMALL);
|
||||
proto.field(LOCATION_mapping_index, 0);
|
||||
|
||||
long lineMark = proto.startField(LOCATION_line, MSG_SMALL);
|
||||
proto.field(LINE_function_index, line.functionIdx);
|
||||
proto.field(LINE_line, line.lineNumber);
|
||||
proto.commitField(lineMark);
|
||||
|
||||
proto.commitField(locMark);
|
||||
}
|
||||
|
||||
// Write string table
|
||||
for (String s : stringPool.keys()) {
|
||||
proto.field(PROFILES_DICTIONARY_string_table, s);
|
||||
}
|
||||
|
||||
// Write attributes table
|
||||
for (KeyValue kv : attributesPool.keys()) {
|
||||
long aMark = proto.startField(PROFILES_DICTIONARY_attribute_table, MSG_LARGE);
|
||||
proto.field(KEY_VALUE_key, kv.key);
|
||||
|
||||
long vMark = proto.startField(KEY_VALUE_value, MSG_LARGE);
|
||||
proto.field(ANY_VALUE_string_value, kv.value);
|
||||
proto.commitField(vMark);
|
||||
|
||||
proto.commitField(aMark);
|
||||
}
|
||||
|
||||
proto.commitField(profilesDictionaryMark);
|
||||
}
|
||||
|
||||
public static void convert(String input, String output, Arguments args) throws IOException {
|
||||
JfrToOtlp converter;
|
||||
try (JfrReader jfr = new JfrReader(input)) {
|
||||
converter = new JfrToOtlp(jfr, args);
|
||||
converter.convert();
|
||||
}
|
||||
try (FileOutputStream out = new FileOutputStream(output)) {
|
||||
converter.dump(out);
|
||||
}
|
||||
}
|
||||
|
||||
private final class OtlpEventToSampleVisitor implements EventCollector.Visitor {
|
||||
private final List<Integer> locationIndices;
|
||||
private final double factor = counterFactor();
|
||||
private final double ticksPerNanosecond = jfr.ticksPerSec / 1_000_000_000.0;
|
||||
|
||||
// JFR constant pool stacktrace ID to Range
|
||||
private final Map<Integer, Range> idToRange = new HashMap<>();
|
||||
|
||||
// Next index to be used for a location into Profile.location_indices
|
||||
private int nextLocationIdx = 0;
|
||||
|
||||
public OtlpEventToSampleVisitor(List<Integer> locationIndices) {
|
||||
this.locationIndices = locationIndices;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(Event event, long samples, long value) {
|
||||
long nanosFromStart = (long) ((event.time - jfr.chunkStartTicks) / ticksPerNanosecond);
|
||||
long timeNanos = jfr.chunkStartNanos + nanosFromStart;
|
||||
|
||||
Range range = idToRange.computeIfAbsent(event.stackTraceId, this::computeLocationRange);
|
||||
|
||||
long sMark = proto.startField(PROFILE_sample, MSG_SMALL);
|
||||
proto.field(SAMPLE_locations_start_index, range.start);
|
||||
proto.field(SAMPLE_locations_length, range.length);
|
||||
proto.field(SAMPLE_timestamps_unix_nano, timeNanos);
|
||||
|
||||
KeyValue threadName = new KeyValue("thread.name", getThreadName(event.tid));
|
||||
proto.field(SAMPLE_attribute_indices, attributesPool.index(threadName));
|
||||
|
||||
long svMark = proto.startField(SAMPLE_value, MSG_SMALL);
|
||||
proto.writeLong(samples);
|
||||
proto.writeLong(factor == 1.0 ? value : (long) (value * factor));
|
||||
proto.commitField(svMark);
|
||||
|
||||
proto.commitField(sMark);
|
||||
}
|
||||
|
||||
// Range of values in Profile.location_indices
|
||||
private Range computeLocationRange(int stackTraceId) {
|
||||
StackTrace st = jfr.stackTraces.get(stackTraceId);
|
||||
if (st == null) {
|
||||
return new Range(0, 0);
|
||||
}
|
||||
for (int i = 0; i < st.methods.length; ++i) {
|
||||
locationIndices.add(linePool.index(makeLine(st, i)));
|
||||
}
|
||||
Range range = new Range(nextLocationIdx, st.methods.length);
|
||||
nextLocationIdx += st.methods.length;
|
||||
return range;
|
||||
}
|
||||
|
||||
private Line makeLine(StackTrace stackTrace, int i) {
|
||||
String methodName = getMethodName(stackTrace.methods[i], stackTrace.types[i]);
|
||||
int lineNumber = stackTrace.locations[i] >>> 16;
|
||||
int functionIdx = functionPool.index(methodName);
|
||||
return new Line(functionIdx, lineNumber);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Range {
|
||||
final int start;
|
||||
final int length;
|
||||
|
||||
public Range(int start, int length) {
|
||||
this.start = start;
|
||||
this.length = length;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Line {
|
||||
static final Line EMPTY = new Line(0, 0);
|
||||
|
||||
final int functionIdx;
|
||||
final int lineNumber;
|
||||
|
||||
private Line(int functionIdx, int lineNumber) {
|
||||
this.functionIdx = functionIdx;
|
||||
this.lineNumber = lineNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof Line)) return false;
|
||||
|
||||
Line other = (Line) o;
|
||||
return functionIdx == other.functionIdx && lineNumber == other.lineNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = 17;
|
||||
result = 31 * result + functionIdx;
|
||||
return 31 * result + lineNumber;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class KeyValue {
|
||||
static final KeyValue EMPTY = new KeyValue("", "");
|
||||
|
||||
final String key;
|
||||
// Only string value is fine for now
|
||||
final String value;
|
||||
|
||||
private KeyValue(String key, String value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof KeyValue)) return false;
|
||||
|
||||
KeyValue other = (KeyValue) o;
|
||||
return key.equals(other.key) && value.equals(other.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = 17;
|
||||
result = 31 * result + key.hashCode();
|
||||
return 31 * result + value.hashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,18 +27,7 @@ public class JfrToPprof extends JfrConverter {
|
||||
public JfrToPprof(JfrReader jfr, Arguments args) {
|
||||
super(jfr, args);
|
||||
|
||||
Proto sampleType;
|
||||
if (args.nativemem) {
|
||||
sampleType = valueType("malloc", args.total ? "bytes" : "count");
|
||||
} else if (args.alloc || args.live) {
|
||||
sampleType = valueType("allocations", args.total ? "bytes" : "count");
|
||||
} else if (args.lock) {
|
||||
sampleType = valueType("locks", args.total ? "nanoseconds" : "count");
|
||||
} else {
|
||||
sampleType = valueType("cpu", args.total ? "nanoseconds" : "count");
|
||||
}
|
||||
|
||||
profile.field(1, sampleType)
|
||||
profile.field(1, valueType(getValueType(), args.total ? getTotalUnits() : getSampleUnits()))
|
||||
.field(13, strings.index("Produced by async-profiler"));
|
||||
}
|
||||
|
||||
@@ -80,7 +69,7 @@ public class JfrToPprof extends JfrConverter {
|
||||
}
|
||||
|
||||
private Proto sample(Proto s, Event event, long value) {
|
||||
int packedLocations = s.startField(1);
|
||||
long packedLocations = s.startField(1, 3);
|
||||
|
||||
long classId = event.classId();
|
||||
if (classId != 0) {
|
||||
|
||||
61
src/converter/one/convert/OtlpConstants.java
Normal file
61
src/converter/one/convert/OtlpConstants.java
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package one.convert;
|
||||
|
||||
final class OtlpConstants {
|
||||
|
||||
static final int
|
||||
PROFILES_DICTIONARY_mapping_table = 1,
|
||||
PROFILES_DICTIONARY_location_table = 2,
|
||||
PROFILES_DICTIONARY_function_table = 3,
|
||||
PROFILES_DICTIONARY_string_table = 5,
|
||||
PROFILES_DICTIONARY_attribute_table = 6;
|
||||
|
||||
static final int
|
||||
PROFILES_DATA_resource_profiles = 1,
|
||||
PROFILES_DATA_dictionary = 2;
|
||||
|
||||
static final int RESOURCE_PROFILES_scope_profiles = 2;
|
||||
|
||||
static final int SCOPE_PROFILES_profiles = 2;
|
||||
|
||||
static final int
|
||||
PROFILE_sample_type = 1,
|
||||
PROFILE_sample = 2,
|
||||
PROFILE_location_indices = 3,
|
||||
PROFILE_time_nanos = 4,
|
||||
PROFILE_duration_nanos = 5;
|
||||
|
||||
static final int
|
||||
VALUE_TYPE_type_strindex = 1,
|
||||
VALUE_TYPE_unit_strindex = 2,
|
||||
VALUE_TYPE_aggregation_temporality = 3;
|
||||
|
||||
static final int
|
||||
SAMPLE_locations_start_index = 1,
|
||||
SAMPLE_locations_length = 2,
|
||||
SAMPLE_value = 3,
|
||||
SAMPLE_attribute_indices = 4,
|
||||
SAMPLE_timestamps_unix_nano = 6;
|
||||
|
||||
static final int
|
||||
LOCATION_mapping_index = 1,
|
||||
LOCATION_line = 3;
|
||||
|
||||
static final int
|
||||
LINE_function_index = 1,
|
||||
LINE_line = 2;
|
||||
|
||||
static final int FUNCTION_name_strindex = 1;
|
||||
|
||||
static final int AGGREGATION_TEMPORARALITY_cumulative = 2;
|
||||
|
||||
static final int
|
||||
KEY_VALUE_key = 1,
|
||||
KEY_VALUE_value = 2;
|
||||
|
||||
static final int ANY_VALUE_string_value = 1;
|
||||
}
|
||||
@@ -118,6 +118,10 @@ public class JfrReader implements Closeable {
|
||||
return endNanos - startNanos;
|
||||
}
|
||||
|
||||
public long chunkDurationNanos() {
|
||||
return chunkEndNanos - chunkStartNanos;
|
||||
}
|
||||
|
||||
public <E extends Event> void registerEvent(String name, Class<E> eventClass) {
|
||||
JfrClass type = typesByName.get(name);
|
||||
if (type != null) {
|
||||
|
||||
@@ -12,17 +12,26 @@ import java.util.HashMap;
|
||||
|
||||
public class MallocLeakAggregator implements EventCollector {
|
||||
private final EventCollector wrapped;
|
||||
private final double tail;
|
||||
private final Map<Long, MallocEvent> addresses;
|
||||
private List<MallocEvent> events;
|
||||
private long minTime = Long.MAX_VALUE;
|
||||
private long maxTime = Long.MIN_VALUE;
|
||||
|
||||
public MallocLeakAggregator(EventCollector wrapped) {
|
||||
public MallocLeakAggregator(EventCollector wrapped, double tail) {
|
||||
if (tail < 0.0 || tail > 1.0) {
|
||||
throw new IllegalArgumentException("tail must be between 0 and 1");
|
||||
}
|
||||
this.wrapped = wrapped;
|
||||
this.tail = tail;
|
||||
this.addresses = new HashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void collect(Event e) {
|
||||
events.add((MallocEvent) e);
|
||||
minTime = Math.min(minTime, e.time);
|
||||
maxTime = Math.max(maxTime, e.time);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -47,9 +56,15 @@ public class MallocLeakAggregator implements EventCollector {
|
||||
|
||||
@Override
|
||||
public boolean finish() {
|
||||
// Ignore tail allocations made in the last N% of profiling session:
|
||||
// they are too young to be considered a leak
|
||||
long timeCutoff = (long) (minTime * tail + maxTime * (1.0 - tail));
|
||||
|
||||
wrapped.beforeChunk();
|
||||
for (Event e : addresses.values()) {
|
||||
wrapped.collect(e);
|
||||
if (e.time <= timeCutoff) {
|
||||
wrapped.collect(e);
|
||||
}
|
||||
}
|
||||
wrapped.afterChunk();
|
||||
|
||||
|
||||
@@ -69,21 +69,30 @@ public class Proto {
|
||||
return this;
|
||||
}
|
||||
|
||||
public int startField(int index) {
|
||||
// 32 bits for the start position
|
||||
// 32 bits for the max length byte count
|
||||
public long startField(int index, int maxLenByteCount) {
|
||||
tag(index, 2);
|
||||
ensureCapacity(3);
|
||||
return pos += 3;
|
||||
ensureCapacity(maxLenByteCount);
|
||||
pos += maxLenByteCount;
|
||||
return ((long) pos << 32) | maxLenByteCount;
|
||||
}
|
||||
|
||||
public void commitField(int mark) {
|
||||
int length = pos - mark;
|
||||
if (length >= 1 << (7 * 3)) {
|
||||
public void commitField(long mark) {
|
||||
int messageStart = (int) (mark >> 32);
|
||||
int maxLenByteCount = (int) mark;
|
||||
|
||||
int actualLength = pos - messageStart;
|
||||
if (actualLength >= 1L << (7 * maxLenByteCount)) {
|
||||
throw new IllegalArgumentException("Field too large");
|
||||
}
|
||||
|
||||
buf[mark - 3] = (byte) (0x80 | (length & 0x7f));
|
||||
buf[mark - 2] = (byte) (0x80 | ((length >>> 7) & 0x7f));
|
||||
buf[mark - 1] = (byte) (length >>> 14);
|
||||
int lenBytesStart = messageStart - maxLenByteCount;
|
||||
for (int i = 0; i < maxLenByteCount - 1; ++i) {
|
||||
buf[lenBytesStart + i] = (byte) (0x80 | actualLength);
|
||||
actualLength >>>= 7;
|
||||
}
|
||||
buf[lenBytesStart + maxLenByteCount - 1] = (byte) actualLength;
|
||||
}
|
||||
|
||||
public void writeInt(int n) {
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
[
|
||||
{
|
||||
"name": "one.convert.Arguments",
|
||||
"allPublicFields": true
|
||||
},
|
||||
{
|
||||
"name" : "one.jfr.event.CPULoad",
|
||||
"allPublicConstructors": true
|
||||
},
|
||||
{
|
||||
"name" : "one.jfr.event.GCHeapSummary",
|
||||
"allPublicConstructors": true
|
||||
},
|
||||
{
|
||||
"name" : "one.jfr.event.ObjectCount",
|
||||
"allPublicConstructors": true
|
||||
}
|
||||
]
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"resources": {
|
||||
"includes": [{"pattern": ".*html$"}]
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ int CTimer::createForThread(int tid) {
|
||||
sev.sigev_value.sival_ptr = NULL;
|
||||
sev.sigev_signo = _signal;
|
||||
sev.sigev_notify = SIGEV_THREAD_ID;
|
||||
((int*)&sev.sigev_notify)[1] = tid;
|
||||
(&sev.sigev_notify)[1] = tid;
|
||||
|
||||
// Use raw syscalls, since libc wrapper allows only predefined clocks
|
||||
clockid_t clock = thread_cpu_clock(tid);
|
||||
|
||||
@@ -16,8 +16,8 @@ enum EventType {
|
||||
PERF_SAMPLE,
|
||||
EXECUTION_SAMPLE,
|
||||
WALL_CLOCK_SAMPLE,
|
||||
INSTRUMENTED_METHOD,
|
||||
MALLOC_SAMPLE,
|
||||
INSTRUMENTED_METHOD,
|
||||
ALLOC_SAMPLE,
|
||||
ALLOC_OUTSIDE_TLAB,
|
||||
LIVE_OBJECT,
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
|
||||
#define RESTARTABLE(call) ({ ssize_t ret; while ((ret = call) < 0 && errno == EINTR); ret; })
|
||||
|
||||
#define MAX_PROBE_LEN 256
|
||||
|
||||
|
||||
// base header for all requests
|
||||
enum request_type {
|
||||
@@ -34,6 +36,7 @@ struct perf_fd_request {
|
||||
int tid;
|
||||
int target_cpu;
|
||||
struct perf_event_attr attr;
|
||||
char probe_name[MAX_PROBE_LEN];
|
||||
};
|
||||
|
||||
struct fd_response {
|
||||
|
||||
@@ -26,7 +26,7 @@ class FdTransferClient {
|
||||
}
|
||||
}
|
||||
|
||||
static int requestPerfFd(int *tid, int target_cpu, struct perf_event_attr *attr);
|
||||
static int requestPerfFd(int* tid, int target_cpu, struct perf_event_attr* attr, const char* probe_name);
|
||||
static int requestKallsymsFd();
|
||||
};
|
||||
|
||||
|
||||
@@ -47,14 +47,16 @@ bool FdTransferClient::connectToServer(const char *path) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int FdTransferClient::requestPerfFd(int *tid, int target_cpu, struct perf_event_attr *attr) {
|
||||
int FdTransferClient::requestPerfFd(int* tid, int target_cpu, struct perf_event_attr* attr, const char* probe_name) {
|
||||
struct perf_fd_request request;
|
||||
request.header.type = PERF_FD;
|
||||
request.tid = *tid;
|
||||
request.target_cpu = target_cpu;
|
||||
memcpy(&request.attr, attr, sizeof(request.attr));
|
||||
*stpncpy(request.probe_name, probe_name, sizeof(request.probe_name) - 1) = 0;
|
||||
|
||||
if (RESTARTABLE(send(_peer, &request, sizeof(request), 0)) != sizeof(request)) {
|
||||
size_t request_size = sizeof(request) - sizeof(request.probe_name) + strlen(request.probe_name) + 1;
|
||||
if (RESTARTABLE(send(_peer, &request, request_size, 0)) != request_size) {
|
||||
Log::warn("FdTransferClient send(): %s", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -254,7 +254,7 @@ void FlameGraph::printTreeFrame(Writer& out, const Trie& f, int level, const cha
|
||||
}
|
||||
out << _buf;
|
||||
|
||||
if (trie->_children.size() > 0) {
|
||||
if (!trie->_children.empty()) {
|
||||
out << "<ul>\n";
|
||||
if (trie->_total >= _mintotal) {
|
||||
printTreeFrame(out, *trie, level + 1, names);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <arpa/inet.h>
|
||||
@@ -58,7 +59,7 @@ static jmethodID _start_method;
|
||||
static jmethodID _stop_method;
|
||||
static jmethodID _box_method;
|
||||
|
||||
static const char* const SETTING_CSTACK[] = {NULL, "no", "fp", "dwarf", "lbr", "vm"};
|
||||
static const char* const SETTING_CSTACK[] = {NULL, "no", "fp", "dwarf", "lbr", "vm", "vmx"};
|
||||
|
||||
|
||||
struct CpuTime {
|
||||
@@ -173,31 +174,24 @@ class Lookup {
|
||||
}
|
||||
|
||||
bool fillJavaMethodInfo(MethodInfo* mi, jmethodID method, bool first_time) {
|
||||
if (VMStructs::hasMethodStructs()) {
|
||||
// Workaround for JDK-8313816
|
||||
VMMethod* vm_method = VMMethod::fromMethodID(method);
|
||||
if (vm_method == NULL || vm_method->id() == NULL) {
|
||||
return false;
|
||||
}
|
||||
if (VMMethod::isStaleMethodId(method)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
jvmtiEnv* jvmti = VM::jvmti();
|
||||
|
||||
jclass method_class = NULL;
|
||||
char* class_name = NULL;
|
||||
char* method_name = NULL;
|
||||
char* method_sig = NULL;
|
||||
|
||||
if (jvmti->GetMethodName(method, &method_name, &method_sig, NULL) == 0 &&
|
||||
jvmti->GetMethodDeclaringClass(method, &method_class) == 0 &&
|
||||
jvmti->GetClassSignature(method_class, &class_name, NULL) == 0) {
|
||||
jvmtiEnv* jvmti = VM::jvmti();
|
||||
jvmtiError err;
|
||||
|
||||
if ((err = jvmti->GetMethodName(method, &method_name, &method_sig, NULL)) == 0 &&
|
||||
(err = jvmti->GetMethodDeclaringClass(method, &method_class)) == 0 &&
|
||||
(err = jvmti->GetClassSignature(method_class, &class_name, NULL)) == 0) {
|
||||
mi->_class = _classes->lookup(class_name + 1, strlen(class_name) - 2);
|
||||
mi->_name = _symbols.lookup(method_name);
|
||||
mi->_sig = _symbols.lookup(method_sig);
|
||||
} else {
|
||||
mi->_class = _classes->lookup("");
|
||||
mi->_name = _symbols.lookup("jvmtiError");
|
||||
mi->_sig = _symbols.lookup("()L;");
|
||||
}
|
||||
|
||||
if (method_class) {
|
||||
@@ -207,6 +201,10 @@ class Lookup {
|
||||
jvmti->Deallocate((unsigned char*)method_name);
|
||||
jvmti->Deallocate((unsigned char*)class_name);
|
||||
|
||||
if (err != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (first_time && jvmti->GetMethodModifiers(method, &mi->_modifiers) != 0) {
|
||||
mi->_modifiers = 0;
|
||||
}
|
||||
@@ -778,11 +776,11 @@ class Recording {
|
||||
buf->putVar32(0);
|
||||
buf->putVar32(0x7fffffff); // must not clash with JFR metadata ID, or 'jfr print' will break
|
||||
|
||||
std::vector<std::string>& strings = JfrMetadata::strings();
|
||||
const Index& strings = JfrMetadata::strings();
|
||||
buf->putVar32(strings.size());
|
||||
for (int i = 0; i < strings.size(); i++) {
|
||||
buf->putUtf8(strings[i].c_str());
|
||||
}
|
||||
strings.forEachOrdered([&] (const std::string& s) {
|
||||
buf->putUtf8(s.c_str());
|
||||
});
|
||||
|
||||
writeElement(buf, JfrMetadata::root());
|
||||
|
||||
@@ -819,6 +817,7 @@ class Recording {
|
||||
}
|
||||
|
||||
void writeSettings(Buffer* buf, Arguments& args) {
|
||||
assert(args._cstack < sizeof(SETTING_CSTACK) / sizeof(char*));
|
||||
writeStringSetting(buf, T_ACTIVE_RECORDING, "version", PROFILER_VERSION);
|
||||
writeStringSetting(buf, T_ACTIVE_RECORDING, "engine", Profiler::instance()->_engine->type());
|
||||
writeStringSetting(buf, T_ACTIVE_RECORDING, "cstack", SETTING_CSTACK[args._cstack]);
|
||||
|
||||
@@ -44,6 +44,8 @@ Matcher::Matcher(const Matcher& m) {
|
||||
}
|
||||
|
||||
Matcher& Matcher::operator=(const Matcher& m) {
|
||||
if (this == &m) return *this;
|
||||
|
||||
free(_pattern);
|
||||
|
||||
_type = m._type;
|
||||
@@ -153,13 +155,9 @@ const char* FrameName::typeSuffix(FrameTypeId type) {
|
||||
}
|
||||
|
||||
void FrameName::javaMethodName(jmethodID method) {
|
||||
if (VMStructs::hasMethodStructs()) {
|
||||
// Workaround for JDK-8313816
|
||||
VMMethod* vm_method = VMMethod::fromMethodID(method);
|
||||
if (vm_method == NULL || vm_method->id() == NULL) {
|
||||
_str.assign("[stale_jmethodID]");
|
||||
return;
|
||||
}
|
||||
if (VMMethod::isStaleMethodId(method)) {
|
||||
_str.assign("[stale_jmethodID]");
|
||||
return;
|
||||
}
|
||||
|
||||
jclass method_class = NULL;
|
||||
@@ -184,6 +182,8 @@ void FrameName::javaMethodName(jmethodID method) {
|
||||
}
|
||||
_str.append(method_sig);
|
||||
}
|
||||
} else if (err == JVMTI_ERROR_INVALID_METHODID) {
|
||||
_str.assign("[stale_jmethodID]");
|
||||
} else {
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "[jvmtiError %d]", err);
|
||||
|
||||
Binary file not shown.
@@ -80,6 +80,7 @@ class JfrSync implements FlightRecorderListener {
|
||||
recording.disable("jdk.ObjectAllocationInNewTLAB");
|
||||
recording.disable("jdk.ObjectAllocationOutsideTLAB");
|
||||
recording.disable("jdk.ObjectAllocationSample");
|
||||
recording.disable("jdk.OldObjectSample");
|
||||
}
|
||||
if ((eventMask & 4) != 0) {
|
||||
recording.disable("jdk.JavaMonitorEnter");
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "cpuEngine.h"
|
||||
#include "mallocTracer.h"
|
||||
#include "profiler.h"
|
||||
#include "symbols.h"
|
||||
|
||||
|
||||
#define ADDRESS_OF(sym) ({ \
|
||||
@@ -182,6 +183,11 @@ void Hooks::patchLibraries() {
|
||||
|
||||
while (_patched_libs < native_lib_count) {
|
||||
CodeCache* cc = (*native_libs)[_patched_libs++];
|
||||
UnloadProtection handle(cc);
|
||||
if (!handle.isValid()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!cc->contains((const void*)Hooks::init)) {
|
||||
// Let libasyncProfiler always use original dlopen
|
||||
cc->patchImport(im_dlopen, (void*)dlopen_hook);
|
||||
|
||||
47
src/index.h
Normal file
47
src/index.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef _INDEX_H
|
||||
#define _INDEX_H
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include "arch.h"
|
||||
|
||||
// Keeps track of values seen and their index of occurrence
|
||||
class Index {
|
||||
private:
|
||||
std::unordered_map<std::string, size_t> _idx_map;
|
||||
|
||||
public:
|
||||
Index() = default;
|
||||
|
||||
Index(const Index&) = delete;
|
||||
Index(Index&&) = delete;
|
||||
Index& operator=(const Index&) = delete;
|
||||
Index& operator=(Index&&) = delete;
|
||||
|
||||
size_t indexOf(const std::string& value) {
|
||||
return _idx_map.insert({value, _idx_map.size()}).first->second;
|
||||
}
|
||||
|
||||
size_t size() const {
|
||||
return _idx_map.size();
|
||||
}
|
||||
|
||||
void forEachOrdered(const std::function<void(const std::string&)>& consumer) const {
|
||||
std::vector<const std::string*> arr(_idx_map.size());
|
||||
for (const auto& it : _idx_map) {
|
||||
arr[it.second] = &it.first;
|
||||
}
|
||||
for (size_t idx = 0; idx < size(); ++idx) {
|
||||
consumer(*arr[idx]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif // _INDEX_H
|
||||
@@ -356,7 +356,8 @@ void BytecodeRewriter::rewriteStackMapTable() {
|
||||
} else if (frame_type <= 254) {
|
||||
// append_frame
|
||||
put16(get16());
|
||||
for (int j = 0; j < frame_type - 251; j++) {
|
||||
u8 count = frame_type - (u8)251; // explicit cast to workaround clang type promotion bug
|
||||
for (u8 j = 0; j < count; j++) {
|
||||
rewriteVerificationTypeInfo();
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -125,9 +125,11 @@ static int read_response(int fd, const char* cmd, int print_output) {
|
||||
ssize_t bytes = read(fd, buf + off, size - off);
|
||||
if (bytes == 0) {
|
||||
fprintf(stderr, "Unexpected EOF reading response\n");
|
||||
free(buf);
|
||||
return 1;
|
||||
} else if (bytes < 0) {
|
||||
perror("Error reading response");
|
||||
free(buf);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -95,6 +95,37 @@ Java_one_profiler_AsyncProfiler_execute0(JNIEnv* env, jobject unused, jstring co
|
||||
return NULL;
|
||||
}
|
||||
|
||||
extern "C" DLLEXPORT jbyteArray JNICALL
|
||||
Java_one_profiler_AsyncProfiler_execute1(JNIEnv* env, jobject unused, jstring command) {
|
||||
Arguments args;
|
||||
const char* command_str = env->GetStringUTFChars(command, NULL);
|
||||
Error error = args.parse(command_str);
|
||||
env->ReleaseStringUTFChars(command, command_str);
|
||||
|
||||
if (error) {
|
||||
throwNew(env, "java/lang/IllegalArgumentException", error.message());
|
||||
return NULL;
|
||||
}
|
||||
if (args.hasOutputFile()) {
|
||||
throwNew(env, "java/lang/IllegalArgumentException", "execute1 calls should not specify an output file argument");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Log::open(args);
|
||||
|
||||
BufferWriter out;
|
||||
// TODO: This is doing one more copy than necessary, from ProtoWriter to BufferWriter
|
||||
error = Profiler::instance()->runInternal(args, out);
|
||||
if (error) {
|
||||
throwNew(env, "java/lang/IllegalStateException", error.message());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
jbyteArray output = env->NewByteArray(out.size());
|
||||
env->SetByteArrayRegion(output, 0, out.size(), (const jbyte*) out.buf());
|
||||
return output;
|
||||
}
|
||||
|
||||
extern "C" DLLEXPORT jlong JNICALL
|
||||
Java_one_profiler_AsyncProfiler_getSamples(JNIEnv* env, jobject unused) {
|
||||
return (jlong)Profiler::instance()->total_samples();
|
||||
@@ -124,6 +155,7 @@ static const JNINativeMethod profiler_natives[] = {
|
||||
F(start0, "(Ljava/lang/String;JZ)V"),
|
||||
F(stop0, "()V"),
|
||||
F(execute0, "(Ljava/lang/String;)Ljava/lang/String;"),
|
||||
F(execute1, "(Ljava/lang/String;)[B"),
|
||||
F(getSamples, "()J"),
|
||||
F(filterThread0, "(Ljava/lang/Thread;Z)V"),
|
||||
};
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
#include "jfrMetadata.h"
|
||||
|
||||
|
||||
std::map<std::string, int> Element::_string_map;
|
||||
std::vector<std::string> Element::_strings;
|
||||
Index Element::_strings;
|
||||
|
||||
JfrMetadata JfrMetadata::_root;
|
||||
|
||||
@@ -281,7 +280,4 @@ JfrMetadata::JfrMetadata() : Element("root") {
|
||||
<< type("jdk.jfr.Percentage", T_PERCENTAGE, "Percentage"))
|
||||
|
||||
<< element("region").attribute("locale", "en_US").attribute("gmtOffset", "0");
|
||||
|
||||
// The map is used only during construction
|
||||
_string_map.clear();
|
||||
}
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
#define _JFRMETADATA_H
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "index.h"
|
||||
|
||||
|
||||
enum JfrType {
|
||||
@@ -93,17 +93,10 @@ class Attribute {
|
||||
|
||||
class Element {
|
||||
protected:
|
||||
static std::map<std::string, int> _string_map;
|
||||
static std::vector<std::string> _strings;
|
||||
static Index _strings;
|
||||
|
||||
static int getId(const char* s) {
|
||||
std::string str(s);
|
||||
int id = _string_map[str];
|
||||
if (id == 0) {
|
||||
id = _string_map[str] = _string_map.size();
|
||||
_strings.push_back(str);
|
||||
}
|
||||
return id - 1;
|
||||
return _strings.indexOf(s);
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -236,7 +229,7 @@ class JfrMetadata : Element {
|
||||
return &_root;
|
||||
}
|
||||
|
||||
static std::vector<std::string>& strings() {
|
||||
static const Index& strings() {
|
||||
return _strings;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <dirent.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
|
||||
#ifdef __APPLE__
|
||||
# include <mach-o/dyld.h>
|
||||
# define COMMON_JVM_DIR "/Library/Java/JavaVirtualMachines/"
|
||||
# define CONTENTS_HOME "/Contents/Home"
|
||||
#else
|
||||
# define COMMON_JVM_DIR "/usr/lib/jvm/"
|
||||
# define CONTENTS_HOME ""
|
||||
#endif
|
||||
|
||||
#define JAVA_EXE "java"
|
||||
|
||||
|
||||
static const char VERSION_STRING[] =
|
||||
"JFR converter " PROFILER_VERSION " built on " __DATE__ "\n";
|
||||
|
||||
|
||||
static char exe_path[PATH_MAX];
|
||||
static char java_path[PATH_MAX];
|
||||
|
||||
static bool get_exe_path() {
|
||||
#ifdef __APPLE__
|
||||
char buf[PATH_MAX];
|
||||
uint32_t size = sizeof(buf);
|
||||
return _NSGetExecutablePath(buf, &size) == 0 && realpath(buf, exe_path) != NULL;
|
||||
#else
|
||||
if (realpath("/proc/self/exe", exe_path) == NULL) {
|
||||
// realpath() may fail for a path like /proc/[pid]/root/bin/exe
|
||||
ssize_t size = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1);
|
||||
if (size < 0) {
|
||||
return false;
|
||||
}
|
||||
exe_path[size] = 0;
|
||||
}
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
static const char* const* build_cmdline(int argc, char** argv) {
|
||||
const char** cmd = (const char**)malloc((argc + 6) * sizeof(char*));
|
||||
int count = 0;
|
||||
|
||||
cmd[count++] = JAVA_EXE;
|
||||
cmd[count++] = "-Xss2M";
|
||||
cmd[count++] = "-Dsun.misc.URLClassPath.disableJarChecking";
|
||||
|
||||
for (; argc > 0; argc--, argv++) {
|
||||
if (((strncmp(*argv, "-D", 2) == 0 || strncmp(*argv, "-X", 2) == 0) && (*argv)[2]) ||
|
||||
strncmp(*argv, "-agent", 6) == 0) {
|
||||
cmd[count++] = *argv;
|
||||
} else if (strncmp(*argv, "-J", 2) == 0) {
|
||||
cmd[count++] = *argv + 2;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
cmd[count++] = "-jar";
|
||||
cmd[count++] = exe_path;
|
||||
|
||||
for (; argc > 0; argc--, argv++) {
|
||||
cmd[count++] = *argv;
|
||||
}
|
||||
|
||||
cmd[count] = NULL;
|
||||
return cmd;
|
||||
}
|
||||
|
||||
static bool find_java_at(const char* path, const char* path1 = "", const char* path2 = "") {
|
||||
if (snprintf(java_path, sizeof(java_path), "%s%s%s/" JAVA_EXE, path, path1, path2) >= sizeof(java_path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct stat st;
|
||||
return stat(java_path, &st) == 0 && S_ISREG(st.st_mode) && (st.st_mode & S_IXUSR) != 0;
|
||||
}
|
||||
|
||||
static void run_java(char* const* cmd) {
|
||||
// 1. Get java executable from JAVA_HOME
|
||||
char* java_home = getenv("JAVA_HOME");
|
||||
if (java_home != NULL && find_java_at(java_home, "/bin")) {
|
||||
execv(java_path, cmd);
|
||||
}
|
||||
|
||||
// 2. Try to find java in PATH
|
||||
execvp(JAVA_EXE, cmd);
|
||||
|
||||
// 3. Try /etc/alternatives/java
|
||||
if (find_java_at("/etc/alternatives")) {
|
||||
execv(java_path, cmd);
|
||||
}
|
||||
|
||||
// 4. Look for java in the common directory
|
||||
DIR* dir = opendir(COMMON_JVM_DIR);
|
||||
if (dir != NULL) {
|
||||
struct dirent* entry;
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
if (entry->d_name[0] != '.' && entry->d_type == DT_DIR) {
|
||||
if (find_java_at(COMMON_JVM_DIR, entry->d_name, CONTENTS_HOME "/bin")) {
|
||||
execv(java_path, cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc > 1 && (strcmp(argv[1], "-v") == 0 || strcmp(argv[1], "--version") == 0)) {
|
||||
printf(VERSION_STRING);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!get_exe_path()) {
|
||||
fprintf(stderr, "Failed to get executable path\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char* const* cmd = build_cmdline(argc - 1, argv + 1);
|
||||
run_java((char* const*)cmd);
|
||||
|
||||
// May reach here only if run_java() fails
|
||||
fprintf(stderr, "No JDK found. Set JAVA_HOME or ensure java executable is on the PATH\n");
|
||||
return 1;
|
||||
}
|
||||
70
src/launcher/launcher.sh
Normal file
70
src/launcher/launcher.sh
Normal file
@@ -0,0 +1,70 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Copyright The async-profiler authors
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
if [ "$1" = "-v" ] || [ "$1" = "--version" ]; then
|
||||
echo "JFR converter PROFILER_VERSION built on BUILD_DATE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
JAVA_OPTS="-Xss2M -Dsun.misc.URLClassPath.disableJarChecking"
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
-D*|-X*)
|
||||
if [ ${#1} -gt 2 ]; then
|
||||
JAVA_OPTS="$JAVA_OPTS $1"
|
||||
fi
|
||||
;;
|
||||
-agent*)
|
||||
JAVA_OPTS="$JAVA_OPTS $1"
|
||||
;;
|
||||
-J*)
|
||||
opt=$(echo "$1" | cut -c3-)
|
||||
JAVA_OPTS="$JAVA_OPTS $opt"
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
# 1. Try JAVA_HOME
|
||||
if [ -n "$JAVA_HOME" ] && [ -x "$JAVA_HOME/bin/java" ]; then
|
||||
exec "$JAVA_HOME/bin/java" $JAVA_OPTS -jar "$0" "$@"
|
||||
fi
|
||||
|
||||
# 2. Try PATH
|
||||
if command -v java >/dev/null 2>&1; then
|
||||
exec java $JAVA_OPTS -jar "$0" "$@"
|
||||
fi
|
||||
|
||||
# 3. Try /etc/alternatives/java
|
||||
if [ -x "/etc/alternatives/java" ]; then
|
||||
exec "/etc/alternatives/java" $JAVA_OPTS -jar "$0" "$@"
|
||||
fi
|
||||
|
||||
# 4. Try common JVM directories
|
||||
if [ "$(uname -s)" = "Darwin" ]; then
|
||||
JVM_DIR="/Library/Java/JavaVirtualMachines"
|
||||
CONTENTS_HOME="/Contents/Home"
|
||||
else
|
||||
JVM_DIR="/usr/lib/jvm"
|
||||
CONTENTS_HOME=""
|
||||
fi
|
||||
|
||||
if [ -d "$JVM_DIR" ]; then
|
||||
for JVM in "$JVM_DIR"/*; do
|
||||
if [ -d "$JVM" ]; then
|
||||
JAVA_EXE="$JVM$CONTENTS_HOME/bin/java"
|
||||
if [ -x "$JAVA_EXE" ]; then
|
||||
exec "$JAVA_EXE" $JAVA_OPTS -jar "$0" "$@"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo "No JDK found. Set JAVA_HOME or ensure java executable is on the PATH" >&2
|
||||
exit 1
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -115,6 +116,11 @@ bool FdTransferServer::serveRequests(int peer_pid) {
|
||||
int perf_fd = -1;
|
||||
int error;
|
||||
|
||||
if (request->probe_name[0]) {
|
||||
// kprobe/uprobe name must be in the address space of the current process
|
||||
request->attr.config1 = (__u64)(uintptr_t)request->probe_name;
|
||||
}
|
||||
|
||||
// In pid == 0 mode, allow all perf_event_open requests.
|
||||
// Otherwise, verify the thread belongs to PID.
|
||||
if (peer_pid == 0 || syscall(__NR_tgkill, peer_pid, request->tid, 0) == 0) {
|
||||
|
||||
@@ -55,7 +55,7 @@ static const char USAGE_STRING[] =
|
||||
" -g, --sig print method signatures\n"
|
||||
" -a, --ann annotate Java methods\n"
|
||||
" -l, --lib prepend library names\n"
|
||||
" -o fmt output format: flat|traces|collapsed|flamegraph|tree|jfr\n"
|
||||
" -o fmt output format: flat|traces|collapsed|flamegraph|tree|jfr|otlp\n"
|
||||
" -I include output only stack traces containing the specified pattern\n"
|
||||
" -X exclude exclude stack traces with the specified pattern\n"
|
||||
" -L level log level: debug|info|warn|error|none\n"
|
||||
@@ -160,6 +160,8 @@ class String {
|
||||
}
|
||||
|
||||
String& operator=(const String& other) {
|
||||
if (this == &other) return *this;
|
||||
|
||||
free(_str);
|
||||
_str = strdup(other._str);
|
||||
return *this;
|
||||
@@ -507,7 +509,7 @@ int main(int argc, const char** argv) {
|
||||
params << "," << (arg.str() + 2) << "=" << args.next();
|
||||
|
||||
} else if (arg == "--ttsp") {
|
||||
params << ",begin=SafepointSynchronize::begin,end=RuntimeService::record_safepoint_synchronized";
|
||||
params << ",ttsp";
|
||||
|
||||
} else if (arg == "--nostop") {
|
||||
params << ",nostop";
|
||||
|
||||
@@ -3,15 +3,17 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "asprof.h"
|
||||
#include "assert.h"
|
||||
#include "codeCache.h"
|
||||
#include "mallocTracer.h"
|
||||
#include "os.h"
|
||||
#include "profiler.h"
|
||||
#include "symbols.h"
|
||||
#include "tsc.h"
|
||||
#include <dlfcn.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef __clang__
|
||||
# define NO_OPTIMIZE __attribute__((optnone))
|
||||
@@ -19,8 +21,18 @@
|
||||
# define NO_OPTIMIZE __attribute__((optimize("O1")))
|
||||
#endif
|
||||
|
||||
#define SAVE_IMPORT(FUNC) \
|
||||
_orig_##FUNC = (decltype(_orig_##FUNC))*lib->findImport(im_##FUNC)
|
||||
|
||||
static void* (*_orig_malloc)(size_t);
|
||||
static void (*_orig_free)(void*);
|
||||
static void* (*_orig_calloc)(size_t, size_t);
|
||||
static void* (*_orig_realloc)(void*, size_t);
|
||||
static int (*_orig_posix_memalign)(void**, size_t, size_t);
|
||||
static void* (*_orig_aligned_alloc)(size_t, size_t);
|
||||
|
||||
extern "C" void* malloc_hook(size_t size) {
|
||||
void* ret = malloc(size);
|
||||
void* ret = _orig_malloc(size);
|
||||
if (MallocTracer::running() && ret && size) {
|
||||
MallocTracer::recordMalloc(ret, size);
|
||||
}
|
||||
@@ -28,7 +40,7 @@ extern "C" void* malloc_hook(size_t size) {
|
||||
}
|
||||
|
||||
extern "C" void* calloc_hook(size_t num, size_t size) {
|
||||
void* ret = calloc(num, size);
|
||||
void* ret = _orig_calloc(num, size);
|
||||
if (MallocTracer::running() && ret && num && size) {
|
||||
MallocTracer::recordMalloc(ret, num * size);
|
||||
}
|
||||
@@ -38,11 +50,11 @@ extern "C" void* calloc_hook(size_t num, size_t size) {
|
||||
// Make sure this is not optimized away (function-scoped -fno-optimize-sibling-calls)
|
||||
extern "C" NO_OPTIMIZE
|
||||
void* calloc_hook_dummy(size_t num, size_t size) {
|
||||
return calloc(num, size);
|
||||
return _orig_calloc(num, size);
|
||||
}
|
||||
|
||||
extern "C" void* realloc_hook(void* addr, size_t size) {
|
||||
void* ret = realloc(addr, size);
|
||||
void* ret = _orig_realloc(addr, size);
|
||||
if (MallocTracer::running() && ret) {
|
||||
if (addr && !MallocTracer::nofree()) {
|
||||
MallocTracer::recordFree(addr);
|
||||
@@ -55,14 +67,14 @@ extern "C" void* realloc_hook(void* addr, size_t size) {
|
||||
}
|
||||
|
||||
extern "C" void free_hook(void* addr) {
|
||||
free(addr);
|
||||
_orig_free(addr);
|
||||
if (MallocTracer::running() && !MallocTracer::nofree() && addr) {
|
||||
MallocTracer::recordFree(addr);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" int posix_memalign_hook(void** memptr, size_t alignment, size_t size) {
|
||||
int ret = posix_memalign(memptr, alignment, size);
|
||||
int ret = _orig_posix_memalign(memptr, alignment, size);
|
||||
if (MallocTracer::running() && ret == 0 && memptr && *memptr && size) {
|
||||
MallocTracer::recordMalloc(*memptr, size);
|
||||
}
|
||||
@@ -72,11 +84,11 @@ extern "C" int posix_memalign_hook(void** memptr, size_t alignment, size_t size)
|
||||
// Make sure this is not optimized away (function-scoped -fno-optimize-sibling-calls)
|
||||
extern "C" NO_OPTIMIZE
|
||||
int posix_memalign_hook_dummy(void** memptr, size_t alignment, size_t size) {
|
||||
return posix_memalign(memptr, alignment, size);
|
||||
return _orig_posix_memalign(memptr, alignment, size);
|
||||
}
|
||||
|
||||
extern "C" void* aligned_alloc_hook(size_t alignment, size_t size) {
|
||||
void* ret = aligned_alloc(alignment, size);
|
||||
void* ret = _orig_aligned_alloc(alignment, size);
|
||||
if (MallocTracer::running() && ret && size) {
|
||||
MallocTracer::recordMalloc(ret, size);
|
||||
}
|
||||
@@ -92,10 +104,37 @@ int MallocTracer::_patched_libs = 0;
|
||||
bool MallocTracer::_initialized = false;
|
||||
volatile bool MallocTracer::_running = false;
|
||||
|
||||
// Call each intercepted function at least once to ensure
|
||||
// its GOT entry is updated with a correct target address
|
||||
static void resolveMallocSymbols() {
|
||||
static volatile intptr_t sink;
|
||||
|
||||
void* p0 = malloc(1);
|
||||
void* p1 = realloc(p0, 2);
|
||||
void* p2 = calloc(1, 1);
|
||||
void* p3 = aligned_alloc(1, 1);
|
||||
void* p4 = NULL;
|
||||
if (posix_memalign(&p4, sizeof(void*), sizeof(void*)) == 0) free(p4);
|
||||
free(p3);
|
||||
free(p2);
|
||||
free(p1);
|
||||
|
||||
sink = (intptr_t)p1 + (intptr_t)p2 + (intptr_t)p3 + (intptr_t)p4;
|
||||
}
|
||||
|
||||
void MallocTracer::initialize() {
|
||||
CodeCache* lib = Profiler::instance()->findLibraryByAddress((void*)MallocTracer::initialize);
|
||||
assert(lib);
|
||||
|
||||
resolveMallocSymbols();
|
||||
|
||||
SAVE_IMPORT(malloc);
|
||||
SAVE_IMPORT(free);
|
||||
SAVE_IMPORT(calloc);
|
||||
SAVE_IMPORT(realloc);
|
||||
SAVE_IMPORT(posix_memalign);
|
||||
SAVE_IMPORT(aligned_alloc);
|
||||
|
||||
lib->mark(
|
||||
[](const char* s) -> bool {
|
||||
return strcmp(s, "malloc_hook") == 0
|
||||
@@ -121,8 +160,8 @@ void MallocTracer::patchLibraries() {
|
||||
while (_patched_libs < native_lib_count) {
|
||||
CodeCache* cc = (*native_libs)[_patched_libs++];
|
||||
|
||||
if (cc->contains((const void*)MallocTracer::initialize)) {
|
||||
// Let libasyncProfiler always use original allocation methods
|
||||
UnloadProtection handle(cc);
|
||||
if (!handle.isValid()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#define _MALLOCTRACER_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "engine.h"
|
||||
#include "event.h"
|
||||
#include "mutex.h"
|
||||
|
||||
3
src/os.h
3
src/os.h
@@ -103,6 +103,9 @@ class OS {
|
||||
static int createMemoryFile(const char* name);
|
||||
static void copyFile(int src_fd, int dst_fd, off_t offset, size_t size);
|
||||
static void freePageCache(int fd, off_t start_offset);
|
||||
static int mprotect(void* addr, size_t size, int prot);
|
||||
|
||||
static bool checkPreloaded();
|
||||
};
|
||||
|
||||
#endif // _OS_H
|
||||
|
||||
@@ -8,8 +8,10 @@
|
||||
#include <arpa/inet.h>
|
||||
#include <byteswap.h>
|
||||
#include <dirent.h>
|
||||
#include <dlfcn.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <link.h>
|
||||
#include <sched.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -351,10 +353,10 @@ u64 OS::getTotalCpuTime(u64* utime, u64* stime) {
|
||||
}
|
||||
|
||||
u64 real = (u64)-1;
|
||||
char buf[512];
|
||||
char buf[128] = {0};
|
||||
if (read(fd, buf, sizeof(buf)) >= 12) {
|
||||
u64 user, nice, system, idle;
|
||||
if (sscanf(buf + 4, "%llu %llu %llu %llu", &user, &nice, &system, &idle) == 4) {
|
||||
if (sscanf(buf + 4, "%llu %llu %llu %llu", &user, &nice, &system, &idle) == 4) {
|
||||
*utime = user + nice;
|
||||
*stime = system;
|
||||
real = user + nice + system + idle;
|
||||
@@ -384,4 +386,48 @@ void OS::freePageCache(int fd, off_t start_offset) {
|
||||
posix_fadvise(fd, start_offset & ~page_mask, 0, POSIX_FADV_DONTNEED);
|
||||
}
|
||||
|
||||
int OS::mprotect(void* addr, size_t size, int prot) {
|
||||
return ::mprotect(addr, size, prot);
|
||||
}
|
||||
|
||||
static int checkPreloadedCallback(dl_phdr_info* info, size_t size, void* data) {
|
||||
Dl_info* dl_info = (Dl_info*)data;
|
||||
|
||||
Dl_info libprofiler = dl_info[0];
|
||||
Dl_info libc = dl_info[1];
|
||||
|
||||
if ((void*)info->dlpi_addr == libprofiler.dli_fbase) {
|
||||
// async-profiler found first
|
||||
return 1;
|
||||
} else if ((void*)info->dlpi_addr == libc.dli_fbase) {
|
||||
// libc found first
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Checks if async-profiler is preloaded through the LD_PRELOAD mechanism.
|
||||
// This is done by analyzing the order of loaded dynamic libraries.
|
||||
bool OS::checkPreloaded() {
|
||||
if (getenv("LD_PRELOAD") == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find async-profiler shared object
|
||||
Dl_info libprofiler;
|
||||
if (dladdr((const void*)OS::checkPreloaded, &libprofiler) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find libc shared object
|
||||
Dl_info libc;
|
||||
if (dladdr((const void*)exit, &libc) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Dl_info info[2] = {libprofiler, libc};
|
||||
return dl_iterate_phdr(checkPreloadedCallback, (void*)info) == 1;
|
||||
}
|
||||
|
||||
#endif // __linux__
|
||||
|
||||
@@ -5,13 +5,17 @@
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <libkern/OSByteOrder.h>
|
||||
#include <libproc.h>
|
||||
#include <mach/mach.h>
|
||||
#include <mach/mach_host.h>
|
||||
#include <mach/mach_time.h>
|
||||
#include <mach/processor_info.h>
|
||||
#include <mach/vm_map.h>
|
||||
#include <mach-o/dyld.h>
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/time.h>
|
||||
@@ -361,4 +365,44 @@ void OS::freePageCache(int fd, off_t start_offset) {
|
||||
// Not supported on macOS
|
||||
}
|
||||
|
||||
int OS::mprotect(void* addr, size_t size, int prot) {
|
||||
if (prot & PROT_WRITE) prot |= VM_PROT_COPY;
|
||||
return vm_protect(mach_task_self(), (vm_address_t)addr, size, 0, prot);
|
||||
}
|
||||
|
||||
// Checks if async-profiler is preloaded through the DYLD_INSERT_LIBRARIES mechanism.
|
||||
// This is done by analyzing the order of loaded dynamic libraries.
|
||||
bool OS::checkPreloaded() {
|
||||
if (getenv("DYLD_INSERT_LIBRARIES") == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find async-profiler shared object
|
||||
Dl_info libprofiler;
|
||||
if (dladdr((const void*)OS::checkPreloaded, &libprofiler) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find libc shared object
|
||||
Dl_info libc;
|
||||
if (dladdr((const void*)exit, &libc) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t images = _dyld_image_count();
|
||||
for (uint32_t i = 0; i < images; i++) {
|
||||
void* image_base = (void*)_dyld_get_image_header(i);
|
||||
|
||||
if (image_base == libprofiler.dli_fbase) {
|
||||
// async-profiler found first
|
||||
return true;
|
||||
} else if (image_base == libc.dli_fbase) {
|
||||
// libc found first
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif // __APPLE__
|
||||
|
||||
77
src/otlp.h
Normal file
77
src/otlp.h
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef _OTLP_H
|
||||
#define _OTLP_H
|
||||
|
||||
#include "protobuf.h"
|
||||
|
||||
namespace Otlp {
|
||||
|
||||
const u32 OTLP_BUFFER_INITIAL_SIZE = 5120;
|
||||
|
||||
namespace ProfilesDictionary {
|
||||
const protobuf_index_t mapping_table = 1;
|
||||
const protobuf_index_t location_table = 2;
|
||||
const protobuf_index_t function_table = 3;
|
||||
const protobuf_index_t string_table = 5;
|
||||
}
|
||||
|
||||
namespace ProfilesData {
|
||||
const protobuf_index_t resource_profiles = 1;
|
||||
const protobuf_index_t dictionary = 2;
|
||||
}
|
||||
|
||||
namespace ResourceProfiles {
|
||||
const protobuf_index_t scope_profiles = 2;
|
||||
}
|
||||
|
||||
namespace ScopeProfiles {
|
||||
const protobuf_index_t profiles = 2;
|
||||
}
|
||||
|
||||
namespace Profile {
|
||||
const protobuf_index_t sample_type = 1;
|
||||
const protobuf_index_t sample = 2;
|
||||
const protobuf_index_t location_indices = 3;
|
||||
const protobuf_index_t period_type = 6;
|
||||
const protobuf_index_t period = 7;
|
||||
}
|
||||
|
||||
namespace ValueType {
|
||||
const protobuf_index_t type_strindex = 1;
|
||||
const protobuf_index_t unit_strindex = 2;
|
||||
const protobuf_index_t aggregation_temporality = 3;
|
||||
}
|
||||
|
||||
namespace Sample {
|
||||
const protobuf_index_t locations_start_index = 1;
|
||||
const protobuf_index_t locations_length = 2;
|
||||
const protobuf_index_t value = 3;
|
||||
}
|
||||
|
||||
namespace Location {
|
||||
const protobuf_index_t mapping_index = 1;
|
||||
const protobuf_index_t line = 3;
|
||||
}
|
||||
|
||||
namespace Function {
|
||||
const protobuf_index_t name_strindex = 1;
|
||||
const protobuf_index_t filename_strindex = 3;
|
||||
}
|
||||
|
||||
namespace Line {
|
||||
const protobuf_index_t function_index = 1;
|
||||
}
|
||||
|
||||
namespace AggregationTemporality {
|
||||
const u64 unspecified = 0;
|
||||
const u64 delta = 1;
|
||||
const u64 cumulative = 2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // _OTLP_H
|
||||
@@ -181,7 +181,7 @@ struct PerfEventType {
|
||||
static PerfEventType AVAILABLE_EVENTS[];
|
||||
static FunctionWithCounter KNOWN_FUNCTIONS[];
|
||||
|
||||
static char probe_func[256];
|
||||
static char probe_func[MAX_PROBE_LEN];
|
||||
|
||||
// Find which argument of a known function serves as a profiling counter,
|
||||
// e.g. the first argument of malloc() is allocation size
|
||||
@@ -367,6 +367,9 @@ struct PerfEventType {
|
||||
}
|
||||
|
||||
static PerfEventType* forName(const char* name) {
|
||||
// Reset probe_func, since it is used in FdTransferClient
|
||||
probe_func[0] = 0;
|
||||
|
||||
// "cpu" is an alias for "cpu-clock"
|
||||
if (strcmp(name, EVENT_CPU) == 0) {
|
||||
return &AVAILABLE_EVENTS[IDX_CPU];
|
||||
@@ -487,7 +490,7 @@ FunctionWithCounter PerfEventType::KNOWN_FUNCTIONS[] = {
|
||||
{NULL}
|
||||
};
|
||||
|
||||
char PerfEventType::probe_func[256];
|
||||
char PerfEventType::probe_func[MAX_PROBE_LEN];
|
||||
|
||||
|
||||
class RingBuffer {
|
||||
@@ -597,7 +600,7 @@ int PerfEvents::createForThread(int tid) {
|
||||
|
||||
int fd;
|
||||
if (FdTransferClient::hasPeer()) {
|
||||
fd = FdTransferClient::requestPerfFd(&tid, _target_cpu, &attr);
|
||||
fd = FdTransferClient::requestPerfFd(&tid, _target_cpu, &attr, PerfEventType::probe_func);
|
||||
} else {
|
||||
fd = syscall(__NR_perf_event_open, &attr, tid, _target_cpu, -1, PERF_FLAG_FD_CLOEXEC);
|
||||
if (fd == -1 && errno == EINVAL) {
|
||||
|
||||
159
src/profiler.cpp
159
src/profiler.cpp
@@ -11,6 +11,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/param.h>
|
||||
#include "index.h"
|
||||
#include "profiler.h"
|
||||
#include "perfEvents.h"
|
||||
#include "ctimer.h"
|
||||
@@ -29,6 +30,7 @@
|
||||
#include "fdtransferClient.h"
|
||||
#include "frameName.h"
|
||||
#include "os.h"
|
||||
#include "otlp.h"
|
||||
#include "safeAccess.h"
|
||||
#include "stackFrame.h"
|
||||
#include "stackWalker.h"
|
||||
@@ -44,6 +46,9 @@ Profiler* const Profiler::_instance = new Profiler();
|
||||
static SigAction orig_trapHandler = NULL;
|
||||
static SigAction orig_segvHandler = NULL;
|
||||
|
||||
static uintptr_t profiler_lib_start = 0;
|
||||
static uintptr_t profiler_lib_end = 0;
|
||||
|
||||
static Engine noop_engine;
|
||||
static PerfEvents perf_events;
|
||||
static AllocTracer alloc_tracer;
|
||||
@@ -117,19 +122,6 @@ static inline int makeFrame(ASGCT_CallFrame* frames, jint type, const char* id)
|
||||
}
|
||||
|
||||
|
||||
// Avoid syscall when possible
|
||||
static inline int fastThreadId() {
|
||||
VMThread* vm_thread;
|
||||
if (VMStructs::hasNativeThreadId() && (vm_thread = VMThread::current()) != NULL) {
|
||||
int thread_id = vm_thread->osThreadId();
|
||||
if (thread_id > 0) {
|
||||
return thread_id;
|
||||
}
|
||||
}
|
||||
return OS::threadId();
|
||||
}
|
||||
|
||||
|
||||
void Profiler::addJavaMethod(const void* address, int length, jmethodID method) {
|
||||
CodeHeap::updateBounds(address, (const char*)address + length);
|
||||
}
|
||||
@@ -615,7 +607,7 @@ void Profiler::fillFrameTypes(ASGCT_CallFrame* frames, int num_frames, NMethod*
|
||||
u64 Profiler::recordSample(void* ucontext, u64 counter, EventType event_type, Event* event) {
|
||||
atomicInc(_total_samples);
|
||||
|
||||
int tid = fastThreadId();
|
||||
int tid = OS::threadId();
|
||||
u32 lock_index = getLockIndex(tid);
|
||||
if (!_locks[lock_index].tryLock() &&
|
||||
!_locks[lock_index = (lock_index + 1) % CONCURRENCY_LEVEL].tryLock() &&
|
||||
@@ -658,8 +650,7 @@ u64 Profiler::recordSample(void* ucontext, u64 counter, EventType event_type, Ev
|
||||
|
||||
if (_cstack == CSTACK_VMX) {
|
||||
num_frames += StackWalker::walkVM(ucontext, frames + num_frames, _max_stack_depth, VM_EXPERT);
|
||||
} else if (event_type <= WALL_CLOCK_SAMPLE) {
|
||||
// Async events
|
||||
} else if (event_type <= MALLOC_SAMPLE) {
|
||||
if (_cstack == CSTACK_VM) {
|
||||
num_frames += StackWalker::walkVM(ucontext, frames + num_frames, _max_stack_depth, VM_NORMAL);
|
||||
} else {
|
||||
@@ -679,8 +670,6 @@ u64 Profiler::recordSample(void* ucontext, u64 counter, EventType event_type, Ev
|
||||
} else {
|
||||
num_frames += getJavaTraceAsync(ucontext, frames + num_frames, _max_stack_depth, &java_ctx);
|
||||
}
|
||||
} else if (event_type == MALLOC_SAMPLE) {
|
||||
num_frames += getJavaTraceAsync(ucontext, frames + num_frames, _max_stack_depth, &java_ctx);
|
||||
} else {
|
||||
// Lock events and instrumentation events can safely call synchronous JVM TI stack walker.
|
||||
// Skip Instrument.recordSample() method
|
||||
@@ -762,7 +751,7 @@ void Profiler::recordEventOnly(EventType event_type, Event* event) {
|
||||
return;
|
||||
}
|
||||
|
||||
int tid = fastThreadId();
|
||||
int tid = OS::threadId();
|
||||
u32 lock_index = getLockIndex(tid);
|
||||
if (!_locks[lock_index].tryLock() &&
|
||||
!_locks[lock_index = (lock_index + 1) % CONCURRENCY_LEVEL].tryLock() &&
|
||||
@@ -883,7 +872,9 @@ void Profiler::segvHandler(int signo, siginfo_t* siginfo, void* ucontext) {
|
||||
return;
|
||||
}
|
||||
|
||||
StackWalker::checkFault();
|
||||
if (pc >= profiler_lib_start && pc < profiler_lib_end) {
|
||||
StackWalker::checkFault();
|
||||
}
|
||||
|
||||
// Workaround for JDK-8313796. Setting cstack=dwarf also helps
|
||||
if (VMStructs::isInterpretedFrameValidFunc((const void*)pc) && frame.skipFaultInstruction()) {
|
||||
@@ -910,8 +901,15 @@ void Profiler::setupSignalHandlers() {
|
||||
orig_trapHandler = prev_handler;
|
||||
}
|
||||
|
||||
// HotSpot tolerates interposed SIGSEGV/SIGBUS handler; other JVMs don't
|
||||
if (!VM::isOpenJ9() && !VM::isZing()) {
|
||||
// HotSpot tolerates interposed SIGSEGV/SIGBUS handler; other JVMs probably not
|
||||
CodeCache* profiler_lib = instance()->findLibraryByAddress((void*)setupSignalHandlers);
|
||||
if (profiler_lib != NULL) {
|
||||
// Record boundaries of our own library for the signal handler to check
|
||||
// if a crash has happened in the profiler code
|
||||
profiler_lib_start = (uintptr_t)profiler_lib->minAddress();
|
||||
profiler_lib_end = (uintptr_t)profiler_lib->maxAddress();
|
||||
}
|
||||
orig_segvHandler = OS::replaceCrashHandler(segvHandler);
|
||||
}
|
||||
|
||||
@@ -976,24 +974,24 @@ void Profiler::updateNativeThreadNames() {
|
||||
}
|
||||
|
||||
bool Profiler::excludeTrace(FrameName* fn, CallTrace* trace) {
|
||||
bool checkInclude = fn->hasIncludeList();
|
||||
bool checkExclude = fn->hasExcludeList();
|
||||
if (!(checkInclude || checkExclude)) {
|
||||
bool check_include = fn->hasIncludeList();
|
||||
bool check_exclude = fn->hasExcludeList();
|
||||
if (!(check_include || check_exclude)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < trace->num_frames; i++) {
|
||||
const char* frame_name = fn->name(trace->frames[i], true);
|
||||
if (checkExclude && fn->exclude(frame_name)) {
|
||||
if (check_exclude && fn->exclude(frame_name)) {
|
||||
return true;
|
||||
}
|
||||
if (checkInclude && fn->include(frame_name)) {
|
||||
checkInclude = false;
|
||||
if (!checkExclude) break;
|
||||
if (check_include && fn->include(frame_name)) {
|
||||
check_include = false;
|
||||
if (!check_exclude) break;
|
||||
}
|
||||
}
|
||||
|
||||
return checkInclude;
|
||||
return check_include;
|
||||
}
|
||||
|
||||
Engine* Profiler::selectEngine(const char* event_name) {
|
||||
@@ -1181,7 +1179,7 @@ Error Profiler::start(Arguments& args, bool reset) {
|
||||
return Error("DWARF unwinding is not supported on this platform");
|
||||
} else if (_cstack == CSTACK_LBR && _engine != &perf_events) {
|
||||
return Error("Branch stack is supported only with PMU events");
|
||||
} else if (_cstack >= CSTACK_VM && !VMStructs::hasStackStructs()) {
|
||||
} else if (_cstack >= CSTACK_VM && VM::loaded() && !VMStructs::hasStackStructs()) {
|
||||
return Error("VMStructs stack walking is not supported on this JVM/platform");
|
||||
}
|
||||
|
||||
@@ -1347,7 +1345,7 @@ Error Profiler::check(Arguments& args) {
|
||||
return Error("DWARF unwinding is not supported on this platform");
|
||||
} else if (args._cstack == CSTACK_LBR && _engine != &perf_events) {
|
||||
return Error("Branch stack is supported only with PMU events");
|
||||
} else if (args._cstack >= CSTACK_VM && !VMStructs::hasStackStructs()) {
|
||||
} else if (args._cstack >= CSTACK_VM && VM::loaded() && !VMStructs::hasStackStructs()) {
|
||||
return Error("VMStructs stack walking is not supported on this JVM/platform");
|
||||
}
|
||||
}
|
||||
@@ -1402,6 +1400,9 @@ Error Profiler::dump(Writer& out, Arguments& args) {
|
||||
unlockAll();
|
||||
}
|
||||
break;
|
||||
case OUTPUT_OTLP:
|
||||
dumpOtlp(out, args);
|
||||
break;
|
||||
default:
|
||||
return Error("No output format selected");
|
||||
}
|
||||
@@ -1647,6 +1648,102 @@ void Profiler::dumpText(Writer& out, Arguments& args) {
|
||||
}
|
||||
}
|
||||
|
||||
static void recordSampleType(ProtoBuffer& otlp_buffer, Index& strings, const char* type, const char* units) {
|
||||
using namespace Otlp;
|
||||
protobuf_mark_t sample_type_mark = otlp_buffer.startMessage(Profile::sample_type, 1);
|
||||
otlp_buffer.field(ValueType::type_strindex, strings.indexOf(type));
|
||||
otlp_buffer.field(ValueType::unit_strindex, strings.indexOf(units));
|
||||
otlp_buffer.field(ValueType::aggregation_temporality, AggregationTemporality::cumulative);
|
||||
otlp_buffer.commitMessage(sample_type_mark);
|
||||
}
|
||||
|
||||
void Profiler::dumpOtlp(Writer& out, Arguments& args) {
|
||||
using namespace Otlp;
|
||||
ProtoBuffer otlp_buffer(OTLP_BUFFER_INITIAL_SIZE);
|
||||
Index strings;
|
||||
Index functions;
|
||||
|
||||
protobuf_mark_t resource_profiles_mark = otlp_buffer.startMessage(ProfilesData::resource_profiles);
|
||||
protobuf_mark_t scope_profiles_mark = otlp_buffer.startMessage(ResourceProfiles::scope_profiles);
|
||||
protobuf_mark_t profile_mark = otlp_buffer.startMessage(ScopeProfiles::profiles);
|
||||
|
||||
recordSampleType(otlp_buffer, strings, _engine->type(), "count");
|
||||
recordSampleType(otlp_buffer, strings, _engine->type(), _engine->units());
|
||||
|
||||
std::vector<CallTraceSample*> call_trace_samples;
|
||||
_call_trace_storage.collectSamples(call_trace_samples);
|
||||
|
||||
std::vector<size_t> location_indices;
|
||||
location_indices.reserve(call_trace_samples.size());
|
||||
|
||||
FrameName fn(args, args._style & ~STYLE_ANNOTATE, _epoch, _thread_names_lock, _thread_names);
|
||||
size_t frames_seen = 0;
|
||||
for (const auto& cts : call_trace_samples) {
|
||||
CallTrace* trace = cts->acquireTrace();
|
||||
if (trace == NULL || excludeTrace(&fn, trace) || cts->samples == 0) continue;
|
||||
|
||||
protobuf_mark_t sample_mark = otlp_buffer.startMessage(Profile::sample, 1);
|
||||
otlp_buffer.field(Sample::locations_start_index, frames_seen);
|
||||
otlp_buffer.field(Sample::locations_length, trace->num_frames);
|
||||
protobuf_mark_t sample_value_mark = otlp_buffer.startMessage(Sample::value, 1);
|
||||
otlp_buffer.putVarInt(cts->samples);
|
||||
otlp_buffer.putVarInt(cts->counter);
|
||||
otlp_buffer.commitMessage(sample_value_mark);
|
||||
otlp_buffer.commitMessage(sample_mark);
|
||||
|
||||
for (int j = 0; j < trace->num_frames; j++) {
|
||||
// To be written below in Profile.location_indices
|
||||
location_indices.push_back(functions.indexOf(fn.name(trace->frames[j])));
|
||||
}
|
||||
frames_seen += trace->num_frames;
|
||||
}
|
||||
|
||||
protobuf_mark_t location_indices_mark = otlp_buffer.startMessage(Profile::location_indices);
|
||||
for (size_t i : location_indices) {
|
||||
otlp_buffer.putVarInt(i);
|
||||
}
|
||||
otlp_buffer.commitMessage(location_indices_mark);
|
||||
|
||||
otlp_buffer.commitMessage(profile_mark);
|
||||
otlp_buffer.commitMessage(scope_profiles_mark);
|
||||
otlp_buffer.commitMessage(resource_profiles_mark);
|
||||
|
||||
protobuf_mark_t dictionary_mark = otlp_buffer.startMessage(ProfilesData::dictionary);
|
||||
|
||||
// Write mapping_table. Not currently used, but required by some parsers
|
||||
protobuf_mark_t mapping_mark = otlp_buffer.startMessage(ProfilesDictionary::mapping_table, 1);
|
||||
otlp_buffer.commitMessage(mapping_mark);
|
||||
|
||||
// Write function_table
|
||||
functions.forEachOrdered([&] (const std::string& function_name) {
|
||||
protobuf_mark_t function_mark = otlp_buffer.startMessage(ProfilesDictionary::function_table, 1);
|
||||
otlp_buffer.field(Function::name_strindex, strings.indexOf(function_name));
|
||||
otlp_buffer.commitMessage(function_mark);
|
||||
});
|
||||
|
||||
// Write location_table
|
||||
for (size_t function_idx = 0; function_idx < functions.size(); ++function_idx) {
|
||||
protobuf_mark_t location_mark = otlp_buffer.startMessage(ProfilesDictionary::location_table, 1);
|
||||
// TODO: set to the proper mapping when new mappings are added.
|
||||
// For now we keep a dummy default mapping_index for all locations because some parsers
|
||||
// would fail otherwise
|
||||
otlp_buffer.field(Location::mapping_index, (u64)0);
|
||||
protobuf_mark_t line_mark = otlp_buffer.startMessage(Location::line, 1);
|
||||
otlp_buffer.field(Line::function_index, function_idx);
|
||||
otlp_buffer.commitMessage(line_mark);
|
||||
otlp_buffer.commitMessage(location_mark);
|
||||
}
|
||||
|
||||
// Write string_table
|
||||
strings.forEachOrdered([&] (const std::string& s) {
|
||||
otlp_buffer.field(ProfilesDictionary::string_table, s.data(), s.length());
|
||||
});
|
||||
|
||||
otlp_buffer.commitMessage(dictionary_mark);
|
||||
|
||||
out.write((const char*) otlp_buffer.data(), otlp_buffer.offset());
|
||||
}
|
||||
|
||||
time_t Profiler::addTimeout(time_t start, int timeout) {
|
||||
if (timeout == 0) {
|
||||
return (time_t)0x7fffffff;
|
||||
|
||||
@@ -152,6 +152,7 @@ class Profiler {
|
||||
void dumpCollapsed(Writer& out, Arguments& args);
|
||||
void dumpFlameGraph(Writer& out, Arguments& args, bool tree);
|
||||
void dumpText(Writer& out, Arguments& args);
|
||||
void dumpOtlp(Writer& out, Arguments& args);
|
||||
|
||||
static Profiler* const _instance;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <sys/param.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "assert.h"
|
||||
#include "protobuf.h"
|
||||
|
||||
ProtoBuffer::ProtoBuffer(size_t initial_capacity) : _offset(0) {
|
||||
@@ -35,27 +36,18 @@ void ProtoBuffer::ensureCapacity(size_t new_data_size) {
|
||||
}
|
||||
|
||||
void ProtoBuffer::putVarInt(u64 n) {
|
||||
_offset = putVarInt(_offset, n);
|
||||
}
|
||||
|
||||
size_t ProtoBuffer::putVarInt(size_t offset, u64 n) {
|
||||
ensureCapacity(varIntSize(n));
|
||||
while ((n >> 7) != 0) {
|
||||
_data[offset++] = (unsigned char) (0x80 | (n & 0x7f));
|
||||
_data[_offset++] = (unsigned char) (0x80 | n);
|
||||
n >>= 7;
|
||||
}
|
||||
_data[offset++] = (unsigned char) n;
|
||||
return offset;
|
||||
_data[_offset++] = (unsigned char) n;
|
||||
}
|
||||
|
||||
void ProtoBuffer::tag(protobuf_index_t index, protobuf_t type) {
|
||||
putVarInt((u64) (index << 3 | type));
|
||||
}
|
||||
|
||||
void ProtoBuffer::field(protobuf_index_t index, bool b) {
|
||||
field(index, (u64) b);
|
||||
}
|
||||
|
||||
void ProtoBuffer::field(protobuf_index_t index, u64 n) {
|
||||
tag(index, VARINT);
|
||||
putVarInt(n);
|
||||
@@ -78,20 +70,26 @@ void ProtoBuffer::field(protobuf_index_t index, const unsigned char* s, size_t l
|
||||
_offset += len;
|
||||
}
|
||||
|
||||
protobuf_mark_t ProtoBuffer::startMessage(protobuf_index_t index) {
|
||||
protobuf_mark_t ProtoBuffer::startMessage(protobuf_index_t index, size_t max_len_byte_count) {
|
||||
tag(index, LEN);
|
||||
|
||||
ensureCapacity(NESTED_FIELD_BYTE_COUNT);
|
||||
_offset += NESTED_FIELD_BYTE_COUNT;
|
||||
return _offset;
|
||||
ensureCapacity(max_len_byte_count);
|
||||
|
||||
protobuf_mark_t mark = _offset << 3 | max_len_byte_count;
|
||||
_offset += max_len_byte_count;
|
||||
return mark;
|
||||
}
|
||||
|
||||
void ProtoBuffer::commitMessage(protobuf_mark_t mark) {
|
||||
size_t message_length = _offset - mark;
|
||||
for (size_t i = 0; i < NESTED_FIELD_BYTE_COUNT - 1; ++i) {
|
||||
size_t idx = mark - NESTED_FIELD_BYTE_COUNT + i;
|
||||
_data[idx] = (unsigned char) (0x80 | (message_length & 0x7f));
|
||||
message_length >>= 7;
|
||||
size_t max_len_byte_count = mark & 7;
|
||||
size_t message_start = mark >> 3;
|
||||
|
||||
size_t actual_len = _offset - (message_start + max_len_byte_count);
|
||||
assert(varIntSize(actual_len) <= max_len_byte_count);
|
||||
|
||||
for (size_t i = 0; i < max_len_byte_count - 1; i++) {
|
||||
_data[message_start + i] = (unsigned char) (0x80 | actual_len);
|
||||
actual_len >>= 7;
|
||||
}
|
||||
_data[mark - 1] = (unsigned char) message_length;
|
||||
_data[message_start + max_len_byte_count - 1] = (unsigned char) actual_len;
|
||||
}
|
||||
|
||||
@@ -14,10 +14,11 @@ protobuf_t VARINT = 0;
|
||||
protobuf_t LEN = 2;
|
||||
|
||||
typedef u32 protobuf_index_t;
|
||||
typedef u32 protobuf_mark_t;
|
||||
// 3 bits for the maximum byte count for the message size
|
||||
// 61 bits for the message start
|
||||
typedef u64 protobuf_mark_t;
|
||||
|
||||
// We assume the length of a nested field can be represented with 3 varint bytes.
|
||||
const size_t NESTED_FIELD_BYTE_COUNT = 3;
|
||||
const size_t NESTED_FIELD_BYTE_COUNT = 5;
|
||||
const size_t MINIMUM_INITIAL_SIZE = 16;
|
||||
|
||||
class ProtoBuffer {
|
||||
@@ -26,9 +27,6 @@ class ProtoBuffer {
|
||||
size_t _capacity;
|
||||
size_t _offset;
|
||||
|
||||
void putVarInt(u64 n);
|
||||
size_t putVarInt(size_t offset, u64 n);
|
||||
|
||||
void tag(protobuf_index_t index, protobuf_t type);
|
||||
|
||||
void ensureCapacity(size_t new_data_size);
|
||||
@@ -41,18 +39,18 @@ class ProtoBuffer {
|
||||
|
||||
size_t offset() const { return _offset; }
|
||||
size_t capacity() const { return _capacity; }
|
||||
void reset() { _offset = 0; }
|
||||
|
||||
// VARINT
|
||||
void field(protobuf_index_t index, bool b);
|
||||
void field(protobuf_index_t index, u64 n);
|
||||
// LEN
|
||||
void field(protobuf_index_t index, const char* s);
|
||||
void field(protobuf_index_t index, const char* s, size_t len);
|
||||
void field(protobuf_index_t index, const unsigned char* s, size_t len);
|
||||
|
||||
protobuf_mark_t startMessage(protobuf_index_t index);
|
||||
protobuf_mark_t startMessage(protobuf_index_t index, size_t max_len_byte_count = NESTED_FIELD_BYTE_COUNT);
|
||||
void commitMessage(protobuf_mark_t mark);
|
||||
|
||||
void putVarInt(u64 n);
|
||||
static size_t varIntSize(u64 value);
|
||||
};
|
||||
|
||||
|
||||
@@ -52,11 +52,14 @@ class SafeAccess {
|
||||
}
|
||||
|
||||
static uintptr_t skipLoadArg(uintptr_t pc) {
|
||||
#if defined(__aarch64__)
|
||||
if ((pc - (uintptr_t)load32) < 16 || (pc - (uintptr_t)loadPtr) < 16) {
|
||||
return 4;
|
||||
}
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
if (*(u8*)pc == 0x8b) return 2; // mov eax, [reg]
|
||||
if (*(u16*)pc == 0x8b48) return 3; // mov rax, [reg]
|
||||
#else
|
||||
return sizeof(instruction_t);
|
||||
#endif
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -72,17 +72,104 @@ void StackFrame::ret() {
|
||||
pc() = link();
|
||||
}
|
||||
|
||||
static inline bool isSTP(instruction_t insn) {
|
||||
// stp xn, xm, [sp, #-imm]!
|
||||
// stp dn, dm, [sp, #-imm]!
|
||||
return (insn & 0xffe003e0) == 0xa9a003e0 || (insn & 0xffe003e0) == 0x6da003e0;
|
||||
}
|
||||
|
||||
// Check if this is a well-known leaf stub with a constant size frame
|
||||
static inline bool isFixedSizeFrame(const char* name) {
|
||||
// Dispatch by the first character to optimize lookup
|
||||
switch (name[0]) {
|
||||
case 'i':
|
||||
return strncmp(name, "indexof_linear_", 15) == 0;
|
||||
case 'm':
|
||||
return strncmp(name, "md5_implCompress", 16) == 0;
|
||||
case 's':
|
||||
return strncmp(name, "sha256_implCompress", 19) == 0
|
||||
|| strncmp(name, "string_indexof_linear_", 22) == 0
|
||||
|| strncmp(name, "slow_subtype_check", 18) == 0;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if this is a well-known leaf stub that does not change stack pointer
|
||||
static inline bool isZeroSizeFrame(const char* name) {
|
||||
// Dispatch by the first character to optimize lookup
|
||||
switch (name[0]) {
|
||||
case 'I':
|
||||
return strcmp(name, "InlineCacheBuffer") == 0;
|
||||
case 'S':
|
||||
return strncmp(name, "SafeFetch", 9) == 0;
|
||||
case 'a':
|
||||
return strncmp(name, "atomic", 6) == 0;
|
||||
case 'b':
|
||||
return strncmp(name, "bigInteger", 10) == 0
|
||||
|| strcmp(name, "base64_encodeBlock") == 0;
|
||||
case 'c':
|
||||
return strncmp(name, "copy_", 5) == 0
|
||||
|| strncmp(name, "compare_long_string_", 20) == 0;
|
||||
case 'e':
|
||||
return strcmp(name, "encodeBlock") == 0;
|
||||
case 'f':
|
||||
return strcmp(name, "f2hf") == 0;
|
||||
case 'g':
|
||||
return strcmp(name, "ghash_processBlocks") == 0;
|
||||
case 'h':
|
||||
return strcmp(name, "hf2f") == 0;
|
||||
case 'i':
|
||||
return strncmp(name, "itable", 6) == 0;
|
||||
case 'l':
|
||||
return strcmp(name, "large_byte_array_inflate") == 0
|
||||
|| strncmp(name, "lookup_secondary_supers_", 24) == 0;
|
||||
case 'm':
|
||||
return strncmp(name, "md5_implCompress", 16) == 0;
|
||||
case 's':
|
||||
return strncmp(name, "sha1_implCompress", 17) == 0
|
||||
|| strncmp(name, "compare_long_string_same_encoding", 33) == 0
|
||||
|| strcmp(name, "compare_long_string_LL") == 0
|
||||
|| strcmp(name, "compare_long_string_UU") == 0;
|
||||
case 'u':
|
||||
return strcmp(name, "updateBytesAdler32") == 0;
|
||||
case 'v':
|
||||
return strncmp(name, "vtable", 6) == 0;
|
||||
case 'z':
|
||||
return strncmp(name, "zero_", 5) == 0;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool StackFrame::unwindStub(instruction_t* entry, const char* name, uintptr_t& pc, uintptr_t& sp, uintptr_t& fp) {
|
||||
instruction_t* ip = (instruction_t*)pc;
|
||||
if (ip == entry || *ip == 0xd65f03c0
|
||||
|| strncmp(name, "itable", 6) == 0
|
||||
|| strncmp(name, "vtable", 6) == 0
|
||||
|| strncmp(name, "compare_long_string_", 20) == 0
|
||||
|| strcmp(name, "zero_blocks") == 0
|
||||
|| strcmp(name, "atomic entry points") == 0
|
||||
|| strcmp(name, "InlineCacheBuffer") == 0)
|
||||
{
|
||||
if (ip == entry || *ip == 0xd65f03c0) {
|
||||
pc = link();
|
||||
return true;
|
||||
} else if (entry != NULL && entry[0] == 0xa9bf7bfd) {
|
||||
// The stub begins with
|
||||
// stp x29, x30, [sp, #-16]!
|
||||
// mov x29, sp
|
||||
if (ip == entry + 1) {
|
||||
sp += 16;
|
||||
pc = ((uintptr_t*)sp)[-1];
|
||||
return true;
|
||||
} else if (entry[1] == 0x910003fd && withinCurrentStack(fp)) {
|
||||
sp = fp + 16;
|
||||
fp = ((uintptr_t*)sp)[-2];
|
||||
pc = ((uintptr_t*)sp)[-1];
|
||||
return true;
|
||||
}
|
||||
} else if (entry != NULL && isSTP(entry[0]) && isFixedSizeFrame(name)) {
|
||||
// The stub begins with
|
||||
// stp xn, xm, [sp, #-imm]!
|
||||
int offset = int(entry[0] << 10) >> 25;
|
||||
sp = (intptr_t)sp - offset * 8;
|
||||
pc = link();
|
||||
return true;
|
||||
} else if (isZeroSizeFrame(name)) {
|
||||
// Should be done after isSTP check, since frame size may vary between JVM versions
|
||||
pc = link();
|
||||
return true;
|
||||
} else if (strcmp(name, "forward_copy_longs") == 0
|
||||
@@ -100,27 +187,6 @@ bool StackFrame::unwindStub(instruction_t* entry, const char* name, uintptr_t& p
|
||||
pc = link();
|
||||
}
|
||||
return true;
|
||||
} else if (entry != NULL && entry[0] == 0xa9bf7bfd) {
|
||||
// The stub begins with
|
||||
// stp x29, x30, [sp, #-16]!
|
||||
// mov x29, sp
|
||||
if (ip == entry + 1) {
|
||||
sp += 16;
|
||||
pc = ((uintptr_t*)sp)[-1];
|
||||
return true;
|
||||
} else if (entry[1] == 0x910003fd && withinCurrentStack(fp)) {
|
||||
sp = fp + 16;
|
||||
fp = ((uintptr_t*)sp)[-2];
|
||||
pc = ((uintptr_t*)sp)[-1];
|
||||
return true;
|
||||
}
|
||||
} else if (strncmp(name, "indexof_linear_", 15) == 0 &&
|
||||
entry != NULL && entry[0] == 0xa9be57f4 && entry[1] == 0xa9015ff6) {
|
||||
// JDK-8189103: String.indexOf intrinsic.
|
||||
// Entry and exit are covered by the very first 'if', in all other cases SP is 4 words off.
|
||||
sp += 32;
|
||||
pc = link();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ const intptr_t MAX_FRAME_SIZE = 0x40000;
|
||||
const intptr_t MAX_INTERPRETER_FRAME_SIZE = 0x1000;
|
||||
const intptr_t DEAD_ZONE = 0x1000;
|
||||
|
||||
static ucontext_t empty_ucontext{};
|
||||
|
||||
|
||||
static inline bool aligned(uintptr_t ptr) {
|
||||
return (ptr & (sizeof(uintptr_t) - 1)) == 0;
|
||||
@@ -53,10 +55,7 @@ static inline void fillFrame(ASGCT_CallFrame& frame, FrameTypeId type, int bci,
|
||||
|
||||
static jmethodID getMethodId(VMMethod* method) {
|
||||
if (!inDeadZone(method) && aligned((uintptr_t)method)) {
|
||||
jmethodID method_id = method->id();
|
||||
if (!inDeadZone(method_id) && aligned((uintptr_t)method_id) && VMMethod::fromMethodID(method_id) == method) {
|
||||
return method_id;
|
||||
}
|
||||
return method->validatedId();
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
@@ -169,6 +168,7 @@ int StackWalker::walkDwarf(void* ucontext, const void** callchain, int max_depth
|
||||
break;
|
||||
}
|
||||
|
||||
const void* prev_pc = pc;
|
||||
if (f->fp_off & DW_PC_OFFSET) {
|
||||
pc = (const char*)pc + (f->fp_off >> 1);
|
||||
} else {
|
||||
@@ -193,7 +193,7 @@ int StackWalker::walkDwarf(void* ucontext, const void** callchain, int max_depth
|
||||
}
|
||||
}
|
||||
|
||||
if (inDeadZone(pc)) {
|
||||
if (inDeadZone(pc) || (pc == prev_pc && sp == prev_sp)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -203,7 +203,7 @@ int StackWalker::walkDwarf(void* ucontext, const void** callchain, int max_depth
|
||||
|
||||
int StackWalker::walkVM(void* ucontext, ASGCT_CallFrame* frames, int max_depth, StackDetail detail) {
|
||||
if (ucontext == NULL) {
|
||||
return walkVM(ucontext, frames, max_depth, detail,
|
||||
return walkVM(&empty_ucontext, frames, max_depth, detail,
|
||||
callerPC(), (uintptr_t)callerSP(), (uintptr_t)callerFP());
|
||||
} else {
|
||||
StackFrame frame(ucontext);
|
||||
@@ -411,6 +411,7 @@ int StackWalker::walkVM(void* ucontext, ASGCT_CallFrame* frames, int max_depth,
|
||||
break;
|
||||
}
|
||||
|
||||
const void* prev_pc = pc;
|
||||
if (f->fp_off & DW_PC_OFFSET) {
|
||||
pc = (const char*)pc + (f->fp_off >> 1);
|
||||
} else {
|
||||
@@ -435,7 +436,7 @@ int StackWalker::walkVM(void* ucontext, ASGCT_CallFrame* frames, int max_depth,
|
||||
}
|
||||
}
|
||||
|
||||
if (inDeadZone(pc)) {
|
||||
if (inDeadZone(pc) || (pc == prev_pc && sp == prev_sp)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,4 +25,18 @@ class Symbols {
|
||||
}
|
||||
};
|
||||
|
||||
class UnloadProtection {
|
||||
private:
|
||||
void* _lib_handle;
|
||||
bool _valid;
|
||||
|
||||
public:
|
||||
UnloadProtection(const CodeCache *cc);
|
||||
~UnloadProtection();
|
||||
|
||||
UnloadProtection& operator=(const UnloadProtection& other) = delete;
|
||||
|
||||
bool isValid() const { return _valid; }
|
||||
};
|
||||
|
||||
#endif // _SYMBOLS_H
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <stdio.h>
|
||||
@@ -13,7 +14,6 @@
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mman.h>
|
||||
#include <dlfcn.h>
|
||||
#include <elf.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
@@ -52,8 +52,11 @@ static void applyPatch(CodeCache* cc) {
|
||||
if (patch_libnet) {
|
||||
size_t len = strlen(cc->name());
|
||||
if (len >= 10 && strcmp(cc->name() + len - 10, "/libnet.so") == 0) {
|
||||
cc->patchImport(im_poll, (void*)poll_hook);
|
||||
patch_libnet = false;
|
||||
UnloadProtection handle(cc);
|
||||
if (handle.isValid()) {
|
||||
cc->patchImport(im_poll, (void*)poll_hook);
|
||||
patch_libnet = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,6 +67,25 @@ static void applyPatch(CodeCache* cc) {}
|
||||
|
||||
#endif
|
||||
|
||||
static const void* getMainPhdr() {
|
||||
void* main_phdr = NULL;
|
||||
dl_iterate_phdr([](struct dl_phdr_info* info, size_t size, void* data) {
|
||||
*(const void**)data = info->dlpi_phdr;
|
||||
return 1;
|
||||
}, &main_phdr);
|
||||
return main_phdr;
|
||||
}
|
||||
|
||||
static const void* _main_phdr = getMainPhdr();
|
||||
static const char* _ld_base = (const char*)getauxval(AT_BASE);
|
||||
|
||||
static bool isMainExecutable(const char* image_base, const void* map_end) {
|
||||
return _main_phdr != NULL && _main_phdr >= image_base && _main_phdr < map_end;
|
||||
}
|
||||
|
||||
static bool isLoader(const char* image_base) {
|
||||
return _ld_base == image_base;
|
||||
}
|
||||
|
||||
class SymbolDesc {
|
||||
private:
|
||||
@@ -654,6 +676,7 @@ Mutex Symbols::_parse_lock;
|
||||
bool Symbols::_have_kernel_symbols = false;
|
||||
bool Symbols::_libs_limit_reported = false;
|
||||
static std::unordered_set<u64> _parsed_inodes;
|
||||
static bool _in_parse_libraries = false;
|
||||
|
||||
void Symbols::parseKernelSymbols(CodeCache* cc) {
|
||||
int fd;
|
||||
@@ -757,9 +780,10 @@ static void collectSharedLibraries(std::unordered_map<u64, SharedLibrary>& libs,
|
||||
void Symbols::parseLibraries(CodeCacheArray* array, bool kernel_symbols) {
|
||||
MutexLocker ml(_parse_lock);
|
||||
|
||||
if (array->count() >= MAX_NATIVE_LIBS) {
|
||||
if (_in_parse_libraries || array->count() >= MAX_NATIVE_LIBS) {
|
||||
return;
|
||||
}
|
||||
_in_parse_libraries = true;
|
||||
|
||||
if (kernel_symbols && !haveKernelSymbols()) {
|
||||
CodeCache* cc = new CodeCache("[kernel]");
|
||||
@@ -776,29 +800,12 @@ void Symbols::parseLibraries(CodeCacheArray* array, bool kernel_symbols) {
|
||||
std::unordered_map<u64, SharedLibrary> libs;
|
||||
collectSharedLibraries(libs, MAX_NATIVE_LIBS - array->count());
|
||||
|
||||
const char* ld_base = (const char*)getauxval(AT_BASE);
|
||||
if (!ld_base) {
|
||||
Log::warn("Cannot determine base address of the loader");
|
||||
}
|
||||
|
||||
const void* main_phdr = NULL;
|
||||
dl_iterate_phdr([](struct dl_phdr_info* info, size_t size, void* data) {
|
||||
*(const void**)data = info->dlpi_phdr;
|
||||
return 1;
|
||||
}, &main_phdr);
|
||||
|
||||
for (auto& it : libs) {
|
||||
u64 inode = it.first;
|
||||
_parsed_inodes.insert(inode);
|
||||
|
||||
SharedLibrary& lib = it.second;
|
||||
CodeCache* cc = new CodeCache(lib.file, array->count(), false, lib.map_start, lib.map_end);
|
||||
|
||||
// Strip " (deleted)" suffix so that removed library can be reopened
|
||||
size_t len = strlen(lib.file);
|
||||
if (len > 10 && strcmp(lib.file + len - 10, " (deleted)") == 0) {
|
||||
lib.file[len - 10] = 0;
|
||||
}
|
||||
CodeCache* cc = new CodeCache(lib.file, array->count(), lib.map_start, lib.map_end, lib.image_base);
|
||||
|
||||
if (strchr(lib.file, ':') != NULL) {
|
||||
// Do not try to parse pseudofiles like anon_inode:name, /memfd:name
|
||||
@@ -812,24 +819,10 @@ void Symbols::parseLibraries(CodeCacheArray* array, bool kernel_symbols) {
|
||||
// Parse debug symbols first
|
||||
ElfParser::parseFile(cc, lib.image_base, lib.file, true);
|
||||
|
||||
dlerror(); // reset any error from previous dl function calls
|
||||
|
||||
// Protect library from unloading while parsing in-memory ELF program headers.
|
||||
// Also, dlopen() ensures the library is fully loaded.
|
||||
// Main executable and ld-linux interpreter cannot be dlopen'ed, but dlerror() returns NULL for them on some systems.
|
||||
void* handle = dlopen(lib.file, RTLD_LAZY | RTLD_NOLOAD);
|
||||
|
||||
// Parse main executable and the loader (ld.so) regardless of dlopen result, since they cannot be unloaded.
|
||||
bool is_main_exe = main_phdr >= lib.image_base && main_phdr < lib.map_end;
|
||||
bool is_loader = ld_base == lib.image_base;
|
||||
|
||||
if (handle != NULL || is_main_exe || is_loader) {
|
||||
UnloadProtection handle(cc);
|
||||
if (handle.isValid()) {
|
||||
ElfParser::parseProgramHeaders(cc, lib.image_base, lib.map_end, OS::isMusl());
|
||||
}
|
||||
|
||||
if (handle != NULL) {
|
||||
dlclose(handle);
|
||||
}
|
||||
}
|
||||
|
||||
free(lib.file);
|
||||
@@ -843,6 +836,48 @@ void Symbols::parseLibraries(CodeCacheArray* array, bool kernel_symbols) {
|
||||
Log::warn("Number of parsed libraries reached the limit of %d", MAX_NATIVE_LIBS);
|
||||
_libs_limit_reported = true;
|
||||
}
|
||||
|
||||
_in_parse_libraries = false;
|
||||
}
|
||||
|
||||
// Check that the base address of the shared object has not changed
|
||||
static bool verifyBaseAddress(const CodeCache* cc, void* lib_handle) {
|
||||
Dl_info dl_info;
|
||||
struct link_map* map;
|
||||
|
||||
if (dlinfo(lib_handle, RTLD_DI_LINKMAP, &map) != 0 || dladdr(map->l_ld, &dl_info) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return cc->imageBase() == (const char*)dl_info.dli_fbase;
|
||||
}
|
||||
|
||||
UnloadProtection::UnloadProtection(const CodeCache *cc) {
|
||||
if (OS::isMusl() || isMainExecutable(cc->imageBase(), cc->maxAddress()) || isLoader(cc->imageBase())) {
|
||||
_lib_handle = NULL;
|
||||
_valid = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// dlopen() can reopen previously loaded libraries even if the underlying file has been deleted
|
||||
const char* stripped_name = cc->name();
|
||||
size_t name_len = strlen(stripped_name);
|
||||
if (name_len > 10 && strcmp(stripped_name + name_len - 10, " (deleted)") == 0) {
|
||||
char* buf = (char*) alloca(name_len - 9);
|
||||
*stpncpy(buf, stripped_name, name_len - 10) = 0;
|
||||
stripped_name = buf;
|
||||
}
|
||||
|
||||
// Protect library from unloading while parsing in-memory ELF program headers.
|
||||
// Also, dlopen() ensures the library is fully loaded.
|
||||
_lib_handle = dlopen(stripped_name, RTLD_LAZY | RTLD_NOLOAD);
|
||||
_valid = _lib_handle != NULL && verifyBaseAddress(cc, _lib_handle);
|
||||
}
|
||||
|
||||
UnloadProtection::~UnloadProtection() {
|
||||
if (_lib_handle != NULL) {
|
||||
dlclose(_lib_handle);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // __linux__
|
||||
|
||||
@@ -14,35 +14,50 @@
|
||||
#include "symbols.h"
|
||||
#include "log.h"
|
||||
|
||||
UnloadProtection::UnloadProtection(const CodeCache *cc) {
|
||||
// Protect library from unloading while parsing in-memory ELF program headers.
|
||||
// Also, dlopen() ensures the library is fully loaded.
|
||||
_lib_handle = dlopen(cc->name(), RTLD_LAZY | RTLD_NOLOAD);
|
||||
_valid = _lib_handle != NULL;
|
||||
}
|
||||
|
||||
UnloadProtection::~UnloadProtection() {
|
||||
if (_lib_handle != NULL) {
|
||||
dlclose(_lib_handle);
|
||||
}
|
||||
}
|
||||
|
||||
class MachOParser {
|
||||
private:
|
||||
CodeCache* _cc;
|
||||
const mach_header* _image_base;
|
||||
const char* _vmaddr_slide;
|
||||
|
||||
static const char* add(const void* base, uint64_t offset) {
|
||||
return (const char*)base + offset;
|
||||
}
|
||||
|
||||
const section_64* findSymbolPtrSection(const segment_command_64* sc) {
|
||||
void findSymbolPtrSection(const segment_command_64* sc, const section_64** section_ptr) {
|
||||
const section_64* section = (const section_64*)add(sc, sizeof(segment_command_64));
|
||||
for (uint32_t i = 0; i < sc->nsects; i++) {
|
||||
if (strcmp(section->sectname, "__la_symbol_ptr") == 0) {
|
||||
return section;
|
||||
uint32_t section_type = section->flags & SECTION_TYPE;
|
||||
if (section_type == S_NON_LAZY_SYMBOL_POINTERS) {
|
||||
section_ptr[0] = section;
|
||||
} else if (section_type == S_LAZY_SYMBOL_POINTERS) {
|
||||
section_ptr[1] = section;
|
||||
}
|
||||
section++;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void loadSymbols(const symtab_command* symtab, const char* text_base, const char* link_base) {
|
||||
void loadSymbols(const symtab_command* symtab, const char* link_base) {
|
||||
const nlist_64* sym = (const nlist_64*)add(link_base, symtab->symoff);
|
||||
const char* str_table = add(link_base, symtab->stroff);
|
||||
bool debug_symbols = false;
|
||||
|
||||
for (uint32_t i = 0; i < symtab->nsyms; i++) {
|
||||
if ((sym->n_type & 0xee) == 0x0e && sym->n_value != 0) {
|
||||
const char* addr = text_base + sym->n_value;
|
||||
const char* addr = _vmaddr_slide + sym->n_value;
|
||||
const char* name = str_table + sym->n_un.n_strx;
|
||||
if (name[0] == '_') name++;
|
||||
_cc->add(addr, 0, name);
|
||||
@@ -55,24 +70,26 @@ class MachOParser {
|
||||
}
|
||||
|
||||
void loadImports(const symtab_command* symtab, const dysymtab_command* dysymtab,
|
||||
const section_64* la_symbol_ptr, const char* link_base) {
|
||||
const section_64* symbol_ptr_section, const char* link_base) {
|
||||
const nlist_64* sym = (const nlist_64*)add(link_base, symtab->symoff);
|
||||
const char* str_table = add(link_base, symtab->stroff);
|
||||
|
||||
const uint32_t* isym = (const uint32_t*)add(link_base, dysymtab->indirectsymoff) + la_symbol_ptr->reserved1;
|
||||
uint32_t isym_count = la_symbol_ptr->size / sizeof(void*);
|
||||
void** slot = (void**)add(_image_base, la_symbol_ptr->addr);
|
||||
const uint32_t* isym = (const uint32_t*)add(link_base, dysymtab->indirectsymoff) + symbol_ptr_section->reserved1;
|
||||
uint32_t isym_count = symbol_ptr_section->size / sizeof(void*);
|
||||
void** slot = (void**)(_vmaddr_slide + symbol_ptr_section->addr);
|
||||
|
||||
for (uint32_t i = 0; i < isym_count; i++) {
|
||||
const char* name = str_table + sym[isym[i]].n_un.n_strx;
|
||||
if (name[0] == '_') name++;
|
||||
_cc->addImport(&slot[i], name);
|
||||
if ((isym[i] & (INDIRECT_SYMBOL_LOCAL | INDIRECT_SYMBOL_ABS)) == 0) {
|
||||
const char* name = str_table + sym[isym[i]].n_un.n_strx;
|
||||
if (name[0] == '_') name++;
|
||||
_cc->addImport(&slot[i], name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
MachOParser(CodeCache* cc, const mach_header* image_base) : _cc(cc), _image_base(image_base) {
|
||||
}
|
||||
MachOParser(CodeCache* cc, const mach_header* image_base, const char* vmaddr_slide) :
|
||||
_cc(cc), _image_base(image_base), _vmaddr_slide(vmaddr_slide) {}
|
||||
|
||||
bool parse() {
|
||||
if (_image_base->magic != MH_MAGIC_64) {
|
||||
@@ -82,43 +99,38 @@ class MachOParser {
|
||||
const mach_header_64* header = (const mach_header_64*)_image_base;
|
||||
const load_command* lc = (const load_command*)(header + 1);
|
||||
|
||||
const char* UNDEFINED = (const char*)-1;
|
||||
const char* text_base = UNDEFINED;
|
||||
const char* link_base = UNDEFINED;
|
||||
const section_64* la_symbol_ptr = NULL;
|
||||
const char* link_base = NULL;
|
||||
const section_64* symbol_ptr[2] = {NULL, NULL};
|
||||
const symtab_command* symtab = NULL;
|
||||
const dysymtab_command* dysymtab = NULL;
|
||||
|
||||
for (uint32_t i = 0; i < header->ncmds; i++) {
|
||||
if (lc->cmd == LC_SEGMENT_64) {
|
||||
const segment_command_64* sc = (const segment_command_64*)lc;
|
||||
if ((sc->initprot & 4) != 0) {
|
||||
if (text_base == UNDEFINED || strcmp(sc->segname, "__TEXT") == 0) {
|
||||
text_base = (const char*)_image_base - sc->vmaddr;
|
||||
_cc->setTextBase(text_base);
|
||||
_cc->updateBounds(_image_base, add(_image_base, sc->vmsize));
|
||||
}
|
||||
} else if ((sc->initprot & 7) == 1) {
|
||||
if (link_base == UNDEFINED || strcmp(sc->segname, "__LINKEDIT") == 0) {
|
||||
link_base = text_base + sc->vmaddr - sc->fileoff;
|
||||
}
|
||||
} else if ((sc->initprot & 2) != 0) {
|
||||
if (strcmp(sc->segname, "__DATA") == 0) {
|
||||
la_symbol_ptr = findSymbolPtrSection(sc);
|
||||
}
|
||||
if (strcmp(sc->segname, "__TEXT") == 0) {
|
||||
_cc->updateBounds(_image_base, add(_image_base, sc->vmsize));
|
||||
} else if (strcmp(sc->segname, "__LINKEDIT") == 0) {
|
||||
link_base = _vmaddr_slide + sc->vmaddr - sc->fileoff;
|
||||
} else if (strcmp(sc->segname, "__DATA") == 0 || strcmp(sc->segname, "__DATA_CONST") == 0) {
|
||||
findSymbolPtrSection(sc, symbol_ptr);
|
||||
}
|
||||
} else if (lc->cmd == LC_SYMTAB) {
|
||||
symtab = (const symtab_command*)lc;
|
||||
if (text_base != UNDEFINED && link_base != UNDEFINED) {
|
||||
loadSymbols(symtab, text_base, link_base);
|
||||
}
|
||||
} else if (lc->cmd == LC_DYSYMTAB) {
|
||||
if (la_symbol_ptr != NULL && symtab != NULL && link_base != UNDEFINED) {
|
||||
loadImports(symtab, (const dysymtab_command*)lc, la_symbol_ptr, link_base);
|
||||
}
|
||||
dysymtab = (const dysymtab_command*)lc;
|
||||
}
|
||||
lc = (const load_command*)add(lc, lc->cmdsize);
|
||||
}
|
||||
|
||||
if (symtab != NULL && link_base != NULL) {
|
||||
loadSymbols(symtab, link_base);
|
||||
|
||||
if (dysymtab != NULL) {
|
||||
if (symbol_ptr[0] != NULL) loadImports(symtab, dysymtab, symbol_ptr[0], link_base);
|
||||
if (symbol_ptr[1] != NULL) loadImports(symtab, dysymtab, symbol_ptr[1], link_base);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
@@ -152,22 +164,22 @@ void Symbols::parseLibraries(CodeCacheArray* array, bool kernel_symbols) {
|
||||
}
|
||||
|
||||
const char* path = _dyld_get_image_name(i);
|
||||
const char* vmaddr_slide = (const char*)_dyld_get_image_vmaddr_slide(i);
|
||||
|
||||
// Protect the library from unloading while parsing symbols
|
||||
void* handle = dlopen(path, RTLD_LAZY | RTLD_NOLOAD);
|
||||
if (handle == NULL) {
|
||||
continue;
|
||||
CodeCache* cc = new CodeCache(path, count);
|
||||
cc->setTextBase(vmaddr_slide);
|
||||
|
||||
UnloadProtection handle(cc);
|
||||
if (handle.isValid()) {
|
||||
MachOParser parser(cc, image_base, vmaddr_slide);
|
||||
if (!parser.parse()) {
|
||||
Log::warn("Could not parse symbols from %s", path);
|
||||
}
|
||||
cc->sort();
|
||||
array->add(cc);
|
||||
} else {
|
||||
delete cc;
|
||||
}
|
||||
|
||||
CodeCache* cc = new CodeCache(path, count, true);
|
||||
MachOParser parser(cc, image_base);
|
||||
if (!parser.parse()) {
|
||||
Log::warn("Could not parse symbols from %s", path);
|
||||
}
|
||||
dlclose(handle);
|
||||
|
||||
cc->sort();
|
||||
array->add(cc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ asprof_thread_local_data* ThreadLocalData::initThreadLocalData(pthread_key_t pro
|
||||
return NULL;
|
||||
}
|
||||
val->sample_counter = 0;
|
||||
if (pthread_setspecific(profiler_data_key, (void*)val) < 0) {
|
||||
if (pthread_setspecific(profiler_data_key, (void*)val) != 0) {
|
||||
free((void*)val);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ void Trap::pair(Trap& second) {
|
||||
bool Trap::patch(instruction_t insn) {
|
||||
if (_unprotect) {
|
||||
int prot = WX_MEMORY ? (PROT_READ | PROT_WRITE) : (PROT_READ | PROT_WRITE | PROT_EXEC);
|
||||
if (mprotect((void*)(_entry & -OS::page_size), OS::page_size, prot) != 0) {
|
||||
if (OS::mprotect((void*)(_entry & -OS::page_size), OS::page_size, prot) != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,7 @@ bool Trap::patch(instruction_t insn) {
|
||||
flushCache(_entry);
|
||||
|
||||
if (_protect) {
|
||||
mprotect((void*)(_entry & -OS::page_size), OS::page_size, PROT_READ | PROT_EXEC);
|
||||
OS::mprotect((void*)(_entry & -OS::page_size), OS::page_size, PROT_READ | PROT_EXEC);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -96,7 +96,8 @@ static bool isOpenJ9JvmtiAlloc(const char* blob_name) {
|
||||
}
|
||||
|
||||
static bool isCompilerEntry(const char* blob_name) {
|
||||
return strncmp(blob_name, "_ZN13CompileBroker25invoke_compiler_on_method", 45) == 0;
|
||||
return strncmp(blob_name, "_ZN8Compiler14compile_method", 28) == 0 ||
|
||||
strncmp(blob_name, "_ZN10C2Compiler14compile_method", 31) == 0;
|
||||
}
|
||||
|
||||
static void* resolveMethodId(void** mid) {
|
||||
@@ -354,10 +355,10 @@ void VM::applyPatch(char* func, const char* patch, const char* end_patch) {
|
||||
uintptr_t start_page = (uintptr_t)func & ~OS::page_mask;
|
||||
uintptr_t end_page = ((uintptr_t)func + size + OS::page_mask) & ~OS::page_mask;
|
||||
|
||||
if (mprotect((void*)start_page, end_page - start_page, PROT_READ | PROT_WRITE | PROT_EXEC) == 0) {
|
||||
if (OS::mprotect((void*)start_page, end_page - start_page, PROT_READ | PROT_WRITE | PROT_EXEC) == 0) {
|
||||
memcpy(func, patch, size);
|
||||
__builtin___clear_cache(func, func + size);
|
||||
mprotect((void*)start_page, end_page - start_page, PROT_READ | PROT_EXEC);
|
||||
OS::mprotect((void*)start_page, end_page - start_page, PROT_READ | PROT_EXEC);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ bool VMStructs::_has_stack_structs = false;
|
||||
bool VMStructs::_has_class_loader_data = false;
|
||||
bool VMStructs::_has_native_thread_id = false;
|
||||
bool VMStructs::_has_perm_gen = false;
|
||||
bool VMStructs::_can_dereference_jmethod_id = false;
|
||||
bool VMStructs::_compact_object_headers = false;
|
||||
|
||||
int VMStructs::_klass_name_offset = -1;
|
||||
@@ -519,6 +520,9 @@ void VMStructs::resolveOffsets() {
|
||||
&& _thread_exception_offset >= 0
|
||||
&& _constmethod_size >= 0;
|
||||
|
||||
// Since JDK-8268406, it is no longer possible to get VMMethod* by dereferencing jmethodID
|
||||
_can_dereference_jmethod_id = _has_method_structs && VM::hotspot_version() <= 25;
|
||||
|
||||
if (_code_heap_addr != NULL && _code_heap_low_addr != NULL && _code_heap_high_addr != NULL) {
|
||||
char* code_heaps = *_code_heap_addr;
|
||||
unsigned int code_heap_count = *(unsigned int*)(code_heaps + _array_len_offset);
|
||||
@@ -635,6 +639,15 @@ int VMThread::nativeThreadId(JNIEnv* jni, jthread thread) {
|
||||
return VM::isOpenJ9() ? J9Ext::GetOSThreadID(thread) : -1;
|
||||
}
|
||||
|
||||
int VMThread::osThreadId() {
|
||||
const char* osthread = *(const char**) at(_thread_osthread_offset);
|
||||
if (osthread != NULL) {
|
||||
// Java thread may be in the middle of termination, and its osthread structure just released
|
||||
return SafeAccess::load32((u32*)(osthread + _osthread_id_offset), (u32)-1);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
jmethodID VMMethod::id() {
|
||||
// We may find a bogus NMethod during stack walking, it does not always point to a valid VMMethod
|
||||
const char* const_method = (const char*) SafeAccess::load((void**) at(_method_constmethod_offset));
|
||||
@@ -656,6 +669,16 @@ jmethodID VMMethod::id() {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
jmethodID VMMethod::validatedId() {
|
||||
jmethodID method_id = id();
|
||||
if (goodPtr(method_id)) {
|
||||
if (!_can_dereference_jmethod_id || *(VMMethod**)method_id == this) {
|
||||
return method_id;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
NMethod* CodeHeap::findNMethod(char* heap, const void* pc) {
|
||||
unsigned char* heap_start = *(unsigned char**)(heap + _code_heap_memory_offset + _vs_low_offset);
|
||||
unsigned char* segmap = *(unsigned char**)(heap + _code_heap_segmap_offset + _vs_low_offset);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <jvmti.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <type_traits>
|
||||
#include "codeCache.h"
|
||||
|
||||
|
||||
@@ -25,6 +26,7 @@ class VMStructs {
|
||||
static bool _has_class_loader_data;
|
||||
static bool _has_native_thread_id;
|
||||
static bool _has_perm_gen;
|
||||
static bool _can_dereference_jmethod_id;
|
||||
static bool _compact_object_headers;
|
||||
|
||||
static int _klass_name_offset;
|
||||
@@ -139,6 +141,7 @@ class VMStructs {
|
||||
|
||||
template<typename T>
|
||||
static T align(const void* ptr) {
|
||||
static_assert(std::is_pointer<T>::value, "T must be a pointer type");
|
||||
return (T)((uintptr_t)ptr & ~(sizeof(T) - 1));
|
||||
}
|
||||
|
||||
@@ -347,10 +350,7 @@ class VMThread : VMStructs {
|
||||
|
||||
static int nativeThreadId(JNIEnv* jni, jthread thread);
|
||||
|
||||
int osThreadId() {
|
||||
const char* osthread = *(const char**) at(_thread_osthread_offset);
|
||||
return osthread != NULL ? *(int*)(osthread + _osthread_id_offset) : -1;
|
||||
}
|
||||
int osThreadId();
|
||||
|
||||
int state() {
|
||||
return _thread_state_offset >= 0 ? *(int*) at(_thread_state_offset) : 0;
|
||||
@@ -386,12 +386,18 @@ class VMThread : VMStructs {
|
||||
|
||||
class VMMethod : VMStructs {
|
||||
public:
|
||||
static VMMethod* fromMethodID(jmethodID id) {
|
||||
return *(VMMethod**)id;
|
||||
}
|
||||
|
||||
jmethodID id();
|
||||
|
||||
// Performs extra validation when VMMethod comes from incomplete frame
|
||||
jmethodID validatedId();
|
||||
|
||||
// Workaround for JDK-8313816
|
||||
static bool isStaleMethodId(jmethodID id) {
|
||||
if (!_can_dereference_jmethod_id) return false;
|
||||
VMMethod* vm_method = *(VMMethod**)id;
|
||||
return vm_method == NULL || vm_method->id() == NULL;
|
||||
}
|
||||
|
||||
const char* bytecode() {
|
||||
return *(const char**) at(_method_constmethod_offset) + _constmethod_size;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ class LateInitializer {
|
||||
|
||||
if (!checkJvmLoaded()) {
|
||||
const char* command = getenv("ASPROF_COMMAND");
|
||||
if (command != NULL && Hooks::init(false)) {
|
||||
if (command != NULL && OS::checkPreloaded() && Hooks::init(false)) {
|
||||
startProfiler(command);
|
||||
}
|
||||
}
|
||||
|
||||
BIN
test/gen/opentelemetry-gen-classes.jar
Normal file
BIN
test/gen/opentelemetry-gen-classes.jar
Normal file
Binary file not shown.
49
test/native/libs/jninativestacks.c
Normal file
49
test/native/libs/jninativestacks.c
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <jni.h>
|
||||
#include <math.h>
|
||||
|
||||
JNIEXPORT double doCpuTask() {
|
||||
int i = 0;
|
||||
double result = 0;
|
||||
|
||||
while (i < 100000000) {
|
||||
i++;
|
||||
result += sqrt(i);
|
||||
result += pow(i, sqrt(i));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
JNIEXPORT double generateDeepStack(int count) {
|
||||
char frame[0x20000];
|
||||
// Prevent from being optimized by compiler
|
||||
(void)frame;
|
||||
|
||||
if (count == 0) {
|
||||
return doCpuTask();
|
||||
} else {
|
||||
return generateDeepStack(count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT jdouble JNICALL Java_test_stackwalker_StackGenerator_largeFrame(JNIEnv* env, jclass cls) {
|
||||
char frame[0x50000];
|
||||
// Prevent from being optimized by compiler
|
||||
(void)frame;
|
||||
return doCpuTask();
|
||||
}
|
||||
|
||||
JNIEXPORT jdouble JNICALL Java_test_stackwalker_StackGenerator_deepFrame(JNIEnv* env, jclass cls) {
|
||||
char frame[0x30000];
|
||||
// Prevent from being optimized by compiler
|
||||
(void)frame;
|
||||
return generateDeepStack(6);
|
||||
}
|
||||
|
||||
JNIEXPORT jdouble JNICALL Java_test_stackwalker_StackGenerator_leafFrame(JNIEnv* env, jclass cls) {
|
||||
return doCpuTask();
|
||||
}
|
||||
@@ -7,42 +7,44 @@
|
||||
#include "testRunner.hpp"
|
||||
#include <string.h>
|
||||
|
||||
TEST_CASE(Buffer_test_var32_0) {
|
||||
static u64 readVarInt(const unsigned char* data) {
|
||||
size_t idx = 0;
|
||||
u64 output = 0;
|
||||
while (data[idx] & 0x80) {
|
||||
output += ((u64) data[idx] & 0x7F) << (7 * idx);
|
||||
idx++;
|
||||
}
|
||||
return output + ((u64) data[idx] << (7 * idx));
|
||||
}
|
||||
|
||||
TEST_CASE(Buffer_test_varint_0) {
|
||||
ProtoBuffer buf(100);
|
||||
|
||||
buf.field(3, (u64)0);
|
||||
|
||||
CHECK_EQ(buf.offset(), 2);
|
||||
CHECK_EQ(buf.data()[0], (3 << 3) | VARINT);
|
||||
CHECK_EQ(buf.data()[1], 0);
|
||||
CHECK_EQ(readVarInt(buf.data() + 1), 0);
|
||||
}
|
||||
|
||||
TEST_CASE(Buffer_test_var32_150) {
|
||||
TEST_CASE(Buffer_test_varint_150) {
|
||||
ProtoBuffer buf(100);
|
||||
|
||||
buf.field(3, (u64)150);
|
||||
|
||||
CHECK_EQ(buf.offset(), 3);
|
||||
CHECK_EQ(buf.data()[0], (3 << 3) | VARINT);
|
||||
CHECK_EQ(buf.data()[1], 150);
|
||||
CHECK_EQ(buf.data()[2], 1);
|
||||
CHECK_EQ(readVarInt(buf.data() + 1), 150);
|
||||
}
|
||||
|
||||
TEST_CASE(Buffer_test_var64_LargeNumber) {
|
||||
TEST_CASE(Buffer_test_varint_LargeNumber) {
|
||||
ProtoBuffer buf(100);
|
||||
|
||||
buf.field(1, (u64)17574838338834838);
|
||||
|
||||
CHECK_EQ(buf.offset(), 9);
|
||||
CHECK_EQ(buf.data()[0], (1 << 3) | VARINT);
|
||||
CHECK_EQ(buf.data()[1], 150);
|
||||
CHECK_EQ(buf.data()[2], 163);
|
||||
CHECK_EQ(buf.data()[3], 175);
|
||||
CHECK_EQ(buf.data()[4], 225);
|
||||
CHECK_EQ(buf.data()[5], 142);
|
||||
CHECK_EQ(buf.data()[6], 135);
|
||||
CHECK_EQ(buf.data()[7], 156);
|
||||
CHECK_EQ(buf.data()[8], 31);
|
||||
CHECK_EQ(readVarInt(buf.data() + 1), 17574838338834838);
|
||||
}
|
||||
|
||||
TEST_CASE(Buffer_test_var32_bool) {
|
||||
@@ -88,7 +90,7 @@ TEST_CASE(Buffer_test_string) {
|
||||
TEST_CASE(Buffer_test_nestedField) {
|
||||
ProtoBuffer buf(100);
|
||||
|
||||
protobuf_mark_t mark = buf.startMessage(4);
|
||||
protobuf_mark_t mark = buf.startMessage(4, 3);
|
||||
buf.field(3, (u64)10);
|
||||
buf.field(5, true);
|
||||
buf.commitMessage(mark);
|
||||
@@ -107,8 +109,8 @@ TEST_CASE(Buffer_test_nestedField) {
|
||||
TEST_CASE(Buffer_test_nestedMessageWithString) {
|
||||
ProtoBuffer buf(100);
|
||||
|
||||
protobuf_mark_t mark1 = buf.startMessage(3);
|
||||
protobuf_mark_t mark2 = buf.startMessage(4);
|
||||
protobuf_mark_t mark1 = buf.startMessage(3, 3);
|
||||
protobuf_mark_t mark2 = buf.startMessage(4, 3);
|
||||
buf.field(5, "ciao");
|
||||
buf.commitMessage(mark1);
|
||||
buf.commitMessage(mark2);
|
||||
@@ -131,7 +133,7 @@ TEST_CASE(Buffer_test_maxTag) {
|
||||
ProtoBuffer buf(100);
|
||||
|
||||
// https://protobuf.dev/programming-guides/proto3/#assigning-field-numbers
|
||||
const protobuf_mark_t max_tag = 536870911;
|
||||
const protobuf_index_t max_tag = 536870911;
|
||||
buf.field(max_tag, (u64) 3);
|
||||
|
||||
CHECK_EQ(buf.offset(), 6);
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
*/
|
||||
|
||||
#ifdef __linux__
|
||||
#define EXT ".so"
|
||||
#else
|
||||
#define EXT ".dylib"
|
||||
#endif
|
||||
|
||||
#include "codeCache.h"
|
||||
#include "profiler.h"
|
||||
@@ -22,7 +26,7 @@ const void* resolveSymbol(const char* lib, const char* name) {
|
||||
|
||||
#define ASSERT_RESOLVE(id) \
|
||||
{ \
|
||||
void* result = dlopen("libreladyn.so", RTLD_NOW); /* see reladyn.c */ \
|
||||
void* result = dlopen("libreladyn" EXT, RTLD_NOW); /* see reladyn.c */ \
|
||||
ASSERT(result); \
|
||||
Profiler::instance()->updateSymbols(false); \
|
||||
CodeCache* libreladyn = Profiler::instance()->findLibraryByName("libreladyn"); \
|
||||
@@ -39,12 +43,14 @@ TEST_CASE(ResolveFromRela_dyn_R_GLOB_DAT) {
|
||||
ASSERT_RESOLVE(im_pthread_setspecific);
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
TEST_CASE(ResolveFromRela_dyn_R_ABS64) {
|
||||
ASSERT_RESOLVE(im_pthread_exit);
|
||||
}
|
||||
|
||||
TEST_CASE(VirtAddrDifferentLoadAddr) {
|
||||
const void* sym = resolveSymbol("libvaddrdif.so", "vaddrdif_square");
|
||||
const void* sym = resolveSymbol("libvaddrdif" EXT, "vaddrdif_square");
|
||||
ASSERT(sym);
|
||||
|
||||
int (*square)(int) = (int (*)(int))sym;
|
||||
@@ -52,7 +58,7 @@ TEST_CASE(VirtAddrDifferentLoadAddr) {
|
||||
}
|
||||
|
||||
TEST_CASE(MappedTwiceAtZeroOffset) {
|
||||
const void* sym = resolveSymbol("libtwiceatzero.so", "twiceatzero_hello");
|
||||
const void* sym = resolveSymbol("libtwiceatzero" EXT, "twiceatzero_hello");
|
||||
// Resolving the symbol without crashing is enough for this test case.
|
||||
ASSERT(sym);
|
||||
|
||||
@@ -61,13 +67,13 @@ TEST_CASE(MappedTwiceAtZeroOffset) {
|
||||
}
|
||||
|
||||
TEST_CASE(MultipleMatchingSymbols) {
|
||||
const void* sym = resolveSymbol("multiplematching.so", "Class::function");
|
||||
const void* sym = resolveSymbol("multiplematching" EXT, "Class::function");
|
||||
ASSERT(sym);
|
||||
|
||||
const void* sym_ok = resolveSymbol("multiplematching.so", "_ZN5Class8functionEv");
|
||||
const void* sym_ok = resolveSymbol("multiplematching" EXT, "_ZN5Class8functionEv");
|
||||
ASSERT_EQ(sym, sym_ok);
|
||||
|
||||
const void* sym_cold = resolveSymbol("multiplematching.so", "_ZN5Class8functionEv.cold");
|
||||
const void* sym_cold = resolveSymbol("multiplematching" EXT, "_ZN5Class8functionEv.cold");
|
||||
ASSERT_NE(sym, sym_cold);
|
||||
}
|
||||
|
||||
@@ -96,10 +96,6 @@ public class Runner {
|
||||
return Integer.parseInt(prop);
|
||||
}
|
||||
|
||||
private static boolean enabled(RunnableTest rt, TestDeclaration decl) {
|
||||
return rt.test().enabled() && decl.matches(rt.method());
|
||||
}
|
||||
|
||||
private static boolean applicable(Test test) {
|
||||
Os[] os = test.os();
|
||||
Arch[] arch = test.arch();
|
||||
@@ -112,7 +108,7 @@ public class Runner {
|
||||
}
|
||||
|
||||
private static TestResult run(RunnableTest rt, TestDeclaration decl) {
|
||||
if (!enabled(rt, decl)) {
|
||||
if (!rt.test().enabled() || decl.skips(rt.method())) {
|
||||
return TestResult.skipDisabled();
|
||||
}
|
||||
if (!applicable(rt.test())) {
|
||||
@@ -127,6 +123,9 @@ public class Runner {
|
||||
rt.method().getDeclaringClass().getDeclaredConstructor().newInstance() : null;
|
||||
rt.method().invoke(holder, p);
|
||||
} catch (InvocationTargetException e) {
|
||||
if (e.getTargetException() instanceof NoClassDefFoundError) {
|
||||
return TestResult.skipMissingJar();
|
||||
}
|
||||
return TestResult.fail(e.getTargetException());
|
||||
} catch (Throwable e) {
|
||||
return TestResult.fail(e);
|
||||
@@ -170,6 +169,7 @@ public class Runner {
|
||||
System.out.println("FAIL: " + fail);
|
||||
System.out.println("SKIP (disabled): " + statusCounts.getOrDefault(TestStatus.SKIP_DISABLED, 0));
|
||||
System.out.println("SKIP (config mismatch): " + statusCounts.getOrDefault(TestStatus.SKIP_CONFIG_MISMATCH, 0));
|
||||
System.out.println("SKIP (missing JAR): " + statusCounts.getOrDefault(TestStatus.SKIP_MISSING_JAR, 0));
|
||||
System.out.println("TOTAL: " + testCount);
|
||||
}
|
||||
|
||||
|
||||
@@ -58,13 +58,15 @@ public class TestDeclaration {
|
||||
return new TestDeclaration(allTestDirs, filters, skipFilters);
|
||||
}
|
||||
|
||||
private static List<RunnableTest> getRunnableTests(String dir) {
|
||||
private List<RunnableTest> getRunnableTests(String dir) {
|
||||
String className = "test." + dir + "." + Character.toUpperCase(dir.charAt(0)) + dir.substring(1) + "Tests";
|
||||
try {
|
||||
List<RunnableTest> rts = new ArrayList<>();
|
||||
for (Method m : Class.forName(className).getMethods()) {
|
||||
for (Test t : m.getAnnotationsByType(Test.class)) {
|
||||
rts.add(new RunnableTest(m, t));
|
||||
if (includes(m)) {
|
||||
for (Test t : m.getAnnotationsByType(Test.class)) {
|
||||
rts.add(new RunnableTest(m, t));
|
||||
}
|
||||
}
|
||||
}
|
||||
return rts;
|
||||
@@ -75,19 +77,22 @@ public class TestDeclaration {
|
||||
|
||||
public List<RunnableTest> getRunnableTests() {
|
||||
return allDirs.stream()
|
||||
.flatMap(g -> getRunnableTests(g).stream())
|
||||
.flatMap(dir -> getRunnableTests(dir).stream())
|
||||
.sorted(Comparator.comparing(RunnableTest::testName))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public boolean matches(Method m) {
|
||||
public boolean includes(Method m) {
|
||||
return includeGlobs.isEmpty() || matches(m, includeGlobs);
|
||||
}
|
||||
|
||||
public boolean skips(Method m) {
|
||||
return !skipGlobs.isEmpty() && matches(m, skipGlobs);
|
||||
}
|
||||
|
||||
private static boolean matches(Method m, List<Pattern> patterns) {
|
||||
String name = m.getDeclaringClass().getSimpleName() + '.' + m.getName();
|
||||
|
||||
if (includeGlobs.isEmpty() || includeGlobs.stream().anyMatch(f -> f.matcher(name).matches())) {
|
||||
return skipGlobs.isEmpty() || skipGlobs.stream().noneMatch(f -> f.matcher(name).matches());
|
||||
}
|
||||
|
||||
return false;
|
||||
return patterns.stream().anyMatch(p -> p.matcher(name).matches());
|
||||
}
|
||||
|
||||
private static Pattern filterFrom(String s) {
|
||||
|
||||
@@ -36,7 +36,7 @@ public class TestProcess implements Closeable {
|
||||
|
||||
private static final String JAVA_HOME = System.getProperty("java.home");
|
||||
|
||||
private static final Pattern filePattern = Pattern.compile("(%[a-z]+)(\\.[a-z]+)?");
|
||||
private static final Pattern filePattern = Pattern.compile("(%[a-z][a-z0-9_]*)(\\.[a-z]+)?");
|
||||
|
||||
private static final MethodHandle pid = getPidHandle();
|
||||
|
||||
@@ -241,14 +241,14 @@ public class TestProcess implements Closeable {
|
||||
try {
|
||||
Files.createDirectories(Paths.get(logDir));
|
||||
|
||||
File stdout = tmpFiles.getOrDefault(PROFOUT, tmpFiles.get(STDOUT));
|
||||
moveLog(stdout, "stdout", true);
|
||||
moveLog(tmpFiles.get(STDOUT), "stdout", true);
|
||||
moveLog(tmpFiles.get(STDERR), "stderr", false);
|
||||
|
||||
File stderr = tmpFiles.getOrDefault(PROFERR, tmpFiles.get(STDERR));
|
||||
moveLog(stderr, "stderr", false);
|
||||
|
||||
File profile = tmpFiles.get("%f");
|
||||
moveLog(profile, "profile", true);
|
||||
for (String key : tmpFiles.keySet()) {
|
||||
if (!key.equals(STDERR) && !key.equals(STDOUT)) {
|
||||
moveLog(tmpFiles.get(key), key.substring(1), true);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.log(Level.WARNING, "Failed to move logs", e);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,10 @@ public class TestResult {
|
||||
return new TestResult(TestStatus.SKIP_DISABLED, null);
|
||||
}
|
||||
|
||||
public static TestResult skipMissingJar() {
|
||||
return new TestResult(TestStatus.SKIP_MISSING_JAR, null);
|
||||
}
|
||||
|
||||
public static TestResult pass() {
|
||||
return new TestResult(TestStatus.PASS, null);
|
||||
}
|
||||
|
||||
@@ -10,4 +10,5 @@ public enum TestStatus {
|
||||
FAIL,
|
||||
SKIP_DISABLED,
|
||||
SKIP_CONFIG_MISMATCH,
|
||||
SKIP_MISSING_JAR,
|
||||
}
|
||||
|
||||
8
test/stubs/com/google/protobuf/ByteString.java
Normal file
8
test/stubs/com/google/protobuf/ByteString.java
Normal file
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.google.protobuf;
|
||||
|
||||
public abstract class ByteString {}
|
||||
8
test/stubs/com/google/protobuf/CodedInputStream.java
Normal file
8
test/stubs/com/google/protobuf/CodedInputStream.java
Normal file
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.google.protobuf;
|
||||
|
||||
public abstract class CodedInputStream {}
|
||||
8
test/stubs/com/google/protobuf/GeneratedMessageV3.java
Normal file
8
test/stubs/com/google/protobuf/GeneratedMessageV3.java
Normal file
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.google.protobuf;
|
||||
|
||||
public abstract class GeneratedMessageV3 {}
|
||||
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.google.protobuf;
|
||||
|
||||
public class InvalidProtocolBufferException extends java.io.IOException {}
|
||||
8
test/stubs/com/google/protobuf/MessageOrBuilder.java
Normal file
8
test/stubs/com/google/protobuf/MessageOrBuilder.java
Normal file
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.google.protobuf;
|
||||
|
||||
public interface MessageOrBuilder {}
|
||||
@@ -75,10 +75,10 @@ public class AllocTests {
|
||||
|
||||
// Without liveness tracking, results won't change except for the sampling
|
||||
// error.
|
||||
@Test(mainClass = RandomBlockRetainer.class, jvmVer = {11, Integer.MAX_VALUE}, args = "1.0", agentArgs = "start,alloc=1k,total,file=%f,collapsed")
|
||||
@Test(mainClass = RandomBlockRetainer.class, jvmVer = {11, Integer.MAX_VALUE}, args = "0.0", agentArgs = "start,alloc=1k,total,file=%f,collapsed,live")
|
||||
@Test(mainClass = RandomBlockRetainer.class, jvmVer = {11, Integer.MAX_VALUE}, args = "0.1", agentArgs = "start,alloc=1k,total,file=%f,collapsed,live")
|
||||
@Test(mainClass = RandomBlockRetainer.class, jvmVer = {11, Integer.MAX_VALUE}, args = "1.0", agentArgs = "start,alloc=1k,total,file=%f,collapsed,live")
|
||||
@Test(mainClass = RandomBlockRetainer.class, jvmVer = {11, Integer.MAX_VALUE}, args = "1.0", agentArgs = "start,alloc=1k,total,file=%f,collapsed", nameSuffix = "1.0")
|
||||
@Test(mainClass = RandomBlockRetainer.class, jvmVer = {11, Integer.MAX_VALUE}, args = "0.0", agentArgs = "start,alloc=1k,total,file=%f,collapsed,live", nameSuffix = "0.0+live")
|
||||
@Test(mainClass = RandomBlockRetainer.class, jvmVer = {11, Integer.MAX_VALUE}, args = "0.1", agentArgs = "start,alloc=1k,total,file=%f,collapsed,live", nameSuffix = "0.1+live")
|
||||
@Test(mainClass = RandomBlockRetainer.class, jvmVer = {11, Integer.MAX_VALUE}, args = "1.0", agentArgs = "start,alloc=1k,total,file=%f,collapsed,live", nameSuffix = "1.0+live")
|
||||
public void liveness(TestProcess p) throws Exception {
|
||||
final long TOTAL_BYTES = 50000000;
|
||||
final double tolerance = 0.10;
|
||||
@@ -96,10 +96,10 @@ public class AllocTests {
|
||||
Assert.isGreaterOrEqual(upperLimit, totalBytes);
|
||||
}
|
||||
|
||||
@Test(mainClass = RandomBlockRetainer.class, jvmVer = {11, Integer.MAX_VALUE}, args = "1.0", agentArgs = "start,alloc=1k,total,file=%f.jfr")
|
||||
@Test(mainClass = RandomBlockRetainer.class, jvmVer = {11, Integer.MAX_VALUE}, args = "0.0", agentArgs = "start,alloc=1k,total,file=%f.jfr,live")
|
||||
@Test(mainClass = RandomBlockRetainer.class, jvmVer = {11, Integer.MAX_VALUE}, args = "0.1", agentArgs = "start,alloc=1k,total,file=%f.jfr,live")
|
||||
@Test(mainClass = RandomBlockRetainer.class, jvmVer = {11, Integer.MAX_VALUE}, args = "1.0", agentArgs = "start,alloc=1k,total,file=%f.jfr,live")
|
||||
@Test(mainClass = RandomBlockRetainer.class, jvmVer = {11, Integer.MAX_VALUE}, args = "1.0", agentArgs = "start,alloc=1k,total,file=%f.jfr", nameSuffix = "1.0")
|
||||
@Test(mainClass = RandomBlockRetainer.class, jvmVer = {11, Integer.MAX_VALUE}, args = "0.0", agentArgs = "start,alloc=1k,total,file=%f.jfr,live", nameSuffix = "0.0+live")
|
||||
@Test(mainClass = RandomBlockRetainer.class, jvmVer = {11, Integer.MAX_VALUE}, args = "0.1", agentArgs = "start,alloc=1k,total,file=%f.jfr,live", nameSuffix = "0.1+live")
|
||||
@Test(mainClass = RandomBlockRetainer.class, jvmVer = {11, Integer.MAX_VALUE}, args = "1.0", agentArgs = "start,alloc=1k,total,file=%f.jfr,live", nameSuffix = "1.0+live")
|
||||
public void livenessJfrHasStacks(TestProcess p) throws Exception {
|
||||
p.waitForExit();
|
||||
String filename = p.getFilePath("%f");
|
||||
|
||||
@@ -19,6 +19,12 @@ public class ApiTests {
|
||||
assert out.contains("BusyLoops.method3;");
|
||||
}
|
||||
|
||||
@Test(mainClass = DumpOtlp.class, jvmArgs = "-Djava.library.path=build/lib")
|
||||
public void otlp(TestProcess p) throws Exception {
|
||||
p.waitForExit();
|
||||
assert p.exitCode() == 0;
|
||||
}
|
||||
|
||||
@Test(mainClass = StopResume.class, jvmArgs = "-Djava.library.path=build/lib", output = true)
|
||||
public void stopResume(TestProcess p) throws Exception {
|
||||
Output out = p.waitForExit(TestProcess.STDOUT);
|
||||
|
||||
28
test/test/api/DumpOtlp.java
Normal file
28
test/test/api/DumpOtlp.java
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package test.api;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import one.profiler.AsyncProfiler;
|
||||
import one.profiler.Counter;
|
||||
import one.profiler.Events;
|
||||
|
||||
public class DumpOtlp extends BusyLoops {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
AsyncProfiler.getInstance().start(Events.CPU, 1_000_000);
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
method1();
|
||||
method2();
|
||||
method3();
|
||||
}
|
||||
|
||||
// TODO: Should we test this further?
|
||||
byte[] profile = AsyncProfiler.getInstance().dumpOtlp();
|
||||
System.out.println(profile.length);
|
||||
}
|
||||
}
|
||||
@@ -9,14 +9,21 @@ import one.profiler.test.Output;
|
||||
import one.profiler.test.Test;
|
||||
import one.profiler.test.TestProcess;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class CTests {
|
||||
|
||||
@Test(sh = "%testbin/native_api %f.jfr", output = true)
|
||||
@Test(sh = "%testbin/native_api %api_file.jfr", output = true)
|
||||
@Test(sh = "%testbin/native_api %api_file.jfr", env = {"ASPROF_COMMAND=start,cpu,file=%preload_file.jfr"},
|
||||
output = true, nameSuffix = "fake-preload")
|
||||
public void nativeApi(TestProcess p) throws Exception {
|
||||
Output out = p.waitForExit(TestProcess.STDOUT);
|
||||
assert p.exitCode() == 0;
|
||||
assert out.contains("Starting profiler");
|
||||
assert out.contains("Stopping profiler");
|
||||
assert p.getFile("%f").length() > 0;
|
||||
assert p.getFile("%api_file").length() > 0;
|
||||
|
||||
File preloadFile = p.getFile("%preload_file");
|
||||
assert preloadFile == null || preloadFile.length() == 0;
|
||||
}
|
||||
}
|
||||
|
||||
23
test/test/comptask/ComptaskTests.java
Normal file
23
test/test/comptask/ComptaskTests.java
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package test.comptask;
|
||||
|
||||
import one.profiler.test.*;
|
||||
|
||||
public class ComptaskTests {
|
||||
@Test(
|
||||
mainClass = Main.class,
|
||||
agentArgs = "start,features=comptask,collapsed,interval=1ms,file=%f",
|
||||
jvmArgs = "-Xcomp",
|
||||
jvm = Jvm.HOTSPOT
|
||||
)
|
||||
public void testCompTask(TestProcess p) throws Exception {
|
||||
Output out = p.waitForExit("%f");
|
||||
assert p.exitCode() == 0;
|
||||
assert out.contains(";Compiler::compile_method;java/lang/\\w+\\.");
|
||||
assert out.contains(";C2Compiler::compile_method;java/lang/\\w+\\.");
|
||||
}
|
||||
}
|
||||
16
test/test/comptask/Main.java
Normal file
16
test/test/comptask/Main.java
Normal file
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package test.comptask;
|
||||
|
||||
public class Main {
|
||||
private static final long RUN_DURATION_MS = 1000;
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
long start = System.nanoTime();
|
||||
long end = start + RUN_DURATION_MS * 1_000_000;
|
||||
while (System.nanoTime() < end) {}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,9 @@
|
||||
|
||||
package test.jfr;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
@@ -14,16 +16,17 @@ import java.util.concurrent.ThreadLocalRandom;
|
||||
/**
|
||||
* Process to simulate lock contention and allocate objects.
|
||||
*/
|
||||
public class JfrMutliModeProfiling {
|
||||
public class JfrMultiModeProfiling {
|
||||
private static final Object lock = new Object();
|
||||
|
||||
private static volatile Object sink;
|
||||
private static int count = 0;
|
||||
private static final List<byte[]> holder = new ArrayList<>();
|
||||
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
ExecutorService executor = Executors.newFixedThreadPool(2);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
executor.submit(JfrMutliModeProfiling::cpuIntensiveIncrement);
|
||||
executor.submit(JfrMultiModeProfiling::cpuIntensiveIncrement);
|
||||
}
|
||||
executor.shutdown();
|
||||
allocate();
|
||||
@@ -46,6 +49,9 @@ public class JfrMutliModeProfiling {
|
||||
} else {
|
||||
sink = String.format("some string: %s, some number: %d", new Date(), random.nextInt());
|
||||
}
|
||||
if (holder.size() < 100_000) {
|
||||
holder.add(new byte[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user