Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
eca06bf
fix: prepare v2.8.4 run regression fixes
GaspardKirira Aug 8, 2026
cd083ef
feat: add live build progress and improve diagnostics
GaspardKirira Aug 8, 2026
6f22029
perf: speed up warm vix run execution
GaspardKirira Aug 8, 2026
49a11e4
fix: stabilize vix run script caching
GaspardKirira Aug 9, 2026
3b9ca5e
feat: improve v2.8.4 build workflow and diagnostics
GaspardKirira Aug 9, 2026
ea86292
feat: finalize v2.8.4 runtime and CLI improvements
GaspardKirira Aug 10, 2026
70ca286
fix: improve dev lifecycle and server startup
GaspardKirira Aug 11, 2026
59edadb
fix: keep fast builds configuration-stable
GaspardKirira Aug 11, 2026
6eeb14b
fix: stabilize fast builds and dev recovery
GaspardKirira Aug 11, 2026
c163ad8
fix: stabilize CLI umbrella and dev contracts
GaspardKirira Aug 12, 2026
99cfb6f
perf: reduce compile overhead and finalize v2.8.4 changes
GaspardKirira Aug 12, 2026
15d7c9e
perf: stabilize builds and add public core benchmarks
GaspardKirira Aug 12, 2026
ae3a1f3
fix(core): update core module with stability fixes
GaspardKirira Aug 13, 2026
9b34659
fix(release): update cli help and websocket export handling
GaspardKirira Aug 13, 2026
e9071ef
fix(release): update core config shared_ptr handling
GaspardKirira Aug 13, 2026
fd910a1
fix(release): update cli build help and run contract
GaspardKirira Aug 13, 2026
9e08f2a
fix(release): update cli umbrella run contract
GaspardKirira Aug 13, 2026
2f328b9
fix(release): update cli dev coroutine lifetime handling
GaspardKirira Aug 13, 2026
98bac0a
chore(release): prepare v2.8.4
GaspardKirira Aug 14, 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
266 changes: 266 additions & 0 deletions .github/workflows/core-benchmarks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
name: Core benchmarks

on:
pull_request:
branches:
- main
- dev
- release/**
paths:
- ".github/workflows/core-benchmarks.yml"
- "CMakeLists.txt"
- "cmake/**"
- "modules/**"
- ".gitmodules"
workflow_dispatch:
inputs:
base_ref:
description: "Baseline Git ref (default: repository default branch)"
required: false
type: string
candidate_ref:
description: "Candidate Git ref (default: workflow commit)"
required: false
type: string

permissions:
contents: read

concurrency:
group: core-benchmarks-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
compare:
name: Core runtime benchmark comparison
runs-on: ubuntu-latest
timeout-minutes: 90
env:
CXX: g++
BUILD_JOBS: 2
ARTIFACT_DIR: ${{ github.workspace }}/core-benchmark-artifacts

steps:
- name: Checkout candidate repository
uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: recursive

- name: Install benchmark dependencies
run: |
set -euxo pipefail
sudo apt-get update -y
sudo apt-get install -y --no-install-recommends \
build-essential cmake ninja-build mold pkg-config python3 jq git \
libssl-dev zlib1g-dev nlohmann-json3-dev libspdlog-dev libfmt-dev

- name: Resolve BASE and candidate commits
id: refs
run: |
set -euxo pipefail
if [ "${{ github.event_name }}" = "pull_request" ]; then
base_ref='${{ github.event.pull_request.base.sha }}'
candidate_ref='${{ github.event.pull_request.head.sha }}'
else
base_ref='${{ inputs.base_ref }}'
candidate_ref='${{ inputs.candidate_ref }}'
base_ref="${base_ref:-${{ github.event.repository.default_branch }}}"
candidate_ref="${candidate_ref:-${GITHUB_SHA}}"
fi

echo "base_sha=$(git rev-parse "${base_ref}^{commit}")" >> "$GITHUB_OUTPUT"
echo "candidate_sha=$(git rev-parse "${candidate_ref}^{commit}")" >> "$GITHUB_OUTPUT"

- name: Create isolated BASE and candidate worktrees
env:
BASE_SHA: ${{ steps.refs.outputs.base_sha }}
CANDIDATE_SHA: ${{ steps.refs.outputs.candidate_sha }}
run: |
set -euxo pipefail
base_dir=/tmp/vix-bench-base
candidate_dir=/tmp/vix-bench-candidate
rm -rf "$base_dir" "$candidate_dir"
git worktree add --detach "$base_dir" "$BASE_SHA"
git worktree add --detach "$candidate_dir" "$CANDIDATE_SHA"
git -C "$base_dir" submodule update --init --recursive
git -C "$candidate_dir" submodule update --init --recursive

- name: Benchmark BASE and candidate on this runner
id: benchmark
env:
BASE_SHA: ${{ steps.refs.outputs.base_sha }}
CANDIDATE_SHA: ${{ steps.refs.outputs.candidate_sha }}
run: |
set -euxo pipefail
mkdir -p "$ARTIFACT_DIR"

capture_environment() {
local source_dir="$1"
local label="$2"
local output="$3"
SOURCE_DIR="$source_dir" LABEL="$label" OUTPUT="$output" python3 - <<'PY'
import json, os, platform, subprocess
def command(*args):
return subprocess.check_output(args, text=True).strip()
data = {
"label": os.environ["LABEL"],
"commit": command("git", "-C", os.environ["SOURCE_DIR"], "rev-parse", "HEAD"),
"cpu_model": command("bash", "-lc", "lscpu | sed -n 's/^Model name:[[:space:]]*//p' | head -1"),
"cpu_count": os.cpu_count(),
"memory": command("bash", "-lc", "free -b | awk '/Mem:/ {print $2}'"),
"kernel": platform.release(),
"compiler_path": command("bash", "-lc", "command -v \"${CXX:-g++}\""),
"compiler": command(os.environ.get("CXX", "g++"), "--version").splitlines()[0],
"linker": command("bash", "-lc", "mold --version 2>/dev/null || ld --version | head -1"),
"cmake": command("cmake", "--version").splitlines()[0],
"ninja": command("ninja", "--version"),
"build_type": "Release",
"generator": "Ninja",
"build_jobs": os.environ.get("BUILD_JOBS"),
}
with open(os.environ["OUTPUT"], "w", encoding="utf-8") as f:
json.dump(data, f, indent=2)
f.write("\n")
PY
}

