Compare commits

..

55 Commits

Author SHA1 Message Date
Andrei Pangin
9cd3fdd42b Release binaries with GHA 2025-07-21 13:55:31 +01:00
Andrei Pangin
5930966a92 Release 4.1 2025-07-21 02:35:48 +01:00
Andrei Pangin
7737df342d Updated CHANGELOG 2025-07-21 02:30:07 +01:00
Andrei Pangin
843f1d9f3e Unwind checksum and digest intrinsics on ARM64 (#1400) 2025-07-21 02:28:54 +01:00
Andrei Pangin
733f2a513c Rolled back invalid fragment from previous commit 2025-07-18 20:16:06 +01:00
Andrei Pangin
9824786981 #1389: Incorrect top frame for synchronous events with cstack=vm on ARM64 (#1399) 2025-07-18 16:30:13 +01:00
Soumadipta Roy
5fffdb1eaa Rewrite jfrconv executable to shell (#1366) 2025-07-17 15:59:20 +01:00
Francesco Andreuzzi
7bf8528f75 Separate workflow for automated clang-tidy review (#1384) 2025-07-16 21:45:52 +01:00
Vishesh Ruparelia
80ae8aed19 Improve stack walking termination logic (#1393) 2025-07-15 15:44:13 +01:00
Bara' Hasheesh
1c1a14c1ec Fix intermittent failures of JfrTests with live option (#1376) 2025-07-15 15:36:20 +01:00
Andrei Pangin
83e9bdd9bd Typo in docs 2025-07-14 18:18:26 +01:00
Bara' Hasheesh
22ce08f5ef #1380: Workaround clang type promotion bug (#1390) 2025-07-14 14:22:53 +01:00
Bara' Hasheesh
7c4385b0b1 JFR writer crashes when using cstack=vmx (#1387) 2025-07-11 13:11:09 +01:00
Bara' Hasheesh
461a3c1b93 Correctly check if profiler is preloaded (#1374) 2025-07-10 18:26:45 +01:00
Francesco Andreuzzi
5b178bfc5c Temporarily disable clang-tidy automatic comments (#1382) 2025-07-10 14:19:56 +01:00
Francesco Andreuzzi
520b897dce Create test/deps if it does not exist before running Makefile recipes (#1375) 2025-07-09 19:58:41 +01:00
Bara' Hasheesh
a70f25e00f Save all generated logs for debug purposes (#1373) 2025-07-09 18:44:37 +01:00
Francesco Andreuzzi
f79729167a Test OTLP output format (#1331) 2025-07-09 13:33:39 +01:00
Bara' Hasheesh
f627b3157b Give tests unique suffix names (#1371) 2025-07-08 17:35:53 +01:00
Francesco Andreuzzi
85fefd2800 Publish clang-tidy comments only for non-draft PRs (#1367) 2025-07-07 19:11:45 +01:00
Francesco Andreuzzi
5091304efd Ensure that only files under src/ are checked in cpp-lint-diff (#1365) 2025-07-07 14:12:13 +01:00
Francesco Andreuzzi
c42bf7ad9d Cancel redundant in-progress GHA runs (#1363) 2025-07-07 11:50:25 +01:00
Francesco Andreuzzi
2b8dffff27 JFR to OTLP converter (#1336) 2025-07-04 19:43:22 +01:00
Francesco Andreuzzi
09ad6c1663 Auto-generated clang-tidy review comments (#1360) 2025-07-04 14:51:48 +01:00
Andrei Pangin
40fd71a8a0 #1358: Do not dereference jmethodIDs on JDK 26 (#1362) 2025-07-04 14:03:10 +01:00
Andrei Pangin
557f4adecb Fix nonjava test failure on Alpine 2025-07-03 20:13:37 +01:00
Andrei Pangin
de54c536dc Do not include excess files in test.jar 2025-07-03 19:36:36 +01:00
Andrei Pangin
c74107e53f Suppress javac warnings when compiling tests 2025-07-03 19:08:00 +01:00
Francesco Andreuzzi
b3968f5e38 Simplify location handling in OTLP (#1361) 2025-07-02 20:45:14 +01:00
Bara' Hasheesh
29dd537907 Correctly unwind stack for malloc events in VM stack walking mode (#1357) 2025-07-01 19:41:54 +01:00
Bara' Hasheesh
0330a6e333 Allow cstack=vmx for native applications (#1354) 2025-07-01 15:29:22 +01:00
Francesco Andreuzzi
9b44c2e99d C++ linting via clang-tidy (#1338) 2025-07-01 12:57:45 +01:00
Kerem Kat
5b4450b85c Fix invalid alignment in mallocTracer and zero-init buf in getTotalCpuTime (#1351) 2025-06-26 16:57:26 +01:00
Andrei Pangin
82d13772a5 Disable JFR OldObjectSample event in jfrsync mode (#1350) 2025-06-25 23:03:03 +01:00
Andrei Pangin
bbca9f1817 [test] Avoid listing files in /tmp 2025-06-18 12:20:48 +01:00
Bara' Hasheesh
981619680e Change stackwalker test checks to be more restrictive (#1341) 2025-06-13 17:12:42 +01:00
Francesco Andreuzzi
2b556680dc Use Index in jfrMetadata (#1337) 2025-06-13 12:47:00 +01:00
Francesco Andreuzzi
b3f58429f5 Support for OTLP Profile signals (#1188) 2025-06-12 01:11:46 +01:00
Andrei Pangin
2844e6c5c1 #1327: Merged jattach memory leak fixes
Co-authored-by: tteokbokki-master <0jin.git@gmail.com>
2025-06-09 14:34:06 +01:00
Andrei Pangin
0e1008531b Fixed misc compilation and test failures 2025-06-05 19:57:35 +01:00
Bara' Hasheesh
19ad42cd23 Enable native memory profiling of async-profiler itself (#1323) 2025-06-03 16:31:02 +01:00
Bara' Hasheesh
f76833a2c0 Add integration test for VM/VMX stack walkers for incomplete frame edge cases (#1321) 2025-06-02 14:04:17 +01:00
Andrei Pangin
4b1df29aab #1319: Accessing osThreadId of a terminating thread may fail (#1324) 2025-06-02 01:43:51 +01:00
Bara' Hasheesh
795da942f7 Enable unit tests related to symbol parsing on macOS (#1315) 2025-06-01 18:18:42 +01:00
Bara' Hasheesh
bedffcb080 Updated tests to verify symbol patching on macOS (#1279) 2025-05-25 13:19:36 +01:00
Andrei Pangin
660ffcd5c6 #1193: Parse non-lazy symbol pointers on macOS
Co-authored-by: Bara' Hasheesh <bara.hasheesh@gmail.com>
2025-05-25 12:54:26 +01:00
Bara' Hasheesh
60e79e364a Prevent from exceeding MAX_NATIVE_LIBS limit (#1312) 2025-05-23 15:41:00 +01:00
Andrei Pangin
d89ab7a16c Skip last 10% allocations for leak detection (#1299) 2025-05-21 13:28:48 +01:00
Francesco Andreuzzi
d042e0a8db Fix comptask test flakyness on JDK8 (#1307) 2025-05-21 10:34:24 +01:00
Andrei Pangin
3256fde4c1 Do not count tests that are not in the include list 2025-05-21 01:15:44 +01:00
Francesco Andreuzzi
3bbab49e3c Add corretto-8 to test matrix (#1274) 2025-05-20 15:38:37 +01:00
Francesco Andreuzzi
ed57317281 Test comptask feature (#1293) 2025-05-20 15:34:03 +01:00
Francesco Andreuzzi
c17de4c220 Guard hook installation with dlopen/dlclose (#1264) 2025-05-20 02:39:43 +01:00
Andrei Pangin
3a9252c677 Allow profiling kprobes/uprobes with --fdtransfer (#1300) 2025-05-19 19:03:21 +01:00
Francesco Andreuzzi
fd8ba8b9ee Fix typo in JfrTests (#1303) 2025-05-19 19:02:45 +01:00
118 changed files with 2643 additions and 692 deletions

134
.clang-tidy Normal file
View 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
View 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
View 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/

View File

@@ -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

View File

@@ -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,
});

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View 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"

View File

@@ -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

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.
![nativemem flamegraph](../.assets/images/nativemem_flamegraph.png)
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,

View File

@@ -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>

View File

@@ -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>

View File

@@ -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+");
}

View File

@@ -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);
}

View File

@@ -29,4 +29,5 @@ public interface AsyncProfilerMXBean {
String dumpCollapsed(Counter counter);
String dumpTraces(int maxTraces);
String dumpFlat(int maxMethods);
byte[] dumpOtlp();
}

View File

@@ -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)

View File

@@ -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;

View File

@@ -76,7 +76,8 @@ enum SHORT_ENUM Output {
OUTPUT_COLLAPSED,
OUTPUT_FLAMEGRAPH,
OUTPUT_TREE,
OUTPUT_JFR
OUTPUT_JFR,
OUTPUT_OTLP
};
enum JfrOption {

View File

@@ -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) {

View File

@@ -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;
};

View File

@@ -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" +

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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) {

View 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();
}
}
}

View File

@@ -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) {

View 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;
}

View File

@@ -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) {

View File

@@ -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();

View File

@@ -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) {

View File

@@ -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
}
]

View File

@@ -1,5 +0,0 @@
{
"resources": {
"includes": [{"pattern": ".*html$"}]
}
}

View File

@@ -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);

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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();
};

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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]);

View File

@@ -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);

View File

@@ -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");

View File

@@ -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
View 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

View File

@@ -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 {

View File

@@ -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;
}

View File

@@ -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"),
};

View File

@@ -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();
}

View File

@@ -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;
}
};

View File

@@ -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
View 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

View File

@@ -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) {

View File

@@ -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";

View File

@@ -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;
}

View File

@@ -7,7 +7,6 @@
#define _MALLOCTRACER_H
#include <stdint.h>
#include "engine.h"
#include "event.h"
#include "mutex.h"

View File

@@ -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

View File

@@ -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__

View File

@@ -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
View 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

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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);
};

View File

@@ -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;
}
};

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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__

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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);
}
}

Binary file not shown.

View 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();
}

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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) {

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -10,4 +10,5 @@ public enum TestStatus {
FAIL,
SKIP_DISABLED,
SKIP_CONFIG_MISMATCH,
SKIP_MISSING_JAR,
}

View File

@@ -0,0 +1,8 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package com.google.protobuf;
public abstract class ByteString {}

View File

@@ -0,0 +1,8 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package com.google.protobuf;
public abstract class CodedInputStream {}

View File

@@ -0,0 +1,8 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package com.google.protobuf;
public abstract class GeneratedMessageV3 {}

View File

@@ -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 {}

View File

@@ -0,0 +1,8 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package com.google.protobuf;
public interface MessageOrBuilder {}

View File

@@ -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");

View File

@@ -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);

View 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);
}
}

View File

@@ -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;
}
}

View 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+\\.");
}
}

View 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) {}
}
}

View File

@@ -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