mirror of
https://github.com/apache/nuttx.git
synced 2026-08-01 20:28:58 +00:00
ci/testing: Add MemBrowse Integration
Tracking memory footprint with MemBrowse Signed-off-by: Michael Rogov Papernov / michael@membrowse.com
This commit is contained in:
parent
8857d3673f
commit
b941a715ea
5 changed files with 456 additions and 0 deletions
83
.github/membrowse-targets.json
vendored
Normal file
83
.github/membrowse-targets.json
vendored
Normal file
|
|
@ -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": ""
|
||||
}
|
||||
]
|
||||
42
.github/workflows/membrowse-comment.yml
vendored
Normal file
42
.github/workflows/membrowse-comment.yml
vendored
Normal file
|
|
@ -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
|
||||
98
.github/workflows/membrowse-onboard.yml
vendored
Normal file
98
.github/workflows/membrowse-onboard.yml
vendored
Normal file
|
|
@ -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[@]}"
|
||||
232
.github/workflows/membrowse-report.yml
vendored
Normal file
232
.github/workflows/membrowse-report.yml
vendored
Normal file
|
|
@ -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[@]}"
|
||||
|
|
@ -8,6 +8,7 @@
|
|||
[](https://github.com/apache/nuttx/graphs/contributors)
|
||||
[](https://github.com/apache/nuttx/actions/workflows/build.yml)
|
||||
[](https://nuttx.apache.org/docs/latest/index.html)
|
||||
[](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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue