diff --git a/.gitlab/dd-trace-integration/.gitlab-ci.yml b/.gitlab/dd-trace-integration/.gitlab-ci.yml index 3d7b0ec52c..6ec9ec366c 100644 --- a/.gitlab/dd-trace-integration/.gitlab-ci.yml +++ b/.gitlab/dd-trace-integration/.gitlab-ci.yml @@ -287,7 +287,12 @@ post-pr-comment: stage: integration-test tags: [ "arch:arm64" ] image: registry.ddbuild.io/images/dd-octo-sts-ci-base:2025.06-1 + id_tokens: + DDOCTOSTS_ID_TOKEN: + aud: dd-octo-sts needs: + - job: prepare:start + artifacts: true - job: prepare-patched-agent artifacts: true - job: integration-test-x64-glibc @@ -309,4 +314,34 @@ post-pr-comment: timeout: 5m script: - .gitlab/dd-trace-integration/post-pr-comment.sh integration-test-results - allow_failure: true + +# Loud failure signal for scheduled/main runs, which have no PR to report back to. +notify-slack-dd-trace-integration: + stage: notify + tags: [ "arch:amd64" ] + image: registry.ddbuild.io/slack-notifier:v91289620-4ec922a-latest@sha256:06b24f392ccc383d371c72001520a254edef523bc0bfdc445f487106107b4202 + needs: + - job: prepare:start + artifacts: true + - job: get-versions + artifacts: true + - job: prepare-patched-agent + artifacts: false + - job: integration-test-x64-glibc + artifacts: false + - job: integration-test-x64-musl + artifacts: false + - job: integration-test-arm64-glibc + artifacts: false + - job: integration-test-arm64-musl + artifacts: false + rules: + - if: '$JDK_VERSION != null || $DEBUG_LEVEL != null || $HASH != null || $DOWNSTREAM != null' + when: never + - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' + when: never + - if: '$CI_PIPELINE_SOURCE == "schedule" || $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' + when: on_failure + - when: never + script: + - .gitlab/dd-trace-integration/notify_channel.sh "${CURRENT_VERSION}" diff --git a/.gitlab/dd-trace-integration/install-prerequisites.sh b/.gitlab/dd-trace-integration/install-prerequisites.sh index b54ca4b3c5..de0fac333a 100755 --- a/.gitlab/dd-trace-integration/install-prerequisites.sh +++ b/.gitlab/dd-trace-integration/install-prerequisites.sh @@ -54,14 +54,14 @@ if ! command -v jbang &> /dev/null; then # Verify installation if command -v jbang &> /dev/null; then - JBANG_VERSION=$(jbang version 2>&1 | head -1) + JBANG_VERSION=$(jbang version 2>&1 | head -1 || true) log_info "jbang installed successfully: ${JBANG_VERSION}" else log_warn "jbang installation completed but not found in PATH" log_warn "Please ensure ~/.jbang/bin is in your PATH" fi else - JBANG_VERSION=$(jbang version 2>&1 | head -1) + JBANG_VERSION=$(jbang version 2>&1 | head -1 || true) log_info "jbang already installed: ${JBANG_VERSION}" fi @@ -124,7 +124,7 @@ else if [ -x "$JBANG_JDK_DIR/bin/java" ]; then JDK25_INSTALLED=true log_info "JDK 25 installed manually from Adoptium" - "$JBANG_JDK_DIR/bin/java" -version 2>&1 | head -1 + "$JBANG_JDK_DIR/bin/java" -version 2>&1 | head -1 || true else log_warn "JDK 25 extraction failed" fi @@ -141,20 +141,33 @@ if [ "$JDK25_INSTALLED" = "false" ]; then fi # ======================================== -# Pre-warm jfr-shell backend +# Pre-warm jfr-shell # ======================================== -# jafar-shell resolves its backend plugin (io.btrace:jfr-shell-jafar) from Maven at -# runtime. If that artifact is unavailable (network restriction, version not yet -# published), every validation run fails. Detect this early so we can skip gracefully. +# jafar-shell itself (io.btrace:jafar-shell) is resolved by jbang from Maven Central, +# which CI runners can't reach directly. Route jbang through the same internal Maven +# proxy Gradle already uses (MAVEN_REPOSITORY_PROXY) so resolution doesn't depend on +# public internet egress. +# +# jafar-shell also has an optional pluggable "backend" system (io.btrace:jfr-shell-jafar +# / jfr-shell-jdk) resolved separately at runtime via its own embedded resolver, which +# does not go through jbang's --repos and so cannot be routed through the proxy. That's +# fine: the "open"+"show" flow used by validate-jfr-conformance.sh reads JFR files via +# jafar-shell's own bundled parser and never touches the backend system, so a missing +# backend does not affect real validation and must not be treated as fatal here. if [ ! -f /tmp/skip-jfr-validation ] && command -v jbang &> /dev/null; then - log_info "Pre-warming jfr-shell backend..." - PREWARM_OUT=$(jbang --java 25 jfr-shell@btraceio script /dev/null 2>&1 || true) - if echo "$PREWARM_OUT" | grep -q "No backends found\|No JFR backends available\|Failed to resolve artifact.*jfr-shell-jafar"; then - log_warn "jfr-shell backend unavailable (io.btrace:jfr-shell-jafar not resolvable from Maven)" - log_warn "JFR validation will be skipped" - echo "jfr-shell backend unavailable (io.btrace:jfr-shell-jafar not resolvable from Maven)" > /tmp/skip-jfr-validation + log_info "Pre-warming jfr-shell..." + if [ -n "${MAVEN_REPOSITORY_PROXY:-}" ]; then + JBANG_REPOS_OPT="--repos=central=${MAVEN_REPOSITORY_PROXY}" else - log_info "jfr-shell backend ready" + JBANG_REPOS_OPT="" + fi + if PREWARM_OUT=$(jbang --java 25 ${JBANG_REPOS_OPT} jfr-shell@btraceio script /dev/null 2>&1); then + log_info "jfr-shell ready" + else + log_warn "jfr-shell failed to start (io.btrace:jafar-shell not resolvable from Maven)" + log_warn "JFR validation will be skipped" + echo "jfr-shell failed to start (io.btrace:jafar-shell not resolvable from Maven)" > /tmp/skip-jfr-validation + echo "$PREWARM_OUT" | tail -20 fi fi @@ -164,7 +177,7 @@ fi if [ -z "${JAVA_HOME:-}" ]; then if command -v java &> /dev/null; then log_info "Java found in PATH" - java -version 2>&1 | head -3 + java -version 2>&1 | head -3 || true else echo "ERROR: Java not found. Please set JAVA_HOME or ensure java is in PATH" exit 1 @@ -176,7 +189,7 @@ else fi log_info "Java found at JAVA_HOME: ${JAVA_HOME}" - "${JAVA_HOME}/bin/java" -version 2>&1 | head -3 + "${JAVA_HOME}/bin/java" -version 2>&1 | head -3 || true fi # ======================================== diff --git a/.gitlab/dd-trace-integration/post-pr-comment.sh b/.gitlab/dd-trace-integration/post-pr-comment.sh index a0d8349150..2f76b18138 100755 --- a/.gitlab/dd-trace-integration/post-pr-comment.sh +++ b/.gitlab/dd-trace-integration/post-pr-comment.sh @@ -13,10 +13,12 @@ # Requires: # - DDPROF_COMMIT_BRANCH: Branch name to find PR # - CI_PIPELINE_URL: Link to pipeline -# - pr-commenter tool (available in CI images) +# - dd-octo-sts CLI (for GitHub token exchange, via upsert-github-pr-comment.sh) set -euo pipefail +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + # Colors for logging GREEN='\033[0;32m' YELLOW='\033[1;33m' @@ -28,55 +30,11 @@ log_warn() { echo -e "${YELLOW}[WARN]${NC} $*" >&2; } log_error() { echo -e "${RED}[ERROR]${NC} $*" >&2; } RESULTS_DIR="${1:-integration-test-results}" -REPO="DataDog/java-profiler" # Dashboard URL (GitHub Pages) DASHBOARD_URL="https://datadog.github.io/java-profiler/integration/" -# Check required tools - try to get pr-commenter from benchmarking-platform if not available -PR_COMMENTER_AVAILABLE=false -if command -v pr-commenter >/dev/null 2>&1; then - PR_COMMENTER_AVAILABLE=true -elif [ -n "${CI_JOB_TOKEN:-}" ]; then - # In CI, clone benchmarking-platform to get pr-commenter - log_info "pr-commenter not found, cloning benchmarking-platform..." - PLATFORM_DIR=$(mktemp -d) - trap "rm -rf ${PLATFORM_DIR}" EXIT - git config --global url."https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.ddbuild.io/DataDog/".insteadOf "https://github.com/DataDog/" - if git clone --depth 1 --branch dd-trace-go https://github.com/DataDog/benchmarking-platform "${PLATFORM_DIR}" 2>/dev/null; then - if [ -x "${PLATFORM_DIR}/tools/pr-commenter" ]; then - export PATH="${PLATFORM_DIR}/tools:${PATH}" - PR_COMMENTER_AVAILABLE=true - log_info "pr-commenter available from benchmarking-platform" - elif [ -f "${PLATFORM_DIR}/tools/pr-commenter.py" ]; then - # Try Python version - alias pr-commenter="python3 ${PLATFORM_DIR}/tools/pr-commenter.py" - PR_COMMENTER_AVAILABLE=true - log_info "pr-commenter.py available from benchmarking-platform" - else - log_warn "pr-commenter not found in benchmarking-platform" - ls -la "${PLATFORM_DIR}/tools/" 2>/dev/null || log_warn "No tools directory" - fi - else - log_warn "Failed to clone benchmarking-platform" - fi -else - log_warn "pr-commenter not found and not in CI - will print comment instead" -fi - -# Check required environment -if [ -z "${DDPROF_COMMIT_BRANCH:-}" ]; then - log_warn "DDPROF_COMMIT_BRANCH not set - skipping comment" - exit 0 -fi - -# Skip for main/master branches (no PR) -if [ "${DDPROF_COMMIT_BRANCH}" = "main" ] || [ "${DDPROF_COMMIT_BRANCH}" = "master" ]; then - log_info "Skipping PR comment for ${DDPROF_COMMIT_BRANCH} branch" - exit 0 -fi - -log_info "Posting comment for branch: ${DDPROF_COMMIT_BRANCH}" +log_info "Collecting results for branch: ${DDPROF_COMMIT_BRANCH:-}" # Collect test results log_info "Collecting test results from ${RESULTS_DIR}..." @@ -147,9 +105,12 @@ elif [ "${TOTAL_PASS}" -gt 0 ]; then STATUS_EMOJI=":white_check_mark:" STATUS_TEXT="PASSED" else - OVERALL_STATUS="neutral" - STATUS_EMOJI=":grey_question:" - STATUS_TEXT="NO RESULTS" + # No results at all usually means the test matrix never ran (setup/prerequisite + # failure) rather than a clean run — treat it as a failure so it isn't silently + # swallowed. + OVERALL_STATUS="failure" + STATUS_EMOJI=":rotating_light:" + STATUS_TEXT="COULD NOT RUN" fi log_info "Results: ${TOTAL_PASS} passed, ${TOTAL_FAIL} failed out of ${TOTAL} configurations" @@ -162,6 +123,15 @@ if [ "${OVERALL_STATUS}" = "success" ]; then COMMENT_BODY=":white_check_mark: **All ${TOTAL} integration tests passed** :bar_chart: [Dashboard](${DASHBOARD_URL}) · :construction_worker: [Pipeline](${CI_PIPELINE_URL:-}) · :package: \`${DDPROF_SHA:0:8}\`" +elif [ "${TOTAL}" -eq 0 ]; then + # No results produced at all - the test matrix itself never ran (e.g. a setup + # or prerequisite failure), not a clean pass/fail outcome. + COMMENT_BODY=":rotating_light: **Integration tests could not run — no results were produced** + +The test matrix in \`${RESULTS_DIR}\` is empty. This usually means a setup step +(prerequisite installation, JDK provisioning, etc.) failed before any test could run. + +:construction_worker: [Pipeline](${CI_PIPELINE_URL:-}) · :package: \`${DDPROF_SHA:0:8}\`" else # Some failures or unknowns - show full matrix COMMENT_BODY="${STATUS_EMOJI} **${TOTAL_PASS}** passed, **${TOTAL_FAIL}** failed out of **${TOTAL}** configurations @@ -205,27 +175,15 @@ $(echo -e "${FAILURES}")" - :package: Commit: \`${DDPROF_SHA}\`" fi -# Post comment using pr-commenter -if [ "${PR_COMMENTER_AVAILABLE}" = "true" ]; then - log_info "Posting comment via pr-commenter..." - - if echo "${COMMENT_BODY}" | pr-commenter \ - --for-repo="${REPO}" \ - --for-pr="${DDPROF_COMMIT_BRANCH}" \ - --header="Integration Tests" \ - --on-duplicate=replace; then - log_info "Successfully posted comment" - else - log_error "Failed to post comment via pr-commenter" - log_info "Comment that would be posted:" - echo "${COMMENT_BODY}" - exit 1 - fi -else - log_info "Comment that would be posted to PR:" - echo "" - echo "${COMMENT_BODY}" - echo "" +# Post comment via dd-octo-sts (upsert-github-pr-comment.sh handles missing +# branch/PR/token gracefully, so a comment-posting problem never masks the +# actual test outcome below). +BODY_FILE=$(mktemp) +trap 'rm -f "${BODY_FILE}"' EXIT +echo "${COMMENT_BODY}" > "${BODY_FILE}" +if ! "${HERE}/../scripts/upsert-github-pr-comment.sh" \ + "dd-trace-integration-results" "${DDPROF_COMMIT_BRANCH:-}" "${BODY_FILE}"; then + log_error "Failed to post PR comment (transport failure) — continuing; only the test outcome below determines pipeline status" fi # Exit with failure if tests failed (makes pipeline fail) diff --git a/.gitlab/dd-trace-integration/run-integration-test.sh b/.gitlab/dd-trace-integration/run-integration-test.sh index 555fd0dd6a..48b7d72308 100755 --- a/.gitlab/dd-trace-integration/run-integration-test.sh +++ b/.gitlab/dd-trace-integration/run-integration-test.sh @@ -103,7 +103,7 @@ collect_system_metrics() { local cpu_count=$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo "1") local cpu_quota=$(cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us 2>/dev/null || echo "-1") local cpu_period=$(cat /sys/fs/cgroup/cpu/cpu.cfs_period_us 2>/dev/null || echo "-1") - local load_avg=$(uptime | awk -F'load average:' '{print $2}' | xargs) + local load_avg=$(command -v uptime >/dev/null 2>&1 && uptime | awk -F'load average:' '{print $2}' | xargs || echo "unavailable") local container=$(test -f /.dockerenv && echo "true" || echo "false") # Parse throttling stats @@ -195,7 +195,7 @@ mkdir -p "${RESULTS_DIR}" log_info "Results directory: ${RESULTS_DIR}" log_info "Java version:" -"${JAVA_HOME}/bin/java" -version 2>&1 | head -3 +"${JAVA_HOME}/bin/java" -version 2>&1 | head -3 || true # ======================================== # Install Prerequisites @@ -220,7 +220,7 @@ if ! command -v jbang &> /dev/null; then exit 1 fi -log_info "jbang version: $(jbang version 2>&1 | head -1)" +log_info "jbang version: $(jbang version 2>&1 | head -1 || true)" # ======================================== # Artifact Collection on Exit diff --git a/test-validation/validate-jfr-conformance.sh b/test-validation/validate-jfr-conformance.sh index a9f11538c2..741732e427 100755 --- a/test-validation/validate-jfr-conformance.sh +++ b/test-validation/validate-jfr-conformance.sh @@ -286,7 +286,68 @@ log_info " jdk.ExecutionSample: ${UNEXPECTED_JDK_EXEC}" log_info " jdk.ObjectAllocationSample: ${UNEXPECTED_JDK_ALLOC}" log_info " datadog.ExecutionSample: ${UNEXPECTED_DD_EXEC}" log_info " datadog.ObjectSample: ${UNEXPECTED_DD_ALLOC}" -log_info " datadog.EndpointEvent: ${UNEXPECTED_ENDPOINT}" +log_info " datadog.Endpoint: ${UNEXPECTED_ENDPOINT}" +log_info "" + +# ======================================== +# Detect which event types are actually registered in the recording +# ======================================== +# jfr-shell's query engine hard-fails with "Event type not found" instead of +# returning a zero count when a type isn't in the recording's metadata at all +# (e.g. JDK built-in CPU/allocation events are entirely absent from +# ddprof-only recordings, or an event predates the JDK that produced the +# file). Precompute presence with jfr-shell's own `metadata` command (the +# jafar backend), which lists every registered event type unconditionally, so +# the .jfrs script can skip queries for types that were never registered +# instead of crashing. This must use the same jafar backend as the actual +# validation query below - the stock JDK `jfr summary`/`jfr print` tools +# misparse ddprof's multi-chunk recordings and silently report datadog.* +# event types as absent even when they are present with real events. + +# jfr-shell (jafar) requires Java 25 (class file version 69.0) +# Always use --java 25 to let jbang download the correct JDK +unset JAVA_VERSION # Prevent jbang from picking up test JDK version + +log_info "Using Java 25 for jbang (required by jfr-shell)" + +# jbang resolves jafar-shell from Maven Central, which CI runners can't reach directly. +# Route it through the same internal Maven proxy Gradle uses (MAVEN_REPOSITORY_PROXY). +JBANG_REPOS_OPT="" +if [ -n "${MAVEN_REPOSITORY_PROXY:-}" ]; then + JBANG_REPOS_OPT="--repos=\"central=${MAVEN_REPOSITORY_PROXY}\"" +fi + +EVENT_METADATA="" +if ! EVENT_METADATA=$(eval "jbang --java 25 ${JBANG_REPOS_OPT} jfr-shell@btraceio metadata \"${JFR_FILE}\"" 2>/dev/null); then + log_warn "Could not run 'jfr-shell metadata' on ${JFR_FILE}; assuming all event types are present" + EVENT_METADATA="" +fi + +has_event_type() { + local type="$1" + if [ -z "${EVENT_METADATA}" ]; then + echo "true" + elif echo "${EVENT_METADATA}" | grep -qE -- "- ${type//./\\.}\$"; then + echo "true" + else + echo "false" + fi +} + +HAS_JDK_EXEC=$(has_event_type "jdk.ExecutionSample") +HAS_DD_EXEC=$(has_event_type "datadog.ExecutionSample") +HAS_JDK_ALLOC=$(has_event_type "jdk.ObjectAllocationSample") +HAS_DD_ALLOC=$(has_event_type "datadog.ObjectSample") +HAS_JDK_THREAD_ALLOC=$(has_event_type "jdk.ThreadAllocationStatistics") +HAS_DD_ENDPOINT=$(has_event_type "datadog.Endpoint") + +log_info "Event types registered in recording:" +log_info " jdk.ExecutionSample: ${HAS_JDK_EXEC}" +log_info " datadog.ExecutionSample: ${HAS_DD_EXEC}" +log_info " jdk.ObjectAllocationSample: ${HAS_JDK_ALLOC}" +log_info " datadog.ObjectSample: ${HAS_DD_ALLOC}" +log_info " jdk.ThreadAllocationStatistics: ${HAS_JDK_THREAD_ALLOC}" +log_info " datadog.Endpoint: ${HAS_DD_ENDPOINT}" log_info "" # Prepare validation command @@ -305,21 +366,17 @@ log_info "" # Check if JFR validation should be skipped (JDK 25 unavailable) if [ -f /tmp/skip-jfr-validation ]; then SKIP_REASON=$(cat /tmp/skip-jfr-validation 2>/dev/null || echo "prerequisite unavailable") - log_warn "Skipping JFR validation: ${SKIP_REASON}" + log_error "Cannot run JFR validation: ${SKIP_REASON}" + # A skipped validation is not a pass — no conformance check actually ran, so + # this must not be reported as success further up the chain. if [ -n "${OUTPUT_FILE}" ]; then - echo "VALIDATION_SKIPPED: ${SKIP_REASON}" > "${OUTPUT_FILE}" + echo "VALIDATION_FAILED: Skipped - ${SKIP_REASON}" > "${OUTPUT_FILE}" fi - exit 0 + exit 1 fi -# jfr-shell (jafar) requires Java 25 (class file version 69.0) -# Always use --java 25 to let jbang download the correct JDK -unset JAVA_VERSION # Prevent jbang from picking up test JDK version - -log_info "Using Java 25 for jbang (required by jfr-shell)" - # Pass calculated thresholds directly (single source of truth - no duplicate logic in jfrs) -VALIDATION_CMD="jbang --java 25 jfr-shell@btraceio script \"${VALIDATION_SCRIPT}\" \"${JFR_FILE}\" \"${PROFILE}\" \"${MIN_EXECUTION_SAMPLES}\" \"${MIN_ALLOCATION_SAMPLES}\" \"${MIN_THREAD_COUNT}\" \"${EXPECTED_CPU_EVENT}\" \"${EXPECTED_ALLOC_EVENT}\" \"${CHECK_ENDPOINT}\" \"${UNEXPECTED_JDK_EXEC}\" \"${UNEXPECTED_JDK_ALLOC}\" \"${UNEXPECTED_DD_EXEC}\" \"${UNEXPECTED_DD_ALLOC}\" \"${UNEXPECTED_ENDPOINT}\"" +VALIDATION_CMD="jbang --java 25 ${JBANG_REPOS_OPT} jfr-shell@btraceio script \"${VALIDATION_SCRIPT}\" \"${JFR_FILE}\" \"${PROFILE}\" \"${MIN_EXECUTION_SAMPLES}\" \"${MIN_ALLOCATION_SAMPLES}\" \"${MIN_THREAD_COUNT}\" \"${EXPECTED_CPU_EVENT}\" \"${EXPECTED_ALLOC_EVENT}\" \"${CHECK_ENDPOINT}\" \"${UNEXPECTED_JDK_EXEC}\" \"${UNEXPECTED_JDK_ALLOC}\" \"${UNEXPECTED_DD_EXEC}\" \"${UNEXPECTED_DD_ALLOC}\" \"${UNEXPECTED_ENDPOINT}\" \"${HAS_JDK_EXEC}\" \"${HAS_DD_EXEC}\" \"${HAS_JDK_ALLOC}\" \"${HAS_DD_ALLOC}\" \"${HAS_JDK_THREAD_ALLOC}\" \"${HAS_DD_ENDPOINT}\"" if [ -n "${OUTPUT_FILE}" ]; then # Set up trap to write failure marker if script is killed/crashes diff --git a/test-validation/validate-jfr.jfrs b/test-validation/validate-jfr.jfrs index 087bcdc4b3..fdd48f1aa0 100755 --- a/test-validation/validate-jfr.jfrs +++ b/test-validation/validate-jfr.jfrs @@ -15,12 +15,26 @@ # $10 - Unexpected jdk.ObjectAllocationSample: true|false (optional, default false) # $11 - Unexpected datadog.ExecutionSample: true|false (optional, default false) # $12 - Unexpected datadog.ObjectSample: true|false (optional, default false) -# $13 - Unexpected datadog.EndpointEvent: true|false (optional, default false) +# $13 - Unexpected datadog.Endpoint: true|false (optional, default false) +# $14 - jdk.ExecutionSample registered in recording: true|false (optional, default true) +# $15 - datadog.ExecutionSample registered in recording: true|false (optional, default true) +# $16 - jdk.ObjectAllocationSample registered in recording: true|false (optional, default true) +# $17 - datadog.ObjectSample registered in recording: true|false (optional, default true) +# $18 - jdk.ThreadAllocationStatistics registered in recording: true|false (optional, default true) +# $19 - datadog.Endpoint registered in recording: true|false (optional, default true) # # Thresholds are calculated by the calling bash script (validate-jfr-conformance.sh) # based on platform, JVM type, and libc variant. This eliminates duplicate # threshold logic and provides a single source of truth. # +# The $14-$19 "registered in recording" flags are also calculated by the calling +# script (via `jfr summary`), because jfr-shell's query engine hard-fails with +# "Event type not found" instead of returning a zero count when a type isn't in +# the recording's metadata at all (e.g. JDK built-in events are entirely absent +# from ddprof-only recordings, or an event predates the JDK that produced the +# file). Every query against one of these six types is guarded by its flag so a +# missing type is treated as zero events instead of aborting the script. +# # The script prints SUCCESS or VALIDATION_FAILED at the end. # The calling script should check for "VALIDATION_FAILED" in output. @@ -37,6 +51,12 @@ set unexpected_jdk_alloc = "$10" set unexpected_dd_exec = "$11" set unexpected_dd_alloc = "$12" set unexpected_endpoint = "$13" +set has_jdk_exec = "$14" +set has_dd_exec = "$15" +set has_jdk_alloc = "$16" +set has_dd_alloc = "$17" +set has_jdk_thread_alloc = "$18" +set has_dd_endpoint = "$19" # Default min values if not provided (legacy compatibility) if "${min_execution_samples}" == "" @@ -85,6 +105,33 @@ if "${unexpected_endpoint}" == "" set unexpected_endpoint = "false" endif +# Default type-presence flags to true (legacy compatibility: assume present, +# matching the pre-existing unguarded-query behavior when the caller doesn't +# pass these) +if "${has_jdk_exec}" == "" + set has_jdk_exec = "true" +endif + +if "${has_dd_exec}" == "" + set has_dd_exec = "true" +endif + +if "${has_jdk_alloc}" == "" + set has_jdk_alloc = "true" +endif + +if "${has_dd_alloc}" == "" + set has_dd_alloc = "true" +endif + +if "${has_jdk_thread_alloc}" == "" + set has_jdk_thread_alloc = "true" +endif + +if "${has_dd_endpoint}" == "" + set has_dd_endpoint = "true" +endif + echo "=== JFR Validation ===" echo "Recording: $1" echo "Scenario: ${scenario}" @@ -114,9 +161,19 @@ set validation_failed = "false" # ======================================== echo "[1/8] Validating ExecutionSample events..." -# Check both jdk.ExecutionSample and datadog.ExecutionSample -set jdk_exec_count = events/jdk.ExecutionSample | count() -set dd_exec_count = events/datadog.ExecutionSample | count() +# Check both jdk.ExecutionSample and datadog.ExecutionSample (skip whichever +# isn't registered in this recording's metadata) +if "${has_jdk_exec}" == "true" + set jdk_exec_count = events/jdk.ExecutionSample | count() +else + set jdk_exec_count = 0 +endif + +if "${has_dd_exec}" == "true" + set dd_exec_count = events/datadog.ExecutionSample | count() +else + set dd_exec_count = 0 +endif echo " Found: ${jdk_exec_count.count} jdk.ExecutionSample events" echo " Found: ${dd_exec_count.count} datadog.ExecutionSample events" @@ -194,7 +251,11 @@ echo "[2/8] Validating stack traces..." # Use the event type we detected in step 1 # Validation: all present ExecutionSample events should have stack traces if "${exec_event_type}" == "jdk" - set samples_with_stack = events/jdk.ExecutionSample[exists(stackTrace)] | count() + if "${has_jdk_exec}" == "true" + set samples_with_stack = events/jdk.ExecutionSample[exists(stackTrace)] | count() + else + set samples_with_stack = 0 + endif echo " Samples with stack traces: ${samples_with_stack.count} of ${jdk_exec_count.count}" if ${samples_with_stack.count} < ${jdk_exec_count.count} @@ -205,7 +266,11 @@ if "${exec_event_type}" == "jdk" echo " ✓ All ExecutionSample events have stack traces (${samples_with_stack.count})" endif else - set samples_with_stack = events/datadog.ExecutionSample[exists(stackTrace)] | count() + if "${has_dd_exec}" == "true" + set samples_with_stack = events/datadog.ExecutionSample[exists(stackTrace)] | count() + else + set samples_with_stack = 0 + endif echo " Samples with stack traces: ${samples_with_stack.count} of ${dd_exec_count.count}" if ${samples_with_stack.count} < ${dd_exec_count.count} @@ -225,9 +290,17 @@ echo "[3/8] Validating thread diversity..." # Use the event type we detected in step 1 # Note: datadog.ExecutionSample uses eventThread, jdk.ExecutionSample uses sampledThread if "${exec_event_type}" == "jdk" - set unique_threads = events/jdk.ExecutionSample | groupBy(sampledThread/javaName) | count() + if "${has_jdk_exec}" == "true" + set unique_threads = events/jdk.ExecutionSample | groupBy(sampledThread/javaName) | count() + else + set unique_threads = 0 + endif else - set unique_threads = events/datadog.ExecutionSample | groupBy(eventThread/javaName) | count() + if "${has_dd_exec}" == "true" + set unique_threads = events/datadog.ExecutionSample | groupBy(eventThread/javaName) | count() + else + set unique_threads = 0 + endif endif echo " Unique threads sampled: ${unique_threads.count}" @@ -243,9 +316,17 @@ endif # Show top threads by sample count echo " Top threads by samples:" if "${exec_event_type}" == "jdk" - show events/jdk.ExecutionSample | groupBy(sampledThread/javaName) | top(5, by=count) + if "${has_jdk_exec}" == "true" + show events/jdk.ExecutionSample | groupBy(sampledThread/javaName) | top(5, by=count) + else + echo " (jdk.ExecutionSample not present in recording)" + endif else - show events/datadog.ExecutionSample | groupBy(eventThread/javaName) | top(5, by=count) + if "${has_dd_exec}" == "true" + show events/datadog.ExecutionSample | groupBy(eventThread/javaName) | top(5, by=count) + else + echo " (datadog.ExecutionSample not present in recording)" + endif endif # ======================================== @@ -254,9 +335,19 @@ endif echo "" echo "[4/8] Validating ObjectAllocationSample events..." -# Check both jdk.ObjectAllocationSample and datadog.ObjectSample -set jdk_alloc_count = events/jdk.ObjectAllocationSample | count() -set dd_alloc_count = events/datadog.ObjectSample | count() +# Check both jdk.ObjectAllocationSample and datadog.ObjectSample (skip +# whichever isn't registered in this recording's metadata) +if "${has_jdk_alloc}" == "true" + set jdk_alloc_count = events/jdk.ObjectAllocationSample | count() +else + set jdk_alloc_count = 0 +endif + +if "${has_dd_alloc}" == "true" + set dd_alloc_count = events/datadog.ObjectSample | count() +else + set dd_alloc_count = 0 +endif echo " Found: ${jdk_alloc_count.count} jdk.ObjectAllocationSample events" echo " Found: ${dd_alloc_count.count} datadog.ObjectSample events" @@ -282,7 +373,7 @@ if "${expected_alloc}" == "jdk" # Show top allocation sites if we have any if ${jdk_alloc_count.count} > 0 echo " Top allocation types:" - show events/jdk.ObjectAllocationSample | groupBy(objectClass/name) | top(5, by=sum(weight)) + show events/jdk.ObjectAllocationSample | groupBy(objectClass/name, sortBy=value) | top(5) endif elif "${expected_alloc}" == "datadog" @@ -305,7 +396,7 @@ elif "${expected_alloc}" == "datadog" # Show top allocation sites if we have any if ${dd_alloc_count.count} > 0 echo " Top allocation types:" - show events/datadog.ObjectSample | groupBy(objectClass/name) | top(5, by=sum(weight)) + show events/datadog.ObjectSample | groupBy(objectClass/name, sortBy=value) | top(5) endif else @@ -324,7 +415,7 @@ else # Show top allocation sites if we have any if ${jdk_alloc_count.count} > 0 echo " Top allocation types:" - show events/jdk.ObjectAllocationSample | groupBy(objectClass/name) | top(5, by=sum(weight)) + show events/jdk.ObjectAllocationSample | groupBy(objectClass/name, sortBy=value) | top(5) endif else echo " Total: ${dd_alloc_count.count} allocation events (using datadog.ObjectSample)" @@ -340,7 +431,7 @@ else # Show top allocation sites if we have any if ${dd_alloc_count.count} > 0 echo " Top allocation types:" - show events/datadog.ObjectSample | groupBy(objectClass/name) | top(5, by=sum(weight)) + show events/datadog.ObjectSample | groupBy(objectClass/name, sortBy=value) | top(5) endif endif endif @@ -351,7 +442,11 @@ endif echo "" echo "[5/8] Validating ThreadAllocationStatistics..." -set thread_alloc = events/jdk.ThreadAllocationStatistics | count() +if "${has_jdk_thread_alloc}" == "true" + set thread_alloc = events/jdk.ThreadAllocationStatistics | count() +else + set thread_alloc = 0 +endif echo " Found: ${thread_alloc.count} ThreadAllocationStatistics events" @@ -369,13 +464,18 @@ echo "" echo "[6/8] Validating endpoint events..." if "${check_endpoint}" == "true" - set endpoint_count = events/datadog.EndpointEvent | count() - echo " Found: ${endpoint_count.count} datadog.EndpointEvent events" + if "${has_dd_endpoint}" == "true" + set endpoint_count = events/datadog.Endpoint | count() + echo " Found: ${endpoint_count.count} datadog.Endpoint events" - if ${endpoint_count.count} > 0 - echo " ✓ Endpoint events present (tracer integration working)" + if ${endpoint_count.count} > 0 + echo " ✓ Endpoint events present (tracer integration working)" + else + echo " WARNING: No endpoint events found" + echo " This may indicate tracer is not capturing any requests" + endif else - echo " WARNING: No endpoint events found" + echo " WARNING: datadog.Endpoint event type not present in recording" echo " This may indicate tracer is not capturing any requests" endif else @@ -392,51 +492,61 @@ set has_unexpected = "false" # Check for unexpected jdk.ExecutionSample if "${unexpected_jdk_exec}" == "true" - set check_jdk_exec = events/jdk.ExecutionSample | count() - if ${check_jdk_exec.count} > 0 - echo " ERROR: Found unexpected jdk.ExecutionSample events (${check_jdk_exec.count})" - set validation_failed = "true" - set has_unexpected = "true" + if "${has_jdk_exec}" == "true" + set check_jdk_exec = events/jdk.ExecutionSample | count() + if ${check_jdk_exec.count} > 0 + echo " ERROR: Found unexpected jdk.ExecutionSample events (${check_jdk_exec.count})" + set validation_failed = "true" + set has_unexpected = "true" + endif endif endif # Check for unexpected jdk.ObjectAllocationSample if "${unexpected_jdk_alloc}" == "true" - set check_jdk_alloc = events/jdk.ObjectAllocationSample | count() - if ${check_jdk_alloc.count} > 0 - echo " ERROR: Found unexpected jdk.ObjectAllocationSample events (${check_jdk_alloc.count})" - set validation_failed = "true" - set has_unexpected = "true" + if "${has_jdk_alloc}" == "true" + set check_jdk_alloc = events/jdk.ObjectAllocationSample | count() + if ${check_jdk_alloc.count} > 0 + echo " ERROR: Found unexpected jdk.ObjectAllocationSample events (${check_jdk_alloc.count})" + set validation_failed = "true" + set has_unexpected = "true" + endif endif endif # Check for unexpected datadog.ExecutionSample if "${unexpected_dd_exec}" == "true" - set check_dd_exec = events/datadog.ExecutionSample | count() - if ${check_dd_exec.count} > 0 - echo " ERROR: Found unexpected datadog.ExecutionSample events (${check_dd_exec.count})" - set validation_failed = "true" - set has_unexpected = "true" + if "${has_dd_exec}" == "true" + set check_dd_exec = events/datadog.ExecutionSample | count() + if ${check_dd_exec.count} > 0 + echo " ERROR: Found unexpected datadog.ExecutionSample events (${check_dd_exec.count})" + set validation_failed = "true" + set has_unexpected = "true" + endif endif endif # Check for unexpected datadog.ObjectSample if "${unexpected_dd_alloc}" == "true" - set check_dd_alloc = events/datadog.ObjectSample | count() - if ${check_dd_alloc.count} > 0 - echo " ERROR: Found unexpected datadog.ObjectSample events (${check_dd_alloc.count})" - set validation_failed = "true" - set has_unexpected = "true" + if "${has_dd_alloc}" == "true" + set check_dd_alloc = events/datadog.ObjectSample | count() + if ${check_dd_alloc.count} > 0 + echo " ERROR: Found unexpected datadog.ObjectSample events (${check_dd_alloc.count})" + set validation_failed = "true" + set has_unexpected = "true" + endif endif endif -# Check for unexpected datadog.EndpointEvent +# Check for unexpected datadog.Endpoint if "${unexpected_endpoint}" == "true" - set check_endpoint_evt = events/datadog.EndpointEvent | count() - if ${check_endpoint_evt.count} > 0 - echo " ERROR: Found unexpected datadog.EndpointEvent events (${check_endpoint_evt.count})" - set validation_failed = "true" - set has_unexpected = "true" + if "${has_dd_endpoint}" == "true" + set check_endpoint_evt = events/datadog.Endpoint | count() + if ${check_endpoint_evt.count} > 0 + echo " ERROR: Found unexpected datadog.Endpoint events (${check_endpoint_evt.count})" + set validation_failed = "true" + set has_unexpected = "true" + endif endif endif diff --git a/utils/run-chaos-harness.sh b/utils/run-chaos-harness.sh index 742d3f36a7..b1a62139c7 100755 --- a/utils/run-chaos-harness.sh +++ b/utils/run-chaos-harness.sh @@ -47,7 +47,7 @@ echo "Chaos run: runtime=${RUNTIME}s config=${CONFIG} allocator=${ALLOCATOR}" # doesn't match what's already active; otherwise just use `java` as found. if [ -n "${CHAOS_JDK:-}" ]; then JDK_MAJOR="${CHAOS_JDK%%.*}" - ACTIVE_MAJOR=$(java -version 2>&1 | head -1 | grep -oE '"[0-9]+' | tr -d '"') + ACTIVE_MAJOR=$(java -version 2>&1 | grep -m1 'version "' | grep -oE '"[0-9]+' | tr -d '"') if [ "${ACTIVE_MAJOR}" != "${JDK_MAJOR}" ]; then JDK_ARCH=$(uname -m | sed 's/x86_64/x64/') JDK_INSTALL_DIR="${CHAOS_JDK_DIR:-${WORK_DIR}/jdk-${CHAOS_JDK}}" @@ -73,7 +73,7 @@ if ! command -v java >/dev/null 2>&1; then echo "FAIL:no java on PATH (set CHAOS_JDK to have one downloaded)" >&2 exit 1 fi -echo "Using: $(java -version 2>&1 | head -1)" +echo "Using: $(java -version 2>&1 | grep -m1 'version "')" # --- ddprof jar -------------------------------------------------------------- # Prefer a local build artifact. If absent and CURRENT_VERSION is set (CI