Skip to content

Commit ae231e6

Browse files
authored
Merge branch 'main' into jb/sonatype_migration
2 parents acd7f8a + b2e06ca commit ae231e6

40 files changed

Lines changed: 1028 additions & 247 deletions

.github/scripts/test_alpine_aarch64.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,6 @@ JAVA_VERSION=$("${JAVA_TEST_HOME}/bin/java" -version 2>&1 | awk -F '"' '/version
2929
}')
3030
export JAVA_VERSION
3131

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
32+
apk update && apk add curl moreutils wget hexdump linux-headers bash make g++ clang git cppcheck jq cmake gtest-dev gmock tar binutils >/dev/null
3333

3434
./gradlew -PCI -PkeepJFRs :ddprof-test:test${CONFIG} --no-daemon --parallel --build-cache --no-watch-fs

.github/workflows/test_workflow.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ jobs:
7272
if: steps.set_enabled.outputs.enabled == 'true'
7373
run: |
7474
sudo apt-get update
75-
sudo apt-get install -y curl zip unzip libgtest-dev libgmock-dev
75+
sudo apt-get install -y curl zip unzip libgtest-dev libgmock-dev binutils
7676
if [[ ${{ matrix.java_version }} =~ "-zing" ]]; then
7777
sudo apt-get install -y g++-9 gcc-9
7878
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 100 --slave /usr/bin/g++ g++ /usr/bin/g++-9
@@ -135,7 +135,7 @@ jobs:
135135
steps:
136136
- name: Setup OS
137137
run: |
138-
apk update && apk add curl moreutils wget hexdump linux-headers bash make g++ clang git cppcheck jq cmake gtest-dev gmock tar >/dev/null
138+
apk update && apk add curl moreutils wget hexdump linux-headers bash make g++ clang git cppcheck jq cmake gtest-dev gmock tar binutils >/dev/null
139139
- uses: actions/checkout@v3
140140
- name: Cache Gradle Wrapper Binaries
141141
uses: actions/cache@v4
@@ -286,7 +286,7 @@ jobs:
286286
sudo apt update -y
287287
sudo apt remove -y g++
288288
sudo apt autoremove -y
289-
sudo apt install -y curl zip unzip clang make build-essential
289+
sudo apt install -y curl zip unzip clang make build-essential binutils
290290
if [[ ${{ matrix.java_version }} =~ "-zing" ]]; then
291291
sudo apt -y install g++-9 gcc-9
292292
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 100 --slave /usr/bin/g++ g++ /usr/bin/g++-9

.github/workflows/update_assets.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ jobs:
3535
TAG="$GITHUB_REF_NAME"
3636
fi
3737
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"
38+
ASSET_URL="https://repo1.maven.org/maven2/com/datadoghq/ddprof/${VERSION}/ddprof-${VERSION}.jar"
3939
RESULT=1
4040
while [ $RESULT -ne 0 ]; do
4141
wget -q $ASSET_URL

README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,39 @@ The project includes both Java and C++ unit tests. You can run them using:
6464
### Cross-JDK Testing
6565
`JAVA_TEST_HOME=<path to test JDK> ./gradlew testDebug`
6666

67+
## Release Builds and Debug Information
68+
69+
### Split Debug Information
70+
Release builds automatically generate split debug information to optimize deployment size while preserving debugging capabilities:
71+
72+
- **Stripped libraries** (~1.2MB): Production-ready binaries with symbols removed for deployment
73+
- **Debug symbol files** (~6.1MB): Separate `.debug` files containing full debugging information
74+
- **Debug links**: Stripped libraries include `.gnu_debuglink` sections pointing to debug files
75+
76+
### Build Artifacts Structure
77+
```
78+
ddprof-lib/build/
79+
├── lib/main/release/linux/x64/
80+
│ ├── libjavaProfiler.so # Original library with debug symbols
81+
│ ├── stripped/
82+
│ │ └── libjavaProfiler.so # Stripped library (83% smaller)
83+
│ └── debug/
84+
│ └── libjavaProfiler.so.debug # Debug symbols only
85+
├── native/release/
86+
│ └── META-INF/native-libs/linux-x64/
87+
│ └── libjavaProfiler.so # Final stripped library (deployed)
88+
└── native/release-debug/
89+
└── META-INF/native-libs/linux-x64/
90+
└── libjavaProfiler.so.debug # Debug symbols package
91+
```
92+
93+
### Build Options
94+
- **Skip debug extraction**: `./gradlew buildRelease -Pskip-debug-extraction=true`
95+
- **Debug extraction requires**: `objcopy` (Linux) or `dsymutil` (macOS)
96+
- Ubuntu/Debian: `sudo apt-get install binutils`
97+
- Alpine: `apk add binutils`
98+
- macOS: Included with Xcode command line tools
99+
67100
## Development
68101

