Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
253f9a4
Java integration tests for reference chains
jbachorik Oct 12, 2026
2b7386e
javaApi: re-baseline class-map generation in the population reset seam
jbachorik Oct 12, 2026
95693bb
Add reference-chain repro, sweep, and reporting tooling
jbachorik Oct 12, 2026
149d6ab
Address sphinx review findings on rc-6/rc-7 test tooling
jbachorik Oct 12, 2026
9ffea55
ReferenceChainJfrParserTest: skip when the gtest fixture is absent
jbachorik Oct 12, 2026
afd6fce
Compress comment density in the reference-chain test stack
jbachorik Oct 13, 2026
3e74447
Address light-review findings on the rc-7 test stack
jbachorik Oct 13, 2026
354c808
Drop PersistentAllocator left behind by the #800 squash
jbachorik Oct 20, 2026
2208136
Restore the dropped reference-chain test seams and fix the drain coun…
jbachorik Oct 20, 2026
240a948
Restore reverted reference-chain test fixes and debug-build guards
jbachorik Oct 20, 2026
30c6d98
Keep the chaos antagonist's population growing past the cap; cap repr…
jbachorik Oct 20, 2026
05e4a37
Fix the refchains sweep scripts' aggregation and bash 3.2 compatibility
jbachorik Oct 20, 2026
860860f
Reduce build-and-summarize to log capture and failure extraction
jbachorik Oct 20, 2026
123cefb
sphinx: address review feedback on PR #801
jbachorik Oct 20, 2026
5cb5bb3
Correlate leak tags on (tag, incarnation) and emit locale-stable metrics
jbachorik Oct 20, 2026
fd06b0f
Quarantine TlsPrimingTest on el7-8-release-amd64 (PROF-16281)
jbachorik Oct 20, 2026
7799e82
Drop sphinx spec notes from the PR
jbachorik Oct 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 30 additions & 70 deletions .claude/commands/build-and-summarize
Original file line number Diff line number Diff line change
@@ -1,84 +1,44 @@
#!/usr/bin/env bash
set -euo pipefail
# Runs Gradle with the full output captured to a timestamped log so an agent
# session never ingests a multi-thousand-line build log. Prints the status
# line; on failure, extracts the "What went wrong" blocks and failing-test
# lines. The log file remains the source of truth for anything rarer.
#
# Usage: build-and-summarize [gradle-task...] (default: build)
#
# No live tail and no summary artifacts: a harness tool result arrives
# atomically after the run anyway, so streaming filters only added a
# background process to manage, and the failures section below is everything
# an agent needs before deciding to grep the full log.
set -u

mkdir -p build/logs build/reports/claude .claude/out
STAMP="$(date +%Y%m%d-%H%M%S)"

# Args (default to 'build')
ARGS=("$@")
if [ "${#ARGS[@]}" -eq 0 ]; then
ARGS=(build)
fi
[ "${#ARGS[@]}" -eq 0 ] && ARGS=(build)

# Label for the log file from the first arg
STAMP="$(date +%Y%m%d-%H%M%S)"
LABEL="$(printf '%s' "${ARGS[0]}" | tr '/:' '__')"
LOG="build/logs/${STAMP}-${LABEL}.log"
mkdir -p "$(dirname "$LOG")"

# Ensure we clean the tail on exit
tail_pid=""
cleanup() { [ -n "${tail_pid:-}" ] && kill "$tail_pid" 2>/dev/null || true; }
trap cleanup EXIT INT TERM

echo "▶ Logging full Gradle output to: $LOG"
echo "▶ Running: ./gradlew ${ARGS[*]} -i --console=plain"
echo " (Console output here is minimized; the full log is in the file.)"
echo

