diff --git a/.github/actions/generate-build-matrix/generate_matrix.py b/.github/actions/generate-build-matrix/generate_matrix.py index eb44d4cc..4b8db62a 100644 --- a/.github/actions/generate-build-matrix/generate_matrix.py +++ b/.github/actions/generate-build-matrix/generate_matrix.py @@ -7,10 +7,16 @@ def get_default_combinations(event_name, all_combinations): """Gets the default build combinations based on the GitHub event type.""" - if event_name in ("push", "pull_request", "pull_request_target", "workflow_dispatch"): + if event_name in ( + "push", + "pull_request", + "pull_request_target", + "issue_comment", + "workflow_dispatch", + ): return ["gcc/none"] - elif event_name == "issue_comment": - return ["gcc/none", "clang/none"] + elif event_name == "schedule": + return ["gcc/perfetto"] else: # Default to a minimal safe configuration for unknown events return ["gcc/none"] @@ -23,10 +29,12 @@ def main(): "gcc/asan", "gcc/tsan", "gcc/valgrind", + "gcc/perfetto", "clang/none", "clang/asan", "clang/tsan", "clang/valgrind", + "clang/perfetto", ] user_input = os.getenv("USER_INPUT", "") comment_body = os.getenv("COMMENT_BODY", "") diff --git a/.github/workflows/cmake-build.yaml b/.github/workflows/cmake-build.yaml index d68ea35e..4acfeb73 100644 --- a/.github/workflows/cmake-build.yaml +++ b/.github/workflows/cmake-build.yaml @@ -7,6 +7,8 @@ run-name: "${{ github.actor }} building and testing ${{ github.repository }}" types: [created] push: branches: [main, develop] + schedule: + - cron: "7 18 * * 6" workflow_dispatch: inputs: ref: @@ -24,6 +26,16 @@ run-name: "${{ github.actor }} building and testing ${{ github.repository }}" Default (if empty): Run `gcc/none` required: false default: "" + perfetto-heap-profile: + description: "Enable heap profiling for Perfetto runs" + required: false + type: boolean + default: false + perfetto-cpu-profile: + description: "Enable CPU profiling for Perfetto runs" + required: false + type: boolean + default: true workflow_call: inputs: checkout-path: @@ -72,7 +84,7 @@ jobs: setup: if: > github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request' || github.event_name == 'push' || - github.event_name == 'workflow_call' || ( + github.event_name == 'schedule' || github.event_name == 'workflow_call' || ( github.event_name == 'issue_comment' && github.event.issue.pull_request && contains(fromJSON('["OWNER", "COLLABORATOR", "MEMBER"]'), github.event.comment.author_association) && @@ -140,6 +152,7 @@ jobs: container: image: ghcr.io/framework-r-d/phlex-ci:latest + options: --cap-add=SYS_PTRACE steps: - name: Check out code @@ -175,6 +188,7 @@ jobs: extra-options: | ${{ matrix.sanitizer == 'asan' && format('-D{0}_ENABLE_ASAN=ON', steps.repo_name.outputs.name) || '' }} ${{ matrix.sanitizer == 'tsan' && format('-D{0}_ENABLE_TSAN=ON', steps.repo_name.outputs.name) || '' }} + ${{ matrix.sanitizer == 'perfetto' && format('-D{0}_ENABLE_PERFETTO=ON', steps.repo_name.outputs.name) || '' }} - name: Build id: build @@ -183,7 +197,7 @@ jobs: build-path: ${{ needs.setup.outputs.build_path }} - name: Run tests - if: matrix.sanitizer != 'valgrind' + if: matrix.sanitizer != 'valgrind' && matrix.sanitizer != 'perfetto' working-directory: ${{ needs.setup.outputs.build_path }} run: | . /entrypoint.sh @@ -215,12 +229,67 @@ jobs: echo "⚠️ Valgrind tests failed, but the workflow will continue." fi + - name: Run Perfetto profiling + if: matrix.sanitizer == 'perfetto' + working-directory: ${{ needs.setup.outputs.build_path }} + env: + PERFETTO_HEAP_PROFILE: ${{ github.event.inputs.perfetto-heap-profile || 'false' }} + PERFETTO_CPU_PROFILE: ${{ github.event.inputs.perfetto-cpu-profile || true }} + run: | + . /entrypoint.sh + + echo "➡️ Running tests with Perfetto profiling..." + + # Set perf_event_paranoid for CPU profiling + if [ "$PERFETTO_CPU_PROFILE" = "true" ]; then + echo "Configuring perf_event_paranoid for CPU profiling" + echo -1 | tee /proc/sys/kernel/perf_event_paranoid 2>/dev/null || echo "Warning: Could not set perf_event_paranoid" + fi + + # Configure profiling based on environment + TRACEBOX_ARGS="" + if [ "$PERFETTO_HEAP_PROFILE" = "true" ]; then + echo "Enabling heap profiling" + TRACEBOX_ARGS="$TRACEBOX_ARGS --app '*' --heapprofd" + fi + if [ "$PERFETTO_CPU_PROFILE" = "true" ]; then + echo "Enabling CPU profiling" + TRACEBOX_ARGS="$TRACEBOX_ARGS --cpu-freq --cpu-idle --cpu-sched" + fi + + # Run tests with or without tracebox wrapper + TEST_RESULT=0 + if [ -n "$TRACEBOX_ARGS" ]; then + echo "::group::Running ctest with tracebox" + tracebox $TRACEBOX_ARGS -o "perfetto-trace.pftrace" -- ctest --progress --output-on-failure -j "$(nproc)" || TEST_RESULT=$? + else + echo "::group::Running ctest with Perfetto SDK tracing" + ctest --progress --output-on-failure -j "$(nproc)" || TEST_RESULT=$? + fi + + echo "::endgroup::" + if [ "${TEST_RESULT:-0}" -eq 0 ]; then + echo "✅ Perfetto profiling completed." + else + echo "::error:: Perfetto profiling failed." + exit 1 + fi + + - name: Upload Perfetto traces + if: matrix.sanitizer == 'perfetto' + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + with: + name: perfetto-traces-${{ matrix.compiler }} + path: | + ${{ needs.setup.outputs.build_path }}/**/*.pftrace + retention-days: 30 + if-no-files-found: warn + cmake-build-skipped: needs: [setup] if: > - needs.setup.result == 'success' && github.event_name != 'workflow_dispatch' && - !inputs.skip-relevance-check && needs.setup.outputs.is_act != 'true' && - needs.setup.outputs.has_changes != 'true' + needs.setup.result == 'success' && github.event_name != 'workflow_dispatch' && !inputs.skip-relevance-check && + needs.setup.outputs.is_act != 'true' && needs.setup.outputs.has_changes != 'true' runs-on: ubuntu-latest permissions: contents: read diff --git a/.gitignore b/.gitignore index 931fc492..23bce779 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ /_deps/ /build-*/ /build/ +/out/ /phlex-build/ /phlex-src/ CMakeFiles/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 6f0a53b9..998d9a80 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -94,6 +94,14 @@ include(Modules/private/CreateCoverageTargets.cmake) option(ENABLE_TSAN "Enable Thread Sanitizer" OFF) option(ENABLE_ASAN "Enable Address Sanitizer" OFF) +include(CMakeDependentOption) +cmake_dependent_option( + ENABLE_PERFETTO + "Enable Perfetto profiling" + OFF + "CMAKE_SYSTEM_NAME STREQUAL Linux" + OFF +) option(PHLEX_USE_FORM "Enable experimental integration with FORM" OFF) option(ENABLE_COVERAGE "Enable code coverage instrumentation" OFF) option(ENABLE_BUILD_PROFILING "Enable monitoring of compile and link operations" OFF) @@ -196,6 +204,12 @@ if(ENABLE_ASAN) endif() endif() +# Configure Perfetto profiling if enabled +if(ENABLE_PERFETTO) + message(STATUS "Enabling Perfetto profiling") + find_package(Perfetto REQUIRED) +endif() + # Configure code coverage if enabled if(ENABLE_COVERAGE) # Check if the compiler supports code coverage diff --git a/Modules/FindPerfetto.cmake b/Modules/FindPerfetto.cmake new file mode 100644 index 00000000..0b2d4dae --- /dev/null +++ b/Modules/FindPerfetto.cmake @@ -0,0 +1,31 @@ +# FindPerfetto.cmake +# Finds the Perfetto SDK (single-header implementation) + +include(FindPackageHandleStandardArgs) + +find_path( + Perfetto_INCLUDE_DIR + NAMES perfetto.h + PATHS /opt/perfetto /usr/local/include /usr/include + DOC "Perfetto SDK header location" +) + +find_file( + Perfetto_SOURCE + NAMES perfetto.cc + PATHS /opt/perfetto /usr/local/include /usr/include + DOC "Perfetto SDK implementation file" +) + +find_package_handle_standard_args(Perfetto REQUIRED_VARS Perfetto_INCLUDE_DIR Perfetto_SOURCE) + +if(Perfetto_FOUND AND NOT TARGET Perfetto::Perfetto) + find_package(Threads REQUIRED) + add_library(Perfetto_impl STATIC "${Perfetto_SOURCE}") + target_include_directories(Perfetto_impl PUBLIC "${Perfetto_INCLUDE_DIR}") + target_compile_definitions(Perfetto_impl PUBLIC PERFETTO_ENABLE_TRACING=1) + target_link_libraries(Perfetto_impl PUBLIC Threads::Threads) + add_library(Perfetto::Perfetto ALIAS Perfetto_impl) +endif() + +mark_as_advanced(Perfetto_INCLUDE_DIR Perfetto_SOURCE) diff --git a/ci/Dockerfile b/ci/Dockerfile index 3b6087e7..e94e6725 100644 --- a/ci/Dockerfile +++ b/ci/Dockerfile @@ -280,6 +280,36 @@ set -euo pipefail rm -f /tmp/spack.yaml CLEAN_TEMP_FILES +######################################################################## +# Install Perfetto SDK for profiling + +RUN <<'INSTALL_PERFETTO' +set -euo pipefail + +# Install Perfetto SDK and tools +apt-get update +apt-get install -y --no-install-recommends \ + wget +apt-get clean +rm -rf /var/lib/apt/lists/* + +mkdir -p /opt/perfetto +cd /opt/perfetto +PERFETTO_VERSION=v51.0 +wget -O perfetto.h https://raw.githubusercontent.com/google/perfetto/${PERFETTO_VERSION}/sdk/perfetto.h +wget -O perfetto.cc https://raw.githubusercontent.com/google/perfetto/${PERFETTO_VERSION}/sdk/perfetto.cc +chmod 644 perfetto.h perfetto.cc + +# Install tracebox for system-wide profiling. +# Note: get.perfetto.dev/tracebox serves the latest binary with no versioned +# or checksum-verifiable download currently offered by the Perfetto project. +# Revisit when the project provides versioned tracebox releases with integrity +# metadata. +wget -O tracebox https://get.perfetto.dev/tracebox +chmod +x tracebox +mv tracebox /usr/local/bin/ +INSTALL_PERFETTO + ######################################################################## # Finalize CI image stage diff --git a/phlex/app/CMakeLists.txt b/phlex/app/CMakeLists.txt index 3c0a57af..3f1fd76c 100644 --- a/phlex/app/CMakeLists.txt +++ b/phlex/app/CMakeLists.txt @@ -33,4 +33,8 @@ cet_make_exec( jsonnet::lib ) +if(ENABLE_PERFETTO) + target_link_libraries(phlex PRIVATE Perfetto::Perfetto) +endif() + set_target_properties(phlex PROPERTIES INSTALL_RPATH "$ORIGIN/../lib")