Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
f779834
simplified building
aferaudo Jul 8, 2026
9f4f59f
readme sync
aferaudo Jul 8, 2026
5e0384f
updated asn attributions
aferaudo Jul 9, 2026
4558432
testing native memcpy - this should be replaced
aferaudo Jul 15, 2026
6a6f132
removed boolean dependency
aferaudo Jul 15, 2026
0452aa4
fixing logging
aferaudo Jul 30, 2026
91eaed9
added codelet verifier
aferaudo Aug 1, 2026
f0b185b
various fixing
aferaudo Aug 1, 2026
d8f6343
added config file
aferaudo Aug 1, 2026
47686a2
added support to configuration file
aferaudo Aug 1, 2026
57f0978
various fixing + config switch shm writer
aferaudo Aug 1, 2026
d5302a5
clock monotonic jbpf
aferaudo Aug 6, 2026
2558a13
various cmake fixing
aferaudo Aug 6, 2026
623a39d
various fixing
aferaudo Aug 6, 2026
e6ea31e
jbpf patches
aferaudo Aug 7, 2026
f4103e3
fixed libe3 cmake and filtering methods
aferaudo Aug 18, 2026
bc4dcb7
build fixings
aferaudo Aug 24, 2026
40275e8
Record the slot path's stages with latrec instead of a per-slot CSV (#6)
Thecave3 Sep 10, 2026
9cb0de4
E3Controller: bump libe3 submodule to latest tag 0.2.0 (#7)
aferaudo Oct 2, 2026
417d0a4
Resend the helper config after every codelet load
aferaudo Oct 10, 2026
44a7452
Update the L1-KPM SM to the latest grammar
aferaudo Oct 12, 2026
9d6b72e
Build against the gNB's jbpf from the ocudu checkout (#9)
aferaudo Oct 13, 2026
62ed1cd
Describe writer: gnb as the default in the configs
aferaudo Oct 13, 2026
81db345
Split the README into docs/
aferaudo Oct 13, 2026
22eba14
removed comments
aferaudo Oct 13, 2026
ee56fcb
fixed how to cite
aferaudo Oct 13, 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
217 changes: 217 additions & 0 deletions .github/workflows/build-and-measure.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
# Build the controller both ways, and measure what recording a slot costs.
#
# The matrix leg is the one thing that has to vary: libe3's stage recorder is a
# build-time decision (-DLIBE3_ENABLE_LATREC), carried to us as a PUBLIC compile
# definition, so "traced" and "untraced" are two different binaries rather than
# two run modes. Building both is what stops the untraced path from rotting
# unnoticed - and the untraced leg is the one that proves the stamps really do
# compile away to nothing.
name: build and measure

on:
push:
branches: [main]
pull_request:
workflow_dispatch:

permissions:
contents: read

jobs:
build:
name: build (latrec=${{ matrix.latrec }})
runs-on: ubuntu-latest
# Cold, this is asn1c-from-source + libe3 + ocudu's jbpf and its third-party
# tree + the controller. Comfortably inside an hour; nowhere near the 6h default.
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
latrec: [on, off]

steps:
- uses: actions/checkout@v4
with:
# build.sh fetches the libe3 submodule itself.
submodules: false

# jbpf is not vendored here: the controller is built against the gNB's own
# jbpf, ocudu's external/jbpf, so the two ends of the IPC are always the
# same jbpf. CI takes it from the public ocudu mirror. Only that submodule
# is fetched; the rest of ocudu is not needed to build the controller.
- name: Check out ocudu (for its jbpf and hook contract)
uses: actions/checkout@v4
with:
repository: wineslab/ocudu-e3
ref: e3
path: .deps/ocudu-e3
fetch-depth: 1
submodules: false

- name: Populate ocudu's jbpf
run: git -C .deps/ocudu-e3 submodule update --init --recursive --depth 1 external/jbpf

# The libe3 pin decides which asn1c revision is wanted, so it belongs in
# the cache key. hashFiles cannot read a gitlink, and the submodule is not
# checked out yet, so read the pinned SHA straight out of the tree object.
- name: Read the libe3 pin
id: pin
run: echo "sha=$(git rev-parse HEAD:libe3)" >> "$GITHUB_OUTPUT"

# asn1c is built from the mouse07410 fork, which is minutes of autotools.
# No restore-keys: a stale asn1c is worse than no cache, because it would
# generate against a different grammar than the pinned libe3 expects.
- name: Cache asn1c
id: cache-asn1c
uses: actions/cache@v4
with:
path: /opt/asn1c
key: asn1c-${{ runner.os }}-${{ steps.pin.outputs.sha }}

- name: Cache ccache
uses: actions/cache@v4
with:
path: ~/.ccache
key: ccache-${{ runner.os }}-latrec${{ matrix.latrec }}-${{ github.sha }}
restore-keys: |
ccache-${{ runner.os }}-latrec${{ matrix.latrec }}-
ccache-${{ runner.os }}-

- name: Install dependencies and build
env:
# -march=native would tune to whatever CPU this runner happens to be,
# which breaks both ccache reuse across heterogeneous runners and any
# comparison of the measurements below between runs.
E3C_CMAKE_ARGS: -DUSE_NATIVE=OFF
OCUDU_DIR: ${{ github.workspace }}/.deps/ocudu-e3
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends ccache
export PATH="/usr/lib/ccache:${PATH}"
if [ "${{ matrix.latrec }}" = "on" ]; then
./build.sh --install-deps --latrec
else
./build.sh --install-deps
fi

- name: Record the machine the numbers came from
run: |
{
echo "## Environment (latrec=${{ matrix.latrec }})"
echo
echo '```'
grep -m1 'model name' /proc/cpuinfo || true
echo "nproc: $(nproc)"
echo "/dev/shm: $(df -h /dev/shm | tail -1)"
echo "libe3 pin: $(git -C libe3 rev-parse --short HEAD) ($(cat libe3/VERSION))"
echo "ocudu-e3: $(git -C .deps/ocudu-e3 rev-parse --short HEAD), jbpf pin: $(git -C .deps/ocudu-e3 rev-parse --short HEAD:external/jbpf)"
echo '```'
} > report.md

# The claim being tested: with the recorder off, nothing links against the
# recorder runtime. A timing test cannot show that - only the symbol
# table can.
#
# The check is on global and undefined symbols specifically. libe3 only
# compiles src/core/latrec.c when the recorder is on, so a traced binary
# carries `D latrec_tls` plus `T latrec_seq_next` / `latrec_tls_open_as`
# / `latrec_set_output_dir`; an untraced one carries none of them. What
# an untraced binary can still carry is lowercase `t` entries for the
# inline no-op stubs latrec.h substitutes - local copies the optimiser
# did not bother to discard. Those are empty functions, not the
# recorder, and grepping for them would assert something that is not
# true and would fail for the wrong reason.
- name: Assert nothing links the recorder
if: matrix.latrec == 'off'
run: |
status=0
for binary in out/bin/e3_controller out/bin/bench_stage_recording; do
# Uppercase type letter = global or undefined, i.e. a real link.
if nm -C "$binary" | grep -E ' [A-Z] ' | grep -i latrec; then
echo "::error::$binary links latrec symbols in an untraced build"
status=1
else
echo "ok: $binary links no latrec symbols"
fi
if nm -C "$binary" | grep -qw latrec_tls; then
echo "::error::$binary carries the latrec ring registry"
status=1
fi
done
[ "$status" -eq 0 ] || exit 1
{
echo
echo '## Not linked'
echo
echo 'Neither binary links any latrec symbol, and neither carries the ring'
echo 'registry. Only latrec.h'"'"'s inline no-op stubs remain, as local symbols.'
} >> report.md

- name: Measure what recording a slot costs
run: |
mkdir -p rings
{
echo
./out/bin/bench_stage_recording \
--latrec-dir "$PWD/rings" \
--csv-path "$PWD/bench_csv_arm.log"
} >> report.md 2>&1
# The bench fails if any stamp had to be clamped: that would mean its
# synthetic slot times are not ascending, so its latrec arm would be
# exercising the clamp path rather than the one production takes.

# Converting the capture with libe3's own tool is what checks the parts of
# the contract that live outside this repository: that the ring role maps
# to the component we intend, that the stage ids form a complete source
# leg, and that the emit-tail hop column materialises. A capture that
# wrapped is not a valid measurement, so fail on it.
- name: Convert and validate the capture
if: matrix.latrec == 'on'
run: |
python3 -m pip install --quiet numpy
TOOLS=/usr/local/share/libe3/tools
python3 "$TOOLS/latrec2csv.py" rings | tee convert.log
{
echo
echo '## Capture'
echo
echo '```'
cat convert.log
echo '```'
} >> report.md

# The role must land in ocudu.csv, not other.csv: latrec2csv maps the
# role prefix through its own table, so a rename here silently
# reattributes every record to an unknown component.
test -f rings/csv/ocudu.csv \
|| { echo "::error::records were not attributed to the ocudu component"; exit 1; }

# The emit tail replaces the removed CSV's emit_ns, and only exists
# because (ENCODE_E3SM_DONE -> WAIT_ENTER) is a declared extra hop.
head -2 rings/csv/ocudu.csv | grep -q 'ENCODE_E3SM_DONE__WAIT_ENTER_us' \
|| { echo "::error::the emit-tail hop column is missing"; exit 1; }

python3 - <<'PY'
import csv, sys
with open('rings/csv/rings.csv') as fh:
rows = list(csv.DictReader(fh))
if not rows:
sys.exit('::error::no rings were written')
for r in rows:
# A wrapped ring has lost records off the front, so any span
# computed across the wrap is wrong. Better to fail than to
# publish a number from a truncated capture.
if r['wrapped'] != '0' or int(r['lost_records'] or 0):
sys.exit(f"::error::{r['ring']} wrapped ({r['lost_records']} lost) "
"- shorten the run or raise LATREC_ENTRIES_LOG2")
print(f"ok: {r['ring']} {r['rec_count']} records, no wrap, "
f"clock {r['clock_ns_per_call']} ns/call")
PY

- uses: actions/upload-artifact@v4
with:
name: report-latrec-${{ matrix.latrec }}
path: |
report.md
rings/csv/
if-no-files-found: warn
3 changes: 0 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
[submodule "jbpf"]
path = jbpf
url = https://github.com/microsoft/jbpf.git
[submodule "libe3"]
path = libe3
url = https://github.com/wineslab/libe3.git
Loading
Loading