Merge pull request #288 from plasma-umass/fix-profiler-alignment #127
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [master, wip_*] | |
| pull_request: | |
| branches: [master] | |
| permissions: | |
| contents: read | |
| jobs: | |
| build-linux: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y \ | |
| build-essential \ | |
| cmake \ | |
| pkg-config \ | |
| libsqlite3-dev \ | |
| libbz2-dev | |
| - name: Configure perf permissions | |
| run: | | |
| # Allow perf_event_open for profiling | |
| echo 1 | sudo tee /proc/sys/kernel/perf_event_paranoid | |
| - name: Build coz | |
| run: | | |
| mkdir build | |
| cd build | |
| cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo | |
| make -j$(nproc) | |
| - name: Build benchmarks | |
| run: | | |
| cd build | |
| cmake .. -DBUILD_BENCHMARKS=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo | |
| make -j$(nproc) toy lock_test kmeans | |
| - name: Verify build artifacts | |
| run: | | |
| test -f build/libcoz/libcoz.so | |
| test -x build/benchmarks/toy/toy | |
| test -x build/benchmarks/lock_test/lock_test | |
| test -x build/benchmarks/kmeans/kmeans | |
| echo "Build artifacts verified" | |
| - name: Run unit tests | |
| run: | | |
| cd build | |
| ctest --output-on-failure -j$(nproc) | |
| rm -f profile.jsonl | |
| echo "Unit tests passed" | |
| - name: Run toy benchmark with coz | |
| run: | | |
| cd build | |
| # Run coz with the toy benchmark for a short duration | |
| timeout 30 ../coz run -o profile.jsonl --- ./benchmarks/toy/toy || true | |
| if [ ! -f profile.jsonl ]; then | |
| echo "ERROR: profile.jsonl was not created" | |
| exit 1 | |
| fi | |
| echo "Profile created successfully" | |
| - name: Validate toy profile output | |
| run: | | |
| cd build | |
| if [ ! -s profile.jsonl ]; then | |
| echo "ERROR: profile.jsonl is empty" | |
| exit 1 | |
| fi | |
| echo "=== Profile Contents ===" | |
| cat profile.jsonl | |
| echo "" | |
| # Check for expected profile format (should contain experiment lines) | |
| EXPERIMENT_COUNT=$(grep -c 'experiment' profile.jsonl || echo 0) | |
| echo "=== Profile Summary ===" | |
| echo " Total lines: $(wc -l < profile.jsonl)" | |
| echo " Experiments: $EXPERIMENT_COUNT" | |
| if [ "$EXPERIMENT_COUNT" -eq 0 ]; then | |
| echo "ERROR: No experiment data in profile" | |
| exit 1 | |
| fi | |
| # Validate profile references toy.cpp line 18 (the loop in function a()) | |
| if grep -q "toy.cpp:18" profile.jsonl; then | |
| echo " Found experiments for toy.cpp:18 (loop in function a())" | |
| else | |
| echo "ERROR: toy.cpp:18 not found in profile - expected loop in a() to be profiled" | |
| exit 1 | |
| fi | |
| echo "" | |
| echo "Profile validation PASSED" | |
| - name: Validate toy optimization potential | |
| run: | | |
| cd build | |
| python3 << 'VALIDATION_SCRIPT' | |
| import sys | |
| import json | |
| experiments = [] | |
| current_exp = None | |
| with open('profile.jsonl', 'r') as f: | |
| for line in f: | |
| line = line.strip() | |
| if not line: | |
| continue | |
| obj = json.loads(line) | |
| if obj.get('type') == 'experiment': | |
| current_exp = obj | |
| elif obj.get('type') == 'throughput-point' and current_exp: | |
| current_exp['delta'] = float(obj['delta']) | |
| experiments.append(current_exp) | |
| current_exp = None | |
| # Filter for toy.cpp:18 (the loop in a()) | |
| line18_exps = [e for e in experiments if 'toy.cpp:18' in e['selected']] | |
| if not line18_exps: | |
| print("ERROR: No experiments found for toy.cpp:18") | |
| sys.exit(1) | |
| print(f"Found {len(line18_exps)} experiments for toy.cpp:18") | |
| # Group by speedup percentage and calculate average delta | |
| speedup_deltas = {} | |
| for e in line18_exps: | |
| s = int(float(e['speedup']) * 100) | |
| if s not in speedup_deltas: | |
| speedup_deltas[s] = [] | |
| speedup_deltas[s].append(e['delta']) | |
| print("\nSpeedup vs Progress Delta:") | |
| for speedup in sorted(speedup_deltas.keys()): | |
| deltas = speedup_deltas[speedup] | |
| avg_delta = sum(deltas) / len(deltas) | |
| print(f" {speedup}%: avg_delta={avg_delta:.2f} (n={len(deltas)})") | |
| if len(speedup_deltas) < 3: | |
| print(f"WARNING: Only {len(speedup_deltas)} speedup levels - need more data for accurate validation") | |
| else: | |
| print("\nOptimization potential analysis: PASSED") | |
| print(" - toy.cpp:18 (loop in a()) correctly identified as optimization target") | |
| print("\nProfile validation COMPLETE") | |
| VALIDATION_SCRIPT | |
| - name: Run lock_test benchmark with coz | |
| run: | | |
| cd build | |
| timeout 60 ../coz run -o lock_test_profile.jsonl --- ./benchmarks/lock_test/lock_test || true | |
| if [ ! -f lock_test_profile.jsonl ]; then | |
| echo "ERROR: lock_test_profile.jsonl was not created" | |
| exit 1 | |
| fi | |
| echo "Lock test profile created successfully" | |
| - name: Validate lock_test profile | |
| run: | | |
| cd build | |
| if [ ! -s lock_test_profile.jsonl ]; then | |
| echo "ERROR: lock_test_profile.jsonl is empty" | |
| exit 1 | |
| fi | |
| echo "=== Lock Test Profile Contents ===" | |
| cat lock_test_profile.jsonl | |
| echo "" | |
| python3 << 'VALIDATION_SCRIPT' | |
| import sys | |
| import json | |
| experiments = [] | |
| current_exp = None | |
| with open('lock_test_profile.jsonl', 'r') as f: | |
| for line in f: | |
| line = line.strip() | |
| if not line: | |
| continue | |
| obj = json.loads(line) | |
| if obj.get('type') == 'experiment': | |
| current_exp = obj | |
| elif obj.get('type') == 'throughput-point' and current_exp: | |
| current_exp['delta'] = float(obj['delta']) | |
| experiments.append(current_exp) | |
| current_exp = None | |
| # Check for experiments on critical_work (line 12) and local_work (line 20) | |
| line12_exps = [e for e in experiments if 'lock_test.cpp:12' in e['selected']] | |
| line20_exps = [e for e in experiments if 'lock_test.cpp:20' in e['selected']] | |
| print(f"Experiments for line 12 (critical_work): {len(line12_exps)}") | |
| print(f"Experiments for line 20 (local_work): {len(line20_exps)}") | |
| if not line12_exps: | |
| print("ERROR: No experiments found for lock_test.cpp:12 (critical section)") | |
| sys.exit(1) | |
| # Check samples to verify critical_work dominates | |
| with open('lock_test_profile.jsonl', 'r') as f: | |
| for line in f: | |
| obj = json.loads(line.strip()) | |
| if obj.get('type') == 'samples': | |
| loc = obj['location'] | |
| count = obj['count'] | |
| print(f" Samples: {loc} = {count}") | |
| print("\nLock contention benchmark validation: PASSED") | |
| print(" - lock_test.cpp:12 (critical section) identified as profiling target") | |
| VALIDATION_SCRIPT | |
| - name: Upload profile artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: coz-profiles | |
| path: | | |
| build/profile.jsonl | |
| build/lock_test_profile.jsonl | |
| retention-days: 7 | |
| build-macos: | |
| runs-on: macos-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install dependencies | |
| run: | | |
| # cmake is preinstalled on the runner; make sure pkg-config and sqlite | |
| # (needed to configure the benchmark tree) are available. coreutils | |
| # provides gtimeout, since macOS has no GNU `timeout`. | |
| brew install pkg-config sqlite coreutils || true | |
| - name: Build coz | |
| run: | | |
| mkdir build | |
| cd build | |
| cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo | |
| make -j$(sysctl -n hw.ncpu) | |
| - name: Build benchmarks | |
| run: | | |
| cd build | |
| cmake .. -DBUILD_BENCHMARKS=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo | |
| make -j$(sysctl -n hw.ncpu) toy lock_test kmeans | |
| - name: Verify build artifacts | |
| run: | | |
| # On macOS the injectable library keeps the .so suffix (see | |
| # libcoz/CMakeLists.txt) so it can be used with DYLD_INSERT_LIBRARIES. | |
| test -f build/libcoz/libcoz.so | |
| test -x build/benchmarks/toy/toy | |
| test -x build/benchmarks/lock_test/lock_test | |
| test -x build/benchmarks/kmeans/kmeans | |
| echo "Build artifacts verified" | |
| - name: Run unit tests | |
| run: | | |
| cd build | |
| # Includes test_run_kmeans, which profiles kmeans live via kperf and so | |
| # also validates that macOS sampling actually collects samples. | |
| ctest --output-on-failure -j$(sysctl -n hw.ncpu) | |
| echo "Unit tests passed" | |
| - name: Run toy benchmark with coz | |
| run: | | |
| cd build | |
| gtimeout 30 ../coz run -o profile.jsonl --- ./benchmarks/toy/toy || true | |
| if [ ! -s profile.jsonl ]; then | |
| echo "ERROR: profile.jsonl was not created" | |
| exit 1 | |
| fi | |
| echo "Profile created:" | |
| cat profile.jsonl | |
| lang-rust: | |
| name: Rust bindings (${{ matrix.os }}) | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install dependencies (Linux) | |
| if: runner.os == 'Linux' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y build-essential cmake pkg-config | |
| echo 1 | sudo tee /proc/sys/kernel/perf_event_paranoid | |
| - name: Install dependencies (macOS) | |
| if: runner.os == 'macOS' | |
| run: brew install pkg-config coreutils || true | |
| - name: Build coz | |
| run: | | |
| cmake -S . -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo | |
| cmake --build build -j3 | |
| test -f build/libcoz/libcoz.so || test -f build/libcoz/libcoz.dylib | |
| - name: Check the crate on both platforms | |
| working-directory: rust | |
| run: | | |
| cargo build --release --examples | |
| cargo test --release | |
| - name: Profile the Rust toy under coz | |
| run: | | |
| # `timeout` is GNU-only; coreutils provides gtimeout on macOS. | |
| TIMEOUT=timeout | |
| command -v gtimeout >/dev/null && TIMEOUT=gtimeout | |
| # rust/examples/toy runs ~9e6 work units of 10k iterations each across | |
| # two threads (~1-2 minutes on a 2-core runner); the timeout is a | |
| # safety net since experiments are written to the profile as they run. | |
| $TIMEOUT 300 ./coz run -o profile.jsonl --- ./rust/target/release/examples/toy || true | |
| test -s profile.jsonl | |
| - name: Validate the Rust profile | |
| run: python3 .github/scripts/check_profile.py profile.jsonl toy.rs | |
| lang-go: | |
| name: Go bindings (${{ matrix.os }}) | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-go@v5 | |
| with: | |
| go-version: '1.26' | |
| - name: Install dependencies (Linux) | |
| if: runner.os == 'Linux' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y build-essential cmake pkg-config | |
| echo 1 | sudo tee /proc/sys/kernel/perf_event_paranoid | |
| - name: Install dependencies (macOS) | |
| if: runner.os == 'macOS' | |
| run: brew install pkg-config coreutils || true | |
| - name: Build coz | |
| run: | | |
| cmake -S . -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo | |
| cmake --build build -j3 | |
| test -f build/libcoz/libcoz.so || test -f build/libcoz/libcoz.dylib | |
| - name: Vet, format and test | |
| working-directory: go | |
| run: | | |
| test -z "$(gofmt -l .)" || { echo "gofmt found unformatted files:"; gofmt -l .; exit 1; } | |
| go vet ./... | |
| go test -race ./... | |
| # The package must also build with cgo off, degrading to no-ops. | |
| CGO_ENABLED=0 go build ./... | |
| - name: Build the Go toy | |
| working-directory: go | |
| run: | | |
| # coz cannot read Go's compressed DWARF (.zdebug_* / __zdebug_*). | |
| go build -ldflags=-compressdwarf=false -o "$RUNNER_TEMP/gotoy" ./example | |
| - name: Profile the Go toy under coz | |
| run: | | |
| TIMEOUT=timeout | |
| command -v gtimeout >/dev/null && TIMEOUT=gtimeout | |
| $TIMEOUT 180 ./coz run -o profile.jsonl --- "$RUNNER_TEMP/gotoy" || true | |
| test -s profile.jsonl | |
| - name: Validate the Go profile | |
| run: python3 .github/scripts/check_profile.py profile.jsonl toy.go |