Skip to content

Commit 6e1c955

Browse files
committed
feat: (WIP) CPU isolation, cmake 4 check, shellcheck, makefile changes, housekeeping
1 parent 701f482 commit 6e1c955

14 files changed

Lines changed: 213 additions & 39 deletions

.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,6 @@ SYMBOLS=BTCUSDT,
1010
# CPU PINNING
1111
PX_SESSION_CPU=0
1212
TX_SESSION_CPU=1
13+
# CPU ISOLATION
14+
CPU_SET_NAME=tradercpp
15+
CPU_SET_RANGE="0-1"

.github/workflows/reusable_build.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ jobs:
2626
- name: clang-format
2727
run: hooks/check_clang_format.sh
2828

29+
- name: shellcheck
30+
run: hooks/check_shell.sh
31+
2932
- name: Restore Conan cache
3033
uses: actions/cache@v4
3134
with:

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
cmake_minimum_required(VERSION 3.16)
1+
cmake_minimum_required(VERSION 4.0)
22
project(tradercpp)
33

44
set(CMAKE_CXX_STANDARD 23)

Dockerfile_build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,5 +115,6 @@ RUN apt update && apt install -y --no-install-recommends \
115115
make \
116116
ninja-build \
117117
perl \
118+
shellcheck \
118119
unzip \
119120
&& rm -rf /var/lib/apt/lists/*

Makefile

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
#!make
22
SHELL:=/bin/bash
33

4-
#################################################
5-
# A set of basic execution recipes (all PHONY).
6-
# Call `make` on the command line for documentation.
7-
#################################################
4+
# ###############################################
5+
# A set of execution recipes (all PHONY).
6+
# In essence, a programmatic entrypoint into the app.
7+
# Run `make` for help.
8+
# ###############################################
89

910
# pp - pretty print function
1011
yellow := $(shell tput setaf 3)
@@ -18,33 +19,41 @@ help: Makefile
1819
@echo " Choose a command to run:"
1920
@sed -n 's/^##//p' $< | column -t -s ':' | sed -e 's/^/ /'
2021

21-
## withenv: 😭 run `make` with envars from `.env`. like so `make withenv RECIPE=init`
22+
## withenv: 😭 execute `make` with environment variables defined in `.env`. like so > `make withenv RECIPE=init`
2223
.PHONY: withenv
2324
withenv:
2425
test -e .env || cp .env.example .env
2526
bash -c 'set -o allexport; source .env; set +o allexport; make "$$RECIPE"'
2627

27-
## init: 🏌️ initialize the project, fetch dependencies
28+
# TODO: replace ☝️ with `set -a; . .env; set +a;`
29+
30+
## init: 🏌️ initialize the project
2831
.PHONY: init
2932
init:
33+
$(call pp,initializing project)
34+
git config core.hooksPath hooks
3035
rm -rf build && mkdir build
3136
python3 -m venv .venv
3237
source .venv/bin/activate && \
3338
pip install gcovr conan && \
3439
conan install . --lockfile=conan.lock --build=missing -s build_type=Debug && \
3540
conan install . --lockfile=conan.lock --build=missing -s build_type=Release
41+
cmake --preset=release
3642
cmake --preset=debug
43+
$(MAKE) restore-cpus
3744

38-
## lock-conan: 📦 run after adding (but before installing) conan dependencies
39-
.PHONY: lock-conan
40-
lock-conan:
41-
conan lock create . --profile:host=default -s build_type=Debug --lockfile-out=conan.lock
42-
conan lock create . --profile:host=default -s build_type=Release --lockfile=conan.lock --lockfile-out=conan.lock
45+
## lock: 📦 update Conan dependency lock file. (NB run after adding conan dependencies to conanfile.txt, but before installing)
46+
.PHONY: lock
47+
lock:
48+
$(call pp,updating conan lock file to match contents of conanfile.txt)
49+
source .venv/bin/activate && \
50+
conan lock create . --profile:host=default -s build_type=Debug --lockfile-out=conan.lock && \
51+
conan lock create . --profile:host=default -s build_type=Release --lockfile=conan.lock --lockfile-out=conan.lock
4352

4453
## build-debug: 🔨 compile (debug)
4554
.PHONY: build-debug
4655
build-debug:
47-
$(call pp,assuming `make init` has been called)
56+
$(call pp,NB assuming `make init` has been called)
4857
cmake --preset=debug
4958
cmake --build --preset=debug
5059

@@ -68,7 +77,6 @@ test:
6877
## bench: ⏱️ build and run benchmarks
6978
.PHONY: bench
7079
bench:
71-
$(call pp,assuming `make build-release` has been called)
7280
cmake --build --preset release
7381
build/Release/benchmarks/benchmarks \
7482
--benchmark_out=bench_results.json \
@@ -80,18 +88,26 @@ bench:
8088
tidy:
8189
find src/ tests/ benchmarks/ \( -name '*.cpp' -o -name '*.hpp' -o -name '*.c' -o -name '*.h' \) -exec clang-format -i {} +
8290
hooks/check_clang_tidy.sh
91+
hooks/check_shell.sh
8392

84-
## run-debug: 🏃‍♂️ run the app (debug) (don't forget `withenv`)
93+
## run-debug: 🏃‍♂️ run the app (debug) (don't forget `withenv`)
8594
.PHONY: run-debug
8695
run-debug:
8796
ASAN_OPTIONS=detect_leaks=1:leak_check_at_exit=1:fast_unwind_on_malloc=0 \
8897
build/Debug/tradercpp
8998

90-
## run-release: 🏎️ run the app (prod)
99+
## run-release: 🏎️ run the app (prod)
91100
.PHONY: run-release
92101
run-release:
93-
$(call pp,starting app. dont forget to run `scripts/cpu_shield_start.sh` and `scripts/irqs_move.sh`)
94-
build/Release/tradercpp
102+
# set -a; . .env; set +a; sudo -E scripts/pin_irqs.sh
103+
set -a; . .env; set +a; sudo -E scripts/pin_cpus.sh build/Release/tradercpp
104+
105+
## restore-cpus: 🖥️ hand back pinned CPUs and IRQs to the operating system. (NB app must not be running)
106+
.PHONY: restore-cpus
107+
restore-cpus:
108+
$(call pp,handing CPU and IRQ management back to the kernel. NB: app must not be running`)
109+
set -a; . .env; set +a; sudo -E scripts/unpin_cpus.sh
110+
set -a; . .env; set +a; sudo -E scripts/unpin_irqs.sh
95111

96112
# CONTAINERISATION RECIPES ----------------------------------------------------
97113

hooks/check_shell.sh

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/usr/bin/env bash
2+
3+
################################################################
4+
# check_clang_tidy.sh
5+
# checks for any lint warnings and errors out.
6+
# does not modify files in-place.
7+
# uses run-clang-tidy for parallel execution.
8+
################################################################
9+
10+
set -euo pipefail
11+
12+
echo ">> Running shellcheck on all scripts"
13+
14+
while IFS= read -r file; do
15+
shellcheck "$file"
16+
# find files that start with a sh/bash shebang, exclude build/ and .git/hooks folders
17+
done < <(find . \( -type d -name build -o -path './.git/hooks' \) -prune -o -type f -exec grep -Iq '^#!.*sh' {} \; -print)

scripts/cpu_shield_start.sh

Lines changed: 0 additions & 9 deletions
This file was deleted.

scripts/cpu_shield_stop.sh

Lines changed: 0 additions & 9 deletions
This file was deleted.

scripts/pin_cpus.sh

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# ----------------------------------------------------------
5+
# CPU isolation launcher for Linux (cgroup v1 & v2)
6+
#
7+
# How it works:
8+
# 1. Root required: The script checks for root because writing to /sys/fs/cgroup requires privileges.
9+
# 2. Validation: Ensures CPU_SET_NAME and CPU_SET_RANGE exist.
10+
# 3. Cgroup detection: Automatically handles v1 and v2.
11+
# 4. CPU and memory configuration: Writes cpuset.cpus and cpuset.mems.
12+
# 5. Associating the application:
13+
# - $$ is the PID of the script itself.
14+
# - When we exec "$@", the application replaces the script and inherits the cgroup.
15+
# - This is the standard way to bind the app to the cpuset.
16+
# ----------------------------------------------------------
17+
18+
# Validate environment variables
19+
: "${CPU_SET_NAME:?Environment variable CPU_SET_NAME is required}"
20+
: "${CPU_SET_RANGE:?Environment variable CPU_SET_RANGE is required}"
21+
echo "CPU isolation launcher starting..."
22+
echo "CPU_SET_NAME = $CPU_SET_NAME"
23+
echo "CPU_SET_RANGE = $CPU_SET_RANGE"
24+
25+
# Check for root privileges
26+
if [[ $EUID -ne 0 ]]; then
27+
echo "WARNING: root privileges are required for true CPU isolation."
28+
echo "Proceeding without isolation..."
29+
exec "$@"
30+
fi
31+
32+
# Detect cgroup version
33+
if [[ -f /sys/fs/cgroup/cgroup.controllers ]]; then
34+
CGROUP_VERSION=2
35+
BASE_CGROUP="/sys/fs/cgroup"
36+
else
37+
CGROUP_VERSION=1
38+
BASE_CGROUP="/sys/fs/cgroup/cpuset"
39+
fi
40+
echo "Detected cgroup version: $CGROUP_VERSION"
41+
42+
GROUP_PATH="$BASE_CGROUP/$CPU_SET_NAME"
43+
44+
# Create cpuset directory if it doesn't exist
45+
if [[ ! -d "$GROUP_PATH" ]]; then
46+
mkdir -p "$GROUP_PATH"
47+
echo "Created cpuset directory: $GROUP_PATH"
48+
fi
49+
50+
# Write CPUs
51+
CPU_FILE="$GROUP_PATH/cpuset.cpus"
52+
echo "$CPU_SET_RANGE" > "$CPU_FILE"
53+
echo "Wrote CPUs to $CPU_FILE"
54+
55+
# Write memory nodes (required for cpuset)
56+
MEM_FILE="$GROUP_PATH/cpuset.mems"
57+
if [[ ! -f "$MEM_FILE" ]]; then
58+
# assume NUMA node 0
59+
echo "0" > "$MEM_FILE"
60+
fi
61+
echo "Set memory nodes in $MEM_FILE"
62+
63+
# TODO(MILS): WARN ABOUT MULTI-NUMA CPU RANGES AND REFUSE TO CONTINUE
64+
65+
# Add current process to the cpuset
66+
# - $$ is the PID of the script itself.
67+
# - When we exec "$@", the application replaces the script and inherits the cgroup.
68+
PROCS_FILE="$GROUP_PATH/cgroup.procs"
69+
echo "$$" > "$PROCS_FILE"
70+
echo "Added PID $$ to $PROCS_FILE"
71+
echo "Launching application in isolated cpuset..."
72+
exec "$@"
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@
44
# ------------------------------------------------------------
55

66
set -euo pipefail
7+
8+
# Check for root
9+
if [ "$EUID" -ne 0 ]; then
10+
echo "This script must be run as root."
11+
echo "Try: sudo $0"
12+
exit 1
13+
fi
14+
15+
# Handle filenames with whitespace
716
IFS=$'\n\t'
817

918
# List of NIC interfaces to protect (keep IRQs on isolated cores)

0 commit comments

Comments
 (0)