build_and_run() {
local label="$1"
local source_dir="$2"
local build_dir="/tmp/vix-bench-build-${label}"
local result_dir="$ARTIFACT_DIR/${label}/runtime"
rm -rf "$build_dir"
mkdir -p "$result_dir"

capture_environment "$source_dir" "$label" "$ARTIFACT_DIR/${label}/environment.json"
cmake -S "$source_dir/modules/core" -B "$build_dir" -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_CXX_COMPILER="$CXX" \
-DCMAKE_EXE_LINKER_FLAGS=-fuse-ld=mold \
-DVIX_CORE_BUILD_BENCHMARKS=ON \
-DVIX_CORE_BUILD_TESTS=OFF \
-DVIX_CORE_ENABLE_INSTALL=OFF
cmake --build "$build_dir" --target core_benchmarks --parallel "$BUILD_JOBS"
"$source_dir/modules/core/scripts/run_core_benchmarks.sh" \
--bin-dir "$build_dir/benchmarks/core" \
--out-dir "$result_dir" \
--version "$(git -C "$source_dir" rev-parse --short HEAD)" \
--runner "github-actions-${GITHUB_RUN_ID}" \
--machine "${RUNNER_OS}-${RUNNER_ARCH}"

# This is intentionally a compile-only consumer target: no ccache and no link.
local consumer_dir="/tmp/vix-bench-consumer-${label}"
rm -rf "$consumer_dir"
mkdir -p "$consumer_dir"
cat > "$consumer_dir/CMakeLists.txt" <<EOF
cmake_minimum_required(VERSION 3.20)
project(vix_core_compile_consumer LANGUAGES CXX)
add_subdirectory("$source_dir/modules/core" core)
add_executable(vix_compile_consumer main.cpp)
target_link_libraries(vix_compile_consumer PRIVATE vix::core)
target_compile_features(vix_compile_consumer PRIVATE cxx_std_20)
EOF
cat > "$consumer_dir/main.cpp" <<'EOF'
#include <vix.hpp>

int main()
{
vix::App app;
app.get("/health", [](vix::Request&, vix::ResponseWrapper&) {});
return 0;
}
EOF
cmake -S "$consumer_dir" -B "$consumer_dir/build" -G Ninja \
-DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER="$CXX" \
-DVIX_CORE_BUILD_BENCHMARKS=OFF -DVIX_CORE_BUILD_TESTS=OFF \
-DVIX_CORE_ENABLE_INSTALL=OFF
CCACHE_DISABLE=1 /usr/bin/time -v ninja -C "$consumer_dir/build" \
CMakeFiles/vix_compile_consumer.dir/main.cpp.o \
> "$ARTIFACT_DIR/${label}/compile-consumer.stdout" \
2> "$ARTIFACT_DIR/${label}/compile-consumer.time"
}

