This is an automated email from the ASF dual-hosted git repository.

acassis pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git


The following commit(s) were added to refs/heads/master by this push:
     new b941a715ea3 ci/testing: Add MemBrowse Integration
b941a715ea3 is described below

commit b941a715ea392d1764f87e0e3c391f33c4250126
Author: Michael Rogov Papernov <[email protected]>
AuthorDate: Sun Apr 12 14:20:56 2026 +0100

    ci/testing: Add MemBrowse Integration
    
    Tracking memory footprint with MemBrowse
    
    Signed-off-by: Michael Rogov Papernov / [email protected]
---
 .github/membrowse-targets.json          |  83 ++++++++++++
 .github/workflows/membrowse-comment.yml |  42 ++++++
 .github/workflows/membrowse-onboard.yml |  98 ++++++++++++++
 .github/workflows/membrowse-report.yml  | 232 ++++++++++++++++++++++++++++++++
 README.md                               |   1 +
 5 files changed, 456 insertions(+)

diff --git a/.github/membrowse-targets.json b/.github/membrowse-targets.json
new file mode 100644
index 00000000000..f0a227f4dbb
--- /dev/null
+++ b/.github/membrowse-targets.json
@@ -0,0 +1,83 @@
+[
+  {
+    "target_name": "stm32-nucleo-f103rb",
+    "board_config": "nucleo-f103rb:nsh",
+    "elf": "nuttx",
+    "ld": "boards/arm/stm32/nucleo-f103rb/scripts/ld.script",
+    "map_file": "nuttx.map",
+    "linker_vars": "",
+    "config_overrides": ""
+  },
+  {
+    "target_name": "arduino-mega2560",
+    "board_config": "arduino-mega2560:nsh",
+    "elf": "nuttx.elf",
+    "ld": "boards/avr/atmega/arduino-mega2560/scripts/flash.ld",
+    "map_file": "nuttx.map",
+    "linker_vars": "",
+    "config_overrides": ""
+  },
+  {
+    "target_name": "qemu-armv8a",
+    "board_config": "qemu-armv8a:nsh",
+    "elf": "nuttx",
+    "ld": "",
+    "map_file": "nuttx.map",
+    "linker_vars": "",
+    "config_overrides": ""
+  },
+  {
+    "target_name": "mirtoo",
+    "board_config": "mirtoo:nsh",
+    "elf": "nuttx",
+    "ld": "boards/mips/pic32mx/mirtoo/scripts/pinguino-debug.ld",
+    "map_file": "nuttx.map",
+    "linker_vars": "",
+    "config_overrides": "-d MIPS32_TOOLCHAIN_GNU_ELF -e 
MIPS32_TOOLCHAIN_PINGUINOL"
+  },
+  {
+    "target_name": "hifive1-revb",
+    "board_config": "hifive1-revb:nsh",
+    "elf": "nuttx",
+    "ld": "boards/risc-v/fe310/hifive1-revb/scripts/ld.script",
+    "map_file": "nuttx.map",
+    "linker_vars": "",
+    "config_overrides": ""
+  },
+  {
+    "target_name": "rx65n-rsk2mb",
+    "board_config": "rx65n-rsk2mb:nsh",
+    "elf": "nuttx",
+    "ld": "boards/renesas/rx65n/rx65n-rsk2mb/scripts/linker_script.ld",
+    "map_file": "",
+    "linker_vars": "",
+    "config_overrides": ""
+  },
+  {
+    "target_name": "s698pm-dkit",
+    "board_config": "s698pm-dkit:nsh",
+    "elf": "nuttx",
+    "ld": "",
+    "map_file": "",
+    "linker_vars": "",
+    "config_overrides": ""
+  },
+  {
+    "target_name": "qemu-intel64",
+    "board_config": "qemu-intel64:nsh",
+    "elf": "nuttx",
+    "ld": "",
+    "map_file": "nuttx.map",
+    "linker_vars": "",
+    "config_overrides": ""
+  },
+  {
+    "target_name": "esp32-devkitc",
+    "board_config": "esp32-devkitc:nsh",
+    "elf": "nuttx",
+    "ld": "boards/xtensa/esp32/common/scripts/flat_memory.ld.tmp 
boards/xtensa/esp32/common/scripts/esp32_sections.ld.tmp",
+    "map_file": "nuttx.map",
+    "linker_vars": "",
+    "config_overrides": ""
+  }
+]
diff --git a/.github/workflows/membrowse-comment.yml 
b/.github/workflows/membrowse-comment.yml
new file mode 100644
index 00000000000..05a578a533c
--- /dev/null
+++ b/.github/workflows/membrowse-comment.yml
@@ -0,0 +1,42 @@
+name: MemBrowse PR Comment
+
+on:
+  workflow_run:
+    workflows: [MemBrowse Memory Report]
+    types:
+      - completed
+
+jobs:
+  comment:
+    runs-on: ubuntu-latest
+    if: >
+      github.event.workflow_run.event == 'pull_request' &&
+      github.event.workflow_run.conclusion != 'cancelled'
+    permissions:
+      contents: read
+      pull-requests: write
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v5
+
+      - uses: actions/setup-python@v6
+        with:
+          python-version: '3.11'
+
+      - name: Install membrowse
+        run: pip install --no-cache-dir membrowse
+
+      - name: Post combined PR comment
+        if: ${{ env.MEMBROWSE_API_KEY != '' }}
+        env:
+          MEMBROWSE_API_KEY: ${{ secrets.MEMBROWSE_API_KEY }}
+          MEMBROWSE_API_URL: ${{ vars.MEMBROWSE_API_URL }}
+          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
+        run: |
+          SUMMARY_ARGS=("$HEAD_SHA" --api-key "$MEMBROWSE_API_KEY" --json)
+          if [ -n "$MEMBROWSE_API_URL" ]; then
+            SUMMARY_ARGS+=(--api-url "$MEMBROWSE_API_URL")
+          fi
+          membrowse summary "${SUMMARY_ARGS[@]}" > /tmp/membrowse-summary.json
+          python -m membrowse.utils.github_comment --summary-json 
/tmp/membrowse-summary.json
diff --git a/.github/workflows/membrowse-onboard.yml 
b/.github/workflows/membrowse-onboard.yml
new file mode 100644
index 00000000000..3ae7af7c421
--- /dev/null
+++ b/.github/workflows/membrowse-onboard.yml
@@ -0,0 +1,98 @@
+name: Onboard to MemBrowse
+
+on:
+  workflow_dispatch:
+    inputs:
+      num_commits:
+        description: 'Number of commits to process'
+        required: true
+        default: '100'
+        type: string
+
+jobs:
+  load-targets:
+    runs-on: ubuntu-latest
+    outputs:
+      matrix: ${{ steps.set-matrix.outputs.matrix }}
+    steps:
+      - uses: actions/checkout@v6
+      - id: set-matrix
+        run: echo "matrix=$(jq -c '.' .github/membrowse-targets.json)" >> 
$GITHUB_OUTPUT
+
+  onboard:
+    needs: load-targets
+    runs-on: ubuntu-latest
+    container:
+      image: ghcr.io/apache/nuttx/apache-nuttx-ci-linux
+      credentials:
+        username: ${{ github.actor }}
+        password: ${{ secrets.GITHUB_TOKEN }}
+    strategy:
+      fail-fast: false
+      matrix:
+        include: ${{ fromJson(needs.load-targets.outputs.matrix) }}
+
+    steps:
+      - name: Checkout nuttx
+        uses: actions/checkout@v6
+        with:
+          fetch-depth: 0
+
+      - name: Clone nuttx-apps
+        run: |
+          git config --global --add safe.directory '*'
+          git clone --depth=1 https://github.com/apache/nuttx-apps.git ../apps
+
+      - name: Install membrowse
+        run: python3 -m pip install --no-cache-dir membrowse
+
+      - name: Fetch full git history
+        run: |
+          git fetch --unshallow || true
+          git fetch --all
+
+      - name: Run MemBrowse Onboard
+        env:
+          MEMBROWSE_API_KEY: ${{ secrets.MEMBROWSE_API_KEY }}
+          MEMBROWSE_API_URL: ${{ vars.MEMBROWSE_API_URL }}
+          NUM_COMMITS: ${{ github.event.inputs.num_commits }}
+          TARGET_NAME: ${{ matrix.target_name }}
+          ELF: ${{ matrix.elf }}
+          LD: ${{ matrix.ld }}
+          MAP_FILE: ${{ matrix.map_file }}
+          LINKER_VARS: ${{ matrix.linker_vars }}
+        run: |
+          BUILD_SCRIPT=$(cat <<'BUILD_EOF'
+          ./tools/configure.sh -l ${{ matrix.board_config }}
+          echo CONFIG_DEBUG_SYMBOLS=y >> .config
+          echo CONFIG_DEBUG_LINK_MAP=y >> .config
+          if [ -n "${{ matrix.config_overrides }}" ]; then
+            kconfig-tweak ${{ matrix.config_overrides }}
+          fi
+          make olddefconfig
+          find arch -name Makefile -exec sed -i '/DELFILE, $(addsuffix 
.tmp,$(ARCHSCRIPT))/d' {} +
+          trap 'git checkout -- arch/' EXIT
+          make -j$(nproc)
+          BUILD_EOF
+          )
+
+          ARGS=("$NUM_COMMITS" "$BUILD_SCRIPT" "$ELF" "$TARGET_NAME" 
"$MEMBROWSE_API_KEY")
+          if [ -n "$MEMBROWSE_API_URL" ]; then
+            ARGS+=(--api-url "$MEMBROWSE_API_URL")
+          fi
+          if [ -n "$LD" ]; then
+            ARGS+=(--ld-scripts "$LD")
+          fi
+          if [ -n "$LINKER_VARS" ]; then
+            set -f
+            for var in $LINKER_VARS; do
+              ARGS+=(--def "$var")
+            done
+            set +f
+          fi
+          if [ -n "$MAP_FILE" ]; then
+            ARGS+=(--map-file "$MAP_FILE")
+          fi
+          ARGS+=(--binary-search)
+
+          membrowse onboard "${ARGS[@]}"
diff --git a/.github/workflows/membrowse-report.yml 
b/.github/workflows/membrowse-report.yml
new file mode 100644
index 00000000000..04e61f57541
--- /dev/null
+++ b/.github/workflows/membrowse-report.yml
@@ -0,0 +1,232 @@
+name: MemBrowse Memory Report
+
+on:
+  pull_request:
+  push:
+    branches:
+      - master
+      - "releases/*"
+
+permissions:
+  contents: read
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.event.pull_request.number || 
github.ref }}
+  cancel-in-progress: ${{ github.event_name == 'pull_request' }}
+
+jobs:
+  # Detect whether any source files (i.e. non-doc/CODEOWNERS) changed.
+  # When only docs/CODEOWNERS change we skip the build and post an "identical"
+  # MemBrowse report instead.
+  changes-filter:
+    runs-on: ubuntu-latest
+    outputs:
+      source: ${{ steps.filter.outputs.source }}
+    steps:
+      # Shallow checkout; the base commit needed for the diff is fetched
+      # explicitly below instead of cloning the repo's full history.
+      - uses: actions/checkout@v6
+        with:
+          fetch-depth: 2
+      # Detect whether any non-doc/CODEOWNERS source files changed.
+      # Implemented with plain git instead of a third-party paths-filter
+      # action, since Apache's GitHub Actions allowlist forbids actions that
+      # aren't GitHub-created or explicitly permitted.
+      - id: filter
+        env:
+          BASE_SHA: ${{ github.event_name == 'pull_request' && 
github.event.pull_request.base.sha || github.event.before }}
+          HEAD_SHA: ${{ github.event_name == 'pull_request' && 
github.event.pull_request.head.sha || github.sha }}
+        run: |
+          # On the very first push to a branch, github.event.before is 
all-zeros.
+          if [ -z "$BASE_SHA" ] || [ "$BASE_SHA" = 
"0000000000000000000000000000000000000000" ]; then
+            echo "source=true" >> "$GITHUB_OUTPUT"
+            echo "No usable base SHA; treating as source change."
+            exit 0
+          fi
+          # The shallow checkout may not contain BASE_SHA (multi-commit pushes,
+          # long-lived PR branches). Fetch just that commit; fall back to a 
full
+          # unshallow only if the targeted fetch fails.
+          if ! git cat-file -e "$BASE_SHA^{commit}" 2>/dev/null; then
+            git fetch --depth=1 origin "$BASE_SHA" 2>/dev/null \
+              || git fetch --unshallow origin 2>/dev/null \
+              || git fetch --unshallow 2>/dev/null || true
+          fi
+          changed=$(git diff --name-only "$BASE_SHA" "$HEAD_SHA")
+          echo "Changed files:"
+          echo "$changed"
+          # Drop paths that should NOT count as source changes; if anything
+          # survives, real source changed.
+          source=$(echo "$changed" | grep -vE \
+            -e '^AUTHORS$' \
+            -e '^CONTRIBUTING\.md$' \
+            -e '(^|/)CODEOWNERS$' \
+            -e '^Documentation/' \
+            -e '^tools/ci/docker/linux/' \
+            -e '^tools/codeowners/[^/]+$' \
+            || true)
+          if [ -n "$source" ]; then
+            echo "source=true" >> "$GITHUB_OUTPUT"
+          else
+            echo "source=false" >> "$GITHUB_OUTPUT"
+          fi
+
+  load-targets:
+    runs-on: ubuntu-latest
+    outputs:
+      matrix: ${{ steps.set-matrix.outputs.matrix }}
+    steps:
+      - uses: actions/checkout@v6
+      - id: set-matrix
+        run: echo "matrix=$(jq -c '.' .github/membrowse-targets.json)" >> 
$GITHUB_OUTPUT
+
+  # Post an "identical" MemBrowse report when only docs/CODEOWNERS changed.
+  # Only runs on push events (master/releases/tags) to keep the baseline
+  # complete; PR events don't produce identical markers.
+  identical:
+    needs: [changes-filter, load-targets]
+    if: needs.changes-filter.outputs.source == 'false' && github.event_name == 
'push'
+    runs-on: ubuntu-latest
+    strategy:
+      fail-fast: false
+      matrix:
+        include: ${{ fromJson(needs.load-targets.outputs.matrix) }}
+    steps:
+      - uses: actions/checkout@v6
+        with:
+          fetch-depth: 2
+      - uses: actions/setup-python@v6
+        with:
+          python-version: '3.11'
+      - name: Install membrowse
+        run: pip install --no-cache-dir membrowse
+      - name: Upload identical - ${{ matrix.target_name }}
+        env:
+          MEMBROWSE_API_KEY: ${{ secrets.MEMBROWSE_API_KEY }}
+          MEMBROWSE_API_URL: ${{ vars.MEMBROWSE_API_URL }}
+          TARGET_NAME: ${{ matrix.target_name }}
+        run: |
+          ARGS=(--upload --github
+                --target-name "$TARGET_NAME"
+                --api-key "$MEMBROWSE_API_KEY"
+                --identical)
+          if [ -n "$MEMBROWSE_API_URL" ]; then
+            ARGS+=(--api-url "$MEMBROWSE_API_URL")
+          fi
+          membrowse report "${ARGS[@]}"
+
+  # Build target with debug symbols + linker map, then upload MemBrowse report.
+  # Uses the NuttX CI Docker image so toolchains, kconfig-frontends, etc. are
+  # already installed.
+  analyze:
+    needs: [changes-filter, load-targets]
+    if: needs.changes-filter.outputs.source == 'true'
+    runs-on: ubuntu-latest
+    env:
+      DOCKER_BUILDKIT: 1
+    strategy:
+      fail-fast: false
+      matrix:
+        include: ${{ fromJson(needs.load-targets.outputs.matrix) }}
+    steps:
+      - name: Checkout nuttx repo
+        uses: actions/checkout@v6
+        with:
+          path: sources/nuttx
+          fetch-depth: 2
+
+      - name: Checkout nuttx-apps repo
+        uses: actions/checkout@v6
+        with:
+          repository: apache/nuttx-apps
+          path: sources/apps
+          fetch-depth: 1
+
+      - name: Docker Login
+        uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2  # 
v4.0.0
+        with:
+          registry: ghcr.io
+          username: ${{ github.actor }}
+          password: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: Docker Pull
+        run: docker pull ghcr.io/apache/nuttx/apache-nuttx-ci-linux
+
+      - name: Build ${{ matrix.target_name }}
+        uses: ./sources/nuttx/.github/actions/ci-container
+        with:
+          run: |
+            git config --global --add safe.directory 
/github/workspace/sources/nuttx
+            git config --global --add safe.directory 
/github/workspace/sources/apps
+            cd /github/workspace/sources/nuttx
+            ./tools/configure.sh -l ${{ matrix.board_config }}
+            echo CONFIG_DEBUG_SYMBOLS=y >> .config
+            echo CONFIG_DEBUG_LINK_MAP=y >> .config
+            if [ -n "${{ matrix.config_overrides }}" ]; then
+              kconfig-tweak ${{ matrix.config_overrides }}
+            fi
+            make olddefconfig
+            # Preserve preprocessed linker scripts (.ld.tmp) so MemBrowse can
+            # read them. Arch Makefiles delete these immediately after linking.
+            find arch -name Makefile -exec sed -i '/DELFILE, $(addsuffix 
.tmp,$(ARCHSCRIPT))/d' {} +
+            make -j$(nproc)
+
+      # MemBrowse action runs from $GITHUB_WORKSPACE and discovers git via CWD,
+      # but nuttx is checked out to sources/nuttx/ (so apps can sit beside it).
+      # Expose the repo's .git at the workspace root so git metadata resolves.
+      - name: Expose nuttx .git to workspace root
+        run: ln -sfn sources/nuttx/.git .git
+
+      - name: Prefix linker script paths
+        id: ld
+        run: |
+          prefixed=""
+          for p in ${{ matrix.ld }}; do
+            prefixed="$prefixed sources/nuttx/$p"
+          done
+          paths=$(echo $prefixed | xargs)
+          echo "paths=$paths" >> "$GITHUB_OUTPUT"
+          echo "Resolved ld paths: [$paths]"
+          for p in $paths; do
+            if [ -e "$p" ]; then
+              echo "  OK   $p ($(stat -c '%s bytes, owner=%U:%G' "$p" 
2>/dev/null))"
+            else
+              echo "  MISS $p"
+              echo "  ls dir:"
+              ls -la "$(dirname "$p")" 2>&1 | head -30
+            fi
+          done
+
+      - uses: actions/setup-python@v6
+        with:
+          python-version: '3.11'
+      - name: Install membrowse
+        run: pip install --no-cache-dir membrowse
+      - name: MemBrowse analysis - ${{ matrix.target_name }}
+        env:
+          MEMBROWSE_API_KEY: ${{ secrets.MEMBROWSE_API_KEY }}
+          MEMBROWSE_API_URL: ${{ vars.MEMBROWSE_API_URL }}
+          TARGET_NAME: ${{ matrix.target_name }}
+          ELF: sources/nuttx/${{ matrix.elf }}
+          LD_PATHS: ${{ steps.ld.outputs.paths }}
+          MAP_FILE: ${{ matrix.map_file != '' && format('sources/nuttx/{0}', 
matrix.map_file) || '' }}
+          LINKER_VARS: ${{ matrix.linker_vars }}
+        run: |
+          set -o pipefail
+          ARGS=("$ELF" "$LD_PATHS"
+                --upload --github
+                --target-name "$TARGET_NAME"
+                --api-key "$MEMBROWSE_API_KEY")
+          if [ -n "$MEMBROWSE_API_URL" ]; then
+            ARGS+=(--api-url "$MEMBROWSE_API_URL")
+          fi
+          if [ -n "$MAP_FILE" ]; then
+            ARGS+=(--map-file "$MAP_FILE")
+          fi
+          if [ -n "$LINKER_VARS" ]; then
+            set -f
+            for var in $LINKER_VARS; do
+              ARGS+=(--def "$var")
+            done
+            set +f
+          fi
+          membrowse --verbose INFO report "${ARGS[@]}"
diff --git a/README.md b/README.md
index 3ec5eba255e..40d6c07114e 100644
--- a/README.md
+++ b/README.md
@@ -8,6 +8,7 @@
 
[![Contributors](https://img.shields.io/github/contributors/apache/nuttx)](https://github.com/apache/nuttx/graphs/contributors)
 [![GitHub Build 
Badge](https://github.com/apache/nuttx/workflows/Build/badge.svg)](https://github.com/apache/nuttx/actions/workflows/build.yml)
 [![Documentation 
Badge](https://github.com/apache/nuttx/workflows/Build%20Documentation/badge.svg)](https://nuttx.apache.org/docs/latest/index.html)
+[![MemBrowse](https://membrowse.com/badge.svg)](https://membrowse.com/public/apache/nuttx)
 
 Apache NuttX is a real-time operating system (RTOS) with an emphasis on
 standards compliance and small footprint. Scalable from 8-bit to 64-bit

Reply via email to