# Start Gradle fully redirected to the log (no stdout/stderr to this session)
# Use stdbuf to make the output line-buffered in the log for timely tailing.
( stdbuf -oL -eL ./gradlew "${ARGS[@]}" -i --console=plain ) >"$LOG" 2>&1 &
gradle_pid=$!
# One log per build, hundreds of KB each, and agent sessions generate many:
# prune to the newest 20 so the directory stays bounded.
ls -t build/logs/*.log 2>/dev/null | tail -n +21 | xargs rm -f 2>/dev/null || true

# Minimal live progress: follow the log and print only interesting lines
# - Task starts
# - Final build status
# - Test summary lines
# tail itself is the background job, so $! is its PID and killing it also ends
# awk. (Backgrounding "tail | awk" would make $! the PID of awk instead: killing
# awk leaves tail following a log that never grows again, so it never notices,
# and since it inherits this script's stderr, a caller piping our output never
# sees end of input.)
tail -n0 -F "$LOG" 2>/dev/null > >(awk '
/^> Task / { print; fflush(); next }
/^BUILD (SUCCESSFUL|FAILED)/ { print; fflush(); next }
/[0-9]+ tests? (successful|failed|skipped)/ { print; fflush(); next }
') &
tail_pid=$!

# Wait for Gradle to finish
wait "$gradle_pid"
echo "▶ ./gradlew ${ARGS[*]} -i --console=plain (full log: $LOG)"
./gradlew "${ARGS[@]}" -i --console=plain >"$LOG" 2>&1
status=$?

# Stop the tail and print a compact summary from the log
kill "$tail_pid" 2>/dev/null || true
tail_pid=""

echo
echo "=== Summary ==="
# Grab the last BUILD line and nearest test summary lines
awk '
/^BUILD (SUCCESSFUL|FAILED)/ { lastbuild=$0 }
/[0-9]+ tests? (successful|failed|skipped)/ { tests=$0 }
END {
if (lastbuild) print lastbuild;
if (tests) print tests;
}
' "$LOG" || true

echo
if [ $status -eq 0 ]; then
if [ "$status" -eq 0 ]; then
echo "✔ Gradle completed. Full log at: $LOG"
else
echo "✖ Gradle failed with status $status. Full log at: $LOG"
echo
echo "=== What went wrong ==="
sed -n '/^\* What went wrong:/,/^\* Try:/p' "$LOG" | head -40
echo
echo "=== Failing tasks/tests ==="
grep -E '^> Task .+FAILED$' "$LOG" | head -10
grep -E '[0-9]+ tests completed(, [0-9]+ failed)?' "$LOG" | head -5
grep -E '[a-zA-Z0-9_.$]+ > [A-Za-z0-9_.$()]+ FAILED$' "$LOG" | head -20
fi

# Hand over to your logs analyst agent — keep the main session output tiny.
echo
echo "Delegating to gradle-logs-analyst agent…"
# If your CLI supports non-streaming, set it here to avoid verbose output.
# Example (uncomment if supported): export CLAUDE_NO_STREAM=1

# Sub-agents would inherit the full parent env, including CLAUDECODE, which
# apparently causes problems with some versions of Claude (e.g. 4.6).
unset CLAUDECODE
claude "Act as the gradle-logs-analyst agent to parse the build log at: $LOG. Generate the required Gradle summary artifacts as specified in the gradle-logs-analyst agent definition."
exit "$status"
14 changes: 13 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,16 @@ __pycache__/
# CLAUDE.md is auto-generated from AGENTS.md bootstrap instructions
CLAUDE.md

.sphinx
# OS/editor cruft
.DS_Store

# AI review/agent tooling scratch state
.sphinx/
.skill-builder-temp/
.claude/scheduled_tasks.lock

# Python bytecode cache
*.pyc

# AI investigation scratch state (never committed)
.investigations/
18 changes: 5 additions & 13 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,11 @@ You are the **Main Orchestrator** for this repository.

### Goals
- When I ask you to build, you MUST:
1) run the Gradle task with plain console and increased verbosity,
2) capture stdout into `build/logs/<timestamp>-<task>.log`,
3) **delegate** parsing to the sub-agent `gradle-log-analyst`,
4) respond in chat with only a short status and the two output file paths:
- `build/reports/claude/gradle-summary.md`
- `build/reports/claude/gradle-summary.json`
1) invoke the wrapper `./.claude/commands/build-and-summarize <gradle-task...>` —
it runs Gradle with `--console=plain -i`, captures the full output into
`build/logs/<timestamp>-<task>.log`, and prints only the status line;
2) on failure, read the wrapper's extracted "What went wrong" section and the
failing task/test lines; grep the log file for anything rarer.

### Rules
- **Never** paste large log chunks into the chat.
Expand All @@ -57,13 +56,6 @@ You are the **Main Orchestrator** for this repository.
- Assume macOS/Linux unless I explicitly say Windows; if Windows, use PowerShell equivalents.
- If a step fails, print the failing command and a one-line hint, then stop.

### Implementation Hints
- For builds, always use: `--console=plain -i` (or `-d` if I ask for debug).
- Use `mkdir -p build/logs build/reports/claude` before writing.
- Timestamp format: `$(date +%Y%m%d-%H%M%S)`.
- After the build finishes, call the sub-agent like:
"Use `gradle-log-analyst` to parse LOG_PATH; write the two reports; reply with only a 3–6 line status and the two relative file paths."

### Shortcuts I Expect
- `./gradlew <gradle-task...>` to do everything in one step.
- If I just say "build assembleDebugJar", interpret that as the shortcut above.
Expand Down
54 changes: 12 additions & 42 deletions ddprof-lib/src/main/cpp/javaApi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1181,11 +1181,8 @@ Java_com_datadoghq_profiler_JavaProfiler_resetKlassPopulationForTest0(
// recordings (each Profiler::start() clearAll() bumps the generation)
// before this test's seeding, and no GC may land between start() and
// the seeded selectLeakCandidates() call. After the wipe above there is
// no stale-namespace population data left, and
// classMapGenerationSetForTest() itself invalidates every tracking entry's
// cached_klass_id when the generation changes (the same thing
// cleanup_table()'s reset branch does), so adopting the current
// generation is safe.
// no stale-namespace population data left to protect, so adopting the
// current generation is safe.
LivenessTracker::instance()->classMapGenerationSetForTest(
Profiler::instance()->classMap()->generation());
}
Expand Down Expand Up @@ -1242,48 +1239,21 @@ Java_com_datadoghq_profiler_JavaProfiler_pollReferenceChainTargets0(
ReferenceChainTracker::instance()->finishLoopIterationSerialized(jvmti, env);
}

// Counts only the drained chains whose leaf hop (hops are stored leaf-first) is `leaf`'s class,
// so a chain left cached by an earlier test in the same JVM cannot satisfy the caller. The class
// id is resolved with the same GetClassSignature + normalizeClassSignature() + lookupClass()
// sequence the chain collector uses (LivenessTracker::resolveKlassId()).
// Drains and returns the number of reference-chain events queued by
// pollReferenceChainTargets0() so far (the same queue Profiler::dump() drains in production
// to write datadog.ReferenceChain JFR events) - lets a test assert a chain was actually
// reconstructed without needing a real JFR dump.
extern "C" DLLEXPORT jint JNICALL
Java_com_datadoghq_profiler_JavaProfiler_drainReferenceChainEventCountForLeaf0(
JNIEnv *env, jclass unused, jobject leaf) {
Java_com_datadoghq_profiler_JavaProfiler_drainReferenceChainEventCount0(
JNIEnv *env, jclass unused) {
ProfiledThread::initCurrentThreadSignalSafe();
jvmtiEnv *jvmti = VM::jvmti();
if (leaf == nullptr || jvmti == nullptr) {
return 0;
}
u32 leaf_id = 0;
jclass clz = env->GetObjectClass(leaf);
if (clz != nullptr) {
char *sig = nullptr;
if (jvmti->GetClassSignature(clz, &sig, nullptr) == JVMTI_ERROR_NONE &&
sig != nullptr) {
const char *name = nullptr;
size_t name_len = 0;
if (ObjectSampler::normalizeClassSignature(sig, &name, &name_len)) {
int id = Profiler::instance()->lookupClass(name, name_len);
if (id > 0) {
leaf_id = (u32)id;
}
}
jvmti->Deallocate((unsigned char *)sig);
}
env->DeleteLocalRef(clz);
}
if (leaf_id == 0) {
if (jvmti == nullptr) {
return 0;
}
std::vector<ReferenceChainEvent> events;
ReferenceChainTracker::instance()->drainPendingChainEvents(&events);
jint count = 0;
for (const ReferenceChainEvent &event : events) {
if (!event._hops.empty() && event._hops[0].klass_id == leaf_id) {
count++;
}
}
return count;
return (jint)events.size();
}

extern "C" DLLEXPORT void JNICALL
Expand Down Expand Up @@ -1483,8 +1453,8 @@ Java_com_datadoghq_profiler_JavaProfiler_pollReferenceChainTargets0(
JNIEnv *env, jclass unused) {}

extern "C" DLLEXPORT jint JNICALL
Java_com_datadoghq_profiler_JavaProfiler_drainReferenceChainEventCountForLeaf0(
JNIEnv *env, jclass unused, jobject leaf) {
Java_com_datadoghq_profiler_JavaProfiler_drainReferenceChainEventCount0(
JNIEnv *env, jclass unused) {
return 0;
}

Expand Down
Loading
Loading