build_and_run base /tmp/vix-bench-base
build_and_run candidate /tmp/vix-bench-candidate

cmp \
<(jq 'del(.label, .commit)' "$ARTIFACT_DIR/base/environment.json") \
<(jq 'del(.label, .commit)' "$ARTIFACT_DIR/candidate/environment.json")

set +e
python3 modules/core/scripts/compare_core_benchmarks.py \
"$ARTIFACT_DIR/base/runtime" "$ARTIFACT_DIR/candidate/runtime" \
--json-out "$ARTIFACT_DIR/comparison.json" \
> "$ARTIFACT_DIR/comparison.txt" 2>&1
comparison_exit=$?
set -e
echo "comparison_exit=$comparison_exit" >> "$GITHUB_OUTPUT"
if [ "$comparison_exit" -eq 2 ]; then
cat "$ARTIFACT_DIR/comparison.txt"
exit 2
fi

- name: Publish benchmark summary
if: always()
env:
BASE_SHA: ${{ steps.refs.outputs.base_sha }}
CANDIDATE_SHA: ${{ steps.refs.outputs.candidate_sha }}
COMPARISON_EXIT: ${{ steps.benchmark.outputs.comparison_exit }}
run: |
set -euo pipefail
if [ ! -f "$ARTIFACT_DIR/comparison.json" ]; then
echo "# Vix Core Benchmarks" >> "$GITHUB_STEP_SUMMARY"
echo "Benchmark comparison did not complete; inspect the uploaded logs." >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
python3 - <<'PY' >> "$GITHUB_STEP_SUMMARY"
import json, os, pathlib, re
from collections import defaultdict
root = pathlib.Path(os.environ["ARTIFACT_DIR"])
print("# Vix Core Benchmarks")
print()
print(f"Base: `{os.environ['BASE_SHA']}` ")
print(f"Candidate: `{os.environ['CANDIDATE_SHA']}`")
report = json.loads((root / "comparison.json").read_text())
results = report["results"]
improved = sum(r["status"] == "OK" and r["change_percent"] > 0 for r in results)
stable = sum(r["status"] == "OK" and r["change_percent"] <= 0 for r in results)
print()
print(f"{len(results)} benchmarks — improved: {improved}, stable: {stable}, warn: {report['summary']['warn']}, regressed: {report['summary']['fail']}")
print()
groups = defaultdict(lambda: {"total": 0, "warn": 0, "regressed": 0})
for item in results:
group = item["benchmark"].split("/", 1)[0]
groups[group]["total"] += 1
groups[group]["warn"] += item["status"] == "WARN"
groups[group]["regressed"] += item["status"] == "FAIL"
print("| Group | Cases | Warn | Regressed |")
print("| --- | ---: | ---: | ---: |")
for group, counts in sorted(groups.items()):
print(f"| `{group}` | {counts['total']} | {counts['warn']} | {counts['regressed']} |")
print()
print("| Status | Delta | Benchmark |")
print("| --- | ---: | --- |")
for item in results:
status = "REGRESSED" if item["status"] == "FAIL" else item["status"]
delta = "-" if item["change_percent"] is None else f"{item['change_percent']:+.2f}%"
print(f"| {status} | {delta} | `{item['benchmark']}` |")
for label in ("base", "candidate"):
time_file = root / label / "compile-consumer.time"
text = time_file.read_text() if time_file.exists() else "unavailable"
wall = re.search(r"Elapsed \(wall clock\) time .*: (.+)", text)
rss = re.search(r"Maximum resident set size \(kbytes\): (\d+)", text)
print(f"\nCompile consumer ({label}, ccache disabled): wall={wall.group(1) if wall else 'n/a'}, max RSS={rss.group(1) if rss else 'n/a'} KiB")
PY
if [ "${COMPARISON_EXIT:-0}" = "1" ]; then
echo "::warning::Core benchmark comparison contains WARN/REGRESSED results; inspect the same-runner artifact."
fi

- name: Upload BASE, candidate, and comparison artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: core-benchmarks-${{ github.run_id }}-${{ github.run_attempt }}
path: core-benchmark-artifacts/
if-no-files-found: warn
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ cmd.txt
# ======================================================
scripts/changelog-release.sh
scripts/update_changelog.sh
scripts/publish-markdown-issues.sh

# ======================================================
# 🔧 Local test & reference builds
Expand Down
Loading
Loading