├── .clang-format ├── .gitattributes ├── .github ├── PULL_REQUEST_TEMPLATE.md ├── actions │ ├── extract_versions │ │ └── action.yml │ ├── pr_comment │ │ └── action.yml │ └── setup_cached_java │ │ └── action.yml ├── scripts │ ├── cppcheck-gh.xslt │ ├── cppcheck-html.xslt │ ├── cppcheck-suppressions.txt │ ├── java_setup.sh │ ├── prepare_reports.sh │ ├── python_utils.py │ ├── release.sh │ └── test_alpine_aarch64.sh └── workflows │ ├── add-milestone-to-pull-requests.yaml │ ├── approve-trivial.yml │ ├── cache_java.yml │ ├── ci.yml │ ├── codecheck.yml │ ├── create-next-milestone.yaml │ ├── gh_release.yml │ ├── increment-milestones-on-tag.yaml │ ├── nightly.yml │ ├── release.yml │ ├── test_workflow.yml │ └── update_assets.yml ├── .gitignore ├── .gitlab-ci.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── build.gradle ├── common.gradle ├── ddprof-lib ├── benchmarks │ ├── build.gradle │ ├── build_run.sh │ └── src │ │ ├── benchmarkConfig.h │ │ └── unwindFailuresBenchmark.cpp ├── build.gradle ├── gtest │ └── build.gradle ├── settings.gradle └── src │ ├── main │ ├── cpp │ │ ├── arch_dd.h │ │ ├── arguments.cpp │ │ ├── arguments.h │ │ ├── asyncSampleMutex.h │ │ ├── buffers.h │ │ ├── callTraceStorage.cpp │ │ ├── callTraceStorage.h │ │ ├── codeCache.cpp │ │ ├── codeCache.h │ │ ├── common.h │ │ ├── context.cpp │ │ ├── context.h │ │ ├── counters.cpp │ │ ├── counters.h │ │ ├── ctimer.h │ │ ├── ctimer_linux.cpp │ │ ├── debugSupport.cpp │ │ ├── debugSupport.h │ │ ├── dictionary.cpp │ │ ├── dictionary.h │ │ ├── dwarf.cpp │ │ ├── dwarf.h │ │ ├── engine.cpp │ │ ├── engine.h │ │ ├── event.h │ │ ├── flightRecorder.cpp │ │ ├── flightRecorder.h │ │ ├── frame.h │ │ ├── itimer.cpp │ │ ├── itimer.h │ │ ├── j9Ext.cpp │ │ ├── j9Ext.h │ │ ├── j9WallClock.cpp │ │ ├── j9WallClock.h │ │ ├── javaApi.cpp │ │ ├── javaApi.h │ │ ├── jfrMetadata.cpp │ │ ├── jfrMetadata.h │ │ ├── jniHelper.h │ │ ├── jvm.cpp │ │ ├── jvm.h │ │ ├── jvmHeap.h │ │ ├── libraries.cpp │ │ ├── libraries.h │ │ ├── linearAllocator.cpp │ │ ├── linearAllocator.h │ │ ├── livenessTracker.cpp │ │ ├── livenessTracker.h │ │ ├── log.cpp │ │ ├── log.h │ │ ├── objectSampler.cpp │ │ ├── objectSampler.h │ │ ├── os.h │ │ ├── os_linux.cpp │ │ ├── os_macos.cpp │ │ ├── perfEvents.h │ │ ├── perfEvents_linux.cpp │ │ ├── pidController.cpp │ │ ├── pidController.h │ │ ├── profiler.cpp │ │ ├── profiler.h │ │ ├── reservoirSampler.h │ │ ├── rustDemangler.cpp │ │ ├── rustDemangler.h │ │ ├── safeAccess.h │ │ ├── spinLock.h │ │ ├── stackFrame.h │ │ ├── stackWalker_dd.h │ │ ├── symbols.h │ │ ├── symbols_linux.cpp │ │ ├── symbols_linux.h │ │ ├── symbols_macos.cpp │ │ ├── thread.cpp │ │ ├── thread.h │ │ ├── threadFilter.cpp │ │ ├── threadFilter.h │ │ ├── threadInfo.cpp │ │ ├── threadInfo.h │ │ ├── threadLocalData.h │ │ ├── threadState.h │ │ ├── tsc.cpp │ │ ├── tsc.h │ │ ├── unwindStats.cpp │ │ ├── unwindStats.h │ │ ├── vmEntry.cpp │ │ ├── vmEntry.h │ │ ├── vmStructs_dd.cpp │ │ ├── vmStructs_dd.h │ │ ├── wallClock.cpp │ │ └── wallClock.h │ └── java │ │ └── com │ │ └── datadoghq │ │ └── profiler │ │ ├── Arch.java │ │ ├── ContextSetter.java │ │ ├── JVMAccess.java │ │ ├── JavaProfiler.java │ │ ├── LibraryLoader.java │ │ ├── Main.java │ │ ├── OperatingSystem.java │ │ └── Platform.java │ └── test │ ├── cpp │ ├── ddprof_ut.cpp │ ├── demangle_ut.cpp │ └── elfparser_ut.cpp │ ├── make │ └── Makefile │ └── resources │ └── native-libs │ ├── reladyn-lib │ ├── Makefile │ └── reladyn.c │ ├── small-lib │ ├── Makefile │ ├── small_lib.cpp │ └── small_lib.h │ └── unresolved-functions │ ├── Makefile │ ├── linker.ld │ ├── main.c │ └── readme.txt ├── ddprof-stresstest ├── build.gradle └── src │ └── jmh │ └── java │ └── com │ └── datadoghq │ └── profiler │ └── stresstest │ ├── AbstractFormatter.java │ ├── CompositeFormatter.java │ ├── Configuration.java │ ├── Formatter.java │ ├── HtmlCommentFormatter.java │ ├── HtmlFormatter.java │ ├── Main.java │ ├── WhiteboxProfiler.java │ └── scenarios │ ├── CapturingLambdas.java │ ├── DumpRecording.java │ ├── GraphMutation.java │ ├── GraphState.java │ ├── NanoTime.java │ └── TracedParallelWork.java ├── ddprof-test-tracer ├── build.gradle └── src │ └── main │ └── java │ └── com │ └── datadoghq │ └── profiler │ └── context │ ├── ContextExecutor.java │ ├── ContextTask.java │ ├── RegisteringThreadFactory.java │ └── Tracing.java ├── ddprof-test ├── build.gradle └── src │ └── test │ └── java │ └── com │ └── datadoghq │ └── profiler │ ├── AbstractProcessProfilerTest.java │ ├── AbstractProfilerTest.java │ ├── CStackAwareAbstractProfilerTest.java │ ├── ExternalLauncher.java │ ├── JVMAccessTest.java │ ├── JavaProfilerTest.java │ ├── MoreAssertions.java │ ├── MuslDetectionTest.java │ ├── PlatformTest.java │ ├── alloc │ └── AllocationProfilerTest.java │ ├── classgc │ └── ClassGCTest.java │ ├── context │ └── TagContextTest.java │ ├── cpu │ ├── CTimerSamplerTest.java │ ├── ContextCpuTest.java │ ├── IOBoundCode.java │ ├── LightweightContextCpuTest.java │ ├── ProfiledCode.java │ └── SmokeCpuTest.java │ ├── endpoints │ └── EndpointTest.java │ ├── filter │ └── ThreadFilterSmokeTest.java │ ├── jfr │ ├── CpuDumpSmokeTest.java │ ├── JfrDumpTest.java │ ├── ObjectSampleDumpSmokeTest.java │ └── WallclockDumpSmokeTest.java │ ├── junit │ ├── CStack.java │ ├── CStackInjector.java │ └── RetryTest.java │ ├── loadlib │ └── LoadLibraryTest.java │ ├── memleak │ ├── GCGenerationsTest.java │ └── MemleakProfilerTest.java │ ├── metadata │ ├── BoundMethodHandleMetadataSizeTest.java │ └── MetadataNormalisationTest.java │ ├── nativelibs │ └── NativeLibrariesTest.java │ ├── queue │ └── QueueTimeTest.java │ ├── settings │ └── DatadogSettingsTest.java │ ├── shutdown │ └── ShutdownTest.java │ └── wallclock │ ├── BaseContextWallClockTest.java │ ├── CollapsingSleepTest.java │ ├── ContendedWallclockSamplesTest.java │ ├── ContextWallClockTest.java │ ├── JvmtiBasedContextWallClockTest.java │ ├── JvmtiBasedWallClockThreadFilterTest.java │ ├── MegamorphicCallTest.java │ ├── SleepTest.java │ ├── SmokeWallTest.java │ └── WallClockThreadFilterTest.java ├── gradle ├── ap-lock.properties ├── configurations.gradle ├── enforcement │ ├── .clang-format │ ├── codenarc.groovy │ ├── codenarcTest.groovy │ ├── spotless-groovy.properties │ ├── spotless-scalafmt.conf │ └── spotless-xml.properties ├── sanitizers │ ├── asan.supp │ ├── tsan.supp │ └── ubsan.supp ├── scm.gradle ├── semantic-version.gradle ├── spotless.gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── legacy_tests ├── include.sh ├── load-libraries-test.sh ├── loadlibs │ ├── com │ │ └── datadoghq │ │ │ └── loader │ │ │ └── DynamicLibraryLoader.java │ ├── com_datadoghq_loader_DynamicLibraryLoader.cpp │ ├── com_datadoghq_loader_DynamicLibraryLoader.h │ ├── increment.cpp │ └── increment.h ├── run_renaissance.sh └── test-all.sh ├── malloc-shim ├── build.gradle ├── settings.gradle └── src │ └── main │ ├── cpp │ └── malloc_intercept.cpp │ └── public │ └── debug.h ├── pom.xml ├── settings.gradle ├── test ├── native │ ├── libs │ │ └── reladyn.c │ └── symbolsLinuxTest.cpp └── test │ └── nativemem │ └── malloc_plt_dyn.c └── utils ├── cherry.sh └── init_cherypick_repo.sh /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: LLVM 4 | IndentWidth: 4 5 | ColumnLimit: 100 6 | BreakBeforeBraces: Attach 7 | AllowShortFunctionsOnASingleLine: None 8 | AllowShortIfStatementsOnASingleLine: false 9 | AllowShortLoopsOnASingleLine: false 10 | AllowShortBlocksOnASingleLine: false 11 | AllowShortCaseLabelsOnASingleLine: false 12 | AllowShortNamespacesOnASingleLine: false 13 | AllowShortStructsOnASingleLine: false 14 | AllowShortEnumsOnASingleLine: false 15 | AllowShortLambdasOnASingleLine: false 16 | AllowShortCompoundStatementsOnASingleLine: false 17 | AllowShortAlwaysBreakType: None 18 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sh eol=lf 2 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **What does this PR do?**: 2 | 3 | 4 | **Motivation**: 5 | 6 | 7 | **Additional Notes**: 8 | 9 | 10 | **How to test the change?**: 11 | 17 | 18 | **For Datadog employees**: 19 | - [ ] If this PR touches code that signs or publishes builds or packages, or handles 20 | credentials of any kind, I've requested a review from `@DataDog/security-design-and-guidance`. 21 | - [ ] This PR doesn't touch any of that. 22 | - [ ] JIRA: [JIRA-XXXX] 23 | 24 | Unsure? Have a question? Request a review! 25 | -------------------------------------------------------------------------------- /.github/actions/extract_versions/action.yml: -------------------------------------------------------------------------------- 1 | name: Extract Java and Gradle Versions 2 | description: Versions are reported in JAVA_VERSION and GRADLE_VERSION environment variables 3 | 4 | runs: 5 | using: "composite" 6 | steps: 7 | - name: Extract Versions 8 | id: extract_versions 9 | shell: bash 10 | run: | 11 | set +e 12 | 13 | # Extract Java version 14 | ${{ env.JAVA_TEST_HOME }}/bin/java -version 15 | JAVA_VERSION=$(${{ env.JAVA_TEST_HOME }}/bin/java -version 2>&1 | awk -F '"' '/version/ { 16 | split($2, v, "[._]"); 17 | if (v[2] == "") { 18 | # Version is like "24": assume it is major only and add .0.0 19 | printf "%s.0.0\n", v[1] 20 | } else if (v[1] == "1") { 21 | # Java 8 or older: Format is "1.major.minor_update" 22 | printf "%s.%s.%s\n", v[2], v[3], v[4] 23 | } else { 24 | # Java 9 or newer: Format is "major.minor.patch" 25 | printf "%s.%s.%s\n", v[1], v[2], v[3] 26 | } 27 | }') 28 | echo "JAVA_VERSION=${JAVA_VERSION}" 29 | echo "JAVA_VERSION=${JAVA_VERSION}" >> $GITHUB_ENV 30 | 31 | # Extract Gradle version from gradle-wrapper.properties 32 | gradle_version=$(grep 'distributionUrl' gradle/wrapper/gradle-wrapper.properties | cut -d'=' -f2) 33 | gradle_version=${gradle_version#*gradle-} 34 | gradle_version=${gradle_version%-bin.zip} 35 | echo "GRADLE_VERSION=${gradle_version}" >> $GITHUB_ENV -------------------------------------------------------------------------------- /.github/actions/pr_comment/action.yml: -------------------------------------------------------------------------------- 1 | name: "Add or Update Comment" 2 | description: "Adds a comment or updates it based on the given text id" 3 | 4 | inputs: 5 | github-token: 6 | description: "Github token associated with the request" 7 | required: true 8 | commenter: 9 | description: "The commenter identity" 10 | required: true 11 | comment-id: 12 | description: "The text uniquely identifying the comment to update" 13 | required: true 14 | comment-file: 15 | description: "The file containing the comment in HTML format" 16 | required: true 17 | 18 | runs: 19 | using: composite 20 | steps: 21 | - name: Add or Update GitHub comment 22 | uses: actions/github-script@v5 23 | with: 24 | github-token: ${{ inputs.github-token }} 25 | script: | 26 | const fs = require('fs'); 27 | const comment_id = "${{ inputs.comment-id }}"; 28 | const commenter = "${{ inputs.commenter }}" 29 | const new_comment = fs.readFileSync("${{ inputs.comment-file }}", 'utf8'); 30 | 31 | // List all comments in the issue 32 | const comments = await github.rest.issues.listComments({ 33 | owner: context.repo.owner, 34 | repo: context.repo.repo, 35 | issue_number: context.issue.number 36 | }); 37 | 38 | const id = `` 39 | const content = `${id} \n 🔧 Report generated by ${commenter}\n${new_comment}` 40 | // Find the comment with the search text 41 | const comment = comments.data.find(c => c.body.includes(id)); 42 | 43 | if (comment) { 44 | // Update the comment 45 | await github.rest.issues.updateComment({ 46 | owner: context.repo.owner, 47 | repo: context.repo.repo, 48 | comment_id: comment.id, 49 | body: content 50 | }); 51 | } else { 52 | // Add a new comment 53 | await github.rest.issues.createComment({ 54 | owner: context.repo.owner, 55 | repo: context.repo.repo, 56 | issue_number: context.issue.number, 57 | body: content 58 | }); 59 | } -------------------------------------------------------------------------------- /.github/actions/setup_cached_java/action.yml: -------------------------------------------------------------------------------- 1 | name: "Setup test Java environment" 2 | description: "Setup Java environment for testing" 3 | 4 | inputs: 5 | version: 6 | description: "The test JDK version to install" 7 | required: true 8 | default: "11" 9 | arch: 10 | description: "The architecture" 11 | required: true 12 | default: "amd64" 13 | 14 | runs: 15 | using: composite 16 | steps: 17 | - name: Infer Build JDK 18 | shell: bash 19 | id: infer_build_jdk 20 | run: | 21 | echo "Infering JDK 11 [${{ inputs.arch }}]" 22 | if [[ ${{ inputs.arch }} =~ "-musl" ]]; then 23 | echo "build_jdk=jdk11-librca" >> $GITHUB_OUTPUT 24 | else 25 | echo "build_jdk=jdk11" >> $GITHUB_OUTPUT 26 | fi 27 | - name: Cache Build JDK [${{ inputs.arch }}] 28 | id: cache_build_jdk 29 | uses: actions/cache/restore@v4 30 | with: 31 | path: | 32 | jdks/${{ steps.infer_build_jdk.outputs.build_jdk }} 33 | key: ${{ steps.infer_build_jdk.outputs.build_jdk }}-${{ inputs.arch }}--${{ hashFiles('.github/workflows/cache_java.yml', '.github/scripts/java_setup.sh') }} 34 | restore-keys: | 35 | ${{ steps.infer_build_jdk.outputs.build_jdk }}-${{ inputs.arch }}-- 36 | enableCrossOsArchive: true 37 | - name: Cache JDK ${{ inputs.version }} [${{ inputs.arch }}] 38 | id: cache_jdk 39 | uses: actions/cache/restore@v4 40 | with: 41 | path: | 42 | jdks/jdk${{ inputs.version }} 43 | key: jdk${{ inputs.version }}-${{ inputs.arch }}--${{ hashFiles('.github/workflows/cache_java.yml', '.github/scripts/java_setup.sh') }} 44 | restore-keys: | 45 | jdk${{ inputs.version }}-${{ inputs.arch }}-- 46 | enableCrossOsArchive: true 47 | - name: JDK cache miss 48 | if: steps.cache_jdk.outputs.cache-hit != 'true' || steps.cache_build_jdk.outputs.cache-hit != 'true' 49 | shell: bash 50 | run: | 51 | # well, the cache-hit is not alway set to 'true', even when cache is hit (but it is not freshly recreated, whatever that means) 52 | if [ ! -d "jdks/jdk${{ inputs.version }}" ]; then 53 | OWNER=${{ github.repository_owner }} 54 | REPO=${{ github.event.repository.name }} 55 | BRANCH=${{ github.ref_name }} 56 | WORKFLOW="cache_java.yml" 57 | 58 | URL="https://github.com/$OWNER/$REPO/actions/workflows/$WORKFLOW" 59 | 60 | echo "### ⚠️ JDK Cache Miss Detected" >> $GITHUB_STEP_SUMMARY 61 | echo "🛠️ [Click here and select ${BRANCH} branch to manually refresh the cache](<$URL>)" >> $GITHUB_STEP_SUMMARY 62 | exit 1 63 | fi 64 | - name: Setup Environment 65 | shell: bash 66 | run: | 67 | chmod a+rx -R jdks 68 | echo "Setting up JDK ${{ inputs.version }} [${{ inputs.arch }}]" 69 | JAVA_HOME=$(pwd)/jdks/${{ steps.infer_build_jdk.outputs.build_jdk }} 70 | JAVA_TEST_HOME=$(pwd)/jdks/jdk${{ inputs.version }} 71 | PATH=$JAVA_HOME/bin:$PATH 72 | echo "JAVA_HOME=$JAVA_HOME" >> $GITHUB_ENV 73 | echo "JAVA_TEST_HOME=$JAVA_TEST_HOME" >> $GITHUB_ENV 74 | echo "PATH=$JAVA_HOME/bin:$PATH" >> $GITHUB_ENV 75 | -------------------------------------------------------------------------------- /.github/scripts/cppcheck-suppressions.txt: -------------------------------------------------------------------------------- 1 | cstyleCast 2 | constParameter 3 | obsoleteFunctions:flightRecorder.cpp 4 | obsoleteFunctionsalloca:flightRecorder.cpp -------------------------------------------------------------------------------- /.github/scripts/java_setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | function prepareJdk() { 4 | local variant=$1 5 | local arch=$2 6 | local version=${variant%%-*} 7 | local qualifier=${variant#*-} 8 | 9 | local target_path="${GITHUB_WORKSPACE}/${JDKS_DIR}/jdk${variant}" 10 | 11 | mkdir -p ${target_path} 12 | 13 | if [[ ${qualifier} == "librca" ]] && [[ "${arch}" =~ "-musl" ]]; then 14 | local osarch="${arch%-musl}" 15 | local suffix= 16 | if [[ "${osarch}" == "aarch64" ]]; then 17 | suffix="AARCH64_" 18 | fi 19 | URL_VAR="JAVA_${version}_MUSL_${suffix}URL" 20 | URL="${!URL_VAR}" 21 | if [[ -z "${URL}" ]]; then 22 | echo "Musl/Liberica JDK URL not found for ${arch}/${variant}" 23 | exit 1 24 | fi 25 | curl -L --fail "${URL}" | tar -xvzf - -C ${target_path} --strip-components 1 26 | return 27 | fi 28 | 29 | if [[ ${qualifier} == "orcl" ]]; then 30 | if [[ ${version} == "8" ]]; then 31 | mkdir -p "${target_path}" 32 | curl -L --fail "${JAVA_8_ORACLE_URL}" | sudo tar -xvzf - -C ${target_path} --strip-components 1 33 | return 34 | else 35 | echo "Oracle JDK 8 only!" 36 | exit 1 37 | fi 38 | fi 39 | 40 | if [[ ${qualifier} == "ibm" ]]; then 41 | if [[ ${version} == "8" ]]; then 42 | mkdir -p "${target_path}" 43 | curl -L --fail "${JAVA_8_IBM_URL}" | sudo tar -xvzf - -C ${target_path} --strip-components 2 44 | return 45 | else 46 | echo "IBM JDK 8 only!" 47 | exit 1 48 | fi 49 | fi 50 | 51 | if [[ ${qualifier} == "zing" ]]; then 52 | URL_VAR="JAVA_${version}_ZING_URL" 53 | if [[ "${arch}" == "aarch64" ]]; then 54 | URL_VAR="JAVA_${version}_ZING_AARCH64_URL" 55 | fi 56 | 57 | URL="${!URL_VAR}" 58 | if [[ -z "${URL}" ]]; then 59 | echo "Zing JDK URL not found for ${variant}" 60 | exit 1 61 | fi 62 | curl -L --fail "${URL}" | sudo tar -xvzf - -C ${target_path} --strip-components 1 63 | if [[ "${arch}" != "aarch64" ]]; then 64 | # rename the bundled libstdc++.so to avoid conflicts with the system one 65 | sudo mv ${target_path}/etc/libc++/libstdc++.so.6 ${target_path}/etc/libc++/libstdc++.so.6.bak 66 | fi 67 | return 68 | fi 69 | 70 | # below the installation of the SDKMAN-managed JDK 71 | source ~/.sdkman/bin/sdkman-init.sh 72 | 73 | local suffix="tem" 74 | local versionVar="JAVA_${version}_VERSION" 75 | if [[ "${qualifier}" == "j9" ]]; then 76 | suffix="sem" 77 | versionVar="JAVA_${version}_J9_VERSION" 78 | elif [[ "${qualifier}" == "graal" ]]; then 79 | suffix="graal" 80 | versionVar="JAVA_${version}_GRAAL_VERSION" 81 | fi 82 | 83 | local distro_base 84 | distro_base="${!versionVar}" 85 | local jdk_distro="${distro_base}-${suffix}" 86 | 87 | echo 'n' | sdk install java ${jdk_distro} > /dev/null 88 | 89 | rm -rf ${target_path} 90 | mkdir -p "$(dirname ${target_path})" 91 | mv ${SDKMAN_DIR}/candidates/java/${jdk_distro} ${target_path} 92 | } 93 | -------------------------------------------------------------------------------- /.github/scripts/prepare_reports.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | mkdir -p reports 5 | cp /tmp/hs_err* reports/ || true 6 | cp ddprof-test/javacore*.txt reports/ || true 7 | cp ddprof-test/build/hs_err* reports/ || true 8 | cp -r ddprof-lib/build/tmp reports/native_build || true 9 | cp -r ddprof-test/build/reports/tests reports/tests || true 10 | cp -r /tmp/recordings reports/recordings || true 11 | find ddprof-lib/build -name 'libjavaProfiler.*' -exec cp {} reports/ \; || true 12 | -------------------------------------------------------------------------------- /.github/scripts/python_utils.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from bs4 import BeautifulSoup 3 | 4 | def remove_tags(soup, tags_to_remove): 5 | for tag in tags_to_remove: 6 | for element in soup.find_all(tag): 7 | element.decompose() 8 | 9 | def create_scanbuild_code_links(soup, target_branch): 10 | target = None 11 | for element in soup.find_all("td"): 12 | clz = element.get('class') 13 | if clz is None: 14 | src = element.text 15 | target = element 16 | elif 'Q' in clz and target is not None and target.text != 'Function/Method': 17 | line = element.text 18 | link = soup.new_tag('a', href=f'https://github.com/DataDog/java-profiler/blob/{target_branch}/ddprof-lib/src/main/cpp/{src}#L{line}') 19 | link.string = src 20 | target.clear() 21 | target.append(link) 22 | target = None 23 | def parse_table(table): 24 | markdown_table = [] 25 | for row in table.find_all('tr'): 26 | cells = row.find_all(['th', 'td']) 27 | markdown_cells = [cell.get_text(strip=True) for cell in cells] 28 | markdown_table.append('| ' + ' | '.join(markdown_cells) + ' |') 29 | return '\n'.join(markdown_table) 30 | 31 | def scanbuild_cleanup(soup, args): 32 | target_branch = args[0] 33 | remove_tags(soup, ["title", "script", "a"]) 34 | create_scanbuild_code_links(soup, target_branch) 35 | title = soup.find('h1') 36 | title.string = 'Scan-Build Report' 37 | return str(soup) 38 | 39 | def cppcheck_cleanup(soup, args): 40 | remove_tags(soup, ["title", "style", "head"]) 41 | return str(soup) 42 | 43 | def usage(soup, args): 44 | return "Usage" 45 | 46 | if __name__ == "__main__": 47 | actions = { 48 | "scanbuild_cleanup": scanbuild_cleanup, 49 | "cppcheck_cleanup": cppcheck_cleanup, 50 | } 51 | action = actions.get(sys.argv[1], usage) 52 | input_file = sys.argv[2] 53 | args = sys.argv[3:] 54 | 55 | with open(input_file, "r") as file: 56 | html_content = file.read() 57 | 58 | soup = BeautifulSoup(html_content, "html.parser") 59 | print(action(soup, args)) 60 | -------------------------------------------------------------------------------- /.github/scripts/release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -x 4 | set -e 5 | 6 | TYPE=$1 7 | DRYRUN=$2 8 | 9 | BRANCH=$(git branch --show-current) 10 | RELEASE_BRANCH= 11 | 12 | BASE=$(./gradlew printVersion -Psnapshot=false | grep 'Version:' | cut -f2 -d' ') 13 | # BASE == 0.0.1 14 | 15 | if [ "$TYPE" == "MINOR" ] || [ "$TYPE" == "MAJOR" ]; then 16 | if [ "$BRANCH" != "main" ] && [ -z "$DRYRUN" ]; then 17 | echo "Major or minor release can be performed only from 'main' branch." 18 | exit 1 19 | fi 20 | if [ "$TYPE" == "MAJOR" ]; then 21 | # 0.1.0 -> 1.0.0 22 | ./gradlew incrementVersion --versionIncrementType=MAJOR 23 | BASE=$(./gradlew printVersion -Psnapshot=false | grep 'Version:' | cut -f2 -d' ') 24 | # BASE == 1.0.0 25 | fi 26 | RELEASE_BRANCH="release/${BASE%.*}._" 27 | git tag -f v_$BASE 28 | fi 29 | 30 | if [ "$TYPE" == "PATCH" ]; then 31 | if [[ ! $BRANCH =~ ^release\/[0-9]+\.[0-9]+\._$ ]] && [ -z "$DRYRUN" ]; then 32 | echo "Patch release can be created only for 'release/*' branch." 33 | exit 1 34 | fi 35 | RELEASE_BRANCH="release/${BASE%.*}._" 36 | fi 37 | 38 | if [ "$BRANCH" != "$RELEASE_BRANCH" ]; then 39 | git checkout -b $RELEASE_BRANCH 40 | if ! git diff --quiet; then 41 | git add build.gradle 42 | git commit -m "[Automated] Release ${BASE}" 43 | fi 44 | git push $DRYRUN --atomic --set-upstream origin $RELEASE_BRANCH 45 | git checkout $BRANCH 46 | fi 47 | 48 | if [ "$TYPE" == "MAJOR" ] || [ "$TYPE" == "MINOR" ]; then 49 | ./gradlew incrementVersion --versionIncrementType=MINOR 50 | else 51 | ./gradlew incrementVersion --versionIncrementType=PATCH 52 | fi 53 | 54 | CANDIDATE=$(./gradlew printVersion -Psnapshot=false | grep 'Version:' | cut -f2 -d' ') 55 | 56 | git add build.gradle 57 | git commit -m "[Automated] Bump dev version to ${CANDIDATE}" 58 | 59 | git push $DRYRUN --atomic --set-upstream origin $BRANCH 60 | git push $DRYRUN -f --atomic --tags 61 | -------------------------------------------------------------------------------- /.github/scripts/test_alpine_aarch64.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | set -e 4 | set +x 5 | 6 | export KEEP_JFRS=true 7 | export TEST_COMMIT="${1}" 8 | export TEST_CONFIGURATION="${2}" 9 | export LIBRARY="musl" 10 | export CONFIG="${3}" 11 | export JAVA_HOME="${4}" 12 | export JAVA_TEST_HOME="${5}" 13 | 14 | export PATH="${JAVA_HOME}/bin":${PATH} 15 | 16 | # due to env hell in GHA containers, we need to re-do the logic from Extract Versions here 17 | JAVA_VERSION=$("${JAVA_TEST_HOME}/bin/java" -version 2>&1 | awk -F '"' '/version/ { 18 | split($2, v, "[._]"); 19 | if (v[2] == "") { 20 | # Version is like "24": assume it is major only and add .0.0 21 | printf "%s.0.0\n", v[1] 22 | } else if (v[1] == "1") { 23 | # Java 8 or older: Format is "1.major.minor_update" 24 | printf "%s.%s.%s\n", v[2], v[3], v[4] 25 | } else { 26 | # Java 9 or newer: Format is "major.minor.patch" 27 | printf "%s.%s.%s\n", v[1], v[2], v[3] 28 | } 29 | }') 30 | export JAVA_VERSION 31 | 32 | apk update && apk add curl moreutils wget hexdump linux-headers bash make g++ clang git cppcheck jq cmake gtest-dev gmock tar >/dev/null 33 | 34 | ./gradlew -PCI -PkeepJFRs :ddprof-test:test${CONFIG} --no-daemon --parallel --build-cache --no-watch-fs -------------------------------------------------------------------------------- /.github/workflows/add-milestone-to-pull-requests.yaml: -------------------------------------------------------------------------------- 1 | name: Add milestone to pull requests 2 | on: 3 | pull_request: 4 | types: [closed] 5 | branches: 6 | - main 7 | 8 | permissions: 9 | contents: read 10 | pull-requests: write 11 | issues: write 12 | 13 | jobs: 14 | add_milestone_to_merged: 15 | if: github.event.pull_request.merged && github.event.pull_request.milestone == null 16 | name: Add milestone to merged pull requests 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Get project milestones 20 | id: milestones 21 | uses: actions/github-script@47f7cf65b5ced0830a325f705cad64f2f58dddf7 # 3.1.0 22 | with: 23 | github-token: ${{secrets.GITHUB_TOKEN}} 24 | script: | 25 | const list = await github.issues.listMilestones({ 26 | owner: context.repo.owner, 27 | repo: context.repo.repo, 28 | state: 'open' 29 | }) 30 | // Need to manually sort because "sort by number" isn't part of the api 31 | // highest number first 32 | const milestones = list.data.sort((a,b) => (b.number - a.number)) 33 | 34 | return milestones.length == 0 ? null : milestones[0].number 35 | - name: Update Pull Request 36 | if: steps.milestones.outputs.result != null 37 | uses: actions/github-script@47f7cf65b5ced0830a325f705cad64f2f58dddf7 # 3.1.0 38 | with: 39 | github-token: ${{secrets.GITHUB_TOKEN}} 40 | script: | 41 | // Confusingly, the issues api is used because pull requests are issues 42 | await github.issues.update({ 43 | owner: context.repo.owner, 44 | repo: context.repo.repo, 45 | issue_number: ${{ github.event.pull_request.number }}, 46 | milestone: ${{ steps.milestones.outputs.result }}, 47 | }); 48 | -------------------------------------------------------------------------------- /.github/workflows/approve-trivial.yml: -------------------------------------------------------------------------------- 1 | name: Auto-Approve Trivial PRs 2 | 3 | on: 4 | pull_request_target: 5 | types: [labeled] 6 | 7 | permissions: 8 | pull-requests: write 9 | contents: read 10 | 11 | jobs: 12 | auto-approve: 13 | if: contains(github.event.pull_request.labels.*.name, 'trivial') || contains(github.event.pull_request.labels.*.name, 'no-review') 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Auto-approve PR 17 | uses: hmarr/auto-approve-action@v4 18 | with: 19 | github-token: ${{ secrets.GITHUB_TOKEN }} 20 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI Run 2 | 3 | concurrency: 4 | group: pr-ci_${{ github.event.pull_request.number }} 5 | cancel-in-progress: true 6 | 7 | on: 8 | push: 9 | branches: 10 | - '*' 11 | tags-ignore: 12 | - v* 13 | pull_request: 14 | workflow_dispatch: 15 | 16 | permissions: 17 | contents: read 18 | pull-requests: read 19 | actions: read 20 | 21 | jobs: 22 | check-for-pr: 23 | runs-on: ubuntu-latest 24 | outputs: 25 | skip: ${{ steps.check.outputs.skip }} 26 | steps: 27 | - name: Check if PR exists 28 | id: check 29 | env: 30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | HEAD_REF: ${{ github.head_ref }} 32 | run: | 33 | if [ -z "${{ github.base_ref }}" ]; then 34 | prs=$(gh pr list \ 35 | --repo "$GITHUB_REPOSITORY" \ 36 | --json baseRefName,headRefName \ 37 | --jq ' 38 | map(select(.baseRefName == "${{ github.base_ref }}" and .headRefName == "$HEAD_REF}")) 39 | | length 40 | ') 41 | if ((prs > 0)); then 42 | echo "skip=true" >> "$GITHUB_OUTPUT" 43 | fi 44 | fi 45 | check-formatting: 46 | runs-on: ubuntu-22.04 47 | needs: check-for-pr 48 | if: needs.check-for-pr.outputs.skip != 'true' 49 | steps: 50 | - uses: actions/checkout@v3 51 | - name: Setup OS 52 | run: | 53 | sudo apt-get update 54 | sudo apt-get install -y clang-format-11 55 | # we need this to make sure we are actually using clang-format v. 11 56 | sudo mv /usr/bin/clang-format /usr/bin/clang-format-14 57 | sudo mv /usr/bin/clang-format-11 /usr/bin/clang-format 58 | 59 | - name: Cache Gradle Wrapper Binaries 60 | uses: actions/cache@v4 61 | with: 62 | path: ~/.gradle/wrapper/dists 63 | key: gradle-wrapper-${{ runner.os }}-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }} 64 | restore-keys: | 65 | gradle-wrapper-${{ runner.os }}- 66 | 67 | - name: Cache Gradle User Home 68 | uses: actions/cache@v4 69 | with: 70 | path: ~/.gradle/caches 71 | key: gradle-caches-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} 72 | restore-keys: | 73 | gradle-caches-${{ runner.os }}- 74 | 75 | - name: Check 76 | run: | 77 | ./gradlew spotlessCheck --no-daemon --parallel --build-cache --no-watch-fs 78 | 79 | test-matrix: 80 | needs: check-formatting 81 | if: needs.check-for-pr.outputs.skip != 'true' 82 | uses: ./.github/workflows/test_workflow.yml 83 | with: 84 | configuration: '["debug"]' 85 | 86 | gh-release: 87 | if: startsWith(github.event.ref, 'refs/heads/release/') 88 | runs-on: ubuntu-latest 89 | needs: [test-matrix] 90 | steps: 91 | - name: Create Github Release 92 | uses: ./.github/workflows/gh_release.yml@gh-release 93 | with: 94 | release_branch: ${GITHUB_REF_NAME} 95 | -------------------------------------------------------------------------------- /.github/workflows/create-next-milestone.yaml: -------------------------------------------------------------------------------- 1 | name: Create next milestone 2 | on: 3 | milestone: 4 | types: [closed] 5 | 6 | permissions: 7 | contents: read 8 | issues: write 9 | 10 | jobs: 11 | create_next_milestone: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Get next minor version 15 | id: semvers 16 | uses: WyriHaximus/github-action-next-semvers@33d116a4c239252582a60a1ba8dbba63ad493ffd # 1.1.0 17 | with: 18 | version: ${{ github.event.milestone.title }} 19 | - name: Create next milestone 20 | uses: WyriHaximus/github-action-create-milestone@b86699ba7511fa3b61154ac8675d86b01938fc16 # 1.0.0 21 | with: 22 | title: ${{ steps.semvers.outputs.minor }} 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | -------------------------------------------------------------------------------- /.github/workflows/gh_release.yml: -------------------------------------------------------------------------------- 1 | name: Github Release 2 | run-name: Release ${{ inputs.release_tag }} ${{ github.event.ref_name }} 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | release_tag: 7 | type: string 8 | description: "Release tag" 9 | required: true 10 | workflow_call: 11 | inputs: 12 | release_tag: 13 | type: string 14 | description: "Release tag" 15 | required: false 16 | push: 17 | tags: 18 | - v_*.*.* 19 | 20 | permissions: 21 | contents: write 22 | actions: read 23 | 24 | jobs: 25 | gh-release: 26 | if: (startsWith(github.event.ref, 'refs/tags/v_') || inputs.release_tag != '') && !endsWith(github.event.ref, '-SNAPSHOT') 27 | runs-on: ubuntu-latest 28 | steps: 29 | - uses: actions/checkout@v3 30 | with: 31 | fetch-depth: 0 32 | - uses: webfactory/ssh-agent@v0.7.0 33 | with: 34 | ssh-private-key: ${{ secrets.SSH_PUSH_SECRET }} 35 | - name: Create Release [automatic] 36 | id: create_release_auto 37 | uses: ncipollo/release-action@v1 38 | if: ${{ startsWith(github.ref, 'refs/tags/') }} 39 | with: 40 | generateReleaseNotes: true 41 | allowUpdates: true 42 | draft: true 43 | env: 44 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 45 | - name: Create Release [manual] 46 | id: create_release_manual 47 | uses: ncipollo/release-action@v1 48 | if: ${{ !startsWith(github.ref, 'refs/tags/') }} 49 | with: 50 | generateReleaseNotes: true 51 | allowUpdates: true 52 | tag: ${{ inputs.release_tag}} 53 | draft: true 54 | env: 55 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/increment-milestones-on-tag.yaml: -------------------------------------------------------------------------------- 1 | name: Increment milestones on tag 2 | on: 3 | create 4 | 5 | permissions: 6 | contents: read 7 | issues: write 8 | 9 | jobs: 10 | increment_milestone: 11 | if: github.event.ref_type == 'tag' && github.event.master_branch == 'main' 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Get milestone title 15 | id: milestoneTitle 16 | uses: actions/github-script@47f7cf65b5ced0830a325f705cad64f2f58dddf7 # 3.1.0 17 | with: 18 | result-encoding: string 19 | script: | 20 | // Our tags are of the form v_X.X.X and milestones don't have the "v" 21 | return '${{github.event.ref}}'.startsWith('v_') ? '${{github.event.ref}}'.substring(2) : '${{github.event.ref}}'; 22 | - name: Get milestone for tag 23 | id: milestone 24 | uses: actions/github-script@47f7cf65b5ced0830a325f705cad64f2f58dddf7 # 3.1.0 25 | with: 26 | github-token: ${{secrets.GITHUB_TOKEN}} 27 | script: | 28 | const milestones = await github.paginate(github.issues.listMilestones, { 29 | owner: context.repo.owner, 30 | repo: context.repo.repo, 31 | state: 'all' 32 | }) 33 | 34 | const milestone = milestones.find(milestone => milestone.title == '${{steps.milestoneTitle.outputs.result}}') 35 | 36 | if (milestone) { 37 | return milestone.number 38 | } else { 39 | return null 40 | } 41 | - name: Close milestone 42 | if: fromJSON(steps.milestone.outputs.result) 43 | uses: actions/github-script@47f7cf65b5ced0830a325f705cad64f2f58dddf7 # 3.1.0 44 | with: 45 | github-token: ${{secrets.GITHUB_TOKEN}} 46 | script: | 47 | await github.issues.updateMilestone({ 48 | owner: context.repo.owner, 49 | repo: context.repo.repo, 50 | state: 'closed', 51 | milestone_number: ${{steps.milestone.outputs.result}} 52 | }) 53 | - name: Get next minor version 54 | if: fromJSON(steps.milestone.outputs.result) 55 | id: semvers 56 | uses: WyriHaximus/github-action-next-semvers@33d116a4c239252582a60a1ba8dbba63ad493ffd # 1.1.0 57 | with: 58 | version: ${{steps.milestoneTitle.outputs.result}} 59 | - name: Create next milestone 60 | if: fromJSON(steps.milestone.outputs.result) 61 | uses: WyriHaximus/github-action-create-milestone@b86699ba7511fa3b61154ac8675d86b01938fc16 # 1.0.0 62 | with: 63 | title: ${{ steps.semvers.outputs.minor }} 64 | env: 65 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 66 | -------------------------------------------------------------------------------- /.github/workflows/nightly.yml: -------------------------------------------------------------------------------- 1 | name: Nightly Sanitized Run 2 | 3 | on: 4 | schedule: 5 | # Runs every day at 03:00 UTC 6 | - cron: '0 3 * * *' 7 | workflow_dispatch: 8 | 9 | permissions: 10 | contents: read 11 | actions: read 12 | 13 | jobs: 14 | run-test: 15 | uses: ./.github/workflows/test_workflow.yml 16 | with: 17 | configuration: '["asan"]' # Ignoring tsan for now '["asan", "tsan"]' 18 | report-failures: 19 | runs-on: ubuntu-latest 20 | needs: run-test 21 | if: failure() 22 | steps: 23 | - name: Download failed tests artifact 24 | uses: actions/download-artifact@v4 25 | with: 26 | name: failures 27 | path: ./artifacts 28 | - name: Report failures 29 | run: | 30 | find ./artifacts -name 'failures_*' -exec cat {} \; > ./artifacts/failures.txt 31 | scenarios=$(cat ./artifacts/failures.txt | tr '\n' ',') 32 | 33 | echo "Failed scenarios: $scenarios" 34 | 35 | curl -X POST "${{ secrets.SLACK_WEBHOOK }}" \ 36 | -H 'Content-Type: application/json' \ 37 | -d "{'scenarios': '${scenarios}', 'failed_run_url': '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}'}" 38 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Automated Release 2 | run-name: "${{ inputs.dry_run && 'Dry-run for ' || 'Preform ' }} ${{ inputs.release_type }} release of ${{ github.ref }} branch" 3 | 4 | on: 5 | workflow_dispatch: 6 | inputs: 7 | release_type: 8 | type: choice 9 | description: The release type 10 | options: 11 | - "major" 12 | - "minor" 13 | - "patch" 14 | default: "minor" 15 | dry_run: 16 | description: Perform the release dry-run 17 | required: true 18 | type: boolean 19 | default: true 20 | 21 | permissions: 22 | contents: write 23 | actions: read 24 | 25 | jobs: 26 | release-branch: 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: Output Inputs 30 | run: | 31 | echo "${{ toJSON(github.event.inputs) }}" 32 | echo "${{ toJSON(inputs) }}" 33 | echo "${{ inputs.release_type }}" 34 | - uses: webfactory/ssh-agent@v0.7.0 35 | with: 36 | ssh-private-key: ${{ secrets.SSH_PUSH_SECRET }} 37 | - name: Checkout ${{ env.GITHUB_REPOSITORY }} 38 | run: git clone git@github.com:$GITHUB_REPOSITORY.git java-profiler 39 | - name: Configure git env 40 | run: | 41 | git config --global user.email "java-profiler@datadoghq.com" 42 | git config --global user.name "Datadog Java Profiler" 43 | - name: Create release 44 | env: 45 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 46 | run: | 47 | if [ "${{ inputs.dry_run }}" != "true" ]; then 48 | if [ "${{ inputs.release_type }}" != "patch" ]&& [[ ${GITHUB_REF_NAME} =~ release/.* ]]; then 49 | echo "::error Only patch release can be performed from a release branch" 50 | exit 1 51 | fi 52 | if [ "${{ inputs.release_type }}" == "patch" ]&& [[ ! ${GITHUB_REF_NAME} =~ release/.* ]]; then 53 | echo "::error::A patch release can be performed only from a release branch" 54 | exit 1 55 | fi 56 | if [ "${{ inputs.release_type }}" != "patch" ]&& [[ ! ${GITHUB_REF_NAME} =~ main ]]; then 57 | echo "::error::A major or minor release can be performed only from 'main' branch" 58 | exit 1 59 | fi 60 | else 61 | DRY_RUN="--dry-run" 62 | fi 63 | 64 | TYPE="${{ inputs.release_type }}" 65 | cd java-profiler 66 | git checkout $GITHUB_REF_NAME 67 | ./.github/scripts/release.sh ${TYPE^^} $DRY_RUN -------------------------------------------------------------------------------- /.github/workflows/update_assets.yml: -------------------------------------------------------------------------------- 1 | name: Update Release Assets 2 | run-name: Update assets for ${{ inputs.release_tag }} 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | release_tag: 7 | type: string 8 | description: "Release tag" 9 | required: true 10 | push: 11 | tags: 12 | - v_*.*.* 13 | 14 | permissions: 15 | contents: write 16 | actions: read 17 | 18 | jobs: 19 | update-assets-and-releaase: 20 | if: (startsWith(github.event.ref, 'refs/tags/v_') || inputs.release_tag != '') && !endsWith(github.event.ref, '-SNAPSHOT') 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Setup System 24 | id: setup-system 25 | run: | 26 | sudo apt update && sudo apt install -y wget unzip 27 | - name: Download Assets 28 | id: download-assets 29 | timeout-minutes: 30 30 | run: | 31 | # ignore errors to allow reattempted downloads 32 | set +e 33 | TAG=${{ inputs.release_tag }} 34 | if [ -z "$TAG" ]; then 35 | TAG="$GITHUB_REF_NAME" 36 | fi 37 | VERSION=$(echo "${TAG}" | sed -e 's/v_//g') 38 | ASSET_URL="https://oss.sonatype.org/service/local/repositories/releases/content/com/datadoghq/ddprof/${VERSION}/ddprof-${VERSION}.jar" 39 | RESULT=1 40 | while [ $RESULT -ne 0 ]; do 41 | wget -q $ASSET_URL 42 | RESULT=$? 43 | if [ $RESULT -ne 0 ]; then 44 | echo "Artifact not available. Retrying in 30 seconds." 45 | sleep 30 46 | fi 47 | done 48 | echo "VERSION=${VERSION}" >> $GITHUB_ENV 49 | - name: Prepare Assets 50 | id: prepare-assets 51 | run: | 52 | LIB_BASE_DIR="META-INF/native-libs" 53 | mkdir assets 54 | cp ddprof-${VERSION}.jar assets/ddprof.jar 55 | cp ddprof-${VERSION}.jar assets/ddprof-${VERSION}.jar 56 | unzip ddprof-${VERSION}.jar 57 | mv ${LIB_BASE_DIR}/linux-arm64/libjavaProfiler.so assets/libjavaProfiler_linux-arm64.so 58 | mv ${LIB_BASE_DIR}/linux-x64/libjavaProfiler.so assets/libjavaProfiler_linux-x64.so 59 | mv ${LIB_BASE_DIR}/linux-arm64-musl/libjavaProfiler.so assets/libjavaProfiler_linux-arm64-musl.so 60 | mv ${LIB_BASE_DIR}/linux-x64-musl/libjavaProfiler.so assets/libjavaProfiler_linux-x64-musl.so 61 | - name: Update release ${{ inputs.release_tag }} 62 | id: update-release 63 | uses: ncipollo/release-action@v1 64 | with: 65 | token: ${{ secrets.GITHUB_TOKEN }} 66 | tag: "v_${{ env.VERSION }}" 67 | allowUpdates: true 68 | generateReleaseNotes: true 69 | omitBodyDuringUpdate: true 70 | artifacts: assets/ddprof*.jar,assets/*.so 71 | draft: false 72 | makeLatest: true 73 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/build/ 2 | **/build_*/ 3 | **/build-*/ 4 | /nbproject/ 5 | /out/ 6 | /.idea/ 7 | /target/ 8 | **/*.class 9 | **/*.class.h 10 | **/*.so 11 | **/*.o 12 | .vscode 13 | .classpath 14 | .project 15 | .settings 16 | .gradle 17 | .tmp 18 | *.iml 19 | /ddprof-stresstest/jmh-result.* 20 | /ddprof-lib/src/main/cpp-external/**/* 21 | 22 | **/.resources/ 23 | 24 | # ignore all temporary locations related to maven builds 25 | datadog/maven/tmp 26 | datadog/maven/repository 27 | datadog/maven/resources 28 | 29 | **/harness* 30 | **/launcher* 31 | /gradle.properties 32 | **/hs_err* 33 | 34 | # cursor AI history 35 | .history 36 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # Triggers a build within the Datadog infrastructure in the ddprof-build repository 2 | trigger_internal_build: 3 | rules: 4 | - if: $CI_COMMIT_BRANCH =~ /release\/.*/ 5 | when: never 6 | - when: always 7 | allow_failure: false 8 | variables: 9 | DOWNSTREAM_BRANCH: "main" 10 | DDPROF_DEFAULT_BRANCH: "main" 11 | DDPROF_COMMIT_BRANCH: ${CI_COMMIT_BRANCH} 12 | DDROF_COMMIT_SHA: ${CI_COMMIT_SHA} 13 | DPROF_SHORT_COMMIT_SHA: ${CI_COMMIT_SHORT_SHA} 14 | DDPROF_COMMIT_TAG: ${CI_COMMIT_TAG} 15 | trigger: 16 | project: DataDog/apm-reliability/async-profiler-build 17 | strategy: depend 18 | branch: $DOWNSTREAM_BRANCH 19 | forward: 20 | pipeline_variables: true -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | !TODO! 3 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | dependencies { 3 | classpath("com.dipien:semantic-version-gradle-plugin:2.0.0") 4 | } 5 | repositories { 6 | mavenLocal() 7 | mavenCentral() 8 | gradlePluginPortal() 9 | } 10 | } 11 | 12 | plugins { 13 | id 'io.github.gradle-nexus.publish-plugin' version '2.0.0' 14 | id "com.diffplug.spotless" version "6.11.0" 15 | } 16 | 17 | version = "1.28.0" 18 | 19 | apply plugin: "com.dipien.semantic-version" 20 | version = project.findProperty("ddprof_version") ?: version 21 | 22 | allprojects { 23 | repositories { 24 | mavenCentral() 25 | mavenCentral() 26 | gradlePluginPortal() 27 | } 28 | } 29 | 30 | repositories { 31 | mavenLocal() 32 | mavenCentral() 33 | gradlePluginPortal() 34 | 35 | maven { 36 | content { 37 | includeGroup "com.datadoghq" 38 | } 39 | mavenContent { 40 | snapshotsOnly() 41 | } 42 | url 'https://oss.sonatype.org/content/repositories/snapshots/' 43 | } 44 | } 45 | 46 | allprojects { 47 | group = 'com.datadoghq' 48 | 49 | apply from: rootProject.file('common.gradle') 50 | apply from: rootProject.file('gradle/configurations.gradle') 51 | apply from: "$rootDir/gradle/spotless.gradle" 52 | } 53 | 54 | subprojects { 55 | version = rootProject.version 56 | } 57 | 58 | apply from: rootProject.file('common.gradle') 59 | apply from: rootProject.file('gradle/configurations.gradle') 60 | 61 | def isCI = System.getenv("CI") != null 62 | 63 | nexusPublishing { 64 | repositories { 65 | def forceLocal = project.hasProperty('forceLocal') 66 | 67 | if (forceLocal && !isCI) { 68 | local { 69 | // For testing use with https://hub.docker.com/r/sonatype/nexus 70 | // docker run --rm -d -p 8081:8081 --name nexus sonatype/nexus 71 | // Doesn't work for testing releases though... (due to staging) 72 | nexusUrl = uri("http://localhost:8081/nexus/content/repositories/releases/") 73 | snapshotRepositoryUrl = uri("http://localhost:8081/nexus/content/repositories/snapshots/") 74 | username = "admin" 75 | password = "admin123" 76 | } 77 | } else { 78 | sonatype { 79 | username = System.getenv("SONATYPE_USERNAME") 80 | password = System.getenv("SONATYPE_PASSWORD") 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /ddprof-lib/benchmarks/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'cpp-application' 3 | } 4 | 5 | // this feels weird but it is the only way invoking `./gradlew :ddprof-lib:*` tasks will work 6 | if (rootDir.toString().endsWith("ddprof-lib/gradle")) { 7 | apply from: rootProject.file('../../common.gradle') 8 | } 9 | 10 | application { 11 | baseName = "unwind_failures_benchmark" 12 | source.from file('src') 13 | privateHeaders.from file('src') 14 | 15 | targetMachines = [machines.macOS, machines.linux.x86_64] 16 | } 17 | 18 | // Include the main library headers 19 | tasks.withType(CppCompile).configureEach { 20 | includes file('../src/main/cpp').toString() 21 | } 22 | 23 | // Add a task to run the benchmark 24 | tasks.register('runBenchmark', Exec) { 25 | dependsOn 'assemble' 26 | workingDir = buildDir 27 | 28 | doFirst { 29 | // Find the executable by looking for it in the build directory 30 | def executableName = "unwind_failures_benchmark" 31 | def executable = null 32 | 33 | // Search for the executable in the build directory 34 | buildDir.eachFileRecurse { file -> 35 | if (file.isFile() && file.name == executableName && file.canExecute()) { 36 | executable = file 37 | return true // Stop searching once found 38 | } 39 | } 40 | 41 | if (executable == null) { 42 | throw new GradleException("Executable '${executableName}' not found in ${buildDir.absolutePath}. Make sure the build was successful.") 43 | } 44 | 45 | // Build command line with the executable path and any additional arguments 46 | def cmd = [executable.absolutePath] 47 | 48 | // Add any additional arguments passed to the Gradle task 49 | if (project.hasProperty('args')) { 50 | cmd.addAll(project.args.split(' ')) 51 | } 52 | 53 | println "Running benchmark using executable at: ${executable.absolutePath}" 54 | commandLine = cmd 55 | } 56 | 57 | doLast { 58 | println "Benchmark completed." 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ddprof-lib/benchmarks/build_run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 6 | cd "${HERE}/.." 7 | 8 | # Build and run the benchmark using Gradle 9 | ./gradlew :ddprof-lib:benchmarks:runBenchmark 10 | -------------------------------------------------------------------------------- /ddprof-lib/benchmarks/src/benchmarkConfig.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | struct BenchmarkResult { 7 | std::string name; 8 | long long total_time_ns; 9 | int iterations; 10 | double avg_time_ns; 11 | }; 12 | 13 | struct BenchmarkConfig { 14 | int warmup_iterations; 15 | int measurement_iterations; 16 | std::string csv_file; 17 | std::string json_file; 18 | bool debug; 19 | 20 | BenchmarkConfig() : warmup_iterations(100000), measurement_iterations(1000000), debug(false) { 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /ddprof-lib/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = "JavaProfiler" 2 | 3 | include ':ddprof-lib:benchmarks' 4 | -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/arch_dd.h: -------------------------------------------------------------------------------- 1 | #ifndef _ARCH_DD_H 2 | #define _ARCH_DD_H 3 | 4 | #include "arch.h" 5 | 6 | #include 7 | 8 | static inline long long atomicInc(volatile long long &var, 9 | long long increment = 1) { 10 | return __sync_fetch_and_add(&var, increment); 11 | } 12 | 13 | static inline u64 loadAcquire(volatile u64 &var) { 14 | return __atomic_load_n(&var, __ATOMIC_ACQUIRE); 15 | } 16 | 17 | static inline size_t loadAcquire(volatile size_t &var) { 18 | return __atomic_load_n(&var, __ATOMIC_ACQUIRE); 19 | } 20 | 21 | static inline void storeRelease(volatile long long &var, long long value) { 22 | return __atomic_store_n(&var, value, __ATOMIC_RELEASE); 23 | } 24 | 25 | static inline void storeRelease(volatile size_t &var, size_t value) { 26 | return __atomic_store_n(&var, value, __ATOMIC_RELEASE); 27 | } 28 | 29 | #endif // _ARCH_DD_H 30 | -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/asyncSampleMutex.h: -------------------------------------------------------------------------------- 1 | #ifndef ASYNCSAMPLEMUTEX_H 2 | #define ASYNCSAMPLEMUTEX_H 3 | 4 | #include "threadLocalData.h" 5 | 6 | // controls access to AGCT 7 | class AsyncSampleMutex { 8 | private: 9 | ThreadLocalData *_threadLocalData; 10 | bool _acquired; 11 | 12 | bool try_acquire() { 13 | if (_threadLocalData != nullptr && !_threadLocalData->is_unwinding_Java()) { 14 | _threadLocalData->set_unwinding_Java(true); 15 | return true; 16 | } 17 | return false; 18 | } 19 | 20 | public: 21 | AsyncSampleMutex(ThreadLocalData *threadLocalData) 22 | : _threadLocalData(threadLocalData) { 23 | _acquired = try_acquire(); 24 | } 25 | 26 | AsyncSampleMutex(AsyncSampleMutex &other) = delete; 27 | 28 | ~AsyncSampleMutex() { 29 | if (_acquired) { 30 | _threadLocalData->set_unwinding_Java(false); 31 | } 32 | } 33 | 34 | bool acquired() { return _acquired; } 35 | }; 36 | 37 | #endif // ASYNCSAMPLEMUTEX_H 38 | -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/callTraceStorage.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Andrei Pangin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef _CALLTRACESTORAGE_H 18 | #define _CALLTRACESTORAGE_H 19 | 20 | #include "arch_dd.h" 21 | #include "linearAllocator.h" 22 | #include "spinLock.h" 23 | #include "vmEntry.h" 24 | #include 25 | #include 26 | 27 | class LongHashTable; 28 | 29 | struct CallTrace { 30 | bool truncated; 31 | int num_frames; 32 | ASGCT_CallFrame frames[1]; 33 | }; 34 | 35 | struct CallTraceSample { 36 | CallTrace *trace; 37 | u64 samples; 38 | u64 counter; 39 | 40 | CallTrace *acquireTrace() { 41 | return __atomic_load_n(&trace, __ATOMIC_ACQUIRE); 42 | } 43 | 44 | void setTrace(CallTrace *value) { 45 | return __atomic_store_n(&trace, value, __ATOMIC_RELEASE); 46 | } 47 | 48 | CallTraceSample &operator+=(const CallTraceSample &s) { 49 | trace = s.trace; 50 | samples += s.samples; 51 | counter += s.counter; 52 | return *this; 53 | } 54 | 55 | bool operator<(const CallTraceSample &other) const { 56 | return counter > other.counter; 57 | } 58 | }; 59 | 60 | class CallTraceStorage { 61 | private: 62 | static CallTrace _overflow_trace; 63 | 64 | LinearAllocator _allocator; 65 | LongHashTable *_current_table; 66 | u64 _overflow; 67 | 68 | SpinLock _lock; 69 | 70 | u64 calcHash(int num_frames, ASGCT_CallFrame *frames, bool truncated); 71 | CallTrace *storeCallTrace(int num_frames, ASGCT_CallFrame *frames, 72 | bool truncated); 73 | CallTrace *findCallTrace(LongHashTable *table, u64 hash); 74 | 75 | public: 76 | CallTraceStorage(); 77 | ~CallTraceStorage(); 78 | 79 | void clear(); 80 | void collectTraces(std::map &map); 81 | 82 | u32 put(int num_frames, ASGCT_CallFrame *frames, bool truncated, u64 weight); 83 | }; 84 | 85 | #endif // _CALLTRACESTORAGE 86 | -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/common.h: -------------------------------------------------------------------------------- 1 | #ifndef _COMMON_H 2 | #define _COMMON_H 3 | 4 | #ifdef DEBUG 5 | #define TEST_LOG(fmt, ...) do { \ 6 | fprintf(stdout, "[TEST::INFO] " fmt "\n", ##__VA_ARGS__); \ 7 | fflush(stdout); \ 8 | } while (0) 9 | #else 10 | #define TEST_LOG(fmt, ...) // No-op in non-debug mode 11 | #endif 12 | 13 | #endif // _COMMON_H -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/context.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021, 2022 Datadog, Inc 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef _CONTEXT_H 18 | #define _CONTEXT_H 19 | 20 | #include "arch_dd.h" 21 | #include "arguments.h" 22 | #include "os.h" 23 | 24 | static const u32 DD_TAGS_CAPACITY = 10; 25 | 26 | typedef struct { 27 | u32 value; 28 | } Tag; 29 | 30 | class Context { 31 | public: 32 | u64 spanId; 33 | u64 rootSpanId; 34 | u64 checksum; 35 | Tag tags[DD_TAGS_CAPACITY]; 36 | 37 | Tag get_tag(int i) { return tags[i]; } 38 | }; 39 | 40 | // must be kept in sync with PAGE_SIZE in JavaProfiler.java 41 | const int DD_CONTEXT_PAGE_SIZE = 1024; 42 | const int DD_CONTEXT_PAGE_MASK = DD_CONTEXT_PAGE_SIZE - 1; 43 | const int DD_CONTEXT_PAGE_SHIFT = __builtin_popcount(DD_CONTEXT_PAGE_MASK); 44 | 45 | typedef struct { 46 | const int capacity; 47 | const Context *storage; 48 | } ContextPage; 49 | 50 | class Contexts { 51 | 52 | private: 53 | static int _max_pages; 54 | static Context **_pages; 55 | static bool initialize(int pageIndex); 56 | 57 | public: 58 | // get must not allocate 59 | static Context &get(int tid); 60 | static Context &empty(); 61 | // not to be called except to share with Java callers as a DirectByteBuffer 62 | static ContextPage getPage(int tid); 63 | static int getMaxPages(int maxTid = OS::getMaxThreadId()); 64 | 65 | // this *MUST* be called only when the profiler is completely stopped 66 | static void reset(); 67 | }; 68 | 69 | #endif /* _CONTEXT_H */ 70 | -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/counters.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Datadog, Inc 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #include "counters.h" 17 | #include 18 | 19 | long long *Counters::init() { 20 | u32 alignment = sizeof(long long) * ALIGNMENT; 21 | long long *counters = 22 | (long long *)aligned_alloc(alignment, DD_NUM_COUNTERS * alignment); 23 | memset(counters, 0, DD_NUM_COUNTERS * alignment); 24 | return counters; 25 | } 26 | -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/ctimer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Andrei Pangin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef _CTIMER_H 18 | #define _CTIMER_H 19 | 20 | #include "engine.h" 21 | #include 22 | #ifdef __linux__ 23 | 24 | #include "arch_dd.h" 25 | #include 26 | 27 | class CTimer : public Engine { 28 | private: 29 | static std::atomic _enabled; 30 | static long _interval; 31 | static CStack _cstack; 32 | static int _signal; 33 | 34 | static int _max_timers; 35 | static int *_timers; 36 | 37 | int registerThread(int tid); 38 | void unregisterThread(int tid); 39 | 40 | // cppcheck-suppress unusedPrivateFunction 41 | static void signalHandler(int signo, siginfo_t *siginfo, void *ucontext); 42 | 43 | public: 44 | const char *units() { return "ns"; } 45 | 46 | const char *name() { return "CTimer"; } 47 | 48 | long interval() const { return _interval; } 49 | 50 | Error check(Arguments &args); 51 | Error start(Arguments &args); 52 | void stop(); 53 | 54 | inline void enableEvents(bool enabled) { 55 | _enabled.store(enabled, std::memory_order_release); 56 | } 57 | }; 58 | 59 | #else 60 | 61 | class CTimer : public Engine { 62 | public: 63 | Error check(Arguments &args) { 64 | return Error("CTimer is not supported on this platform"); 65 | } 66 | 67 | Error start(Arguments &args) { 68 | return Error("CTimer is not supported on this platform"); 69 | } 70 | 71 | static bool supported() { return false; } 72 | }; 73 | 74 | #endif // __linux__ 75 | 76 | #endif // _CTIMER_H -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/debugSupport.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "debug.h" 5 | #include "debugSupport.h" 6 | 7 | Shims Shims::_instance; 8 | 9 | Shims::Shims() : _tid_setter_ref(NULL) { 10 | #ifdef DEBUG 11 | if (_tid_setter_ref == NULL) { 12 | void *sym_handle = dlsym(RTLD_DEFAULT, "set_sighandler_tid"); 13 | __atomic_compare_exchange_n(&_tid_setter_ref, 14 | (SetSigHandlerTidRef *)(&sym_handle), NULL, 15 | false, __ATOMIC_ACQ_REL, __ATOMIC_RELAXED); 16 | } 17 | #endif 18 | } 19 | 20 | void Shims::setSighandlerTid(int tid) { 21 | #ifdef DEBUG 22 | SetSigHandlerTidRef ref = __atomic_load_n(&_tid_setter_ref, __ATOMIC_ACQUIRE); 23 | if (ref != NULL) { 24 | ref(tid); 25 | } 26 | #endif 27 | } -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/debugSupport.h: -------------------------------------------------------------------------------- 1 | #ifndef _DEBUGSUPPORT_H 2 | #define _DEBUGSUPPORT_H 3 | 4 | typedef void (*SetSigHandlerTidRef)(int tid); 5 | 6 | class Shims { 7 | private: 8 | static Shims _instance; 9 | volatile SetSigHandlerTidRef _tid_setter_ref; 10 | Shims(); 11 | 12 | public: 13 | void setSighandlerTid(int tid); 14 | inline static Shims instance() { return _instance; } 15 | }; 16 | 17 | #endif //_DEBUGSUPPORT_H -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/dictionary.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Andrei Pangin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef _DICTIONARY_H 18 | #define _DICTIONARY_H 19 | 20 | #include "counters.h" 21 | #include 22 | #include 23 | #include 24 | 25 | #define ROW_BITS 7 26 | #define ROWS (1 << ROW_BITS) 27 | #define CELLS 3 28 | #define TABLE_CAPACITY (ROWS * CELLS) 29 | 30 | struct DictTable; 31 | 32 | struct DictRow { 33 | char *keys[CELLS]; 34 | DictTable *next; 35 | }; 36 | 37 | struct DictTable { 38 | DictRow rows[ROWS]; 39 | unsigned int base_index; 40 | 41 | unsigned int index(int row, int col) { 42 | return base_index + (col << ROW_BITS) + row; 43 | } 44 | }; 45 | 46 | // Append-only concurrent hash table based on multi-level arrays 47 | class Dictionary { 48 | private: 49 | DictTable *_table; 50 | const int _id; 51 | volatile unsigned int _base_index; 52 | volatile int _size; 53 | 54 | static void clear(DictTable *table, int id); 55 | 56 | static unsigned int hash(const char *key, size_t length); 57 | 58 | static void collect(std::map &map, 59 | DictTable *table); 60 | 61 | unsigned int lookup(const char *key, size_t length, bool for_insert, 62 | unsigned int sentinel); 63 | 64 | public: 65 | Dictionary() : Dictionary(0) {} 66 | Dictionary(int id) : _id(id) { 67 | _table = (DictTable *)calloc(1, sizeof(DictTable)); 68 | Counters::set(DICTIONARY_PAGES, 1, id); 69 | Counters::set(DICTIONARY_BYTES, sizeof(DictTable), id); 70 | _table->base_index = _base_index = 1; 71 | _size = 0; 72 | } 73 | ~Dictionary(); 74 | 75 | void clear(); 76 | 77 | bool check(const char* key); 78 | unsigned int lookup(const char *key); 79 | unsigned int lookup(const char *key, size_t length); 80 | unsigned int bounded_lookup(const char *key, size_t length, int size_limit); 81 | 82 | void collect(std::map &map); 83 | }; 84 | 85 | #endif // _DICTIONARY_H 86 | -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/engine.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Andrei Pangin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "engine.h" 18 | 19 | Error Engine::check(Arguments &args) { return Error::OK; } 20 | 21 | Error Engine::start(Arguments &args) { return Error::OK; } 22 | 23 | void Engine::stop() {} 24 | -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/engine.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Andrei Pangin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef _ENGINE_H 18 | #define _ENGINE_H 19 | 20 | #include "arguments.h" 21 | 22 | class Engine { 23 | protected: 24 | static bool updateCounter(volatile unsigned long long &counter, 25 | unsigned long long value, 26 | unsigned long long interval) { 27 | if (interval <= 1) { 28 | return true; 29 | } 30 | 31 | while (true) { 32 | unsigned long long prev = counter; 33 | unsigned long long next = prev + value; 34 | if (next < interval) { 35 | if (__sync_bool_compare_and_swap(&counter, prev, next)) { 36 | return false; 37 | } 38 | } else { 39 | if (__sync_bool_compare_and_swap(&counter, prev, next % interval)) { 40 | return true; 41 | } 42 | } 43 | } 44 | } 45 | 46 | public: 47 | virtual const char *name() { return "None"; } 48 | 49 | virtual Error check(Arguments &args); 50 | virtual Error start(Arguments &args); 51 | virtual void stop(); 52 | virtual long interval() const { return 0L; } 53 | 54 | virtual int registerThread(int tid) { return -1; } 55 | virtual void unregisterThread(int tid) {} 56 | 57 | virtual void enableEvents(bool enabled) { 58 | // do nothing 59 | } 60 | }; 61 | 62 | #endif // _ENGINE_H 63 | -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/frame.h: -------------------------------------------------------------------------------- 1 | #ifndef _FRAME_H 2 | #define _FRAME_H 3 | 4 | enum FrameTypeId { 5 | FRAME_INTERPRETED = 0, 6 | FRAME_JIT_COMPILED = 1, 7 | FRAME_INLINED = 2, 8 | FRAME_NATIVE = 3, 9 | FRAME_CPP = 4, 10 | FRAME_KERNEL = 5, 11 | FRAME_C1_COMPILED = 6, 12 | }; 13 | 14 | class FrameType { 15 | public: 16 | static inline int encode(int type, int bci) { 17 | return (1 << 24) | (type << 25) | (bci & 0xffffff); 18 | } 19 | 20 | static inline FrameTypeId decode(int bci) { 21 | return (bci >> 24) > 0 ? (FrameTypeId)(bci >> 25) : FRAME_JIT_COMPILED; 22 | } 23 | }; 24 | 25 | #endif // _FRAME_H 26 | -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/itimer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Andrei Pangin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "itimer.h" 18 | #include "debugSupport.h" 19 | #include "os.h" 20 | #include "profiler.h" 21 | #include "stackWalker.h" 22 | #include "thread.h" 23 | #include "vmStructs.h" 24 | #include 25 | 26 | volatile bool ITimer::_enabled = false; 27 | long ITimer::_interval; 28 | CStack ITimer::_cstack; 29 | 30 | void ITimer::signalHandler(int signo, siginfo_t *siginfo, void *ucontext) { 31 | if (!_enabled) 32 | return; 33 | int tid = 0; 34 | ProfiledThread *current = ProfiledThread::current(); 35 | if (current != NULL) { 36 | current->noteCPUSample(Profiler::instance()->recordingEpoch()); 37 | tid = current->tid(); 38 | } else { 39 | tid = OS::threadId(); 40 | } 41 | Shims::instance().setSighandlerTid(tid); 42 | 43 | ExecutionEvent event; 44 | VMThread *vm_thread = VMThread::current(); 45 | if (vm_thread) { 46 | event._execution_mode = VM::jni() != NULL 47 | ? convertJvmExecutionState(vm_thread->state()) 48 | : ExecutionMode::JVM; 49 | } 50 | Profiler::instance()->recordSample(ucontext, _interval, tid, BCI_CPU, 0, 51 | &event); 52 | Shims::instance().setSighandlerTid(-1); 53 | } 54 | 55 | Error ITimer::check(Arguments &args) { 56 | OS::installSignalHandler(SIGPROF, NULL, SIG_IGN); 57 | 58 | struct itimerval tv_on = {{1, 0}, {1, 0}}; 59 | if (setitimer(ITIMER_PROF, &tv_on, NULL) != 0) { 60 | return Error("ITIMER_PROF is not supported on this system"); 61 | } 62 | 63 | struct itimerval tv_off = {{0, 0}, {0, 0}}; 64 | setitimer(ITIMER_PROF, &tv_off, NULL); 65 | 66 | return Error::OK; 67 | } 68 | 69 | Error ITimer::start(Arguments &args) { 70 | _interval = args.cpuSamplerInterval(); 71 | _cstack = args._cstack; 72 | 73 | OS::installSignalHandler(SIGPROF, signalHandler); 74 | 75 | time_t sec = _interval / 1000000000; 76 | suseconds_t usec = (_interval % 1000000000) / 1000; 77 | struct itimerval tv = {{sec, usec}, {sec, usec}}; 78 | 79 | if (setitimer(ITIMER_PROF, &tv, NULL) != 0) { 80 | return Error("ITIMER_PROF is not supported on this system"); 81 | } 82 | 83 | return Error::OK; 84 | } 85 | 86 | void ITimer::stop() { 87 | struct itimerval tv = {{0, 0}, {0, 0}}; 88 | setitimer(ITIMER_PROF, &tv, NULL); 89 | } 90 | -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/itimer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Andrei Pangin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef _ITIMER_H 18 | #define _ITIMER_H 19 | 20 | #include "engine.h" 21 | #include 22 | 23 | class ITimer : public Engine { 24 | private: 25 | static volatile bool _enabled; 26 | static long _interval; 27 | static CStack _cstack; 28 | 29 | static void signalHandler(int signo, siginfo_t *siginfo, void *ucontext); 30 | 31 | public: 32 | const char *units() { return "ns"; } 33 | 34 | const char *name() { return "ITimer"; } 35 | 36 | long interval() const { return _interval; } 37 | 38 | Error check(Arguments &args); 39 | Error start(Arguments &args); 40 | void stop(); 41 | 42 | inline void enableEvents(bool enabled) { _enabled = enabled; } 43 | }; 44 | 45 | #endif // _ITIMER_H 46 | -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/j9Ext.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Andrei Pangin 3 | * Copyright 2024 Datadog, Inc 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include "j9Ext.h" 19 | #include "os.h" 20 | #include 21 | 22 | jvmtiEnv *J9Ext::_jvmti; 23 | 24 | void *(*J9Ext::_j9thread_self)() = NULL; 25 | 26 | jvmtiExtensionFunction J9Ext::_GetOSThreadID = NULL; 27 | jvmtiExtensionFunction J9Ext::_GetJ9vmThread = NULL; 28 | jvmtiExtensionFunction J9Ext::_GetStackTraceExtended = NULL; 29 | jvmtiExtensionFunction J9Ext::_GetAllStackTracesExtended = NULL; 30 | 31 | int J9Ext::InstrumentableObjectAlloc_id = -1; 32 | 33 | // Look for OpenJ9-specific JVM TI extension 34 | bool J9Ext::initialize(jvmtiEnv *jvmti, const void *j9thread_self) { 35 | _jvmti = jvmti; 36 | _j9thread_self = (void *(*)())j9thread_self; 37 | 38 | jint ext_count; 39 | jvmtiExtensionFunctionInfo *ext_functions; 40 | if (jvmti->GetExtensionFunctions(&ext_count, &ext_functions) == 0) { 41 | for (int i = 0; i < ext_count; i++) { 42 | if (strcmp(ext_functions[i].id, "com.ibm.GetOSThreadID") == 0) { 43 | _GetOSThreadID = ext_functions[i].func; 44 | } else if (strcmp(ext_functions[i].id, "com.ibm.GetJ9vmThread") == 0) { 45 | _GetJ9vmThread = ext_functions[i].func; 46 | } else if (strcmp(ext_functions[i].id, "com.ibm.GetStackTraceExtended") == 47 | 0) { 48 | _GetStackTraceExtended = ext_functions[i].func; 49 | } else if (strcmp(ext_functions[i].id, 50 | "com.ibm.GetAllStackTracesExtended") == 0) { 51 | _GetAllStackTracesExtended = ext_functions[i].func; 52 | } 53 | } 54 | jvmti->Deallocate((unsigned char *)ext_functions); 55 | } 56 | 57 | return _GetOSThreadID != NULL && _GetStackTraceExtended != NULL && 58 | _GetAllStackTracesExtended != NULL; 59 | } 60 | -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/j9WallClock.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Andrei Pangin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef _J9WALLCLOCK_H 18 | #define _J9WALLCLOCK_H 19 | 20 | #include "engine.h" 21 | #include 22 | 23 | class J9WallClock : public Engine { 24 | private: 25 | static volatile bool _enabled; 26 | static long _interval; 27 | 28 | bool _sample_idle_threads; 29 | int _max_stack_depth; 30 | volatile bool _running; 31 | pthread_t _thread; 32 | 33 | static void *threadEntry(void *wall_clock) { 34 | ((J9WallClock *)wall_clock)->timerLoop(); 35 | return NULL; 36 | } 37 | 38 | void timerLoop(); 39 | 40 | public: 41 | const char *units() { return "ns"; } 42 | 43 | const char *name() { 44 | return _sample_idle_threads ? "J9WallClock" : "J9Execution"; 45 | } 46 | 47 | virtual long interval() const { return _interval; } 48 | 49 | inline void sampleIdleThreads() { _sample_idle_threads = true; } 50 | 51 | Error start(Arguments &args); 52 | void stop(); 53 | 54 | inline void enableEvents(bool enabled) { _enabled = enabled; } 55 | }; 56 | 57 | #endif // _J9WALLCLOCK_H 58 | -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/javaApi.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/java-profiler/996bcbff2a362baa9dcfe23b679810428d175c2b/ddprof-lib/src/main/cpp/javaApi.h -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/jniHelper.h: -------------------------------------------------------------------------------- 1 | #ifndef JAVA_PROFILER_JNIHELPER_H 2 | #define JAVA_PROFILER_JNIHELPER_H 3 | 4 | #include 5 | 6 | static bool jniExceptionCheck(JNIEnv *jni, bool describe = false) { 7 | if (jni->ExceptionCheck()) { 8 | if (describe) { 9 | jni->ExceptionDescribe(); 10 | } 11 | jni->ExceptionClear(); 12 | return true; 13 | } 14 | return false; 15 | } 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/jvm.cpp: -------------------------------------------------------------------------------- 1 | #include "jvm.h" 2 | #include "log.h" 3 | #include "vmStructs.h" 4 | 5 | bool JVM::_is_readable_pointer_resolved = false; 6 | is_readable_pointer_fn JVM::_is_readable_pointer = NULL; 7 | 8 | bool JVM::is_readable_pointer(void *ptr) { 9 | if (!_is_readable_pointer_resolved) { 10 | const char *sym_name = "_ZN2os19is_readable_pointerEPKv"; 11 | _is_readable_pointer_resolved = true; 12 | _is_readable_pointer = 13 | (is_readable_pointer_fn)VMStructs::libjvm()->findSymbol(sym_name); 14 | if (_is_readable_pointer == NULL) { 15 | Log::error("Can not resolve symbol %s in JVM lib\n", sym_name); 16 | } 17 | } 18 | // if we have no access to the symbol we can not really attempt the pointer 19 | // sanitation 20 | return _is_readable_pointer ? _is_readable_pointer(ptr) : true; 21 | } -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/jvm.h: -------------------------------------------------------------------------------- 1 | #ifndef _JVM_H 2 | #define _JVM_H 3 | 4 | typedef bool (*is_readable_pointer_fn)(void *); 5 | 6 | class JVM { 7 | private: 8 | static bool _is_readable_pointer_resolved; 9 | static is_readable_pointer_fn _is_readable_pointer; 10 | 11 | public: 12 | static bool is_readable_pointer(void *ptr); 13 | }; 14 | #endif // _JVM_H -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/jvmHeap.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Datadog, Inc 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef _JVMHEAP_H 18 | #define _JVMHEAP_H 19 | 20 | #include 21 | 22 | /** 23 | * This class only defines a layout compatible with the JDKs VirtualSpaceSummary 24 | * class and particularly its subclasses 25 | */ 26 | class VirtualSpaceSummary { 27 | private: 28 | void *_start; 29 | void *_committed_end; 30 | void *_reserved_end; 31 | 32 | public: 33 | long maxSize() { return (long)_reserved_end - (long)_start; } 34 | }; 35 | 36 | /** 37 | * This class only defines a layout compatible with the JDKs GCHeapSummary class 38 | * and particularly its subclasses 39 | */ 40 | class GCHeapSummary { 41 | private: 42 | void *vptr; // only 1-st level subclasses are used so we need to define the 43 | // 'synthetic' vptr field here 44 | VirtualSpaceSummary _heap; 45 | size_t _used; 46 | 47 | public: 48 | long used() { return (long)_used; } 49 | 50 | long maxSize() { return _heap.maxSize(); } 51 | }; 52 | 53 | /** 54 | * This class only defines a layout compatible with the JDKs CollectedHeap 55 | * class and particularly its subclasses 56 | */ 57 | class CollectedHeapWrapper { 58 | private: 59 | void *vptr; // only 1-st level subclasses are used so we need to define the 60 | // 'synthetic' vptr field here 61 | void *_gc_heap_log; // ignored 62 | public: 63 | // Historic gc information 64 | size_t _capacity_at_last_gc; 65 | size_t _used_at_last_gc; 66 | }; 67 | 68 | #endif // _JVMHEAP_H -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/libraries.cpp: -------------------------------------------------------------------------------- 1 | #include "codeCache.h" 2 | #include "libraries.h" 3 | #include "log.h" 4 | #include "symbols.h" 5 | #include "vmEntry.h" 6 | #include "vmStructs.h" 7 | 8 | void Libraries::mangle(const char *name, char *buf, size_t size) { 9 | char *buf_end = buf + size; 10 | strcpy(buf, "_ZN"); 11 | buf += 3; 12 | 13 | const char *c; 14 | while ((c = strstr(name, "::")) != NULL && buf + (c - name) + 4 < buf_end) { 15 | int n = snprintf(buf, buf_end - buf, "%d", (int)(c - name)); 16 | if (n < 0 || n >= buf_end - buf) { 17 | if (n < 0) { 18 | Log::debug("Error in snprintf."); 19 | } 20 | goto end; 21 | } 22 | buf += n; 23 | memcpy(buf, name, c - name); 24 | buf += c - name; 25 | name = c + 2; 26 | } 27 | if (buf < buf_end) { 28 | snprintf(buf, buf_end - buf, "%d%sE*", (int)strlen(name), name); 29 | } 30 | 31 | end: 32 | buf_end[-1] = '\0'; 33 | } 34 | 35 | void Libraries::updateSymbols(bool kernel_symbols) { 36 | Symbols::parseLibraries(&_native_libs, kernel_symbols); 37 | } 38 | 39 | const void *Libraries::resolveSymbol(const char *name) { 40 | char mangled_name[256]; 41 | if (strstr(name, "::") != NULL) { 42 | mangle(name, mangled_name, sizeof(mangled_name)); 43 | name = mangled_name; 44 | } 45 | 46 | size_t len = strlen(name); 47 | int native_lib_count = _native_libs.count(); 48 | if (len > 0 && name[len - 1] == '*') { 49 | for (int i = 0; i < native_lib_count; i++) { 50 | const void *address = _native_libs[i]->findSymbolByPrefix(name, len - 1); 51 | if (address != NULL) { 52 | return address; 53 | } 54 | } 55 | } else { 56 | for (int i = 0; i < native_lib_count; i++) { 57 | const void *address = _native_libs[i]->findSymbol(name); 58 | if (address != NULL) { 59 | return address; 60 | } 61 | } 62 | } 63 | 64 | return NULL; 65 | } 66 | 67 | CodeCache *Libraries::findJvmLibrary(const char *j9_lib_name) { 68 | return VM::isOpenJ9() ? findLibraryByName(j9_lib_name) : VMStructs::libjvm(); 69 | } 70 | 71 | CodeCache *Libraries::findLibraryByName(const char *lib_name) { 72 | const size_t lib_name_len = strlen(lib_name); 73 | const int native_lib_count = _native_libs.count(); 74 | for (int i = 0; i < native_lib_count; i++) { 75 | const char *s = _native_libs[i]->name(); 76 | if (s != NULL) { 77 | const char *p = strrchr(s, '/'); 78 | if (p != NULL && strncmp(p + 1, lib_name, lib_name_len) == 0) { 79 | return _native_libs[i]; 80 | } 81 | } 82 | } 83 | return NULL; 84 | } 85 | 86 | CodeCache *Libraries::findLibraryByAddress(const void *address) { 87 | const int native_lib_count = _native_libs.count(); 88 | for (int i = 0; i < native_lib_count; i++) { 89 | if (_native_libs[i]->contains(address)) { 90 | return _native_libs[i]; 91 | } 92 | } 93 | return NULL; 94 | } -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/libraries.h: -------------------------------------------------------------------------------- 1 | #ifndef _LIBRARIES_H 2 | #define _LIBRARIES_H 3 | 4 | #include "codeCache.h" 5 | 6 | class Libraries { 7 | private: 8 | CodeCacheArray _native_libs; 9 | CodeCache _runtime_stubs; 10 | 11 | static void mangle(const char *name, char *buf, size_t size); 12 | public: 13 | Libraries() : _native_libs(), _runtime_stubs("runtime stubs") {} 14 | void updateSymbols(bool kernel_symbols); 15 | const void *resolveSymbol(const char *name); 16 | // In J9 the 'libjvm' functionality is spread across multiple libraries 17 | // This function will return the 'libjvm' on non-J9 VMs and the library with the given name on J9 VMs 18 | CodeCache *findJvmLibrary(const char *j9_lib_name); 19 | CodeCache *findLibraryByName(const char *lib_name); 20 | CodeCache *findLibraryByAddress(const void *address); 21 | 22 | static Libraries *instance() { 23 | static Libraries instance; 24 | return &instance; 25 | } 26 | 27 | // Delete copy constructor and assignment operator to prevent copies 28 | Libraries(const Libraries&) = delete; 29 | Libraries& operator=(const Libraries&) = delete; 30 | }; 31 | 32 | #endif // _LIBRARIES_H -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/linearAllocator.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Andrei Pangin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef _LINEARALLOCATOR_H 18 | #define _LINEARALLOCATOR_H 19 | 20 | #include 21 | 22 | struct Chunk { 23 | Chunk *prev; 24 | volatile size_t offs; 25 | // To avoid false sharing 26 | char _padding[56]; 27 | }; 28 | 29 | class LinearAllocator { 30 | private: 31 | size_t _chunk_size; 32 | Chunk *_tail; 33 | Chunk *_reserve; 34 | 35 | Chunk *allocateChunk(Chunk *current); 36 | void freeChunk(Chunk *current); 37 | void reserveChunk(Chunk *current); 38 | Chunk *getNextChunk(Chunk *current); 39 | 40 | public: 41 | explicit LinearAllocator(size_t chunk_size); 42 | ~LinearAllocator(); 43 | 44 | void clear(); 45 | 46 | void *alloc(size_t size); 47 | }; 48 | 49 | #endif // _LINEARALLOCATOR_H 50 | -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/livenessTracker.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021, 2023 Datadog, Inc 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef _LIVENESSTRACKER_H 18 | #define _LIVENESSTRACKER_H 19 | 20 | #include "arch_dd.h" 21 | #include "context.h" 22 | #include "engine.h" 23 | #include "event.h" 24 | #include "spinLock.h" 25 | #include 26 | #include 27 | #include 28 | 29 | class Recording; 30 | 31 | typedef struct TrackingEntry { 32 | jweak ref; 33 | AllocEvent alloc; 34 | double skipped; 35 | u32 call_trace_id; 36 | jint tid; 37 | jlong time; 38 | jlong age; 39 | Context ctx; 40 | } TrackingEntry; 41 | 42 | class LivenessTracker { 43 | friend Recording; 44 | 45 | private: 46 | // pre-c++17 we should mark these inline(or out of class) 47 | constexpr static int MAX_TRACKING_TABLE_SIZE = 262144; 48 | constexpr static int MIN_SAMPLING_INTERVAL = 524288; // 512kiB 49 | 50 | bool _initialized; 51 | bool _enabled; 52 | Error _stored_error; 53 | 54 | SpinLock _table_lock; 55 | volatile int _table_size; 56 | int _table_cap; 57 | int _table_max_cap; 58 | TrackingEntry *_table; 59 | 60 | double _subsample_ratio; 61 | 62 | bool _record_heap_usage; 63 | 64 | jclass _Class; 65 | jmethodID _Class_getName; 66 | 67 | volatile u64 _gc_epoch; 68 | volatile u64 _last_gc_epoch; 69 | 70 | size_t _used_after_last_gc; 71 | 72 | Error initialize(Arguments &args); 73 | Error initialize_table(JNIEnv *jni, int sampling_interval); 74 | 75 | void cleanup_table(bool force = false); 76 | 77 | void flush_table(std::set *tracked_thread_ids); 78 | 79 | void onGC(); 80 | void runCleanup(); 81 | 82 | jlong getMaxMemory(JNIEnv *env); 83 | 84 | public: 85 | static LivenessTracker *instance() { 86 | static LivenessTracker instance; 87 | return &instance; 88 | } 89 | // Delete copy constructor and assignment operator to prevent copies 90 | LivenessTracker(const LivenessTracker&) = delete; 91 | LivenessTracker& operator=(const LivenessTracker&) = delete; 92 | 93 | LivenessTracker() 94 | : _initialized(false), _enabled(false), _stored_error(Error::OK), 95 | _table_size(0), _table_cap(0), _table_max_cap(0), _table(NULL), 96 | _subsample_ratio(0.1), _record_heap_usage(false), _Class(NULL), 97 | _Class_getName(0), _gc_epoch(0), _last_gc_epoch(0), 98 | _used_after_last_gc(0) {} 99 | 100 | Error start(Arguments &args); 101 | void stop(); 102 | void track(JNIEnv *env, AllocEvent &event, jint tid, jobject object, u32 call_trace_id); 103 | void flush(std::set &tracked_thread_ids); 104 | 105 | static void JNICALL GarbageCollectionFinish(jvmtiEnv *jvmti_env); 106 | }; 107 | 108 | #endif // _LIVENESSTRACKER_H 109 | -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/log.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Andrei Pangin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef _LOG_H 18 | #define _LOG_H 19 | 20 | #include 21 | #include 22 | 23 | #ifdef __GNUC__ 24 | #define ATTR_FORMAT __attribute__((format(printf, 1, 2))) 25 | #else 26 | #define ATTR_FORMAT 27 | #endif 28 | 29 | enum LogLevel { LOG_TRACE, LOG_DEBUG, LOG_INFO, LOG_WARN, LOG_ERROR, LOG_NONE }; 30 | 31 | class Arguments; 32 | 33 | class Log { 34 | private: 35 | static FILE *_file; 36 | static LogLevel _level; 37 | 38 | public: 39 | static const char *const LEVEL_NAME[]; 40 | 41 | static void open(Arguments &args); 42 | static void open(const char *file_name, const char *level); 43 | static void close(); 44 | 45 | static void log(LogLevel level, const char *msg, va_list args); 46 | 47 | static void ATTR_FORMAT trace(const char *msg, ...); 48 | static void ATTR_FORMAT debug(const char *msg, ...); 49 | static void ATTR_FORMAT info(const char *msg, ...); 50 | static void ATTR_FORMAT warn(const char *msg, ...); 51 | static void ATTR_FORMAT error(const char *msg, ...); 52 | 53 | static LogLevel level() { return _level; } 54 | }; 55 | 56 | #endif // _LOG_H 57 | -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/objectSampler.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Andrei Pangin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef _OBJECTSAMPLER_H 18 | #define _OBJECTSAMPLER_H 19 | 20 | #include "arch_dd.h" 21 | #include "engine.h" 22 | #include "jfrMetadata.h" 23 | #include "livenessTracker.h" 24 | #include 25 | #include 26 | 27 | typedef int (*get_sampling_interval)(); 28 | 29 | class ObjectSampler : public Engine { 30 | friend Recording; 31 | 32 | private: 33 | static ObjectSampler *const _instance; 34 | 35 | int _interval; 36 | int _configured_interval; 37 | bool _record_allocations; 38 | bool _record_liveness; 39 | bool _gc_generations; 40 | int _max_stack_depth; 41 | 42 | u64 _last_config_update_ts; 43 | u64 _alloc_event_count; 44 | 45 | const static int CONFIG_UPDATE_CHECK_PERIOD_SECS = 1; 46 | int _target_samples_per_window = 100; // ~6k samples per minute by default 47 | 48 | Error updateConfiguration(u64 events, double time_coefficient); 49 | 50 | ObjectSampler() 51 | : _interval(0), _configured_interval(0), _record_allocations(false), 52 | _record_liveness(false), _gc_generations(false), _max_stack_depth(0), 53 | _last_config_update_ts(0), _alloc_event_count(0) {} 54 | 55 | protected: 56 | void recordAllocation(jvmtiEnv *jvmti, JNIEnv *jni, jthread thread, 57 | int event_type, jobject object, jclass object_klass, 58 | jlong size); 59 | 60 | public: 61 | static ObjectSampler *const instance() { return _instance; } 62 | 63 | Error check(Arguments &args); 64 | Error start(Arguments &args); 65 | void stop(); 66 | 67 | virtual long interval() const { return _interval; } 68 | 69 | static void JNICALL SampledObjectAlloc(jvmtiEnv *jvmti, JNIEnv *jni, 70 | jthread thread, jobject object, 71 | jclass object_klass, jlong size); 72 | }; 73 | 74 | #endif // _OBJECTSAMPLER_H 75 | -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/os.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Andrei Pangin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef _OS_H 18 | #define _OS_H 19 | 20 | #include "arch_dd.h" 21 | #include 22 | #include 23 | #include 24 | 25 | typedef void (*SigAction)(int, siginfo_t *, void *); 26 | typedef void (*SigHandler)(int); 27 | typedef void (*TimerCallback)(void *); 28 | 29 | // Interrupt threads with this signal. The same signal is used inside JDK to 30 | // interrupt I/O operations. 31 | const int WAKEUP_SIGNAL = SIGIO; 32 | 33 | class ThreadList { 34 | public: 35 | virtual ~ThreadList() {} 36 | virtual void rewind() = 0; 37 | virtual int next() = 0; 38 | virtual int size() = 0; 39 | }; 40 | 41 | // W^X memory support 42 | class JitWriteProtection { 43 | private: 44 | u64 _prev; 45 | bool _restore; 46 | 47 | public: 48 | explicit JitWriteProtection(bool enable); 49 | ~JitWriteProtection(); 50 | }; 51 | 52 | class OS { 53 | public: 54 | static const size_t page_size; 55 | static const size_t page_mask; 56 | 57 | static u64 nanotime(); 58 | static u64 cputime(); 59 | static u64 micros(); 60 | static u64 processStartTime(); 61 | static void sleep(u64 nanos); 62 | 63 | static u64 hton64(u64 x); 64 | static u64 ntoh64(u64 x); 65 | 66 | static int getMaxThreadId(); 67 | static int getMaxThreadId(int floor) { 68 | int maxThreadId = getMaxThreadId(); 69 | return maxThreadId < floor ? floor : maxThreadId; 70 | } 71 | static int processId(); 72 | static int threadId(); 73 | static const char *schedPolicy(int thread_id); 74 | static bool threadName(int thread_id, char *name_buf, size_t name_len); 75 | static ThreadList *listThreads(); 76 | 77 | static bool isLinux(); 78 | 79 | static SigAction installSignalHandler(int signo, SigAction action, 80 | SigHandler handler = NULL); 81 | static SigAction replaceSigsegvHandler(SigAction action); 82 | static SigAction replaceSigbusHandler(SigAction action); 83 | static bool sendSignalToThread(int thread_id, int signo); 84 | 85 | static void *safeAlloc(size_t size); 86 | static void safeFree(void *addr, size_t size); 87 | 88 | static bool getCpuDescription(char *buf, size_t size); 89 | static u64 getProcessCpuTime(u64 *utime, u64 *stime); 90 | static u64 getTotalCpuTime(u64 *utime, u64 *stime); 91 | 92 | static void copyFile(int src_fd, int dst_fd, off_t offset, size_t size); 93 | static int fileSize(int fd); 94 | static int truncateFile(int fd); 95 | static void freePageCache(int fd, off_t start_offset); 96 | 97 | static void mallocArenaMax(int arena_max); 98 | }; 99 | 100 | #endif // _OS_H 101 | -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/perfEvents.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Andrei Pangin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef _PERFEVENTS_H 18 | #define _PERFEVENTS_H 19 | 20 | #ifdef __linux__ 21 | 22 | #include "arch_dd.h" 23 | #include "engine.h" 24 | #include 25 | 26 | class PerfEvent; 27 | class PerfEventType; 28 | class StackContext; 29 | 30 | class PerfEvents : public Engine { 31 | private: 32 | static volatile bool _enabled; 33 | static int _max_events; 34 | static PerfEvent *_events; 35 | static PerfEventType *_event_type; 36 | static long _interval; 37 | static Ring _ring; 38 | static CStack _cstack; 39 | static bool _use_mmap_page; 40 | 41 | // cppcheck-suppress unusedPrivateFunction 42 | static u64 readCounter(siginfo_t *siginfo, void *ucontext); 43 | // cppcheck-suppress unusedPrivateFunction 44 | static void signalHandler(int signo, siginfo_t *siginfo, void *ucontext); 45 | 46 | public: 47 | Error check(Arguments &args); 48 | Error start(Arguments &args); 49 | void stop(); 50 | 51 | virtual int registerThread(int tid); 52 | virtual void unregisterThread(int tid); 53 | long interval() const { return _interval; } 54 | 55 | const char *name() { return "PerfEvents"; } 56 | 57 | static int walkKernel(int tid, const void **callchain, int max_depth, 58 | StackContext *java_ctx); 59 | 60 | static void resetBuffer(int tid); 61 | 62 | static const char *getEventName(int event_id); 63 | 64 | inline void enableEvents(bool enabled) { _enabled = enabled; } 65 | }; 66 | 67 | #else 68 | 69 | #include "engine.h" 70 | 71 | class StackContext; 72 | 73 | class PerfEvents : public Engine { 74 | public: 75 | Error check(Arguments &args) { 76 | return Error("PerfEvents are unsupported on this platform"); 77 | } 78 | 79 | Error start(Arguments &args) { 80 | return Error("PerfEvents are unsupported on this platform"); 81 | } 82 | 83 | static int walkKernel(int tid, const void **callchain, int max_depth, 84 | StackContext *java_ctx) { 85 | return 0; 86 | } 87 | 88 | inline void enableEvents(bool enabled) {} 89 | 90 | static void resetBuffer(int tid) {} 91 | 92 | static const char *getEventName(int event_id) { return NULL; } 93 | 94 | virtual int registerThread(int tid) { return -1; } 95 | virtual void unregisterThread(int tid) {} 96 | 97 | long interval() const { return -1; } 98 | }; 99 | 100 | #endif // __linux__ 101 | 102 | #endif // _PERFEVENTS_H 103 | -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/pidController.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Datadog, Inc 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "pidController.h" 18 | 19 | double PidController::compute(u64 input, double time_delta_coefficient) { 20 | // time_delta_coefficient allows variable sampling window 21 | // the values are linearly scaled using that coefficient to reinterpret the 22 | // given value within the expected sampling window 23 | double absolute_error = 24 | static_cast(_target) - input * time_delta_coefficient; 25 | 26 | double avg_error = (_alpha * absolute_error) + ((1 - _alpha) * _avg_error); 27 | double derivative = avg_error - _avg_error; 28 | 29 | // PID formula: 30 | // u[k] = Kp e[k] + Ki e_i[k] + Kd e_d[k], control signal 31 | double signal = _proportional_gain * absolute_error + 32 | _integral_gain * _integral_value + 33 | _derivative_gain * derivative; 34 | 35 | _integral_value += absolute_error; 36 | _avg_error = avg_error; 37 | 38 | return signal; 39 | } 40 | -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/pidController.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Datadog, Inc 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef _PIDCONTROLLER_H 18 | #define _PIDCONTROLLER_H 19 | 20 | #include "arch_dd.h" 21 | #include 22 | 23 | /* 24 | * A simple implementation of a PID controller. 25 | * Heavily influenced by 26 | * https://tttapa.github.io/Pages/Arduino/Control-Theory/Motor-Fader/PID-Cpp-Implementation.html 27 | */ 28 | class PidController { 29 | private: 30 | u64 _target; 31 | double _proportional_gain; 32 | double _derivative_gain; 33 | double _integral_gain; 34 | double _alpha; 35 | 36 | double _avg_error; 37 | double _integral_value; 38 | 39 | inline static double computeAlpha(float cutoff) { 40 | if (cutoff <= 0) 41 | return 1; 42 | // α(fₙ) = cos(2πfₙ) - 1 + √( cos(2πfₙ)² - 4 cos(2πfₙ) + 3 ) 43 | const double c = std::cos(2 * double(M_PI) * cutoff); 44 | return c - 1 + std::sqrt(c * c - 4 * c + 3); 45 | } 46 | 47 | public: 48 | PidController(u64 target_per_second, double proportional_gain, 49 | double integral_gain, double derivative_gain, 50 | int sampling_window, double cutoff_secs) 51 | : _target(target_per_second * sampling_window), 52 | _proportional_gain(proportional_gain), 53 | _integral_gain(integral_gain * sampling_window), 54 | _derivative_gain(derivative_gain / sampling_window), 55 | _alpha(computeAlpha(sampling_window / cutoff_secs)), _avg_error(0), 56 | _integral_value(0) {} 57 | 58 | double compute(u64 input, double time_delta_seconds); 59 | }; 60 | 61 | #endif -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/reservoirSampler.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Datadog 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef RESERVOIR_SAMPLER_H 18 | #define RESERVOIR_SAMPLER_H 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | 25 | template 26 | class ReservoirSampler { 27 | private: 28 | const int _size; 29 | std::mt19937 _generator; 30 | std::uniform_real_distribution _uniform; 31 | std::uniform_int_distribution _random_index; 32 | std::vector _reservoir; 33 | 34 | public: 35 | ReservoirSampler(const int size) : 36 | _size(size), 37 | _generator([]() { 38 | std::random_device rd; 39 | std::seed_seq seed_seq{rd(), rd(), rd(), rd()}; 40 | return std::mt19937(seed_seq); 41 | }()), 42 | _uniform(1e-16, 1.0), 43 | _random_index(0, size - 1) { 44 | _reservoir.reserve(size); 45 | } 46 | 47 | std::vector& sample(const std::vector &input) { 48 | _reservoir.clear(); 49 | for (int i = 0; i < _size && i < input.size(); i++) { 50 | _reservoir.push_back(input[i]); 51 | } 52 | double weight = exp(log(_uniform(_generator)) / _size); 53 | int target = _size + (int) (log(_uniform(_generator)) / log(1 - weight)); 54 | while (target < input.size()) { 55 | _reservoir[_random_index(_generator)] = input[target]; 56 | weight *= exp(log(_uniform(_generator)) / _size); 57 | target += (int) (log(_uniform(_generator)) / log(1 - weight)); 58 | } 59 | return _reservoir; 60 | } 61 | }; 62 | 63 | #endif //RESERVOIR_SAMPLER_H -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/rustDemangler.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021, 2023 Datadog, Inc 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | #include 19 | 20 | namespace RustDemangler { 21 | bool is_probably_rust_legacy(const std::string &str); 22 | std::string demangle(const std::string &str); 23 | }; // namespace RustDemangler 24 | -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/safeAccess.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Andrei Pangin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef _SAFEACCESS_H 18 | #define _SAFEACCESS_H 19 | 20 | #include "arch_dd.h" 21 | #include 22 | 23 | #ifdef __clang__ 24 | #define NOINLINE __attribute__((noinline)) 25 | #else 26 | #define NOINLINE __attribute__((noinline, noclone)) 27 | #endif 28 | #define NOADDRSANITIZE __attribute__((no_sanitize("address"))) 29 | 30 | class SafeAccess { 31 | public: 32 | NOINLINE NOADDRSANITIZE __attribute__((aligned(16))) static void *load(void **ptr) { 33 | return *ptr; 34 | } 35 | 36 | NOINLINE NOADDRSANITIZE __attribute__((aligned(16))) static u32 load32(u32 *ptr, 37 | u32 default_value) { 38 | return *ptr; 39 | } 40 | 41 | NOINLINE NOADDRSANITIZE __attribute__((aligned(16))) static void * 42 | loadPtr(void **ptr, void *default_value) { 43 | return *ptr; 44 | } 45 | 46 | NOADDRSANITIZE static uintptr_t skipLoad(uintptr_t pc) { 47 | if (pc - (uintptr_t)load < sizeSafeLoadFunc) { 48 | #if defined(__x86_64__) 49 | return *(u16 *)pc == 0x8b48 ? 3 : 0; // mov rax, [reg] 50 | #elif defined(__i386__) 51 | return *(u8 *)pc == 0x8b ? 2 : 0; // mov eax, [reg] 52 | #elif defined(__arm__) || defined(__thumb__) 53 | return (*(instruction_t *)pc & 0x0e50f000) == 0x04100000 54 | ? 4 55 | : 0; // ldr r0, [reg] 56 | #elif defined(__aarch64__) 57 | return (*(instruction_t *)pc & 0xffc0001f) == 0xf9400000 58 | ? 4 59 | : 0; // ldr x0, [reg] 60 | #else 61 | return sizeof(instruction_t); 62 | #endif 63 | } 64 | return 0; 65 | } 66 | 67 | static uintptr_t skipLoadArg(uintptr_t pc) { 68 | #if defined(__aarch64__) 69 | if ((pc - (uintptr_t)load32) < sizeSafeLoadFunc || 70 | (pc - (uintptr_t)loadPtr) < sizeSafeLoadFunc) { 71 | return 4; 72 | } 73 | #endif 74 | return 0; 75 | } 76 | #ifndef __SANITIZE_ADDRESS__ 77 | constexpr static size_t sizeSafeLoadFunc = 16; 78 | #else 79 | // asan significantly increases the size of the load function 80 | // checking disassembled code can help adjust the value 81 | // gdb --batch -ex 'disas _ZN10SafeAccess4loadEPPv' ./elfparser_ut 82 | // I see that the functions can also have a 156 bytes size for the load32 83 | // and 136 for the loadPtr functions 84 | constexpr static inline size_t sizeSafeLoadFunc = 132; 85 | #endif 86 | }; 87 | 88 | #endif // _SAFEACCESS_H 89 | -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/spinLock.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Andrei Pangin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef _SPINLOCK_H 18 | #define _SPINLOCK_H 19 | 20 | #include "arch_dd.h" 21 | 22 | // Cannot use regular mutexes inside signal handler. 23 | // This lock is based on CAS busy loop. GCC atomic builtins imply full barrier. 24 | class SpinLock { 25 | private: 26 | // 0 - unlocked 27 | // 1 - exclusive lock 28 | // <0 - shared lock 29 | volatile int _lock; 30 | 31 | public: 32 | constexpr SpinLock(int initial_state = 0) : _lock(initial_state) {} 33 | 34 | void reset() { _lock = 0; } 35 | 36 | bool tryLock() { return __sync_bool_compare_and_swap(&_lock, 0, 1); } 37 | 38 | void lock() { 39 | while (!tryLock()) { 40 | spinPause(); 41 | } 42 | } 43 | 44 | void unlock() { __sync_fetch_and_sub(&_lock, 1); } 45 | 46 | bool tryLockShared() { 47 | int value; 48 | // we use relaxed as the compare already offers the guarantees we need 49 | while ((value = __atomic_load_n(&_lock, __ATOMIC_RELAXED)) <= 0) { 50 | if (__sync_bool_compare_and_swap(&_lock, value, value - 1)) { 51 | return true; 52 | } 53 | } 54 | return false; 55 | } 56 | 57 | void lockShared() { 58 | int value; 59 | while ((value = __atomic_load_n(&_lock, __ATOMIC_RELAXED)) > 0 || 60 | !__sync_bool_compare_and_swap(&_lock, value, value - 1)) { 61 | spinPause(); 62 | } 63 | } 64 | 65 | void unlockShared() { __sync_fetch_and_add(&_lock, 1); } 66 | }; 67 | 68 | #endif // _SPINLOCK_H 69 | -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/stackFrame.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Andrei Pangin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef _STACKFRAME_H 18 | #define _STACKFRAME_H 19 | 20 | #include "arch_dd.h" 21 | #include 22 | #include 23 | 24 | class NMethod; 25 | 26 | class StackFrame { 27 | private: 28 | ucontext_t *_ucontext; 29 | 30 | static bool withinCurrentStack(uintptr_t address) { 31 | // Check that the address is not too far from the stack pointer of current 32 | // context 33 | void *real_sp; 34 | return address - (uintptr_t)&real_sp <= 0xffff; 35 | } 36 | 37 | public: 38 | explicit StackFrame(void *ucontext) { _ucontext = (ucontext_t *)ucontext; } 39 | 40 | void restore(uintptr_t saved_pc, uintptr_t saved_sp, uintptr_t saved_fp) { 41 | if (_ucontext != NULL) { 42 | pc() = saved_pc; 43 | sp() = saved_sp; 44 | fp() = saved_fp; 45 | } 46 | } 47 | 48 | uintptr_t stackAt(int slot) { return ((uintptr_t *)sp())[slot]; } 49 | 50 | uintptr_t &pc(); 51 | uintptr_t &sp(); 52 | uintptr_t &fp(); 53 | 54 | uintptr_t &retval(); 55 | uintptr_t link(); 56 | uintptr_t arg0(); 57 | uintptr_t arg1(); 58 | uintptr_t arg2(); 59 | uintptr_t arg3(); 60 | uintptr_t jarg0(); 61 | uintptr_t method(); 62 | uintptr_t senderSP(); 63 | 64 | void ret(); 65 | 66 | bool unwindStub(instruction_t *entry, const char *name) { 67 | return unwindStub(entry, name, pc(), sp(), fp()); 68 | } 69 | 70 | bool unwindCompiled(NMethod *nm) { 71 | return unwindCompiled(nm, pc(), sp(), fp()); 72 | } 73 | 74 | bool unwindStub(instruction_t *entry, const char *name, uintptr_t &pc, 75 | uintptr_t &sp, uintptr_t &fp); 76 | bool unwindCompiled(NMethod *nm, uintptr_t &pc, uintptr_t &sp, uintptr_t &fp); 77 | bool unwindAtomicStub(const void*& pc); 78 | 79 | void adjustSP(const void *entry, const void *pc, uintptr_t &sp); 80 | 81 | bool skipFaultInstruction(); 82 | 83 | bool checkInterruptedSyscall(); 84 | 85 | // Check if PC points to a syscall instruction 86 | static bool isSyscall(instruction_t *pc); 87 | }; 88 | 89 | #endif // _STACKFRAME_H 90 | -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/symbols.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Andrei Pangin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef _SYMBOLS_H 18 | #define _SYMBOLS_H 19 | 20 | #include "codeCache.h" 21 | #include "mutex.h" 22 | 23 | class Symbols { 24 | private: 25 | static Mutex _parse_lock; 26 | static bool _have_kernel_symbols; 27 | 28 | public: 29 | static void parseKernelSymbols(CodeCache *cc); 30 | static void parseLibraries(CodeCacheArray *array, bool kernel_symbols); 31 | // The clear function is mainly for test purposes 32 | // There are internal caches that are not associated to the array 33 | static void clearParsingCaches(); 34 | static bool haveKernelSymbols() { return _have_kernel_symbols; } 35 | 36 | // Some symbols are always roots - eg. no unwinding should be attempted once they are encountered 37 | static bool isRootSymbol(const void* address); 38 | }; 39 | 40 | #endif // _SYMBOLS_H 41 | -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/threadFilter.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Andrei Pangin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef _THREADFILTER_H 18 | #define _THREADFILTER_H 19 | 20 | #include "arch_dd.h" 21 | #include 22 | 23 | // The size of thread ID bitmap in bytes. Must be at least 64K to allow mmap() 24 | const u32 BITMAP_SIZE = 65536; 25 | // How many thread IDs one bitmap can hold 26 | const u32 BITMAP_CAPACITY = BITMAP_SIZE * 8; 27 | 28 | // ThreadFilter query operations must be lock-free and signal-safe; 29 | // update operations are mostly lock-free, except rare bitmap allocations 30 | class ThreadFilter { 31 | private: 32 | // Total number of bitmaps required to hold the entire range of thread IDs 33 | u32 _max_thread_id; 34 | u32 _max_bitmaps; 35 | u64 **_bitmap; 36 | bool _enabled; 37 | volatile int _size; 38 | 39 | u64 *bitmap(int thread_id) { 40 | if (thread_id >= _max_thread_id) { 41 | return NULL; 42 | } 43 | return __atomic_load_n( 44 | &(_bitmap[static_cast(thread_id) / BITMAP_CAPACITY]), 45 | __ATOMIC_ACQUIRE); 46 | } 47 | 48 | u64 &word(u64 *bitmap, int thread_id) { 49 | // todo: add thread safe APIs 50 | return bitmap[((u32)thread_id % BITMAP_CAPACITY) >> 6]; 51 | } 52 | 53 | public: 54 | ThreadFilter(); 55 | ThreadFilter(ThreadFilter &threadFilter) = delete; 56 | ~ThreadFilter(); 57 | 58 | bool enabled() { return _enabled; } 59 | 60 | int size() { return _size; } 61 | 62 | void init(const char *filter); 63 | void clear(); 64 | 65 | bool accept(int thread_id); 66 | void add(int thread_id); 67 | void remove(int thread_id); 68 | 69 | void collect(std::vector &v); 70 | }; 71 | 72 | #endif // _THREADFILTER_H 73 | -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/threadInfo.cpp: -------------------------------------------------------------------------------- 1 | #include "threadInfo.h" 2 | #include "counters.h" 3 | #include "mutex.h" 4 | 5 | void ThreadInfo::set(int tid, const char *name, u64 java_thread_id) { 6 | MutexLocker ml(_ti_lock); 7 | _thread_names[tid] = std::string(name); 8 | _thread_ids[tid] = java_thread_id; 9 | } 10 | 11 | std::pair, u64> ThreadInfo::get(int threadId) { 12 | MutexLocker ml(_ti_lock); 13 | auto it = _thread_names.find(threadId); 14 | if (it != _thread_names.end()) { 15 | return std::make_pair(std::make_shared(it->second), 16 | _thread_ids[threadId]); 17 | } 18 | return std::make_pair(nullptr, 0); 19 | } 20 | 21 | int ThreadInfo::getThreadId(int threadId) { 22 | MutexLocker ml(_ti_lock); 23 | auto it = _thread_ids.find(threadId); 24 | if (it != _thread_ids.end()) { 25 | return it->second; 26 | } 27 | return -1; 28 | } 29 | 30 | void ThreadInfo::clearAll() { 31 | MutexLocker ml(_ti_lock); 32 | _thread_names.clear(); 33 | _thread_ids.clear(); 34 | } 35 | 36 | void ThreadInfo::clearAll(std::set &live_thread_ids) { 37 | // Reset thread names and IDs 38 | MutexLocker ml(_ti_lock); 39 | if (live_thread_ids.empty()) { 40 | // take the fast path 41 | _thread_names.clear(); 42 | _thread_ids.clear(); 43 | } else { 44 | // we need to honor the thread referenced from the liveness tracker 45 | std::map::iterator name_itr = _thread_names.begin(); 46 | while (name_itr != _thread_names.end()) { 47 | if (live_thread_ids.find(name_itr->first) == live_thread_ids.end()) { 48 | name_itr = _thread_names.erase(name_itr); 49 | } else { 50 | ++name_itr; 51 | } 52 | } 53 | std::map::iterator id_itr = _thread_ids.begin(); 54 | while (id_itr != _thread_ids.end()) { 55 | if (live_thread_ids.find(id_itr->first) == live_thread_ids.end()) { 56 | id_itr = _thread_ids.erase(id_itr); 57 | } else { 58 | ++id_itr; 59 | } 60 | } 61 | } 62 | } 63 | 64 | int ThreadInfo::size() { 65 | MutexLocker ml(_ti_lock); 66 | return _thread_names.size(); 67 | } 68 | 69 | void ThreadInfo::updateThreadName( 70 | int tid, std::function resolver) { 71 | MutexLocker ml(_ti_lock); 72 | auto it = _thread_names.find(tid); 73 | if (it == _thread_names.end()) { 74 | // Thread ID not found, insert new entry 75 | std::string name = resolver(tid); 76 | if (!name.empty()) { 77 | _thread_names.emplace(tid, std::move(name)); 78 | } 79 | } 80 | } 81 | 82 | void ThreadInfo::reportCounters() { 83 | MutexLocker ml(_ti_lock); 84 | Counters::set(THREAD_IDS_COUNT, _thread_ids.size()); 85 | Counters::set(THREAD_NAMES_COUNT, _thread_names.size()); 86 | } -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/threadInfo.h: -------------------------------------------------------------------------------- 1 | #include "mutex.h" 2 | #include "os.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class ThreadInfo { 10 | private: 11 | Mutex _ti_lock; 12 | std::map _thread_names; 13 | std::map _thread_ids; 14 | 15 | public: 16 | // disallow copy and assign to avoid issues with the mutex 17 | ThreadInfo(const ThreadInfo &) = delete; 18 | ThreadInfo &operator=(const ThreadInfo &) = delete; 19 | 20 | ThreadInfo() {} 21 | 22 | void set(int tid, const char *name, u64 java_thread_id); 23 | std::pair, u64> get(int tid); 24 | 25 | void updateThreadName(int tid, std::function resolver); 26 | 27 | int size(); 28 | 29 | void clearAll(std::set &live_thread_ids); 30 | void clearAll(); 31 | 32 | void reportCounters(); 33 | 34 | // For testing 35 | int getThreadId(int threadId); 36 | }; 37 | -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/threadLocalData.h: -------------------------------------------------------------------------------- 1 | #ifndef THREADLOCALDATA_H 2 | #define THREADLOCALDATA_H 3 | 4 | class ThreadLocalData { 5 | protected: 6 | bool _unwinding_Java; 7 | 8 | public: 9 | ThreadLocalData() : _unwinding_Java(false) {} 10 | virtual bool is_unwinding_Java() final { return _unwinding_Java; } 11 | 12 | virtual void set_unwinding_Java(bool unwinding_Java) final { 13 | _unwinding_Java = unwinding_Java; 14 | } 15 | }; 16 | 17 | #endif // THREADLOCALDATA_H 18 | -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/threadState.h: -------------------------------------------------------------------------------- 1 | #ifndef JAVA_PROFILER_LIBRARY_THREAD_STATE_H 2 | #define JAVA_PROFILER_LIBRARY_THREAD_STATE_H 3 | 4 | #include "jvmti.h" 5 | 6 | enum class OSThreadState : int { 7 | UNKNOWN = 0, 8 | NEW = 1, // The thread has been initialized but yet started 9 | RUNNABLE = 2, // Has been started and is runnable, but not necessarily running 10 | MONITOR_WAIT = 3, // Waiting on a contended monitor lock 11 | CONDVAR_WAIT = 4, // Waiting on a condition variable 12 | OBJECT_WAIT = 5, // Waiting on an Object.wait() call 13 | BREAKPOINTED = 6, // Suspended at breakpoint 14 | SLEEPING = 7, // Thread.sleep() 15 | TERMINATED = 8, // All done, but not reclaimed yet 16 | SYSCALL = 9 // does not originate in the JVM, used when the current frame is 17 | // known to be a syscall 18 | }; 19 | 20 | enum class ExecutionMode : int { 21 | UNKNOWN = 0, 22 | JAVA = 1, 23 | JVM = 2, 24 | NATIVE = 3, 25 | SAFEPOINT = 4, 26 | SYSCALL = 5 27 | }; 28 | 29 | static ExecutionMode convertJvmExecutionState(int state) { 30 | switch (state) { 31 | case 4: 32 | case 5: 33 | return ExecutionMode::NATIVE; 34 | case 6: 35 | case 7: 36 | return ExecutionMode::JVM; 37 | case 8: 38 | case 9: 39 | return ExecutionMode::JAVA; 40 | case 10: 41 | case 11: 42 | return ExecutionMode::SAFEPOINT; 43 | default: 44 | return ExecutionMode::UNKNOWN; 45 | } 46 | } 47 | 48 | #endif // JAVA_PROFILER_LIBRARY_THREAD_STATE_H 49 | -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/tsc.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Andrei Pangin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "tsc.h" 18 | #include "jniHelper.h" 19 | #include "vmEntry.h" 20 | #include 21 | 22 | bool TSC::_initialized = false; 23 | bool TSC::_enabled = false; 24 | u64 TSC::_offset = 0; 25 | u64 TSC::_frequency = 1000000000; 26 | 27 | void TSC::initialize() { 28 | JNIEnv *env = VM::jni(); 29 | 30 | jfieldID jvm; 31 | jmethodID getTicksFrequency, counterTime; 32 | jclass cls = env->FindClass("jdk/jfr/internal/JVM"); 33 | if (cls != NULL && 34 | ((jvm = env->GetStaticFieldID(cls, "jvm", "Ljdk/jfr/internal/JVM;")) != 35 | NULL) && 36 | ((getTicksFrequency = 37 | env->GetMethodID(cls, "getTicksFrequency", "()J")) != NULL) && 38 | ((counterTime = env->GetStaticMethodID(cls, "counterTime", "()J")) != 39 | NULL)) { 40 | 41 | u64 frequency = env->CallLongMethod(env->GetStaticObjectField(cls, jvm), 42 | getTicksFrequency); 43 | if (jniExceptionCheck(env, true)) { 44 | frequency = 0; 45 | } 46 | if (frequency > 1000000000) { 47 | // Default 1GHz frequency might mean that rdtsc is not available 48 | u64 jvm_ticks = env->CallStaticLongMethod(cls, counterTime); 49 | _offset = rdtsc() - jvm_ticks; 50 | _frequency = frequency; 51 | _enabled = true; 52 | } 53 | } 54 | 55 | env->ExceptionClear(); 56 | _initialized = true; 57 | } 58 | -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/tsc.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Andrei Pangin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef _TSC_H 18 | #define _TSC_H 19 | 20 | #include "os.h" 21 | 22 | #if defined(__x86_64__) 23 | 24 | #define TSC_SUPPORTED true 25 | 26 | static inline u64 rdtsc() { 27 | u32 lo, hi; 28 | asm volatile("rdtsc" : "=a"(lo), "=d"(hi)); 29 | return ((u64)hi << 32) | lo; 30 | } 31 | 32 | #elif defined(__i386__) 33 | 34 | #define TSC_SUPPORTED true 35 | 36 | static inline u64 rdtsc() { 37 | u64 result; 38 | asm volatile("rdtsc" : "=A"(result)); 39 | return result; 40 | } 41 | 42 | #else 43 | 44 | #define TSC_SUPPORTED false 45 | #define rdtsc() 0 46 | 47 | #endif 48 | 49 | class TSC { 50 | private: 51 | static bool _initialized; 52 | static bool _enabled; 53 | static u64 _offset; 54 | static u64 _frequency; 55 | 56 | public: 57 | static void initialize(); 58 | 59 | static bool initialized() { return TSC_SUPPORTED ? _initialized : true; } 60 | 61 | static bool enabled() { return TSC_SUPPORTED && _enabled; } 62 | 63 | static u64 ticks() { return enabled() ? rdtsc() - _offset : OS::nanotime(); } 64 | 65 | static u64 frequency() { return _frequency; } 66 | 67 | static u64 ticks_to_millis(u64 ticks) { 68 | return TSC_SUPPORTED ? 1000 * ticks / _frequency : ticks / 1000 / 1000; 69 | } 70 | }; 71 | 72 | #endif // _TSC_H 73 | -------------------------------------------------------------------------------- /ddprof-lib/src/main/cpp/unwindStats.cpp: -------------------------------------------------------------------------------- 1 | #include "unwindStats.h" 2 | 3 | // initialize static members 4 | SpinLock UnwindStats::_lock; 5 | UnwindFailures UnwindStats::_unwind_failures; 6 | -------------------------------------------------------------------------------- /ddprof-lib/src/main/java/com/datadoghq/profiler/Arch.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler; 2 | 3 | import java.util.Arrays; 4 | import java.util.EnumSet; 5 | import java.util.HashSet; 6 | import java.util.Set; 7 | 8 | /** A simple implementation to detect the current architecture */ 9 | enum Arch { 10 | x64("x86_64", "amd64", "k8"), 11 | x86("x86", "i386", "i486", "i586", "i686"), 12 | arm("ARM", "aarch32"), 13 | arm64("arm64", "aarch64"), 14 | unknown(); 15 | 16 | private final Set identifiers; 17 | 18 | Arch(String... identifiers) { 19 | this.identifiers = new HashSet<>(Arrays.asList(identifiers)); 20 | } 21 | 22 | public static Arch of(String identifier) { 23 | for (Arch arch : EnumSet.allOf(Arch.class)) { 24 | if (arch.identifiers.contains(identifier)) { 25 | return arch; 26 | } 27 | } 28 | return unknown; 29 | } 30 | 31 | public static Arch current() { 32 | return Arch.of(System.getProperty("os.arch")); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /ddprof-lib/src/main/java/com/datadoghq/profiler/ContextSetter.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashSet; 5 | import java.util.List; 6 | import java.util.Set; 7 | import java.util.concurrent.ConcurrentHashMap; 8 | 9 | public class ContextSetter { 10 | 11 | private static final int TAGS_STORAGE_LIMIT = 10; 12 | private final List attributes; 13 | private final JavaProfiler profiler; 14 | 15 | private final ConcurrentHashMap jniCache = new ConcurrentHashMap<>(); 16 | 17 | public ContextSetter(JavaProfiler profiler, List attributes) { 18 | this.profiler = profiler; 19 | Set unique = new HashSet<>(attributes); 20 | this.attributes = new ArrayList<>(unique.size()); 21 | for (int i = 0; i < Math.min(attributes.size(), TAGS_STORAGE_LIMIT); i++) { 22 | String attribute = attributes.get(i); 23 | if (unique.remove(attribute)) { 24 | this.attributes.add(attribute); 25 | } 26 | } 27 | } 28 | 29 | public int encode(String key) { 30 | if (key != null) { 31 | Integer encoding = jniCache.get(key); 32 | if (encoding != null) { 33 | return encoding; 34 | } else if (jniCache.size() <= 1 << 16) { 35 | int e = profiler.registerConstant(key); 36 | if (e > 0 && jniCache.putIfAbsent(key, e) == null) { 37 | return e; 38 | } 39 | } 40 | } 41 | return 0; 42 | } 43 | 44 | public int[] snapshotTags() { 45 | int[] snapshot = new int[attributes.size()]; 46 | snapshotTags(snapshot); 47 | return snapshot; 48 | } 49 | 50 | public void snapshotTags(int[] snapshot) { 51 | if (snapshot.length <= attributes.size()) { 52 | profiler.copyTags(snapshot); 53 | } 54 | } 55 | 56 | public int offsetOf(String attribute) { 57 | return attributes.indexOf(attribute); 58 | } 59 | 60 | public boolean setContextValue(String attribute, String value) { 61 | return setContextValue(offsetOf(attribute), value); 62 | } 63 | 64 | public boolean setContextValue(int offset, String value) { 65 | if (offset >= 0) { 66 | int encoding = encode(value); 67 | if (encoding >= 0) { 68 | setContextValue(offset, encoding); 69 | return true; 70 | } 71 | } 72 | return false; 73 | } 74 | 75 | public boolean setContextValue(int offset, int encoding) { 76 | if (offset >= 0 && encoding >= 0) { 77 | profiler.setContextValue(offset, encoding); 78 | return true; 79 | } 80 | return false; 81 | } 82 | 83 | public boolean clearContextValue(String attribute) { 84 | return clearContextValue(offsetOf(attribute)); 85 | } 86 | 87 | public boolean clearContextValue(int offset) { 88 | if (offset >= 0) { 89 | profiler.setContextValue(offset, 0); 90 | return true; 91 | } 92 | return false; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /ddprof-lib/src/main/java/com/datadoghq/profiler/Main.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler; 2 | 3 | import java.io.IOException; 4 | 5 | public class Main { 6 | public static void main(String... args) throws IOException { 7 | String command = args.length > 0 ? args[0] : "status"; 8 | JavaProfiler profiler = JavaProfiler.getInstance(); 9 | profiler.execute(command); 10 | if (command.contains("start")) { 11 | profiler.stop(); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /ddprof-lib/src/test/make/Makefile: -------------------------------------------------------------------------------- 1 | CC := g++ 2 | SRCDIR := ../../main/cpp-external ../../main/cpp 3 | OBJDIR := ./../../../build/scanbuild_obj 4 | CFLAGS := -O0 -Wall -std=c++17 -fno-omit-frame-pointer -momit-leaf-frame-pointer -fvisibility=hidden 5 | SRCS := ${wildcard ${SRCDIR}/*.cpp } 6 | OBJS=${patsubst ${SRCDIR}/%.cpp,${OBJDIR}/%.o,${SRCS}} 7 | INCLUDES := -I$(JAVA_HOME)/include -I../../../../malloc-shim/src/main/public 8 | 9 | OS := $(shell uname -s) 10 | ifeq ($(OS),Darwin) 11 | CFLAGS += -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE 12 | INCLUDES += -I$(JAVA_HOME)/include/darwin 13 | else 14 | CFLAGS += -Wl,-z,defs -Wl,-z,nodelete 15 | INCLUDES += -I$(JAVA_HOME)/include/linux 16 | ifeq ($(findstring musl,$(shell ldd /bin/ls)),musl) 17 | CFLAGS += -D__musl__ 18 | endif 19 | endif 20 | 21 | .PHONY: all clean 22 | 23 | all: $(OBJDIR) $(OBJS) 24 | 25 | $(OBJDIR): 26 | mkdir -p $(OBJDIR) 27 | 28 | $(OBJDIR)/%.o : ${SRCDIR}/%.cpp 29 | ${CC} ${CFLAGS} -DEBUG -DPROFILER_VERSION=\"snapshot\" ${INCLUDES} -c $< -o $@ 30 | 31 | clean : 32 | @rm -rf $(OBJDIR) -------------------------------------------------------------------------------- /ddprof-lib/src/test/resources/native-libs/reladyn-lib/Makefile: -------------------------------------------------------------------------------- 1 | TARGET_DIR = ../build/test/resources/native-libs/reladyn-lib 2 | all: 3 | g++ -fPIC -shared -o $(TARGET_DIR)/libreladyn.so reladyn.c 4 | -------------------------------------------------------------------------------- /ddprof-lib/src/test/resources/native-libs/reladyn-lib/reladyn.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The async-profiler authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | #include 6 | #include 7 | // Force pthread_setspecific into .rela.dyn with R_X86_64_GLOB_DAT. 8 | int (*indirect_pthread_setspecific)(pthread_key_t, const void*); 9 | // Force pthread_exit into .rela.dyn with R_X86_64_64. 10 | void (*static_pthread_exit)(void*) = pthread_exit; 11 | void* thread_function(void* arg) { 12 | printf("Thread running\n"); 13 | return NULL; 14 | } 15 | // Not indended to be executed. 16 | int reladyn() { 17 | pthread_t thread; 18 | pthread_key_t key; 19 | pthread_key_create(&key, NULL); 20 | // Direct call, forces into .rela.plt. 21 | pthread_create(&thread, NULL, thread_function, NULL); 22 | // Assign to a function pointer at runtime, forces into .rela.dyn as R_X86_64_GLOB_DAT. 23 | indirect_pthread_setspecific = pthread_setspecific; 24 | indirect_pthread_setspecific(key, "Thread-specific value"); 25 | // Use pthread_exit via the static pointer, forces into .rela.dyn as R_X86_64_64. 26 | static_pthread_exit(NULL); 27 | return 0; 28 | } -------------------------------------------------------------------------------- /ddprof-lib/src/test/resources/native-libs/small-lib/Makefile: -------------------------------------------------------------------------------- 1 | TARGET_DIR = ../build/test/resources/native-libs/small-lib 2 | all: 3 | g++ -fPIC -shared -o $(TARGET_DIR)/libsmall-lib.so small_lib.cpp 4 | -------------------------------------------------------------------------------- /ddprof-lib/src/test/resources/native-libs/small-lib/small_lib.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "small_lib.h" 3 | 4 | extern "C" void hello() { 5 | std::cout << "Hello, World from shared library!" << std::endl; 6 | } 7 | -------------------------------------------------------------------------------- /ddprof-lib/src/test/resources/native-libs/small-lib/small_lib.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | extern "C" { 4 | void hello(); 5 | } -------------------------------------------------------------------------------- /ddprof-lib/src/test/resources/native-libs/unresolved-functions/Makefile: -------------------------------------------------------------------------------- 1 | TARGET_DIR = ../build/test/resources/unresolved-functions 2 | all: 3 | gcc -c main.c -o $(TARGET_DIR)/main.o 4 | gcc -o $(TARGET_DIR)/main $(TARGET_DIR)/main.o -T linker.ld -------------------------------------------------------------------------------- /ddprof-lib/src/test/resources/native-libs/unresolved-functions/linker.ld: -------------------------------------------------------------------------------- 1 | PHDRS 2 | { 3 | headers PT_PHDR PHDRS ; 4 | interp PT_INTERP ; 5 | text PT_LOAD FILEHDR PHDRS ; 6 | data PT_LOAD ; 7 | } 8 | 9 | SECTIONS 10 | { 11 | . = 0x10000; 12 | .text : { 13 | *(.text) 14 | } :text 15 | 16 | . = 0x20000; 17 | .data : { 18 | *(.data) 19 | } :data 20 | 21 | .bss : { 22 | *(.bss) 23 | } 24 | 25 | . = 0x30000; 26 | unresolved_symbol = .; 27 | . = 0xffffffffffffffff; 28 | unresolved_function = .; 29 | 30 | /* Add the .init_array section */ 31 | .init_array : { 32 | __init_array_start = .; 33 | KEEP(*(.init_array)) 34 | __init_array_end = .; 35 | } 36 | 37 | /* Add the .fini_array section */ 38 | .fini_array : { 39 | __fini_array_start = .; 40 | KEEP(*(.fini_array)) 41 | __fini_array_end = .; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /ddprof-lib/src/test/resources/native-libs/unresolved-functions/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | extern int unresolved_symbol; 4 | extern int unresolved_function(); 5 | 6 | int main() { 7 | printf("Value of unresolved_symbol: %p\n", &unresolved_symbol); 8 | printf("Value of unresolved_function: %p\n", &unresolved_function); 9 | return 0; 10 | } 11 | -------------------------------------------------------------------------------- /ddprof-lib/src/test/resources/native-libs/unresolved-functions/readme.txt: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | This binary tests that we are able to parse symbols even when they point to unresolved functions. 4 | The function is set to point to a 0xffffffffffffffff address. 5 | -------------------------------------------------------------------------------- /ddprof-stresstest/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'me.champeau.jmh' version '0.7.1' 4 | } 5 | 6 | repositories { 7 | mavenCentral() 8 | } 9 | 10 | dependencies { 11 | implementation project(path: ":ddprof-lib", configuration: 'debug') 12 | implementation project(path: ":ddprof-test-tracer") 13 | implementation 'org.openjdk.jmh:jmh-core:1.36' 14 | implementation 'org.openjdk.jmh:jmh-generator-annprocess:1.36' 15 | } 16 | 17 | sourceSets { 18 | jmh { 19 | java { 20 | runtimeClasspath += project(':ddprof-lib').sourceSets.main.output 21 | } 22 | } 23 | } 24 | 25 | jmhJar { 26 | manifest { 27 | attributes( 28 | 'Main-Class': 'com.datadoghq.profiler.stresstest.Main' 29 | ) 30 | } 31 | archiveFileName = "stresstests.jar" 32 | } 33 | 34 | task runStressTests(type: Exec) { 35 | dependsOn jmhJar 36 | def javaHome = System.getenv("JAVA_TEST_HOME") 37 | if (javaHome == null) { 38 | javaHome = System.getenv("JAVA_HOME") 39 | } 40 | group = 'Execution' 41 | description = 'Run JMH stresstests' 42 | commandLine "${javaHome}/bin/java", '-jar', 'build/libs/stresstests.jar' 43 | } 44 | 45 | tasks.withType(JavaCompile).configureEach { 46 | options.compilerArgs.addAll(['--release', '8']) 47 | } -------------------------------------------------------------------------------- /ddprof-stresstest/src/jmh/java/com/datadoghq/profiler/stresstest/AbstractFormatter.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler.stresstest; 2 | 3 | import java.io.IOException; 4 | import java.io.PrintStream; 5 | import java.nio.file.Files; 6 | import java.nio.file.Path; 7 | 8 | public abstract class AbstractFormatter implements Formatter { 9 | protected final PrintStream out; 10 | 11 | AbstractFormatter(Path file) throws IOException { 12 | Files.deleteIfExists(file); 13 | Files.createFile(file); 14 | this.out = new PrintStream(file.toFile()); 15 | } 16 | 17 | final public void format() { 18 | print(); 19 | } 20 | 21 | abstract protected void print(); 22 | } 23 | -------------------------------------------------------------------------------- /ddprof-stresstest/src/jmh/java/com/datadoghq/profiler/stresstest/CompositeFormatter.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler.stresstest; 2 | 3 | import java.util.Arrays; 4 | import java.util.HashSet; 5 | import java.util.Set; 6 | 7 | public class CompositeFormatter implements Formatter { 8 | private final Set formatters = new HashSet<>(); 9 | 10 | public static Formatter of(Formatter formatter, Formatter ... formatters) { 11 | return new CompositeFormatter(formatter, formatters); 12 | } 13 | 14 | private CompositeFormatter(Formatter formatter, Formatter ... formatters) { 15 | this.formatters.add(formatter); 16 | this.formatters.addAll(Arrays.asList(formatters)); 17 | } 18 | 19 | @Override 20 | public void format() { 21 | formatters.forEach(Formatter::format); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ddprof-stresstest/src/jmh/java/com/datadoghq/profiler/stresstest/Configuration.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler.stresstest; 2 | 3 | import org.openjdk.jmh.annotations.Param; 4 | import org.openjdk.jmh.annotations.Scope; 5 | import org.openjdk.jmh.annotations.State; 6 | 7 | @State(Scope.Benchmark) 8 | public class Configuration { 9 | 10 | public static final String BASE_COMMAND = "cpu=100us,wall=100us"; 11 | 12 | @Param({BASE_COMMAND}) 13 | public String command; 14 | } 15 | -------------------------------------------------------------------------------- /ddprof-stresstest/src/jmh/java/com/datadoghq/profiler/stresstest/Formatter.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler.stresstest; 2 | 3 | @FunctionalInterface 4 | public interface Formatter { 5 | void format(); 6 | } 7 | -------------------------------------------------------------------------------- /ddprof-stresstest/src/jmh/java/com/datadoghq/profiler/stresstest/Main.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler.stresstest; 2 | 3 | import org.openjdk.jmh.annotations.Mode; 4 | import org.openjdk.jmh.results.RunResult; 5 | import org.openjdk.jmh.runner.Runner; 6 | import org.openjdk.jmh.runner.options.CommandLineOptions; 7 | import org.openjdk.jmh.runner.options.Options; 8 | import org.openjdk.jmh.runner.options.OptionsBuilder; 9 | import org.openjdk.jmh.runner.options.TimeValue; 10 | 11 | import java.util.Collection; 12 | import java.util.concurrent.TimeUnit; 13 | 14 | public class Main { 15 | 16 | public static final String SCENARIOS_PACKAGE = "com.datadoghq.profiler.stresstest.scenarios."; 17 | 18 | public static void main(String... args) throws Exception { 19 | CommandLineOptions commandLineOptions = new CommandLineOptions(args); 20 | Mode mode = Mode.AverageTime; 21 | Options options = new OptionsBuilder() 22 | .parent(new CommandLineOptions(args)) 23 | .include(SCENARIOS_PACKAGE + "*") 24 | .addProfiler(WhiteboxProfiler.class) 25 | .forks(commandLineOptions.getForkCount().orElse(1)) 26 | .warmupIterations(commandLineOptions.getWarmupIterations().orElse(0)) 27 | .measurementIterations(commandLineOptions.getMeasurementIterations().orElse(1)) 28 | .measurementTime(commandLineOptions.getMeasurementTime().orElse(TimeValue.seconds(5))) 29 | .timeUnit(commandLineOptions.getTimeUnit().orElse(TimeUnit.MICROSECONDS)) 30 | .mode(mode) 31 | .build(); 32 | Collection results = new Runner(options).run(); 33 | CompositeFormatter.of(new HtmlCommentFormatter(results, mode), new HtmlFormatter(results, mode)).format(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ddprof-stresstest/src/jmh/java/com/datadoghq/profiler/stresstest/WhiteboxProfiler.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler.stresstest; 2 | 3 | import com.datadoghq.profiler.JavaProfiler; 4 | import org.openjdk.jmh.infra.BenchmarkParams; 5 | import org.openjdk.jmh.infra.IterationParams; 6 | import org.openjdk.jmh.profile.InternalProfiler; 7 | import org.openjdk.jmh.results.AggregationPolicy; 8 | import org.openjdk.jmh.results.IterationResult; 9 | import org.openjdk.jmh.results.Result; 10 | import org.openjdk.jmh.results.ScalarResult; 11 | 12 | import java.io.IOException; 13 | import java.nio.file.Files; 14 | import java.nio.file.Path; 15 | import java.util.ArrayList; 16 | import java.util.Collection; 17 | import java.util.Collections; 18 | import java.util.List; 19 | import java.util.Map; 20 | 21 | public class WhiteboxProfiler implements InternalProfiler { 22 | 23 | private Path jfr; 24 | 25 | @Override 26 | public String getDescription() { 27 | return "ddprof-whitebox"; 28 | } 29 | 30 | @Override 31 | public void beforeIteration(BenchmarkParams benchmarkParams, IterationParams iterationParams) { 32 | try { 33 | jfr = Files.createTempFile(benchmarkParams.getBenchmark() + System.currentTimeMillis(), ".jfr"); 34 | String command = "start," + benchmarkParams.getParam("command") 35 | + ",jfr,file=" + jfr.toAbsolutePath(); 36 | JavaProfiler.getInstance().execute(command); 37 | } catch (IOException e) { 38 | e.printStackTrace(); 39 | } 40 | } 41 | 42 | @Override 43 | public Collection afterIteration(BenchmarkParams benchmarkParams, IterationParams iterationParams, IterationResult result) { 44 | // TODO unit encoded in counter name for now, so results are effectively dimensionless 45 | try { 46 | JavaProfiler.getInstance().stop(); 47 | long fileSize = Files.size(jfr); 48 | Files.deleteIfExists(jfr); 49 | List results = new ArrayList<>(); 50 | results.add(new ScalarResult("jfr_filesize_bytes", fileSize, "", AggregationPolicy.MAX)); 51 | for (Map.Entry counter : JavaProfiler.getInstance().getDebugCounters().entrySet()) { 52 | results.add(new ScalarResult(counter.getKey(), counter.getValue(), "", AggregationPolicy.MAX)); 53 | } 54 | return results; 55 | } catch (IOException e) { 56 | e.printStackTrace(); 57 | return Collections.emptyList(); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ddprof-stresstest/src/jmh/java/com/datadoghq/profiler/stresstest/scenarios/CapturingLambdas.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler.stresstest.scenarios; 2 | 3 | import com.datadoghq.profiler.stresstest.Configuration; 4 | import org.openjdk.jmh.annotations.Benchmark; 5 | import org.openjdk.jmh.annotations.CompilerControl; 6 | import org.openjdk.jmh.annotations.Param; 7 | import org.openjdk.jmh.annotations.Scope; 8 | import org.openjdk.jmh.annotations.State; 9 | 10 | import java.util.UUID; 11 | import java.util.concurrent.ThreadLocalRandom; 12 | import java.util.function.Supplier; 13 | 14 | @State(Scope.Benchmark) 15 | public class CapturingLambdas extends Configuration { 16 | 17 | @Param(BASE_COMMAND + ",memory=1048576:a") 18 | public String command; 19 | 20 | @Benchmark 21 | public Object capturingLambda() { 22 | return lambda(UUID.randomUUID()).get(); 23 | } 24 | 25 | 26 | @CompilerControl(CompilerControl.Mode.DONT_INLINE) 27 | public Supplier lambda(UUID state) { 28 | return () -> state.getLeastSignificantBits() * ThreadLocalRandom.current().nextLong(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ddprof-stresstest/src/jmh/java/com/datadoghq/profiler/stresstest/scenarios/DumpRecording.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler.stresstest.scenarios; 2 | 3 | import com.datadoghq.profiler.JavaProfiler; 4 | import com.datadoghq.profiler.stresstest.Configuration; 5 | import org.openjdk.jmh.annotations.Benchmark; 6 | import org.openjdk.jmh.annotations.Param; 7 | import org.openjdk.jmh.annotations.Scope; 8 | import org.openjdk.jmh.annotations.State; 9 | import org.openjdk.jmh.annotations.Threads; 10 | 11 | import java.io.IOException; 12 | import java.nio.file.Files; 13 | import java.nio.file.Path; 14 | import java.util.UUID; 15 | import java.util.concurrent.ThreadLocalRandom; 16 | 17 | @State(Scope.Benchmark) 18 | public class DumpRecording extends Configuration { 19 | 20 | @Param(BASE_COMMAND + ",memory=1048576:a") 21 | public String command; 22 | 23 | @Benchmark 24 | @Threads(2) 25 | public Object dumpRecording(GraphState graph) throws IOException { 26 | for (int i = 0; i < 100; i++) { 27 | int object = ThreadLocalRandom.current().nextInt(graph.nodeCount); 28 | int subject = ThreadLocalRandom.current().nextInt(graph.nodeCount); 29 | graph.nodes[subject].link(graph.nodes[object]); 30 | } 31 | Path tmpRecording = Files.createTempFile(UUID.randomUUID().toString(), ".jfr"); 32 | JavaProfiler.getInstance().dump(tmpRecording); 33 | return Files.deleteIfExists(tmpRecording); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ddprof-stresstest/src/jmh/java/com/datadoghq/profiler/stresstest/scenarios/GraphMutation.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler.stresstest.scenarios; 2 | 3 | import org.openjdk.jmh.annotations.Benchmark; 4 | import org.openjdk.jmh.annotations.Threads; 5 | 6 | import java.util.Queue; 7 | import java.util.concurrent.ConcurrentLinkedQueue; 8 | import java.util.concurrent.ThreadLocalRandom; 9 | 10 | public class GraphMutation { 11 | 12 | public static class GraphNode { 13 | private final Queue nodes = new ConcurrentLinkedQueue<>(); 14 | 15 | public void link(GraphNode node) { 16 | nodes.add(node); 17 | } 18 | } 19 | 20 | @Benchmark 21 | @Threads(8) 22 | public void mutateGraph(GraphState graph) { 23 | int object = ThreadLocalRandom.current().nextInt(graph.nodeCount); 24 | int subject = ThreadLocalRandom.current().nextInt(graph.nodeCount); 25 | graph.nodes[subject].link(graph.nodes[object]); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ddprof-stresstest/src/jmh/java/com/datadoghq/profiler/stresstest/scenarios/GraphState.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler.stresstest.scenarios; 2 | 3 | import com.datadoghq.profiler.stresstest.Configuration; 4 | import org.openjdk.jmh.annotations.*; 5 | 6 | import java.util.Arrays; 7 | 8 | @State(Scope.Benchmark) 9 | public class GraphState extends Configuration { 10 | @Param("1024") 11 | int nodeCount; 12 | 13 | public GraphMutation.GraphNode[] nodes; 14 | 15 | @Setup(Level.Iteration) 16 | public void setup() { 17 | nodes = new GraphMutation.GraphNode[nodeCount]; 18 | Arrays.setAll(nodes, i -> new GraphMutation.GraphNode()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ddprof-stresstest/src/jmh/java/com/datadoghq/profiler/stresstest/scenarios/NanoTime.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler.stresstest.scenarios; 2 | 3 | import com.datadoghq.profiler.stresstest.Configuration; 4 | import org.openjdk.jmh.annotations.Benchmark; 5 | 6 | public class NanoTime { 7 | 8 | @Benchmark 9 | public long nanoTime(Configuration config) { 10 | return System.nanoTime(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ddprof-stresstest/src/jmh/java/com/datadoghq/profiler/stresstest/scenarios/TracedParallelWork.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler.stresstest.scenarios; 2 | 3 | import com.datadoghq.profiler.ContextSetter; 4 | import com.datadoghq.profiler.JavaProfiler; 5 | import com.datadoghq.profiler.context.ContextExecutor; 6 | import com.datadoghq.profiler.context.Tracing; 7 | import com.datadoghq.profiler.stresstest.Configuration; 8 | import org.openjdk.jmh.annotations.*; 9 | import org.openjdk.jmh.infra.Blackhole; 10 | 11 | import java.io.IOException; 12 | import java.util.Arrays; 13 | import java.util.UUID; 14 | import java.util.concurrent.ExecutionException; 15 | import java.util.concurrent.Future; 16 | import java.util.concurrent.ThreadLocalRandom; 17 | import java.util.stream.IntStream; 18 | 19 | public class TracedParallelWork { 20 | 21 | @State(Scope.Benchmark) 22 | public static class BenchmarkState extends Configuration { 23 | 24 | public static final String COMMAND = BASE_COMMAND + ",attributes=tag0;tag1"; 25 | 26 | @Param(COMMAND) 27 | String command; 28 | @Param({"10", "100", "1000"}) 29 | int tagCardinality; 30 | ContextExecutor executor; 31 | JavaProfiler profiler; 32 | ContextSetter contextSetter; 33 | 34 | int tag0; 35 | int tag1; 36 | 37 | String[] tagValues; 38 | 39 | public long newTraceId() { 40 | return ThreadLocalRandom.current().nextLong(); 41 | } 42 | 43 | public String getTag(long id) { 44 | int offset = (int) (Math.abs(id) % tagCardinality); 45 | return tagValues[offset]; 46 | } 47 | 48 | @Setup(Level.Trial) 49 | public void setup() throws IOException { 50 | profiler = JavaProfiler.getInstance(); 51 | executor = new ContextExecutor(200, profiler); 52 | contextSetter = new ContextSetter(profiler, Arrays.asList("tag0", "tag1")); 53 | tag0 = contextSetter.offsetOf("tag0"); 54 | tag1 = contextSetter.offsetOf("tag1"); 55 | tagValues = IntStream.range(0, tagCardinality).mapToObj(i -> UUID.randomUUID().toString()) 56 | .toArray(String[]::new); 57 | } 58 | } 59 | 60 | @Benchmark 61 | @Threads(8) 62 | public Object work(BenchmarkState state, Blackhole bh) throws ExecutionException, InterruptedException { 63 | try (Tracing.Context context = Tracing.newContext(state::newTraceId, state.profiler)) { 64 | state.profiler.setContext(context.getSpanId(), context.getRootSpanId()); 65 | state.contextSetter.setContextValue(state.tag0, state.getTag(context.getSpanId())); 66 | state.contextSetter.setContextValue(state.tag1, state.getTag(context.getSpanId() + 1)); 67 | Future f = state.executor.submit(() -> compute(state)); 68 | bh.consume(compute(state)); 69 | return f.get(); 70 | } 71 | } 72 | 73 | public long compute(BenchmarkState state) { 74 | long x = ThreadLocalRandom.current().nextLong(); 75 | for (int i = 0; i < 10_000; i++) { 76 | state.contextSetter.setContextValue(state.tag0, state.getTag(x)); 77 | x ^= ThreadLocalRandom.current().nextLong(); 78 | state.contextSetter.setContextValue(state.tag1, state.getTag(x)); 79 | } 80 | return x; 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /ddprof-test-tracer/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | implementation project(path: ":ddprof-lib", configuration: 'release') 11 | } 12 | 13 | tasks.withType(JavaCompile).configureEach { 14 | options.compilerArgs.addAll(['--release', '8']) 15 | } -------------------------------------------------------------------------------- /ddprof-test-tracer/src/main/java/com/datadoghq/profiler/context/ContextExecutor.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler.context; 2 | 3 | import com.datadoghq.profiler.JavaProfiler; 4 | 5 | import java.util.concurrent.ArrayBlockingQueue; 6 | import java.util.concurrent.RunnableFuture; 7 | import java.util.concurrent.ThreadPoolExecutor; 8 | import java.util.concurrent.TimeUnit; 9 | 10 | public class ContextExecutor extends ThreadPoolExecutor { 11 | 12 | private final JavaProfiler profiler; 13 | public ContextExecutor(int corePoolSize, JavaProfiler profiler) { 14 | super(corePoolSize, corePoolSize, 30, TimeUnit.SECONDS, 15 | new ArrayBlockingQueue<>(128), new RegisteringThreadFactory(profiler)); 16 | this.profiler = profiler; 17 | } 18 | 19 | @Override 20 | protected RunnableFuture newTaskFor(Runnable runnable, T value) { 21 | return ContextTask.wrap(runnable, value); 22 | } 23 | 24 | @Override 25 | protected void beforeExecute(Thread t, Runnable r) { 26 | super.beforeExecute(t, r); 27 | profiler.addThread(); 28 | } 29 | 30 | @Override 31 | protected void afterExecute(Runnable r, Throwable t) { 32 | profiler.removeThread(); 33 | super.afterExecute(r, t); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ddprof-test-tracer/src/main/java/com/datadoghq/profiler/context/ContextTask.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler.context; 2 | 3 | import java.util.concurrent.FutureTask; 4 | 5 | public class ContextTask extends FutureTask { 6 | 7 | public static FutureTask wrap(Runnable task, T value) { 8 | return new ContextTask<>(Tracing.capture(), task, value); 9 | } 10 | 11 | public ContextTask(Tracing.MigratingContext context, Runnable task, T value) { 12 | super(task, value); 13 | this.context = context; 14 | } 15 | 16 | @Override 17 | public void run() { 18 | try (Tracing.Context activation = this.context.activate()) { 19 | super.run(); 20 | } 21 | } 22 | 23 | private final Tracing.MigratingContext context; 24 | } 25 | -------------------------------------------------------------------------------- /ddprof-test-tracer/src/main/java/com/datadoghq/profiler/context/RegisteringThreadFactory.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler.context; 2 | 3 | import com.datadoghq.profiler.JavaProfiler; 4 | 5 | import java.util.concurrent.ThreadFactory; 6 | 7 | final class RegisteringThreadFactory implements ThreadFactory { 8 | private final JavaProfiler profiler; 9 | 10 | RegisteringThreadFactory(JavaProfiler profiler) { 11 | this.profiler = profiler; 12 | } 13 | 14 | @Override 15 | public Thread newThread(Runnable task) { 16 | Thread thread = new Thread(() -> { 17 | profiler.addThread(); 18 | task.run(); 19 | }); 20 | thread.setDaemon(true); 21 | return thread; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ddprof-test/src/test/java/com/datadoghq/profiler/CStackAwareAbstractProfilerTest.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler; 2 | 3 | 4 | import com.datadoghq.profiler.junit.CStack; 5 | import com.datadoghq.profiler.junit.CStackInjector; 6 | import org.junit.jupiter.api.extension.ExtendWith; 7 | 8 | @ExtendWith(CStackInjector.class) 9 | public abstract class CStackAwareAbstractProfilerTest extends AbstractProfilerTest { 10 | public CStackAwareAbstractProfilerTest(@CStack String cstack) { 11 | super(mapOf("cstack", cstack)); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ddprof-test/src/test/java/com/datadoghq/profiler/MoreAssertions.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertTrue; 4 | 5 | public class MoreAssertions { 6 | 7 | public static final int DICTIONARY_PAGE_SIZE = (128 * (3 * 8 + 8) + 4); 8 | 9 | public static void assertBoundedBy(long value, long maximum, String error) { 10 | if (value >= maximum) { 11 | throw new AssertionError(error + ". Too large: " + value + " > " + maximum); 12 | } 13 | } 14 | 15 | public static void assertInRange(double value, double min, double max) { 16 | assertTrue(value >= min && value <= max, value + " not in (" + min + "," + max + ")"); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ddprof-test/src/test/java/com/datadoghq/profiler/MuslDetectionTest.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler; 2 | 3 | import org.junit.jupiter.api.Assumptions; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.io.IOException; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | 10 | public class MuslDetectionTest { 11 | 12 | @Test 13 | public void testIsMusl() throws IOException { 14 | Assumptions.assumeTrue(Platform.isLinux(), "not running on linux"); 15 | String libc = System.getenv("LIBC"); 16 | Assumptions.assumeTrue(libc != null, "not running in CI, so LIBC envvar not set"); 17 | boolean isMusl = "musl".equalsIgnoreCase(libc); 18 | OperatingSystem os = OperatingSystem.current(); 19 | assertEquals(isMusl, os.isMuslProcSelfMaps()); 20 | assertEquals(isMusl, os.isMuslJavaExecutable()); 21 | assertEquals(isMusl, os.isMusl()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ddprof-test/src/test/java/com/datadoghq/profiler/cpu/CTimerSamplerTest.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler.cpu; 2 | 3 | import com.datadoghq.profiler.AbstractProfilerTest; 4 | import com.datadoghq.profiler.CStackAwareAbstractProfilerTest; 5 | import com.datadoghq.profiler.Platform; 6 | import com.datadoghq.profiler.junit.CStack; 7 | import com.datadoghq.profiler.junit.CStackInjector; 8 | import com.datadoghq.profiler.junit.RetryTest; 9 | import org.junit.jupiter.api.Assumptions; 10 | import org.junit.jupiter.api.Test; 11 | import org.junit.jupiter.api.TestTemplate; 12 | import org.junit.jupiter.api.extension.ExtendWith; 13 | import org.junit.jupiter.params.ParameterizedTest; 14 | import org.junit.jupiter.params.provider.ValueSource; 15 | import org.junitpioneer.jupiter.RetryingTest; 16 | import org.openjdk.jmc.common.item.IItem; 17 | import org.openjdk.jmc.common.item.IItemCollection; 18 | import org.openjdk.jmc.common.item.IItemIterable; 19 | import org.openjdk.jmc.common.item.IMemberAccessor; 20 | import org.openjdk.jmc.common.unit.IQuantity; 21 | import org.openjdk.jmc.flightrecorder.jdk.JdkAttributes; 22 | 23 | import java.util.Map; 24 | import java.util.Set; 25 | import java.util.concurrent.ExecutionException; 26 | 27 | import static org.junit.jupiter.api.Assertions.assertFalse; 28 | import static org.junit.jupiter.api.Assumptions.assumeFalse; 29 | import static org.junit.jupiter.api.Assumptions.assumeTrue; 30 | 31 | public class CTimerSamplerTest extends CStackAwareAbstractProfilerTest { 32 | 33 | private ProfiledCode profiledCode; 34 | 35 | public CTimerSamplerTest(@CStack String cstack) { 36 | super(cstack); 37 | } 38 | 39 | @Override 40 | protected void before() { 41 | profiledCode = new ProfiledCode(profiler); 42 | } 43 | 44 | @RetryTest(10) 45 | @TestTemplate 46 | @ValueSource(strings = {"vm", "vmx", "fp", "dwarf"}) 47 | public void test(@CStack String cstack) throws ExecutionException, InterruptedException { 48 | // timer_create is available on Linux only 49 | assumeTrue(Platform.isLinux()); 50 | for (int i = 0, id = 1; i < 100; i++, id += 3) { 51 | profiledCode.method1(id); 52 | } 53 | stopProfiler(); 54 | 55 | verifyCStackSettings(); 56 | 57 | IItemCollection events = verifyEvents("datadog.ExecutionSample"); 58 | 59 | for (IItemIterable cpuSamples : events) { 60 | IMemberAccessor frameAccessor = JdkAttributes.STACK_TRACE_STRING.getAccessor(cpuSamples.getType()); 61 | for (IItem sample : cpuSamples) { 62 | String stackTrace = frameAccessor.getMember(sample); 63 | assertFalse(stackTrace.contains("jvmtiError")); 64 | } 65 | } 66 | } 67 | 68 | @Override 69 | protected void after() throws Exception { 70 | profiledCode.close(); 71 | } 72 | 73 | @Override 74 | protected String getProfilerCommand() { 75 | return "cpu=100us,event=ctimer"; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /ddprof-test/src/test/java/com/datadoghq/profiler/cpu/IOBoundCode.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler.cpu; 2 | 3 | import java.io.InputStream; 4 | import java.io.OutputStream; 5 | import java.net.ServerSocket; 6 | import java.net.Socket; 7 | import java.util.concurrent.ThreadLocalRandom; 8 | 9 | public class IOBoundCode { 10 | private static final class IdleClient extends Thread { 11 | private final String host; 12 | private final int port; 13 | 14 | public IdleClient(String host, int port) { 15 | this.host = host; 16 | this.port = port; 17 | setDaemon(true); 18 | } 19 | 20 | @Override 21 | public void run() { 22 | try { 23 | byte[] buf = new byte[4096]; 24 | 25 | try (Socket s = new Socket(host, port)) { 26 | InputStream in = s.getInputStream(); 27 | while (in.read(buf) >= 0) { 28 | // keep reading 29 | } 30 | System.out.println(Thread.currentThread().getName() + " stopped"); 31 | } 32 | } catch (Exception e) { 33 | e.printStackTrace(); 34 | } 35 | } 36 | } 37 | 38 | private static final class BusyClient extends Thread { 39 | private final String host; 40 | private final int port; 41 | 42 | public BusyClient(String host, int port) { 43 | this.host = host; 44 | this.port = port; 45 | setDaemon(true); 46 | } 47 | 48 | @Override 49 | public void run() { 50 | try { 51 | byte[] buf = new byte[4096]; 52 | 53 | try (Socket s = new Socket(host, port)) { 54 | 55 | InputStream in = s.getInputStream(); 56 | while (in.read(buf) >= 0) { 57 | // keep reading 58 | } 59 | System.out.println(Thread.currentThread().getName() + " stopped"); 60 | } 61 | } catch (Exception e) { 62 | e.printStackTrace(); 63 | } 64 | } 65 | } 66 | 67 | void run() throws Exception { 68 | try (ServerSocket s = new ServerSocket(0)) { 69 | String host = "localhost"; 70 | int port = s.getLocalPort(); 71 | Thread t1 = new IdleClient(host, port); 72 | t1.start(); 73 | OutputStream idleClient = s.accept().getOutputStream(); 74 | 75 | Thread t2 = new BusyClient(host, port); 76 | t2.start(); 77 | OutputStream busyClient = s.accept().getOutputStream(); 78 | 79 | byte[] buf = new byte[4096]; 80 | ThreadLocalRandom.current().nextBytes(buf); 81 | 82 | long target = System.nanoTime() + 3_000_000_000L; 83 | for (int i = 0; ; i++) { 84 | if ((i % 10_000_000) == 0) { 85 | idleClient.write(buf, 0, 1); 86 | } else { 87 | busyClient.write(buf); 88 | } 89 | if (System.nanoTime() >= target) { 90 | t1.interrupt(); 91 | t2.interrupt(); 92 | break; 93 | } 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /ddprof-test/src/test/java/com/datadoghq/profiler/cpu/ProfiledCode.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler.cpu; 2 | 3 | import com.datadoghq.profiler.JavaProfiler; 4 | import com.datadoghq.profiler.context.ContextExecutor; 5 | import com.datadoghq.profiler.context.Tracing; 6 | 7 | import java.util.HashSet; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.Set; 11 | import java.util.concurrent.ConcurrentHashMap; 12 | import java.util.concurrent.CopyOnWriteArrayList; 13 | import java.util.concurrent.ExecutionException; 14 | import java.util.concurrent.Future; 15 | import java.util.concurrent.ThreadLocalRandom; 16 | import java.util.concurrent.TimeUnit; 17 | 18 | public class ProfiledCode implements AutoCloseable { 19 | 20 | private static volatile long sink; 21 | private final ContextExecutor executor; 22 | private final JavaProfiler profiler; 23 | 24 | private final Map> methodsToSpanIds = new ConcurrentHashMap<>(); 25 | 26 | public ProfiledCode(JavaProfiler profiler) { 27 | this.profiler = profiler; 28 | this.executor = new ContextExecutor(1, profiler); 29 | } 30 | 31 | public void method1(int id) throws ExecutionException, InterruptedException { 32 | try (Tracing.Context context = Tracing.newContext(() -> id, profiler)) { 33 | method1Impl(id, context); 34 | } 35 | } 36 | 37 | public void method1Impl(int id, Tracing.Context context) throws ExecutionException, InterruptedException { 38 | burnCycles(); 39 | Future wait = executor.submit(() -> method3(id)); 40 | method2(id); 41 | wait.get(); 42 | record("method1Impl", context); 43 | } 44 | 45 | public void method2(long id) { 46 | try (Tracing.Context context = Tracing.newContext(() -> id + 1, profiler)) { 47 | method2Impl(context); 48 | } 49 | } 50 | 51 | public void method2Impl(Tracing.Context context) { 52 | burnCycles(); 53 | record("method2Impl", context); 54 | } 55 | 56 | public void method3(long id) { 57 | try (Tracing.Context context = Tracing.newContext(() -> id + 2, profiler)) { 58 | method3Impl(context); 59 | } 60 | } 61 | 62 | public void method3Impl(Tracing.Context context) { 63 | burnCycles(); 64 | record("method3Impl", context); 65 | } 66 | 67 | public Set spanIdsForMethod(String methodName) { 68 | return new HashSet<>(methodsToSpanIds.get(methodName)); 69 | } 70 | 71 | public Set allSampledSpanIds() { 72 | Set all = new HashSet<>(); 73 | methodsToSpanIds.values().forEach(all::addAll); 74 | return all; 75 | } 76 | 77 | 78 | private void record(String methodName, Tracing.Context context) { 79 | methodsToSpanIds.computeIfAbsent(methodName, k -> new CopyOnWriteArrayList<>()) 80 | .add(context.getSpanId()); 81 | } 82 | 83 | private void burnCycles() { 84 | long blackhole = sink; 85 | for (int i = 0; i < 1_000_000; i++) { 86 | blackhole ^= ThreadLocalRandom.current().nextLong(); 87 | } 88 | sink = blackhole; 89 | } 90 | 91 | @Override 92 | public void close() throws Exception { 93 | executor.shutdownNow(); 94 | executor.awaitTermination(30, TimeUnit.SECONDS); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /ddprof-test/src/test/java/com/datadoghq/profiler/filter/ThreadFilterSmokeTest.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler.filter; 2 | 3 | import com.datadoghq.profiler.AbstractProfilerTest; 4 | import org.junit.jupiter.api.AfterEach; 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import java.util.concurrent.ExecutorService; 9 | import java.util.concurrent.Executors; 10 | import java.util.concurrent.Future; 11 | 12 | public class ThreadFilterSmokeTest extends AbstractProfilerTest { 13 | private ExecutorService executorService; 14 | 15 | @BeforeEach 16 | public void before() { 17 | executorService = Executors.newCachedThreadPool(); 18 | } 19 | 20 | @AfterEach 21 | public void after() { 22 | executorService.shutdownNow(); 23 | } 24 | 25 | @Override 26 | protected String getProfilerCommand() { 27 | return "wall=~1ms,filter=0"; 28 | } 29 | 30 | @Test 31 | public void smokeTest() throws Exception { 32 | doThreadFiltering(); 33 | } 34 | 35 | private void doThreadFiltering() throws Exception { 36 | Future[] futures = new Future[1000]; 37 | for (int i = 0; i < futures.length; i++) { 38 | int id = i; 39 | futures[i] = executorService.submit(() -> { 40 | profiler.addThread(); 41 | profiler.setContext(id, 42); 42 | try { 43 | Thread.sleep(2); 44 | } catch(InterruptedException e) { 45 | Thread.currentThread().interrupt(); 46 | } 47 | profiler.removeThread(); 48 | }); 49 | } 50 | for (Future future : futures) { 51 | future.get(); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /ddprof-test/src/test/java/com/datadoghq/profiler/jfr/CpuDumpSmokeTest.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler.jfr; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.Timeout; 5 | import org.junitpioneer.jupiter.RetryingTest; 6 | 7 | public class CpuDumpSmokeTest extends JfrDumpTest { 8 | 9 | @Override 10 | protected String getProfilerCommand() { 11 | return "cpu=1ms,cstack=fp"; 12 | } 13 | 14 | @RetryingTest(3) 15 | @Timeout(value = 60) 16 | public void test() throws Exception { 17 | runTest("datadog.ExecutionSample"); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ddprof-test/src/test/java/com/datadoghq/profiler/jfr/JfrDumpTest.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler.jfr; 2 | 3 | import com.datadoghq.profiler.AbstractProfilerTest; 4 | import com.datadoghq.profiler.Platform; 5 | 6 | import java.io.File; 7 | import java.nio.file.Files; 8 | import java.nio.file.Path; 9 | 10 | import org.junit.jupiter.api.Assumptions; 11 | 12 | public abstract class JfrDumpTest extends AbstractProfilerTest { 13 | 14 | public void runTest(String eventName) throws Exception { 15 | runTest(eventName, "method1", "method2", "method3"); 16 | } 17 | 18 | public void runTest(String eventName, String ... patterns) throws Exception { 19 | runTest(eventName, 10, patterns); 20 | } 21 | 22 | public void runTest(String eventName, int dumpCnt, String ... patterns) throws Exception { 23 | Assumptions.assumeTrue(Platform.isJavaVersionAtLeast(11)); 24 | Assumptions.assumeFalse(Platform.isJ9()); 25 | 26 | for (int j = 0; j < dumpCnt; j++) { 27 | Path recording = Files.createTempFile("dump-", ".jfr"); 28 | try { 29 | for (int i = 0; i < 50; i++) { 30 | method1(); 31 | method2(); 32 | method3(); 33 | } 34 | dump(recording); 35 | verifyStackTraces(recording, eventName, patterns); 36 | } finally { 37 | Files.deleteIfExists(recording); 38 | } 39 | } 40 | for (int i = 0; i < 500; i++) { 41 | method1(); 42 | method2(); 43 | method3(); 44 | } 45 | stopProfiler(); 46 | verifyStackTraces(eventName, patterns); 47 | } 48 | 49 | private static volatile int value; 50 | 51 | private static void method1() { 52 | for (int i = 0; i < 1000000; ++i) { 53 | ++value; 54 | } 55 | } 56 | 57 | private static void method2() { 58 | for (int i = 0; i < 1000000; ++i) { 59 | ++value; 60 | } 61 | } 62 | 63 | private static void method3() { 64 | long ts = System.nanoTime(); 65 | for (int i = 0; i < 1000; ++i) { 66 | int cntr = 10; 67 | for (String s : new File("/tmp").list()) { 68 | value += s.substring(0, Math.min(s.length(), 16) ).hashCode(); 69 | if (--cntr < 0) { 70 | break; 71 | } 72 | } 73 | if ((System.nanoTime() - ts) > 20000000L) { 74 | break; 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /ddprof-test/src/test/java/com/datadoghq/profiler/jfr/ObjectSampleDumpSmokeTest.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler.jfr; 2 | 3 | import com.datadoghq.profiler.Platform; 4 | 5 | import org.junit.jupiter.api.Assumptions; 6 | import org.junit.jupiter.api.Timeout; 7 | 8 | import org.junitpioneer.jupiter.RetryingTest; 9 | 10 | public class ObjectSampleDumpSmokeTest extends JfrDumpTest { 11 | @Override 12 | protected boolean isPlatformSupported() { 13 | return !Platform.isJavaVersion(8) && !Platform.isJ9(); 14 | } 15 | 16 | @Override 17 | protected String getProfilerCommand() { 18 | return "memory=128:a"; 19 | } 20 | 21 | @RetryingTest(5) 22 | @Timeout(value = 300) 23 | public void test() throws Exception { 24 | runTest("datadog.ObjectSample", 3, "method3"); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ddprof-test/src/test/java/com/datadoghq/profiler/jfr/WallclockDumpSmokeTest.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler.jfr; 2 | 3 | import org.junit.jupiter.api.Timeout; 4 | 5 | import org.junitpioneer.jupiter.RetryingTest; 6 | 7 | public class WallclockDumpSmokeTest extends JfrDumpTest { 8 | 9 | @Override 10 | protected String getProfilerCommand() { 11 | return "wall=5ms"; 12 | } 13 | 14 | @RetryingTest(3) 15 | @Timeout(value = 60) 16 | public void test() throws Exception { 17 | registerCurrentThreadForWallClockProfiling(); 18 | runTest("datadog.MethodSample"); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ddprof-test/src/test/java/com/datadoghq/profiler/junit/CStack.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler.junit; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.PARAMETER) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface CStack { 11 | } 12 | -------------------------------------------------------------------------------- /ddprof-test/src/test/java/com/datadoghq/profiler/junit/RetryTest.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler.junit; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.METHOD) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface RetryTest { 11 | int value() default 5; 12 | } 13 | -------------------------------------------------------------------------------- /ddprof-test/src/test/java/com/datadoghq/profiler/loadlib/LoadLibraryTest.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler.loadlib; 2 | 3 | import com.datadoghq.profiler.Platform; 4 | import com.datadoghq.profiler.AbstractProfilerTest; 5 | import org.junit.jupiter.api.Assumptions; 6 | import org.junitpioneer.jupiter.RetryingTest; 7 | 8 | import java.lang.management.ClassLoadingMXBean; 9 | import java.lang.management.ManagementFactory; 10 | 11 | public class LoadLibraryTest extends AbstractProfilerTest { 12 | 13 | @RetryingTest(3) 14 | public void testLoadLibrary() throws InterruptedException { 15 | Assumptions.assumeFalse(Platform.isJ9()); 16 | for (int i = 0; i < 200; i++) { 17 | Thread.sleep(10); 18 | } 19 | // Late load of libmanagement.so 20 | ClassLoadingMXBean bean = ManagementFactory.getClassLoadingMXBean(); 21 | 22 | long n = 0; 23 | long tsLimit = System.nanoTime() + 3_000_000_000L; // 3 seconds 24 | while (System.nanoTime() < tsLimit) { 25 | n += bean.getLoadedClassCount(); 26 | n += bean.getTotalLoadedClassCount(); 27 | n += bean.getUnloadedClassCount(); 28 | } 29 | System.out.println("=== accumulated: " + n); 30 | stopProfiler(); 31 | verifyStackTraces("datadog.ExecutionSample", "Java_sun_management"); 32 | } 33 | 34 | @Override 35 | protected String getProfilerCommand() { 36 | return "cpu=1ms"; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ddprof-test/src/test/java/com/datadoghq/profiler/memleak/GCGenerationsTest.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler.memleak; 2 | 3 | import com.datadoghq.profiler.Platform; 4 | import com.datadoghq.profiler.AbstractProfilerTest; 5 | import org.junit.jupiter.api.Disabled; 6 | import org.junit.jupiter.api.Test; 7 | import org.junitpioneer.jupiter.RetryingTest; 8 | import org.openjdk.jmc.common.item.Aggregators; 9 | import org.openjdk.jmc.common.item.IItemCollection; 10 | import org.openjdk.jmc.common.item.ItemFilters; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.concurrent.ThreadLocalRandom; 15 | import java.util.concurrent.atomic.AtomicLong; 16 | 17 | import static org.junit.jupiter.api.Assertions.assertTrue; 18 | import org.junit.jupiter.api.Assumptions; 19 | 20 | public class GCGenerationsTest extends AbstractProfilerTest { 21 | @Override 22 | protected String getProfilerCommand() { 23 | return "memory=128,generations=true,cstack=fp"; 24 | } 25 | 26 | @Override 27 | protected boolean isPlatformSupported() { 28 | return !(Platform.isJavaVersion(8) || Platform.isJ9() || Platform.isZing()); 29 | } 30 | 31 | @RetryingTest(10) 32 | public void shouldGetLiveObjectSamples() throws InterruptedException { 33 | MemLeakTarget target1 = new MemLeakTarget(); 34 | MemLeakTarget target2 = new MemLeakTarget(); 35 | runTests(target1, target2); 36 | verifyEvents("datadog.HeapLiveObject"); 37 | } 38 | 39 | public static class MemLeakTarget extends ClassValue implements Runnable { 40 | private static byte[] leeway = new byte[32 * 1024 * 1024]; // 32MB to release on OOME 41 | public static volatile List sink = new ArrayList<>(); 42 | 43 | @Override 44 | public void run() { 45 | ThreadLocalRandom random = ThreadLocalRandom.current(); 46 | try { 47 | for (int i = 0; i < 100_000; i++) { 48 | allocate(random, random.nextInt(256)); 49 | } 50 | } catch (OutOfMemoryError e) { 51 | leeway = null; 52 | System.gc(); 53 | } finally { 54 | sink.clear(); 55 | } 56 | } 57 | 58 | long getAllocated(Class clazz) { 59 | return get(clazz).get(); 60 | } 61 | 62 | private static void allocate(ThreadLocalRandom random, int depth) { 63 | if (depth > 0) { 64 | allocate(random, depth - 1); 65 | return; 66 | } 67 | 68 | Object obj; 69 | if (random.nextBoolean()) { 70 | obj = new int[random.nextInt(64, 192) * 1000]; 71 | } else { 72 | obj = new Integer[random.nextInt(64, 192) * 1000]; 73 | } 74 | 75 | if (random.nextInt(100) <= 30 && sink.size() < 100_000) { 76 | sink.add(obj); 77 | } 78 | if (random.nextInt(10000) == 0) { 79 | System.out.println("Triggering GC"); 80 | System.gc(); 81 | } 82 | } 83 | 84 | @Override 85 | protected AtomicLong computeValue(Class type) { 86 | return new AtomicLong(); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /ddprof-test/src/test/java/com/datadoghq/profiler/metadata/BoundMethodHandleMetadataSizeTest.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler.metadata; 2 | 3 | import com.datadoghq.profiler.Platform; 4 | import com.datadoghq.profiler.AbstractProfilerTest; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.lang.invoke.MethodHandle; 8 | import java.lang.invoke.MethodHandles; 9 | import java.lang.invoke.MethodType; 10 | import java.util.Map; 11 | 12 | import static org.junit.jupiter.api.Assertions.assertFalse; 13 | import static org.junit.jupiter.api.Assertions.assertTrue; 14 | import static org.junit.jupiter.api.Assumptions.assumeFalse; 15 | 16 | public class BoundMethodHandleMetadataSizeTest extends AbstractProfilerTest { 17 | @Override 18 | protected String getProfilerCommand() { 19 | return "wall=100us"; 20 | } 21 | 22 | @Test 23 | public void test() throws Throwable { 24 | assumeFalse(Platform.isJ9() && isAsan()); // running this test on j9 and asan is weirdly crashy 25 | assumeFalse(Platform.isJ9() && Platform.isJavaVersion(17)); // JVMTI::GetClassSignature() is reliably crashing on a valid 'class' instance 26 | assumeFalse(Platform.isAarch64() && Platform.isMusl() && !Platform.isJavaVersionAtLeast(11)); // aarch64 + musl + jdk 8 will crash very often 27 | registerCurrentThreadForWallClockProfiling(); 28 | int numBoundMethodHandles = 10_000; 29 | int x = generateBoundMethodHandles(numBoundMethodHandles); 30 | assertTrue(x != 0); 31 | stopProfiler(); 32 | verifyEvents("datadog.MethodSample"); 33 | Map counters = profiler.getDebugCounters(); 34 | assertFalse(counters.isEmpty()); 35 | // assert about the size of metadata here 36 | } 37 | 38 | 39 | 40 | public static String append(String string, int suffix) { 41 | return string + suffix; 42 | } 43 | 44 | public static int generateBoundMethodHandles(int howMany) throws Throwable { 45 | int total = 0; 46 | MethodHandle append = MethodHandles.lookup() 47 | .findStatic(BoundMethodHandleMetadataSizeTest.class, 48 | "append", 49 | MethodType.methodType(String.class, String.class, int.class)); 50 | for (int i = 0; i < howMany; i++) { 51 | // binding many constants amplifies the effect of class generation below 52 | MethodHandle bound = append.bindTo("string" + i); 53 | for (int j = 0; j < 1024; j++) { 54 | // many invocations has the effect of generate a new class 55 | total += ((String) bound.invokeExact(j)).length(); 56 | } 57 | } 58 | return total; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ddprof-test/src/test/java/com/datadoghq/profiler/metadata/MetadataNormalisationTest.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler.metadata; 2 | 3 | import com.datadoghq.profiler.AbstractProfilerTest; 4 | import org.junit.jupiter.api.Test; 5 | import org.openjdk.jmc.common.item.IItem; 6 | import org.openjdk.jmc.common.item.IItemCollection; 7 | import org.openjdk.jmc.common.item.IItemIterable; 8 | import org.openjdk.jmc.common.item.IMemberAccessor; 9 | import org.openjdk.jmc.flightrecorder.jdk.JdkAttributes; 10 | 11 | import java.lang.reflect.Constructor; 12 | import java.lang.reflect.Method; 13 | import java.util.ArrayList; 14 | import java.util.LinkedList; 15 | import java.util.List; 16 | import java.util.regex.Matcher; 17 | import java.util.regex.Pattern; 18 | import java.util.stream.Stream; 19 | 20 | import static org.junit.jupiter.api.Assertions.assertFalse; 21 | 22 | public class MetadataNormalisationTest extends AbstractProfilerTest { 23 | 24 | @Test 25 | public void test() throws Exception { 26 | Constructor arrayListConstructor = ArrayList.class.getConstructor(); 27 | Method arrayListAdd = ArrayList.class.getDeclaredMethod("add", Object.class); 28 | Constructor linkedListConstructor = LinkedList.class.getConstructor(); 29 | Method linkedListAdd = LinkedList.class.getDeclaredMethod("add", Object.class); 30 | // need to invoke enough times to result in generation of accessors and to record some cpu time 31 | int count = 0; 32 | for (int i = 0; i < 100_000; i++) { 33 | Object list = arrayListConstructor.newInstance(); 34 | arrayListAdd.invoke(list, "element"); 35 | count += ((List) list).size(); 36 | } 37 | for (int i = 0; i < 100_000; i++) { 38 | Object list = linkedListConstructor.newInstance(); 39 | linkedListAdd.invoke(list, "element"); 40 | count += ((List) list).size(); 41 | } 42 | System.out.println(count); 43 | stopProfiler(); 44 | IItemCollection executionSamples = verifyEvents("datadog.ExecutionSample"); 45 | Matcher[] forbiddenPatternMatchers = Stream.of( 46 | "MH.*0x[A-Fa-f0-9]{3}", // method handles 47 | "GeneratedConstructorAccessor\\d+", 48 | "GeneratedMethodAccessor\\d+" 49 | ) 50 | .map(regex -> Pattern.compile(regex).matcher("")) 51 | .toArray(Matcher[]::new); 52 | for (IItemIterable samples : executionSamples) { 53 | IMemberAccessor stacktraceAccessor = JdkAttributes.STACK_TRACE_STRING.getAccessor(samples.getType()); 54 | for (IItem item : samples) { 55 | String stacktrace = stacktraceAccessor.getMember(item); 56 | for (Matcher matcher : forbiddenPatternMatchers) { 57 | matcher.reset(stacktrace); 58 | assertFalse(matcher.find(), () -> matcher.pattern() + "\n" + stacktrace); 59 | } 60 | } 61 | } 62 | } 63 | 64 | @Override 65 | protected String getProfilerCommand() { 66 | return "cpu=100us"; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /ddprof-test/src/test/java/com/datadoghq/profiler/shutdown/ShutdownTest.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler.shutdown; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.io.IOException; 6 | import java.nio.file.Files; 7 | import java.nio.file.Path; 8 | import java.nio.file.Paths; 9 | import java.util.Queue; 10 | import java.util.concurrent.ExecutorService; 11 | import java.util.concurrent.Executors; 12 | import java.util.concurrent.LinkedBlockingQueue; 13 | 14 | import com.datadoghq.profiler.JavaProfiler; 15 | 16 | import static org.junit.jupiter.api.Assertions.fail; 17 | 18 | public class ShutdownTest { 19 | 20 | 21 | @Test 22 | public void testShutdownCpu() throws IOException { 23 | System.out.println("=== testShutdownCpu()"); 24 | JavaProfiler profiler = JavaProfiler.getInstance(); 25 | runTest(profiler, "start,cpu=10us,filter=0"); 26 | } 27 | 28 | @Test 29 | public void testShutdownWall() throws IOException { 30 | System.out.println("=== testShutdownWall()"); 31 | JavaProfiler profiler = JavaProfiler.getInstance(); 32 | profiler.addThread(); 33 | runTest(profiler, "start,wall=10us,filter=0"); 34 | } 35 | 36 | @Test 37 | public void testShutdownCpuAndWall() throws IOException { 38 | System.out.println("=== testShutdownCpuAndWall()"); 39 | JavaProfiler profiler = JavaProfiler.getInstance(); 40 | profiler.addThread(); 41 | runTest(profiler, "start,cpu=10us,wall=~10us,filter=0"); 42 | } 43 | 44 | private static void runTest(JavaProfiler profiler, String command) throws IOException { 45 | Path rootDir = Paths.get("/tmp/recordings"); 46 | Files.createDirectories(rootDir); 47 | Path jfrDump = Files.createTempFile(rootDir, "shutdown-test", ".jfr"); 48 | String commandWithDump = command + ",jfr,file=" + jfrDump.toAbsolutePath(); 49 | ExecutorService executor = Executors.newSingleThreadExecutor(); 50 | Queue errors = new LinkedBlockingQueue<>(); 51 | try { 52 | executor.submit(new Runnable() { 53 | @Override 54 | public void run() { 55 | for (int i = 0; i < 100; i++) { 56 | try { 57 | profiler.execute(commandWithDump); 58 | try { 59 | Thread.sleep(20); 60 | } catch (InterruptedException e) { 61 | Thread.currentThread().interrupt(); 62 | } finally { 63 | profiler.stop(); 64 | } 65 | } catch (Throwable error) { 66 | errors.offer(error); 67 | return; 68 | } 69 | } 70 | } 71 | }).get(); 72 | } catch (Throwable t) { 73 | fail(t.getMessage()); 74 | } finally { 75 | executor.shutdownNow(); 76 | } 77 | if (!errors.isEmpty()) { 78 | fail(errors.poll()); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /ddprof-test/src/test/java/com/datadoghq/profiler/wallclock/CollapsingSleepTest.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler.wallclock; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertTrue; 4 | import org.junit.jupiter.api.Assumptions; 5 | import org.junit.jupiter.api.Test; 6 | import org.openjdk.jmc.common.item.Aggregators; 7 | import org.openjdk.jmc.common.item.IItemCollection; 8 | 9 | import com.datadoghq.profiler.AbstractProfilerTest; 10 | import com.datadoghq.profiler.Platform; 11 | 12 | public class CollapsingSleepTest extends AbstractProfilerTest { 13 | 14 | @Test 15 | public void testSleep() throws InterruptedException { 16 | Assumptions.assumeTrue(!Platform.isJ9()); 17 | registerCurrentThreadForWallClockProfiling(); 18 | Thread.sleep(1000); 19 | stopProfiler(); 20 | IItemCollection events = verifyEvents("datadog.MethodSample"); 21 | assertTrue(events.hasItems()); 22 | assertTrue(events.getAggregate(Aggregators.sum(WEIGHT)).longValue() > 900); 23 | assertTrue(events.getAggregate(Aggregators.count()).longValue() > 9); 24 | } 25 | 26 | @Override 27 | protected String getProfilerCommand() { 28 | return "wall=~10ms"; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ddprof-test/src/test/java/com/datadoghq/profiler/wallclock/ContextWallClockTest.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler.wallclock; 2 | 3 | import java.util.concurrent.ExecutionException; 4 | 5 | import com.datadoghq.profiler.CStackAwareAbstractProfilerTest; 6 | import com.datadoghq.profiler.Platform; 7 | import com.datadoghq.profiler.junit.CStack; 8 | import com.datadoghq.profiler.junit.RetryTest; 9 | import org.junit.jupiter.api.Assumptions; 10 | import org.junit.jupiter.api.TestTemplate; 11 | import org.junit.jupiter.params.provider.ValueSource; 12 | 13 | import static org.junit.jupiter.api.Assumptions.assumeTrue; 14 | 15 | public class ContextWallClockTest extends CStackAwareAbstractProfilerTest { 16 | private final BaseContextWallClockTest base = new BaseContextWallClockTest(() -> profiler); 17 | 18 | public ContextWallClockTest(@CStack String cstack) { 19 | super(cstack); 20 | } 21 | 22 | @Override 23 | protected void before() { 24 | base.before(); 25 | } 26 | 27 | @Override 28 | protected void after() throws InterruptedException { 29 | base.after(); 30 | } 31 | 32 | @RetryTest(5) 33 | @TestTemplate 34 | @ValueSource(strings = {"vm", "vmx", "fp", "dwarf"}) 35 | public void test(@CStack String cstack) throws ExecutionException, InterruptedException, Exception { 36 | base.test(this); 37 | } 38 | 39 | @Override 40 | protected String getProfilerCommand() { 41 | return "wall=~1ms,filter=0,loglevel=warn"; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /ddprof-test/src/test/java/com/datadoghq/profiler/wallclock/JvmtiBasedContextWallClockTest.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler.wallclock; 2 | 3 | import com.datadoghq.profiler.AbstractProfilerTest; 4 | import org.junitpioneer.jupiter.RetryingTest; 5 | 6 | import java.util.concurrent.ExecutionException; 7 | 8 | public class JvmtiBasedContextWallClockTest extends AbstractProfilerTest { 9 | private final BaseContextWallClockTest base = new BaseContextWallClockTest(() -> profiler); 10 | 11 | @Override 12 | protected void before() { 13 | base.before(); 14 | } 15 | 16 | @Override 17 | protected void after() throws InterruptedException { 18 | base.after(); 19 | } 20 | 21 | @RetryingTest(5) 22 | public void test() throws ExecutionException, InterruptedException { 23 | base.test(this); 24 | } 25 | 26 | @Override 27 | protected String getProfilerCommand() { 28 | return "wall=~1ms,filter=0,loglevel=warn;wallsampler=jvmti"; 29 | } 30 | } -------------------------------------------------------------------------------- /ddprof-test/src/test/java/com/datadoghq/profiler/wallclock/JvmtiBasedWallClockThreadFilterTest.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler.wallclock; 2 | 3 | public class JvmtiBasedWallClockThreadFilterTest extends WallClockThreadFilterTest { 4 | 5 | @Override 6 | protected String getProfilerCommand() { 7 | return super.getProfilerCommand() + ";wallsampler=jvmti"; 8 | } 9 | } -------------------------------------------------------------------------------- /ddprof-test/src/test/java/com/datadoghq/profiler/wallclock/SleepTest.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler.wallclock; 2 | 3 | import com.datadoghq.profiler.AbstractProfilerTest; 4 | import org.junit.jupiter.api.Test; 5 | import org.openjdk.jmc.common.item.Aggregators; 6 | 7 | import static org.junit.jupiter.api.Assertions.assertTrue; 8 | 9 | public class SleepTest extends AbstractProfilerTest { 10 | 11 | @Test 12 | public void testSleep() throws InterruptedException { 13 | registerCurrentThreadForWallClockProfiling(); 14 | Thread.sleep(1000); 15 | stopProfiler(); 16 | assertTrue(verifyEvents("datadog.MethodSample").getAggregate(Aggregators.count()).longValue() > 90); 17 | } 18 | 19 | @Override 20 | protected String getProfilerCommand() { 21 | return "wall=10ms"; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ddprof-test/src/test/java/com/datadoghq/profiler/wallclock/SmokeWallTest.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler.wallclock; 2 | 3 | import com.datadoghq.profiler.CStackAwareAbstractProfilerTest; 4 | import com.datadoghq.profiler.Platform; 5 | import com.datadoghq.profiler.cpu.ProfiledCode; 6 | import com.datadoghq.profiler.junit.CStack; 7 | import com.datadoghq.profiler.junit.RetryTest; 8 | import org.junit.jupiter.api.TestTemplate; 9 | import org.junit.jupiter.params.provider.ValueSource; 10 | import org.openjdk.jmc.common.item.IItem; 11 | import org.openjdk.jmc.common.item.IItemCollection; 12 | import org.openjdk.jmc.common.item.IItemIterable; 13 | import org.openjdk.jmc.common.item.IMemberAccessor; 14 | import org.openjdk.jmc.flightrecorder.jdk.JdkAttributes; 15 | 16 | import java.util.concurrent.ExecutionException; 17 | 18 | import static org.junit.jupiter.api.Assertions.assertFalse; 19 | import static org.junit.jupiter.api.Assumptions.assumeFalse; 20 | 21 | public class SmokeWallTest extends CStackAwareAbstractProfilerTest { 22 | private ProfiledCode profiledCode; 23 | 24 | public SmokeWallTest(@CStack String cstack) { 25 | super(cstack); 26 | } 27 | 28 | @Override 29 | protected void before() { 30 | profiledCode = new ProfiledCode(profiler); 31 | } 32 | 33 | @RetryTest(10) 34 | @TestTemplate 35 | @ValueSource(strings = {"vm", "vmx", "fp", "dwarf"}) 36 | public void test(@CStack String cstack) throws ExecutionException, InterruptedException { 37 | for (int i = 0, id = 1; i < 100; i++, id += 3) { 38 | profiledCode.method1(id); 39 | } 40 | stopProfiler(); 41 | 42 | verifyCStackSettings(); 43 | 44 | IItemCollection events = verifyEvents("datadog.MethodSample"); 45 | 46 | for (IItemIterable cpuSamples : events) { 47 | IMemberAccessor frameAccessor = JdkAttributes.STACK_TRACE_STRING.getAccessor(cpuSamples.getType()); 48 | for (IItem sample : cpuSamples) { 49 | String stackTrace = frameAccessor.getMember(sample); 50 | assertFalse(stackTrace.contains("jvmtiError")); 51 | } 52 | } 53 | } 54 | 55 | @Override 56 | protected void after() throws Exception { 57 | profiledCode.close(); 58 | } 59 | 60 | @Override 61 | protected String getProfilerCommand() { 62 | return "wall=1ms"; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /ddprof-test/src/test/java/com/datadoghq/profiler/wallclock/WallClockThreadFilterTest.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.profiler.wallclock; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import org.junit.jupiter.api.Assumptions; 5 | import org.junitpioneer.jupiter.RetryingTest; 6 | import org.openjdk.jmc.common.item.IItem; 7 | import org.openjdk.jmc.common.item.IItemCollection; 8 | import org.openjdk.jmc.common.item.IItemIterable; 9 | import org.openjdk.jmc.common.item.IMemberAccessor; 10 | import org.openjdk.jmc.common.unit.IQuantity; 11 | import org.openjdk.jmc.flightrecorder.jdk.JdkAttributes; 12 | 13 | import com.datadoghq.profiler.AbstractProfilerTest; 14 | import com.datadoghq.profiler.Platform; 15 | 16 | public class WallClockThreadFilterTest extends AbstractProfilerTest { 17 | 18 | @RetryingTest(5) 19 | public void test() throws InterruptedException { 20 | Assumptions.assumeTrue(!Platform.isJ9()); 21 | registerCurrentThreadForWallClockProfiling(); 22 | Thread.sleep(100); 23 | stopProfiler(); 24 | IItemCollection events = verifyEvents("datadog.MethodSample"); 25 | for (IItemIterable wallclockSamples : events) { 26 | IMemberAccessor javaThreadNameAccessor = JdkAttributes.EVENT_THREAD_NAME 27 | .getAccessor(wallclockSamples.getType()); 28 | IMemberAccessor javaThreadIdAccessor = JdkAttributes.EVENT_THREAD_ID 29 | .getAccessor(wallclockSamples.getType()); 30 | for (IItem sample : wallclockSamples) { 31 | String javaThreadName = javaThreadNameAccessor.getMember(sample); 32 | assertEquals(Thread.currentThread().getName(), javaThreadName); 33 | long javaThreadId = javaThreadIdAccessor.getMember(sample).longValue(); 34 | assertEquals(Thread.currentThread().getId(), javaThreadId); 35 | } 36 | } 37 | } 38 | 39 | @Override 40 | protected String getProfilerCommand() { 41 | return "wall=~1ms,filter=0"; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /gradle/ap-lock.properties: -------------------------------------------------------------------------------- 1 | branch=dd/master 2 | commit=b034e4c3141d0632fd60bc452d7532d84adad572 -------------------------------------------------------------------------------- /gradle/enforcement/.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: LLVM 3 | AlignOperands: false 4 | AllowShortBlocksOnASingleLine: true 5 | AlignConsecutiveBitFields: true 6 | IndentPPDirectives: AfterHash 7 | ColumnLimit: 100 8 | --- -------------------------------------------------------------------------------- /gradle/enforcement/spotless-groovy.properties: -------------------------------------------------------------------------------- 1 | # Disable formatting errors 2 | ignoreFormatterProblems=true 3 | org.eclipse.jdt.core.formatter.tabulation.char=space 4 | org.eclipse.jdt.core.formatter.tabulation.size=2 5 | org.eclipse.jdt.core.formatter.indentation.size=1 6 | org.eclipse.jdt.core.formatter.indentation.text_block_indentation=indent by one 7 | org.eclipse.jdt.core.formatter.indent_empty_lines=false 8 | org.eclipse.jdt.core.formatter.continuation_indentation=1 9 | org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=1 10 | groovy.formatter.longListLength=100 11 | groovy.formatter.multiline.indentation=1 12 | groovy.formatter.remove.unnecessary.semicolons=true 13 | -------------------------------------------------------------------------------- /gradle/enforcement/spotless-scalafmt.conf: -------------------------------------------------------------------------------- 1 | align.preset = more 2 | maxColumn = 100 3 | -------------------------------------------------------------------------------- /gradle/enforcement/spotless-xml.properties: -------------------------------------------------------------------------------- 1 | indentationChar=space 2 | indentationSize=2 3 | lineWidth=120 4 | -------------------------------------------------------------------------------- /gradle/sanitizers/asan.supp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/java-profiler/996bcbff2a362baa9dcfe23b679810428d175c2b/gradle/sanitizers/asan.supp -------------------------------------------------------------------------------- /gradle/sanitizers/tsan.supp: -------------------------------------------------------------------------------- 1 | # Suppress data race in libzip.so 2 | race:libzip.so 3 | # Suppress data races in libjvm.so 4 | race:libjvm.so 5 | # Suppress data races in libj9*.so 6 | race:libj9*.so 7 | # Suppress data races in libjli.so 8 | race:libjli.so 9 | # Suppress data races in libz.so 10 | race:libz.so 11 | 12 | 13 | # # Suppress data race in G1Allocator::survivor_attempt_allocation 14 | # race:G1Allocator::survivor_attempt_allocation 15 | # race:G1CollectedHeap::allocate_new_tlab 16 | # race:G1CollectedHeap::mem_allocate 17 | # race:OopMapCache::lookup 18 | # race:G1CodeRootSet::clear 19 | # race:Metaspace::allocate 20 | # race:FilterQueue 21 | # race:PlatformParker::~PlatformParker 22 | # race:ObjAllocator::initialize 23 | # # Todo: jvmti could be interesting 24 | # race:JvmtiExport::post_sampled_object_alloc 25 | # # Suppress JVM internals 26 | # race:SymbolTable::do_add_if_needed 27 | # race:SymbolTable::do_lookup 28 | # race:G1ParScanThreadState::copy_to_survivor_space 29 | # race:G1RemSetScanState::G1ClearCardTableTask 30 | # race:ObjArrayAllocator::initialize 31 | # race:CompileBroker::set_last_compile 32 | # race:os::PlatformEvent::unpark 33 | # race:os::malloc 34 | # race:os::vsnprintf 35 | # race:PerfString::set_string 36 | # race:JvmtiDeferredEventQueue::dequeue 37 | # race:JvmtiExport::post_dynamic_code_generated_internal 38 | # race:InlineCacheBuffer::update_inline_caches 39 | # race:CodeBuffer::copy_relocations_to 40 | # race:MoveAndUpdateClosure::copy_partial_obj 41 | # race:CardTableModRefBS::write_ref_array_work 42 | # race:TypeArrayKlass::allocate_common 43 | # race:PSScavenge::clean_up_failed_promotion 44 | # race:InstanceKlass::allocate_instance 45 | # race:TypeArrayKlass::allocate_common 46 | # race:ObjArrayKlass::allocate 47 | # race:DerivedPointerTable::update_pointers 48 | # race:j9ThunkTableEquals 49 | # race:hashTableAddNodeInList 50 | # race:internalCreateRAMClassFromROMClassImpl 51 | # race:monitor_notify_one_or_all 52 | # race:omrmem_free_memory 53 | # race:findLocallyDefinedClass 54 | # race:addClassName 55 | # race:j9bcv_verifyBytecodes 56 | # race:pool_newElement 57 | # race:monitor_exit.part.0 58 | # race:J9Object* MM_Scavenger::copyForVariant 59 | # race:__tsan_atomic64_compare_exchange_strong 60 | # race:strncpy 61 | # race:free 62 | # race:libjvm.so$ClassAllocator::initialize 63 | # race:libjvm.so$G1BlockOffsetTablePart::alloc_block_work 64 | # race:libjvm.so$ClassAllocator::initialize 65 | # race:libjvm.so$G1BlockOffsetTablePart::alloc_block_work 66 | # race:libjvm.so$G1BlockOffsetTablePart::alloc_block_work 67 | # race:libjvm.so$JavaThread::~JavaThread 68 | -------------------------------------------------------------------------------- /gradle/sanitizers/ubsan.supp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/java-profiler/996bcbff2a362baa9dcfe23b679810428d175c2b/gradle/sanitizers/ubsan.supp -------------------------------------------------------------------------------- /gradle/scm.gradle: -------------------------------------------------------------------------------- 1 | scmVersion { 2 | tag { 3 | prefix = 'v_' 4 | versionSeparator = '' 5 | initialVersion({config, position -> '0.31.0'}) 6 | } 7 | 8 | versionIncrementer 'incrementMinor' 9 | 10 | checks { 11 | uncommittedChanges = true 12 | aheadOfRemote = true 13 | snapshotDependencies = false 14 | } 15 | 16 | nextVersion { 17 | suffix.set("SNAPSHOT") 18 | separator.set("-") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /gradle/semantic-version.gradle: -------------------------------------------------------------------------------- 1 | version = "0.58.0" -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/java-profiler/996bcbff2a362baa9dcfe23b679810428d175c2b/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 1>&2 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 48 | echo. 1>&2 49 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 50 | echo location of your Java installation. 1>&2 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 1>&2 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 62 | echo. 1>&2 63 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 64 | echo location of your Java installation. 1>&2 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /legacy_tests/include.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | function collapseJfr() { 4 | local event=$1 5 | local input=$2 6 | local output=$3 7 | 8 | local json_out="${input}.json" 9 | $JAVA_HOME/bin/jfr print --events $1 --stack-depth 10 --json $input > $json_out 10 | jq -r '[.recording.events[]?.values.stackTrace | select(.truncated == false) | .frames | map("\(.method.type.name).\(.method.name)") | reverse | join(";")]' $json_out | grep -v '.no_Java_frame' | sed 's/"//g' | sed 's/,/ /g' > $output 11 | } 12 | 13 | function assert_string() { 14 | if ! grep -q "$1" $2; then 15 | echo "Required text "$1" was not found in $2" 16 | exit 1 17 | fi 18 | } 19 | -------------------------------------------------------------------------------- /legacy_tests/load-libraries-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e # exit on any failure 4 | set -x # print all executed lines 5 | 6 | if [ -z "${JAVA_HOME}" ]; then 7 | echo "JAVA_HOME is not set" 8 | exit 1 9 | fi 10 | 11 | HERE=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 12 | 13 | source ${HERE}/include.sh 14 | 15 | cd ${HERE}/loadlibs 16 | # build some dynamic libraries to load 17 | g++ -c -fPIC -o increment.o increment.cpp 18 | gcc -shared -o libincrement.so increment.o 19 | 20 | g++ -ldl -c -fPIC -I$JAVA_HOME/include/linux/ -I$JAVA_HOME/include/ com_datadoghq_loader_DynamicLibraryLoader.cpp -o com_datadoghq_loader_DynamicLibraryLoader.o 21 | g++ -shared -fPIC -o libloader.so com_datadoghq_loader_DynamicLibraryLoader.o -lc 22 | 23 | JFR=/tmp/load-libraries-test.jfr 24 | rm -f $JFR 25 | 26 | # need to package the profiler JAR with the native artifacts 27 | # will skip tests and native build because they will be initiated elsewhere 28 | if [ -f ${HERE}/../build/libjavaProfielr.so ]; then 29 | SKIP_NATIVE_ARG="-Dskip-native" 30 | fi 31 | 32 | CLASSPATH=$(find ${HERE}/../target -name 'ddprof-*.jar') 33 | 34 | export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:. 35 | $JAVA_HOME/bin/javac -cp $CLASSPATH com/datadoghq/loader/DynamicLibraryLoader.java 36 | $JAVA_HOME/bin/java -cp .:$CLASSPATH \ 37 | -Djava.library.path=. com.datadoghq.loader.DynamicLibraryLoader \ 38 | $JFR ./libincrement.so:increment 39 | 40 | # $JAVA_HOME/bin/jfr print --json $JFR 41 | # $JAVA_HOME/bin/jfr summary $JFR 42 | 43 | 44 | -------------------------------------------------------------------------------- /legacy_tests/loadlibs/com/datadoghq/loader/DynamicLibraryLoader.java: -------------------------------------------------------------------------------- 1 | package com.datadoghq.loader; 2 | 3 | import java.nio.file.Path; 4 | import java.nio.file.Paths; 5 | import java.util.concurrent.ThreadLocalRandom; 6 | 7 | import com.datadoghq.profiler.JavaProfiler; 8 | 9 | public class DynamicLibraryLoader { 10 | 11 | static { 12 | System.loadLibrary("loader"); 13 | } 14 | 15 | public static void main(String... args) throws Exception { 16 | DynamicLibraryLoader loader = new DynamicLibraryLoader(); 17 | String jfrDump = args[0]; 18 | for (int i = 1; i < args.length; i++) { 19 | String[] split = args[i].split(":"); 20 | if (!loader.loadLibrary(split[0], split[1])) { 21 | System.err.println("Could not load " + split[0] + " and invoke " + split[1]); 22 | System.exit(1); 23 | } 24 | } 25 | startProfilerAndDoWork(jfrDump); 26 | } 27 | 28 | private static void startProfilerAndDoWork(String jfrFile) throws Exception { 29 | JavaProfiler ap = JavaProfiler.getInstance(); 30 | Path jfrDump = Paths.get(jfrFile); 31 | ap.execute("start,loglevel=TRACE,cpu=1m,wall=1ms,filter=0,jfr,file=" + jfrDump.toAbsolutePath()); 32 | ap.addThread(); 33 | work(); 34 | ap.stop(); 35 | } 36 | 37 | private static void work() throws Exception { 38 | Thread.sleep(10); 39 | long blackhole = System.nanoTime(); 40 | for (int i = 0; i < 10_000_000; i++) { 41 | blackhole ^= ThreadLocalRandom.current().nextLong(); 42 | } 43 | Thread.sleep(10); 44 | System.err.println(blackhole); 45 | } 46 | 47 | 48 | private native boolean loadLibrary(String libraryFile, String functionName); 49 | 50 | } 51 | -------------------------------------------------------------------------------- /legacy_tests/loadlibs/com_datadoghq_loader_DynamicLibraryLoader.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "com_datadoghq_loader_DynamicLibraryLoader.h" 3 | #include 4 | #include 5 | 6 | JNIEXPORT jboolean JNICALL Java_com_datadoghq_loader_DynamicLibraryLoader_loadLibrary(JNIEnv* env, jobject unused, jstring library, jstring name) { 7 | int (*function)(int i); 8 | const char* libraryName = env->GetStringUTFChars(library, 0); 9 | void* handle = dlopen(libraryName, RTLD_LAZY); 10 | if (NULL == handle) { 11 | std::cout << "could not load " << libraryName << std::endl; 12 | return false; 13 | } else { 14 | const char *functionName = env->GetStringUTFChars(name, 0); 15 | function = (int(*)(int)) dlsym(handle, functionName); 16 | int next = (*function)(1); 17 | std::cout << "loaded " << libraryName << " and computed: " << next << std::endl; 18 | return true; 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /legacy_tests/loadlibs/com_datadoghq_loader_DynamicLibraryLoader.h: -------------------------------------------------------------------------------- 1 | #ifndef JAVA_PROFILER_COM_DATADOGHQ_LOADER_DYNAMICLIBRARYLOADER_H 2 | #define JAVA_PROFILER_COM_DATADOGHQ_LOADER_DYNAMICLIBRARYLOADER_H 3 | 4 | #include 5 | 6 | extern "C" { 7 | JNIEXPORT jboolean JNICALL Java_com_datadoghq_loader_DynamicLibraryLoader_loadLibrary(JNIEnv *, jobject, jstring, jstring); 8 | } 9 | 10 | #endif //JAVA_PROFILER_COM_DATADOGHQ_LOADER_DYNAMICLIBRARYLOADER_H 11 | -------------------------------------------------------------------------------- /legacy_tests/loadlibs/increment.cpp: -------------------------------------------------------------------------------- 1 | #include "increment.h" 2 | 3 | int increment(int x) { 4 | return x + 1; 5 | } 6 | -------------------------------------------------------------------------------- /legacy_tests/loadlibs/increment.h: -------------------------------------------------------------------------------- 1 | #ifndef JAVA_PROFILER_INCREMENT_H 2 | #define JAVA_PROFILER_INCREMENT_H 3 | 4 | extern "C" { 5 | int increment(int x); 6 | } 7 | 8 | #endif //JAVA_PROFILER_INCREMENT_H 9 | -------------------------------------------------------------------------------- /legacy_tests/run_renaissance.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e # exit on any failure 4 | set -x # print all executed lines 5 | 6 | if [ -z "${JAVA_HOME}" ]; then 7 | echo "JAVA_HOME is not set" 8 | exit 1 9 | fi 10 | 11 | WGET=$(which wget) 12 | if [ -z "$WGET" ]; then 13 | echo "Missing wget" 14 | exit 1 15 | fi 16 | 17 | function help() { 18 | echo "Usage: run_renaissance.sh -pa " 19 | } 20 | 21 | if [ $# -eq 0 ]; then 22 | help 23 | exit 1 24 | fi 25 | 26 | while [ $# -gt 1 ]; do 27 | KEY=$1 28 | case "$KEY" in 29 | "-pa") 30 | shift 31 | PROFILER_ARGS=$1 32 | ;; 33 | *) 34 | if [ -z "$PROFILER_ARGS" ]; then 35 | help 36 | exit 1 37 | fi 38 | BENCHMARK_ARGS=("$@") 39 | break 40 | ;; 41 | esac 42 | shift 43 | done 44 | 45 | mkdir -p .resources 46 | (cd .resources && if [ ! -e renaissance.jar ]; then wget https://github.com/renaissance-benchmarks/renaissance/releases/download/v0.14.0/renaissance-mit-0.14.0.jar -nv -O renaissance.jar; fi) 47 | 48 | HERE=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 49 | BASEDIR="${HERE}/.." 50 | 51 | AGENT_PATH=${BASEDIR}/build/libjavaProfiler.so 52 | if [ ! -f "$AGENT_PATH" ]; then 53 | # we are running in CI - the library will be in a different place 54 | AGENT_PATH=${BASEDIR}/target/classes/META-INF/native/linux-x64/libjavaProfiler.so 55 | fi 56 | echo "${JAVA_HOME}/bin/java -agentpath:${AGENT_PATH}=start,${PROFILER_ARGS} -jar .resources/renaissance.jar ${BENCHMARK_ARGS[@]}" 57 | ${JAVA_HOME}/bin/java -agentpath:${AGENT_PATH}=start,${PROFILER_ARGS} -jar .resources/renaissance.jar "${BENCHMARK_ARGS[@]}" -------------------------------------------------------------------------------- /legacy_tests/test-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | for TEST in $(find . -name '*-test.sh'); do 6 | echo "=== $TEST" 7 | bash $TEST 8 | done 9 | 10 | echo "All tests passed" -------------------------------------------------------------------------------- /malloc-shim/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'cpp-library' 3 | } 4 | 5 | group = 'com.datadoghq' 6 | version = '0.1' 7 | 8 | tasks.withType(CppCompile).configureEach { 9 | compilerArgs.addAll( 10 | [ 11 | "-O3", 12 | "-fno-omit-frame-pointer", 13 | "-fvisibility=hidden", 14 | "-std=c++17", 15 | "-DPROFILER_VERSION=\"${project.getProperty('version')}\"" 16 | ] 17 | ) 18 | } 19 | 20 | library { 21 | baseName = "debug" 22 | targetMachines = [machines.linux.x86_64] 23 | linkage = [Linkage.SHARED] 24 | } -------------------------------------------------------------------------------- /malloc-shim/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = "malloc-shim" -------------------------------------------------------------------------------- /malloc-shim/src/main/cpp/malloc_intercept.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "debug.h" 10 | 11 | #ifdef __linux__ 12 | static int sighandler_tid = -1; 13 | 14 | extern void *__libc_malloc(size_t size); 15 | 16 | void *malloc(size_t size) { 17 | void *result = NULL; 18 | int tid = sighandler_tid != -1 ? syscall(__NR_gettid) : -2; 19 | if (tid == sighandler_tid) { 20 | fprintf(stderr, "!!!! MALLOC in signal handler !!!\n"); 21 | raise(SIGSEGV); 22 | } else { 23 | result = __libc_malloc(size); 24 | } 25 | 26 | return result; 27 | } 28 | 29 | void set_sighandler_tid(int tid) { 30 | sighandler_tid = tid; 31 | } 32 | #endif // __linux__ 33 | -------------------------------------------------------------------------------- /malloc-shim/src/main/public/debug.h: -------------------------------------------------------------------------------- 1 | #ifndef _DEBUG_H 2 | #define _DEBUG_H 3 | 4 | void set_sighandler_tid(int tid); 5 | 6 | #endif // _DEBUG_H -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/java-profiler/996bcbff2a362baa9dcfe23b679810428d175c2b/pom.xml -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':ddprof-lib' 2 | include ':ddprof-lib:gtest' 3 | include ':ddprof-lib:benchmarks' 4 | include ':ddprof-test-tracer' 5 | include ':ddprof-test' 6 | include ':malloc-shim' 7 | include ':ddprof-stresstest' 8 | -------------------------------------------------------------------------------- /test/native/libs/reladyn.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The async-profiler authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | #include 7 | #include 8 | 9 | // Force pthread_setspecific into .rela.dyn with R_X86_64_GLOB_DAT. 10 | int (*indirect_pthread_setspecific)(pthread_key_t, const void*); 11 | 12 | // Force pthread_exit into .rela.dyn with R_X86_64_64. 13 | void (*static_pthread_exit)(void*) = pthread_exit; 14 | 15 | void* thread_function(void* arg) { 16 | printf("Thread running\n"); 17 | return NULL; 18 | } 19 | 20 | // Not indended to be executed. 21 | int reladyn() { 22 | pthread_t thread; 23 | pthread_key_t key; 24 | 25 | pthread_key_create(&key, NULL); 26 | 27 | // Direct call, forces into .rela.plt. 28 | pthread_create(&thread, NULL, thread_function, NULL); 29 | 30 | // Assign to a function pointer at runtime, forces into .rela.dyn as R_X86_64_GLOB_DAT. 31 | indirect_pthread_setspecific = pthread_setspecific; 32 | indirect_pthread_setspecific(key, "Thread-specific value"); 33 | 34 | // Use pthread_exit via the static pointer, forces into .rela.dyn as R_X86_64_64. 35 | static_pthread_exit(NULL); 36 | 37 | return 0; 38 | } 39 | -------------------------------------------------------------------------------- /test/native/symbolsLinuxTest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The async-profiler authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | #ifdef __linux__ 7 | 8 | #include "codeCache.h" 9 | #include "profiler.h" 10 | #include "testRunner.hpp" 11 | #include 12 | 13 | #define ASSERT_RESOLVE(id) \ 14 | { \ 15 | void* result = dlopen("libreladyn.so", RTLD_NOW); /* see reladyn.c */ \ 16 | ASSERT(result); \ 17 | Profiler::instance()->updateSymbols(false); \ 18 | CodeCache* libreladyn = Profiler::instance()->findLibraryByName("libreladyn"); \ 19 | ASSERT(libreladyn); \ 20 | void* sym = libreladyn->findImport(id); \ 21 | ASSERT(sym); \ 22 | } 23 | 24 | TEST_CASE(ResolveFromRela_plt) { 25 | ASSERT_RESOLVE(im_pthread_create); 26 | } 27 | 28 | TEST_CASE(ResolveFromRela_dyn_R_GLOB_DAT) { 29 | ASSERT_RESOLVE(im_pthread_setspecific); 30 | } 31 | 32 | TEST_CASE(ResolveFromRela_dyn_R_ABS64) { 33 | ASSERT_RESOLVE(im_pthread_exit); 34 | } 35 | 36 | #endif // __linux__ 37 | -------------------------------------------------------------------------------- /test/test/nativemem/malloc_plt_dyn.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The async-profiler authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | #include 7 | #include 8 | 9 | const int MALLOC_SIZE = 1999993; 10 | const int MALLOC_DYN_SIZE = 2000003; 11 | 12 | // A global pointer referencing malloc as data -> .rela.dyn 13 | static void* (*malloc_dyn)(size_t) = malloc; 14 | 15 | int main(void) { 16 | // Direct call -> .rela.plt 17 | void* p = malloc(MALLOC_SIZE); 18 | 19 | void* q = malloc_dyn(MALLOC_DYN_SIZE); 20 | 21 | free(p); 22 | free(q); 23 | 24 | return 0; 25 | } 26 | -------------------------------------------------------------------------------- /utils/init_cherypick_repo.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -x 3 | 4 | # This script is used to initialize the cherry-pick repo from async-profiler 5 | 6 | HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 7 | 8 | PATH_REPO="$1" 9 | 10 | if [ -z "$PATH_REPO" ]; then 11 | mkdir -p .tmp 12 | PATH_REPO="$HERE/../.tmp/async-profiler-cherry" 13 | fi 14 | 15 | if [ ! -d "$PATH_REPO" ]; then 16 | git clone https://github.com/async-profiler/async-profiler.git $PATH_REPO 17 | 18 | cd "$PATH_REPO" || exit 1 19 | 20 | git remote add upstream https://github.com/async-profiler/async-profiler.git 21 | fi 22 | 23 | cd "$HERE" || exit 1 24 | 25 | git remote add cherry file://"$PATH_REPO" || true 26 | --------------------------------------------------------------------------------