69102
### Code Quality

build.gradle

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ plugins {
1414
id "com.diffplug.spotless" version "6.11.0"
1515
}
1616

17-
version = "1.28.0"
17+
version = "1.29.0"
1818

1919
apply plugin: "com.dipien.semantic-version"
2020
version = project.findProperty("ddprof_version") ?: version
@@ -39,7 +39,8 @@ repositories {
3939
mavenContent {
4040
snapshotsOnly()
4141
}
42-
url 'https://oss.sonatype.org/content/repositories/snapshots/'
42+
// see https://central.sonatype.org/publish/publish-portal-snapshots/#consuming-via-gradle
43+
url 'https://central.sonatype.com/repository/maven-snapshots/'
4344
}
4445
}
4546

@@ -75,6 +76,10 @@ nexusPublishing {
7576
password = "admin123"
7677
}
7778
} else {
79+
// see https://github.com/gradle-nexus/publish-plugin#publishing-to-maven-central-via-sonatype-central
80+
// For official documentation:
81+
// staging repo publishing https://central.sonatype.org/publish/publish-portal-ossrh-staging-api/#configuration
82+
// snapshot publishing https://central.sonatype.org/publish/publish-portal-snapshots/#publishing-via-other-methods
7883
sonatype {
7984
// see https://central.sonatype.org/publish/publish-portal-ossrh-staging-api/#configuration
8085
// see https://github.com/gradle-nexus/publish-plugin#publishing-to-maven-central-via-sonatype-central

ddprof-lib/build.gradle

Lines changed: 174 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,169 @@ plugins {
88
id 'de.undercouch.download' version '4.1.1'
99
}
1010

11+
// Helper function to check if objcopy is available
12+
def checkObjcopyAvailable() {
13+
try {
14+
def process = ['objcopy', '--version'].execute()
15+
process.waitFor()
16+
return process.exitValue() == 0
17+
} catch (Exception e) {
18+
return false
19+
}
20+
}
21+
22+
// Helper function to check if dsymutil is available (for macOS)
23+
def checkDsymutilAvailable() {
24+
try {
25+
def process = ['dsymutil', '--version'].execute()
26+
process.waitFor()
27+
return process.exitValue() == 0
28+
} catch (Exception e) {
29+
return false
30+
}
31+
}
32+
33+
// Helper function to check if debug extraction should be skipped
34+
def shouldSkipDebugExtraction() {
35+
// Skip if explicitly disabled
36+
if (project.hasProperty('skip-debug-extraction')) {
37+
return true
38+
}
39+
40+
// Skip if required tools are not available
41+
if (os().isLinux() && !checkObjcopyAvailable()) {
42+
return true
43+
}
44+
45+
if (os().isMacOsX() && !checkDsymutilAvailable()) {
46+
return true
47+
}
48+
49+
return false
50+
}
51+
52+
// Helper function to get debug file path for a given config
53+
def getDebugFilePath(config) {
54+
def extension = os().isLinux() ? 'so' : 'dylib'
55+
return file("$buildDir/lib/main/${config.name}/${osIdentifier()}/${archIdentifier()}/debug/libjavaProfiler.${extension}.debug")
56+
}
57+
58+
// Helper function to get stripped file path for a given config
59+
def getStrippedFilePath(config) {
60+
def extension = os().isLinux() ? 'so' : 'dylib'
61+
return file("$buildDir/lib/main/${config.name}/${osIdentifier()}/${archIdentifier()}/stripped/libjavaProfiler.${extension}")
62+
}
63+
64+
// Helper function to create error message for missing tools
65+
def getMissingToolErrorMessage(toolName, installInstructions) {
66+
return """
67+
|${toolName} is not available but is required for split debug information.
68+
|
69+
|To fix this issue:
70+
|${installInstructions}
71+
|
72+
|If you want to build without split debug info, set -Pskip-debug-extraction=true
73+
""".stripMargin()
74+
}
75+
76+
// Helper function to create debug extraction task
77+
def createDebugExtractionTask(config, linkTask) {
78+
return tasks.register('extractDebugLibRelease', Exec) {
79+
onlyIf {
80+
!shouldSkipDebugExtraction()
81+
}
82+
dependsOn linkTask
83+
description = 'Extract debug symbols from release library'
84+
workingDir project.buildDir
85+
86+
// Declare outputs so Gradle knows what files this task creates
87+
outputs.file getDebugFilePath(config)
88+
89+
doFirst {
90+
def sourceFile = linkTask.get().linkedFile.get().asFile
91+
def debugFile = getDebugFilePath(config)
92+
93+
// Ensure debug directory exists
94+
debugFile.parentFile.mkdirs()
95+
96+
// Set the command line based on platform
97+
if (os().isLinux()) {
98+
commandLine = ['objcopy', '--only-keep-debug', sourceFile.absolutePath, debugFile.absolutePath]
99+
} else {
100+
// For macOS, we'll use dsymutil instead
101+
commandLine = ['dsymutil', sourceFile.absolutePath, '-o', debugFile.absolutePath.replace('.debug', '.dSYM')]
102+
}
103+
}
104+
}
105+
}
106+
107+
// Helper function to create debug link task (Linux only)
108+
def createDebugLinkTask(config, linkTask, extractDebugTask) {
109+
return tasks.register('addDebugLinkLibRelease', Exec) {
110+
onlyIf {
111+
os().isLinux() && !shouldSkipDebugExtraction()
112+
}
113+
dependsOn extractDebugTask
114+
description = 'Add debug link to the original library'
115+
116+
doFirst {
117+
def sourceFile = linkTask.get().linkedFile.get().asFile
118+
def debugFile = getDebugFilePath(config)
119+
120+
commandLine = ['objcopy', '--add-gnu-debuglink=' + debugFile.absolutePath, sourceFile.absolutePath]
121+
}
122+
}
123+
}
124+
125+
// Helper function to create debug file copy task
126+
def createDebugCopyTask(config, extractDebugTask) {
127+
return tasks.register('copyReleaseDebugFiles', Copy) {
128+
onlyIf {
129+
!shouldSkipDebugExtraction()
130+
}
131+
dependsOn extractDebugTask
132+
from file("$buildDir/lib/main/${config.name}/${osIdentifier()}/${archIdentifier()}/debug")
133+
into file(libraryTargetPath(config.name))
134+
include '**/*.debug'
135+
include '**/*.dSYM/**'
136+
}
137+
}
138+
139+
// Main function to setup debug extraction for release builds
140+
def setupDebugExtraction(config, linkTask) {
141+
if (config.name == 'release' && config.active && !project.hasProperty('skip-native')) {
142+
// Create all debug-related tasks
143+
def extractDebugTask = createDebugExtractionTask(config, linkTask)
144+
def addDebugLinkTask = createDebugLinkTask(config, linkTask, extractDebugTask)
145+
146+
// Create the strip task and configure it properly
147+
def stripTask = tasks.register('stripLibRelease', StripSymbols) {
148+
// No onlyIf needed here - setupDebugExtraction already handles the main conditions
149+
dependsOn addDebugLinkTask
150+
}
151+
152+
// Configure the strip task after registration
153+
stripTask.configure {
154+
targetPlatform = linkTask.get().targetPlatform
155+
toolChain = linkTask.get().toolChain
156+
binaryFile = linkTask.get().linkedFile.get().asFile
157+
outputFile = getStrippedFilePath(config)
158+
}
159+
160+
def copyDebugTask = createDebugCopyTask(config, extractDebugTask)
161+
162+
// Wire up the copy task to use stripped binaries
163+
def copyTask = tasks.findByName("copyReleaseLibs")
164+
if (copyTask != null) {
165+
copyTask.dependsOn stripTask
166+
copyTask.inputs.files stripTask.get().outputs.files
167+
168+
// Create an extra folder for the debug symbols
169+
copyTask.dependsOn copyDebugTask
170+
}
171+
}
172+
}
173+
11174
def libraryName = "ddprof"
12175

13176
description = "Datadog Java Profiler Library"
@@ -92,6 +255,8 @@ tasks.register('copyExternalLibs', Copy) {
92255
from(project.getProperty("with-libs")) {
93256
include "**/*.so"
94257
include "**/*.dylib"
258+
include "**/*.debug"
259+
include "**/*.dSYM/**"
95260
}
96261
into "${projectDir}/build/classes/java/main/META-INF/native-libs"
97262
}
@@ -105,7 +270,7 @@ def cloneAPTask = tasks.register('cloneAsyncProfiler') {
105270
if (!targetDir.exists()) {
106271
println "Cloning missing async-profiler git subdirectory..."
107272
exec {
108-
commandLine 'git', 'clone', '--branch', branch_lock, '--depth', '1', 'https://github.com/datadog/async-profiler.git', targetDir.absolutePath
273+
commandLine 'git', 'clone', '--branch', branch_lock, 'https://github.com/datadog/async-profiler.git', targetDir.absolutePath
109274
}
110275
exec {
111276
workingDir targetDir.absolutePath
@@ -126,7 +291,10 @@ def cloneAPTask = tasks.register('cloneAsyncProfiler') {
126291
println "async-profiler commit hash differs (current: ${currentCommit}, expected: ${commit_lock}), updating..."
127292
exec {
128293
workingDir targetDir.absolutePath
129-
commandLine 'git', 'fetch', 'origin', branch_lock, '--depth', '1'
294+
commandLine 'rm', '-rf', targetDir.absolutePath
295+
}
296+
exec {
297+
commandLine 'git', 'clone', '--branch', branch_lock, 'https://github.com/datadog/async-profiler.git', targetDir.absolutePath
130298
}
131299
exec {
132300
workingDir targetDir.absolutePath
@@ -273,6 +441,7 @@ buildConfigNames().each { name ->
273441
if (!project.hasProperty('skip-native')) {
274442
dependsOn copyTask
275443
}
444+
276445
from sourceSets.main.output.classesDirs
277446
from files(libraryTargetBase(name)) {
278447
include "**/*"
@@ -366,23 +535,7 @@ tasks.whenTaskAdded { task ->
366535
outputs.file linkedFile
367536
}
368537
if (config.name == 'release') {
369-
def stripTask = tasks.register('stripLibRelease', StripSymbols) {
370-
onlyIf {
371-
config.active
372-
}
373-
dependsOn linkTask
374-
targetPlatform = tasks.linkLibRelease.targetPlatform
375-
toolChain = tasks.linkLibRelease.toolChain
376-
binaryFile = tasks.linkLibRelease.linkedFile.get()
377-
outputFile = file("$buildDir/lib/main/${config.name}/${osIdentifier()}/${archIdentifier()}/stripped/libjavaProfiler.${os().isLinux() ? 'so' : 'dylib'}")
378-
inputs.file binaryFile
379-
outputs.file outputFile
380-
}
381-
def copyTask = tasks.findByName("copyReleaseLibs")
382-
if (copyTask != null) {
383-
copyTask.dependsOn stripTask
384-
copyTask.inputs.files stripTask.get().outputs.files
385-
}
538+
setupDebugExtraction(config, linkTask)
386539
}
387540
}
388541
}
@@ -454,6 +607,8 @@ tasks.register('javadocJar', Jar) {
454607
from javadoc.destinationDir
455608
}
456609

610+
611+
457612
tasks.register('scanBuild', Exec) {
458613
workingDir "${projectDir}/src/test/make"
459614
commandLine 'scan-build'

ddprof-lib/src/main/cpp/codeCache.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,14 @@ char *NativeFunc::create(const char *name, short lib_index) {
2222
void NativeFunc::destroy(char *name) { free(from(name)); }
2323

2424
CodeCache::CodeCache(const char *name, short lib_index, bool imports_patchable,
25-
const void *min_address, const void *max_address) {
25+
const void *min_address, const void *max_address,
26+
const char* image_base) {
2627
_name = NativeFunc::create(name, -1);
2728
_lib_index = lib_index;
2829
_min_address = min_address;
2930
_max_address = max_address;
3031
_text_base = NULL;
32+
_image_base = image_base;
3133

3234
_plt_offset = 0;
3335
_plt_size = 0;

ddprof-lib/src/main/cpp/codeCache.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ class CodeCache {
109109
const void *_min_address;
110110
const void *_max_address;
111111
const char *_text_base;
112+
const char* _image_base;
112113

113114
unsigned int _plt_offset;
114115
unsigned int _plt_size;
@@ -132,7 +133,8 @@ class CodeCache {
132133
explicit CodeCache(const char *name, short lib_index = -1,
133134
bool imports_patchable = false,
134135
const void *min_address = NO_MIN_ADDRESS,
135-
const void *max_address = NO_MAX_ADDRESS);
136+
const void *max_address = NO_MAX_ADDRESS,
137+
const char* image_base = NULL);
136138
// Copy constructor
137139
CodeCache(const CodeCache &other);
138140
// Copy assignment operator
@@ -148,6 +150,8 @@ class CodeCache {
148150

149151
const void *maxAddress() const { return _max_address; }
150152

153+
const char* imageBase() const { return _image_base; }
154+
151155
bool contains(const void *address) const {
152156
return address >= _min_address && address < _max_address;
153157
}

0 commit comments

Comments
 (0)