@opensassi/opencode 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +35 -0
- package/README.md +81 -0
- package/bin/opencode.js +3 -0
- package/lib/cli.js +38 -0
- package/lib/commands/init.js +117 -0
- package/lib/commands/print-agents.js +6 -0
- package/lib/commands/print-skill.js +8 -0
- package/lib/commands/run.js +57 -0
- package/lib/index.js +4 -0
- package/lib/util/paths.js +21 -0
- package/package.json +40 -0
- package/scripts/asm-optimizer/run-baseline.sh +158 -0
- package/scripts/check-artifacts.js +131 -0
- package/scripts/extract-artifacts.js +204 -0
- package/scripts/install/linux/ubuntu-noble-24.04/install.sh +94 -0
- package/scripts/install/osx/macos-sequoia-15.0/install.sh +115 -0
- package/scripts/install/windows/wsl2/install.ps1 +98 -0
- package/scripts/install.ps1 +32 -0
- package/scripts/install.sh +83 -0
- package/scripts/puppeteer-config.json +3 -0
- package/scripts/test-artifacts.js +346 -0
- package/scripts/validate-all.js +18 -0
- package/scripts/verify-artifact.js +157 -0
- package/skills/asm-optimizer/SKILL.md +295 -0
- package/skills/daily-evaluation/SKILL.md +86 -0
- package/skills/git/SKILL.md +100 -0
- package/skills/issue/SKILL.md +104 -0
- package/skills/npm-optimizer/SKILL.md +218 -0
- package/skills/opensassi/SKILL.md +77 -0
- package/skills/opensassi/scripts/ensure-gitignore.sh +89 -0
- package/skills/opensassi/scripts/env-check.ps1 +139 -0
- package/skills/opensassi/scripts/env-check.sh +200 -0
- package/skills/opensassi/scripts/install-flamegraph.sh +32 -0
- package/skills/opensassi/scripts/install-npm-deps.sh +25 -0
- package/skills/profiler/SKILL.md +213 -0
- package/skills/profiler/scripts/benchmark.sh +63 -0
- package/skills/profiler/scripts/common.sh +55 -0
- package/skills/profiler/scripts/compare.sh +63 -0
- package/skills/profiler/scripts/profile.sh +63 -0
- package/skills/profiler/scripts/setup.sh +32 -0
- package/skills/session-evaluation/SKILL.md +128 -0
- package/skills/skill-manager/SKILL.md +251 -0
- package/skills/system-design/SKILL.md +558 -0
- package/skills/system-design-review/SKILL.md +396 -0
- package/skills/todo/SKILL.md +165 -0
- package/skills-index.json +137 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# env-check.sh — Bootstrap environment detection for opencode opensassi skill.
|
|
3
|
+
# Detects OS, ensures git, installs Node.js LTS via nvm if needed.
|
|
4
|
+
# Outputs JSON to stdout: {os, distro, version, codename, pkg_manager, shell,
|
|
5
|
+
# is_wsl, arch, node_version, nvm_version, git_version}
|
|
6
|
+
#
|
|
7
|
+
# Usage: bash env-check.sh
|
|
8
|
+
# Dependencies: bash, common UNIX utilities (uname, grep, cut, etc.)
|
|
9
|
+
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
|
|
12
|
+
# --- OS Detection ---
|
|
13
|
+
OS="$(uname -s)"
|
|
14
|
+
ARCH="$(uname -m)"
|
|
15
|
+
SHELL_NAME="$(basename "${SHELL:-bash}")"
|
|
16
|
+
IS_WSL=false
|
|
17
|
+
|
|
18
|
+
if grep -qi "microsoft\|wsl" /proc/version 2>/dev/null; then
|
|
19
|
+
IS_WSL=true
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
# Normalize arch
|
|
23
|
+
case "$ARCH" in
|
|
24
|
+
x86_64|amd64) ARCH="x64" ;;
|
|
25
|
+
aarch64|arm64) ARCH="arm64" ;;
|
|
26
|
+
esac
|
|
27
|
+
|
|
28
|
+
# --- Distro Detection ---
|
|
29
|
+
DISTRO=""
|
|
30
|
+
VERSION=""
|
|
31
|
+
CODENAME=""
|
|
32
|
+
PKG_MANAGER=""
|
|
33
|
+
|
|
34
|
+
detect_linux() {
|
|
35
|
+
if [ -f /etc/os-release ]; then
|
|
36
|
+
. /etc/os-release
|
|
37
|
+
DISTRO="${ID:-linux}"
|
|
38
|
+
VERSION="${VERSION_ID:-}"
|
|
39
|
+
CODENAME="${VERSION_CODENAME:-$UBUNTU_CODENAME}"
|
|
40
|
+
elif [ -f /etc/lsb-release ]; then
|
|
41
|
+
. /etc/lsb-release
|
|
42
|
+
DISTRO="${DISTRIB_ID:-linux}"
|
|
43
|
+
VERSION="${DISTRIB_RELEASE:-}"
|
|
44
|
+
CODENAME="${DISTRIB_CODENAME:-}"
|
|
45
|
+
fi
|
|
46
|
+
DISTRO="$(echo "$DISTRO" | tr '[:upper:]' '[:lower:]')"
|
|
47
|
+
|
|
48
|
+
# Detect package manager
|
|
49
|
+
for pm in apt dnf yum pacman zypper; do
|
|
50
|
+
if command -v "$pm" &>/dev/null; then
|
|
51
|
+
PKG_MANAGER="$pm"
|
|
52
|
+
break
|
|
53
|
+
fi
|
|
54
|
+
done
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
detect_macos() {
|
|
58
|
+
DISTRO="macos"
|
|
59
|
+
if command -v sw_vers &>/dev/null; then
|
|
60
|
+
VERSION="$(sw_vers -productVersion 2>/dev/null || echo "")"
|
|
61
|
+
case "$VERSION" in
|
|
62
|
+
15.*) CODENAME="sequoia" ;;
|
|
63
|
+
14.*) CODENAME="sonoma" ;;
|
|
64
|
+
13.*) CODENAME="ventura" ;;
|
|
65
|
+
12.*) CODENAME="monterey" ;;
|
|
66
|
+
11.*) CODENAME="big-sur" ;;
|
|
67
|
+
*) CODENAME="unknown" ;;
|
|
68
|
+
esac
|
|
69
|
+
fi
|
|
70
|
+
if command -v brew &>/dev/null; then
|
|
71
|
+
PKG_MANAGER="brew"
|
|
72
|
+
fi
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
case "$OS" in
|
|
76
|
+
Linux)
|
|
77
|
+
detect_linux
|
|
78
|
+
if $IS_WSL; then
|
|
79
|
+
# In WSL, use Windows-side detection for pkg_manager hint
|
|
80
|
+
:
|
|
81
|
+
fi
|
|
82
|
+
;;
|
|
83
|
+
Darwin)
|
|
84
|
+
detect_macos
|
|
85
|
+
;;
|
|
86
|
+
MINGW*|MSYS*|CYGWIN*)
|
|
87
|
+
# Git Bash / MSYS2 on Windows — limited support, point to ps1
|
|
88
|
+
DISTRO="windows"
|
|
89
|
+
OS="win32"
|
|
90
|
+
PKG_MANAGER=""
|
|
91
|
+
if command -v winget &>/dev/null; then
|
|
92
|
+
PKG_MANAGER="winget"
|
|
93
|
+
elif command -v choco &>/dev/null; then
|
|
94
|
+
PKG_MANAGER="choco"
|
|
95
|
+
fi
|
|
96
|
+
;;
|
|
97
|
+
*)
|
|
98
|
+
DISTRO="unknown"
|
|
99
|
+
;;
|
|
100
|
+
esac
|
|
101
|
+
|
|
102
|
+
# --- Ensure git ---
|
|
103
|
+
if ! command -v git &>/dev/null; then
|
|
104
|
+
echo "[INFO] git not found. Installing..." >&2
|
|
105
|
+
case "${PKG_MANAGER:-}" in
|
|
106
|
+
apt) sudo apt update && sudo apt install -y git ;;
|
|
107
|
+
dnf) sudo dnf install -y git ;;
|
|
108
|
+
yum) sudo yum install -y git ;;
|
|
109
|
+
pacman) sudo pacman -Sy --noconfirm git ;;
|
|
110
|
+
zypper) sudo zypper install -y git ;;
|
|
111
|
+
brew) brew install git ;;
|
|
112
|
+
*)
|
|
113
|
+
echo "[WARN] Cannot install git automatically. Install git manually." >&2
|
|
114
|
+
;;
|
|
115
|
+
esac
|
|
116
|
+
fi
|
|
117
|
+
GIT_VERSION="$(git --version 2>/dev/null | sed 's/git version //' || echo "")"
|
|
118
|
+
|
|
119
|
+
# --- Ensure Node.js (LTS via nvm) ---
|
|
120
|
+
NODE_VERSION=""
|
|
121
|
+
NVM_VERSION=""
|
|
122
|
+
|
|
123
|
+
setup_node() {
|
|
124
|
+
# Check if node is already at LTS or later
|
|
125
|
+
if command -v node &>/dev/null; then
|
|
126
|
+
local current
|
|
127
|
+
current="$(node --version | sed 's/^v//')"
|
|
128
|
+
local major
|
|
129
|
+
major="$(echo "$current" | cut -d. -f1)"
|
|
130
|
+
# Use node if >= 18 (LTS cutoff)
|
|
131
|
+
if [ "$major" -ge 18 ] 2>/dev/null; then
|
|
132
|
+
NODE_VERSION="$current"
|
|
133
|
+
echo "[INFO] Node.js v$NODE_VERSION already available" >&2
|
|
134
|
+
return 0
|
|
135
|
+
fi
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
# Find or install nvm
|
|
139
|
+
local nvm_install_dir=""
|
|
140
|
+
if [ -n "${NVM_DIR:-}" ] && [ -s "$NVM_DIR/nvm.sh" ]; then
|
|
141
|
+
nvm_install_dir="$NVM_DIR"
|
|
142
|
+
elif [ -s "$HOME/.nvm/nvm.sh" ]; then
|
|
143
|
+
nvm_install_dir="$HOME/.nvm"
|
|
144
|
+
elif [ -s "$HOME/.local/share/nvm/nvm.sh" ]; then
|
|
145
|
+
nvm_install_dir="$HOME/.local/share/nvm"
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
if [ -n "$nvm_install_dir" ]; then
|
|
149
|
+
echo "[INFO] nvm found at $nvm_install_dir" >&2
|
|
150
|
+
export NVM_DIR="$nvm_install_dir"
|
|
151
|
+
# shellcheck disable=SC1091
|
|
152
|
+
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
|
|
153
|
+
NVM_VERSION="$(nvm --version 2>/dev/null || echo "")"
|
|
154
|
+
else
|
|
155
|
+
echo "[INFO] nvm not found. Installing nvm v0.40.0..." >&2
|
|
156
|
+
export NVM_DIR="$HOME/.nvm"
|
|
157
|
+
mkdir -p "$NVM_DIR"
|
|
158
|
+
curl -o- "https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh" | bash 2>/dev/null || {
|
|
159
|
+
echo "[ERROR] Failed to install nvm. Install manually:" >&2
|
|
160
|
+
echo " curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash" >&2
|
|
161
|
+
return 1
|
|
162
|
+
}
|
|
163
|
+
# shellcheck disable=SC1091
|
|
164
|
+
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
|
|
165
|
+
NVM_VERSION="$(nvm --version 2>/dev/null || echo "")"
|
|
166
|
+
echo "[INFO] nvm v$NVM_VERSION installed" >&2
|
|
167
|
+
fi
|
|
168
|
+
|
|
169
|
+
# Install LTS node (idempotent — no-op if already present)
|
|
170
|
+
if [ -n "${NVM_DIR:-}" ] && [ -s "$NVM_DIR/nvm.sh" ]; then
|
|
171
|
+
echo "[INFO] Installing latest Node.js LTS via nvm..." >&2
|
|
172
|
+
nvm install --lts 2>/dev/null || true
|
|
173
|
+
nvm use --lts 2>/dev/null || true
|
|
174
|
+
# Write .nvmrc for project
|
|
175
|
+
local project_root
|
|
176
|
+
project_root="$(git rev-parse --show-toplevel 2>/dev/null || echo ".")"
|
|
177
|
+
echo "--lts" > "$project_root/.nvmrc" 2>/dev/null || true
|
|
178
|
+
NODE_VERSION="$(node --version 2>/dev/null | sed 's/^v//' || echo "")"
|
|
179
|
+
echo "[INFO] Node.js v$NODE_VERSION (LTS) ready via nvm" >&2
|
|
180
|
+
fi
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
setup_node || true
|
|
184
|
+
|
|
185
|
+
# --- Output JSON ---
|
|
186
|
+
cat <<JSONEOF
|
|
187
|
+
{
|
|
188
|
+
"os": "$OS",
|
|
189
|
+
"distro": "$DISTRO",
|
|
190
|
+
"version": "$VERSION",
|
|
191
|
+
"codename": "$CODENAME",
|
|
192
|
+
"pkg_manager": "$PKG_MANAGER",
|
|
193
|
+
"shell": "$SHELL_NAME",
|
|
194
|
+
"is_wsl": $IS_WSL,
|
|
195
|
+
"arch": "$ARCH",
|
|
196
|
+
"node_version": "$NODE_VERSION",
|
|
197
|
+
"nvm_version": "$NVM_VERSION",
|
|
198
|
+
"git_version": "$GIT_VERSION"
|
|
199
|
+
}
|
|
200
|
+
JSONEOF
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# install-flamegraph.sh — Clone Brendan Gregg's FlameGraph at pinned tag v1.0.
|
|
3
|
+
# Idempotent: updates existing clone if present.
|
|
4
|
+
# Usage: bash install-flamegraph.sh
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || (cd "$(dirname "$0")/../../.." && pwd))"
|
|
9
|
+
FLAMEGRAPH_DIR="$PROJECT_ROOT/scripts/FlameGraph"
|
|
10
|
+
|
|
11
|
+
if [ -d "$FLAMEGRAPH_DIR/.git" ]; then
|
|
12
|
+
echo "[INFO] FlameGraph already cloned. Updating to v1.0..."
|
|
13
|
+
git -C "$FLAMEGRAPH_DIR" fetch --tags --depth=1 2>&1 | tail -1
|
|
14
|
+
git -C "$FLAMEGRAPH_DIR" checkout v1.0 2>&1 || {
|
|
15
|
+
echo "[WARN] Could not checkout v1.0; resetting to tag..."
|
|
16
|
+
git -C "$FLAMEGRAPH_DIR" reset --hard v1.0 2>&1
|
|
17
|
+
}
|
|
18
|
+
else
|
|
19
|
+
echo "[INFO] Cloning FlameGraph v1.0 to $FLAMEGRAPH_DIR..."
|
|
20
|
+
git clone --depth=1 --branch v1.0 https://github.com/brendangregg/FlameGraph.git "$FLAMEGRAPH_DIR" 2>&1 || {
|
|
21
|
+
echo "[ERROR] Failed to clone FlameGraph. Clone manually:"
|
|
22
|
+
echo " git clone --depth=1 --branch v1.0 https://github.com/brendangregg/FlameGraph.git $FLAMEGRAPH_DIR"
|
|
23
|
+
exit 1
|
|
24
|
+
}
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
if [ -f "$FLAMEGRAPH_DIR/flamegraph.pl" ]; then
|
|
28
|
+
echo "[INFO] FlameGraph ready: $FLAMEGRAPH_DIR"
|
|
29
|
+
else
|
|
30
|
+
echo "[WARN] FlameGraph clone may be incomplete — flamegraph.pl not found."
|
|
31
|
+
ls "$FLAMEGRAPH_DIR" | head -10
|
|
32
|
+
fi
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# install-npm-deps.sh — Install npm dependencies for the opencode project.
|
|
3
|
+
# Usage: bash install-npm-deps.sh
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || (cd "$(dirname "$0")/../../.." && pwd))"
|
|
8
|
+
|
|
9
|
+
if ! command -v node &>/dev/null; then
|
|
10
|
+
echo "[ERROR] Node.js not found. Run env-check.sh first."
|
|
11
|
+
exit 1
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
if [ ! -f "$PROJECT_ROOT/package.json" ]; then
|
|
15
|
+
echo "[WARN] No package.json found at $PROJECT_ROOT — skipping npm install"
|
|
16
|
+
exit 0
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
echo "[INFO] Installing npm dependencies..."
|
|
20
|
+
cd "$PROJECT_ROOT"
|
|
21
|
+
npm install 2>&1 | tail -5 || {
|
|
22
|
+
echo "[ERROR] npm install failed"
|
|
23
|
+
exit 1
|
|
24
|
+
}
|
|
25
|
+
echo "[INFO] npm dependencies installed"
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: profiler
|
|
3
|
+
description: Profiling, flamegraph generation, and benchmarking using Linux perf
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Interactive Profiler Agent Prompt
|
|
7
|
+
|
|
8
|
+
## Persona
|
|
9
|
+
|
|
10
|
+
You are a **senior performance engineer** with deep expertise in Linux perf, CPU profiling, SIMD optimization (SSE4.1, ARM Neon/SVE/SVE2).
|
|
11
|
+
Your role is to help users profile their software, generate flamegraphs, run benchmarks with quality regression detection, and target optimization efforts.
|
|
12
|
+
|
|
13
|
+
You always work **interactively** — propose a profiling plan, run tools, analyze results, and only proceed to deeper investigation when the user agrees.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Response Guidelines
|
|
18
|
+
|
|
19
|
+
When activated:
|
|
20
|
+
|
|
21
|
+
1. **Check environment** — Run `check` to verify: `perf` available, Release binary exists, benchmark data at `test/data/`, FlameGraph scripts at `scripts/FlameGraph/`, quality metric tooling if needed.
|
|
22
|
+
2. **Report status** — Output a summary of what's available and what's missing. Do not initiate a profiling session without user direction.
|
|
23
|
+
3. **Propose first steps** — Suggest running a baseline `profile` or `benchmark` depending on the user's goal.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Commands
|
|
28
|
+
|
|
29
|
+
### `check`
|
|
30
|
+
|
|
31
|
+
Verify the profiling toolchain. Runs the following checks and reports pass/fail:
|
|
32
|
+
|
|
33
|
+
- `perf` is installed and accessible
|
|
34
|
+
- Encoder Release binary exists (check `bin/release-static/` or build tree)
|
|
35
|
+
- `test/data/` contains benchmark input data
|
|
36
|
+
- `scripts/FlameGraph/stackcollapse-perf.pl` and `flamegraph.pl` exist (clone via opensassi skill if missing)
|
|
37
|
+
- `ffmpeg` available (needed for `--vmaf`)
|
|
38
|
+
- `vmaf` tool available (needed for `--vmaf`)
|
|
39
|
+
- `.profiler/` output directory exists
|
|
40
|
+
|
|
41
|
+
Saves a brief report to `.profiler/check.json`.
|
|
42
|
+
|
|
43
|
+
### `setup`
|
|
44
|
+
|
|
45
|
+
Download and prepare test data for profiling.
|
|
46
|
+
|
|
47
|
+
Usage:
|
|
48
|
+
```
|
|
49
|
+
setup # default: download/prompt for test data
|
|
50
|
+
setup --frames 50 # override frame count
|
|
51
|
+
setup --resize 1280x720 # produce resized variant via ffmpeg
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
What it does:
|
|
55
|
+
1. Downloads or prepares test input data (configurable for your domain)
|
|
56
|
+
3. If `--resize` given, runs `ffmpeg` to produce resized variants
|
|
57
|
+
4. Creates `.profiler/` directory structure: `flamegraphs/`, `benchmarks/`, `perf_archives/`, `reports/`
|
|
58
|
+
5. Creates `.gitignore` entries for test data and `.profiler/` if not present
|
|
59
|
+
6. Checks that `scripts/FlameGraph/` exists; if not, runs the opensassi skill to clone it
|
|
60
|
+
|
|
61
|
+
Example output files (video encoder project):
|
|
62
|
+
```
|
|
63
|
+
test/data/input_1080p10.yuv (full-res source, 10 frames)
|
|
64
|
+
test/data/input_1280x720f10.yuv (default profile resolution)
|
|
65
|
+
test/data/input_832x480f10.yuv (--resize 832x480)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### `profile`
|
|
69
|
+
|
|
70
|
+
Run `perf record` on your program and produce a flamegraph.
|
|
71
|
+
|
|
72
|
+
Usage:
|
|
73
|
+
```
|
|
74
|
+
profile # default: test data, default config, cycles
|
|
75
|
+
profile --events cache-misses,branch-misses # custom perf events
|
|
76
|
+
profile --frames 50 # override frame count
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
What it does:
|
|
80
|
+
1. Runs: `perf record --call-graph fp -e cycles,cache-misses,branch-misses -o <file> -- <program> <args>`
|
|
81
|
+
2. Generates folded stack: `perf script -i perf.data | stackcollapse-perf.pl > folded.txt`
|
|
82
|
+
3. Generates flamegraph: `flamegraph.pl folded.txt > flame.svg`
|
|
83
|
+
4. Collects hardware counter summary: `perf stat -e cycles,cache-misses,branch-misses -- <program> <args> > perf.stat 2>&1`
|
|
84
|
+
5. Saves all to `.profiler/perf_archives/{label}/`
|
|
85
|
+
|
|
86
|
+
Output artifacts:
|
|
87
|
+
```
|
|
88
|
+
.profiler/perf_archives/<label>/
|
|
89
|
+
├── perf.data (raw, for LLM/tooling analysis)
|
|
90
|
+
├── perf.stat (hardware counter summary)
|
|
91
|
+
├── folded.txt (collapsed stacks for diffing)
|
|
92
|
+
├── flame.svg (interactive flamegraph)
|
|
93
|
+
└── meta.json (program config, args, version)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### `benchmark`
|
|
97
|
+
|
|
98
|
+
Run N iterations of the program with performance metric collection.
|
|
99
|
+
|
|
100
|
+
Usage:
|
|
101
|
+
```
|
|
102
|
+
benchmark # default: 5 iterations
|
|
103
|
+
benchmark --iter 10 # 10 iterations
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Output:
|
|
107
|
+
```
|
|
108
|
+
.profiler/benchmarks/benchmark-{timestamp}.json
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
JSON structure:
|
|
112
|
+
```json
|
|
113
|
+
{
|
|
114
|
+
"label": "benchmark-label",
|
|
115
|
+
"timestamp": "...",
|
|
116
|
+
"iterations": [
|
|
117
|
+
{
|
|
118
|
+
"iter": 1,
|
|
119
|
+
"wall_time_ms": 45230,
|
|
120
|
+
"metric_1": 38.42,
|
|
121
|
+
"metric_2": 44.15
|
|
122
|
+
}
|
|
123
|
+
],
|
|
124
|
+
"summary": {
|
|
125
|
+
"time_avg_ms": 44987,
|
|
126
|
+
"time_min_ms": 44123,
|
|
127
|
+
"time_max_ms": 46234
|
|
128
|
+
},
|
|
129
|
+
"config": {
|
|
130
|
+
"source": "test/data/input.yuv",
|
|
131
|
+
"frames": 50,
|
|
132
|
+
"metrics": ["metric_1"]
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### `compare`
|
|
138
|
+
|
|
139
|
+
Compare two benchmark runs side-by-side.
|
|
140
|
+
|
|
141
|
+
Usage:
|
|
142
|
+
```
|
|
143
|
+
compare <baseline.json> <candidate.json>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Output:
|
|
147
|
+
```
|
|
148
|
+
=== Comparison: baseline vs candidate ===
|
|
149
|
+
Metric Baseline Candidate Δ% Status
|
|
150
|
+
──────────────────────────────────────────────────────────
|
|
151
|
+
Wall time (ms) 44987 41230 -8.35% ✓
|
|
152
|
+
Throughput 1.112 1.213 +9.08% ✓
|
|
153
|
+
Quality 38.41 38.38 -0.08% ⚠ below threshold
|
|
154
|
+
|
|
155
|
+
Regression thresholds:
|
|
156
|
+
Δ time > +2% AND Δ quality < -0.1 → REGRESSION flag
|
|
157
|
+
Status: PASS (no regression detected)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
A regression is flagged when both conditions are met:
|
|
161
|
+
- Wall-clock time increases by more than the threshold (default +2%)
|
|
162
|
+
- Any quality metric drops below its threshold
|
|
163
|
+
|
|
164
|
+
These thresholds are configurable in `common.sh`.
|
|
165
|
+
|
|
166
|
+
### `report`
|
|
167
|
+
|
|
168
|
+
Bundle a profiling session into a report.
|
|
169
|
+
|
|
170
|
+
Usage:
|
|
171
|
+
```
|
|
172
|
+
report # bundle most recent profile + benchmark
|
|
173
|
+
report --profile <label> # specific profile archive
|
|
174
|
+
report --benchmark <file> # specific benchmark JSON
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Produces:
|
|
178
|
+
```
|
|
179
|
+
.profiler/reports/report-{timestamp}/
|
|
180
|
+
├── flame.svg
|
|
181
|
+
├── benchmark-table.txt
|
|
182
|
+
├── perf-summary.txt
|
|
183
|
+
├── system-info.txt (uname, cpuinfo, perf version, encoder version)
|
|
184
|
+
└── meta.json
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Design Principles
|
|
190
|
+
|
|
191
|
+
- **Default workload**: configurable in `common.sh`
|
|
192
|
+
- **Resized input variants** via `setup --resize WxH` for fast iteration
|
|
193
|
+
- **Release build only** (ensure `-fno-omit-frame-pointer` is enabled for meaningful flamegraphs)
|
|
194
|
+
- **5 iterations minimum** for benchmark; raw `perf.data` retained for LLM analysis
|
|
195
|
+
- **FlameGraph scripts** at `scripts/FlameGraph/` (cloned by opensassi skill from Brendan Gregg's repo)
|
|
196
|
+
- **Input data**: `test/data/` (gitignored, downloaded by setup)
|
|
197
|
+
- **Output artifacts**: `.profiler/` (hidden dir, gitignored)
|
|
198
|
+
- **Read-only on source code** — never modifies source or build files
|
|
199
|
+
- **perf events**: `cycles,cache-misses,branch-misses` default; extend via `--events`
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Support Scripts
|
|
204
|
+
|
|
205
|
+
Support scripts live in the `@opensassi/opencode` package. Run them via `npx @opensassi/opencode run --skill profiler <name>`:
|
|
206
|
+
|
|
207
|
+
| Script | Purpose |
|
|
208
|
+
|---|---|
|
|
209
|
+
| `common.sh` | Shared config, paths, defaults, threshold constants |
|
|
210
|
+
| `setup.sh` | Download test data, --resize/--frames, FlameGraph clone, gitignore |
|
|
211
|
+
| `profile.sh` | perf record → flamegraph pipeline |
|
|
212
|
+
| `benchmark.sh` | Iteration loop, metric collection, JSON output |
|
|
213
|
+
| `compare.sh` | Two JSON input, Δ% table, regression detection |
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Benchmark: run N iterations of your program with timing.
|
|
3
|
+
# Usage: ./benchmark.sh [--iter N]
|
|
4
|
+
|
|
5
|
+
source "$(dirname "$0")/common.sh"
|
|
6
|
+
|
|
7
|
+
# --- Parse args ---
|
|
8
|
+
ITERATIONS=$DEFAULT_ITERATIONS
|
|
9
|
+
|
|
10
|
+
while [[ $# -gt 0 ]]; do
|
|
11
|
+
case "$1" in
|
|
12
|
+
--iter) shift; ITERATIONS="$1" ;;
|
|
13
|
+
*) log_error "Unknown option: $1"; exit 1 ;;
|
|
14
|
+
esac
|
|
15
|
+
shift
|
|
16
|
+
done
|
|
17
|
+
|
|
18
|
+
PROGRAM="$(find_program)"
|
|
19
|
+
if [[ -z "$PROGRAM" ]]; then
|
|
20
|
+
log_error "Program binary not found."
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# --- Iteration loop ---
|
|
25
|
+
RESULTS_FILE="$OUTPUT_DIR/benchmarks/benchmark-$(timestamp).json"
|
|
26
|
+
|
|
27
|
+
declare -a ITER_DATA
|
|
28
|
+
for ((i=1; i<=ITERATIONS; i++)); do
|
|
29
|
+
log_info "Iteration $i / $ITERATIONS ..."
|
|
30
|
+
|
|
31
|
+
START_MS=$(date +%s%3N)
|
|
32
|
+
"$PROGRAM" 2>&1 | tee "$OUTPUT_DIR/benchmarks/iter_${i}.log"
|
|
33
|
+
END_MS=$(date +%s%3N)
|
|
34
|
+
WALL_MS=$((END_MS - START_MS))
|
|
35
|
+
|
|
36
|
+
ITEM=$(cat <<ITEMEOF
|
|
37
|
+
{
|
|
38
|
+
"iter": $i,
|
|
39
|
+
"wall_time_ms": $WALL_MS
|
|
40
|
+
}
|
|
41
|
+
ITEMEOF
|
|
42
|
+
)
|
|
43
|
+
ITER_DATA+=("$ITEM")
|
|
44
|
+
done
|
|
45
|
+
|
|
46
|
+
# --- Build JSON ---
|
|
47
|
+
JSON=$(cat <<JSONEOF
|
|
48
|
+
{
|
|
49
|
+
"label": "benchmark-$(timestamp)",
|
|
50
|
+
"timestamp": "$(timestamp)",
|
|
51
|
+
"iterations": [
|
|
52
|
+
$(IFS=,; echo "${ITER_DATA[*]}")
|
|
53
|
+
],
|
|
54
|
+
"config": {
|
|
55
|
+
"program": "$PROGRAM",
|
|
56
|
+
"iterations": $ITERATIONS
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
JSONEOF
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
echo "$JSON" > "$RESULTS_FILE"
|
|
63
|
+
log_info "Benchmark saved: $RESULTS_FILE"
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Common configuration and paths for the profiler skill.
|
|
3
|
+
# Source this from other scripts: source "$(dirname "$0")/common.sh"
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
# --- Paths ---
|
|
8
|
+
PROJECT_ROOT="$(cd "$(dirname "$0")/../../../../" && pwd)"
|
|
9
|
+
SCRIPTS_DIR="$PROJECT_ROOT/.opencode/skills/profiler/scripts"
|
|
10
|
+
OUTPUT_DIR="$PROJECT_ROOT/.profiler"
|
|
11
|
+
DATA_DIR="$PROJECT_ROOT/test/data"
|
|
12
|
+
FLAMEGRAPH_DIR="$PROJECT_ROOT/scripts/FlameGraph"
|
|
13
|
+
|
|
14
|
+
# --- Default workload (customize for your project) ---
|
|
15
|
+
DEFAULT_INPUT="input"
|
|
16
|
+
DEFAULT_RESOLUTION="1280x720"
|
|
17
|
+
DEFAULT_FRAMES=10
|
|
18
|
+
DEFAULT_CONFIG="default"
|
|
19
|
+
|
|
20
|
+
# --- Benchmark defaults ---
|
|
21
|
+
DEFAULT_ITERATIONS=5
|
|
22
|
+
|
|
23
|
+
# --- Regression thresholds ---
|
|
24
|
+
THRESHOLD_TIME_PCT=2.0
|
|
25
|
+
THRESHOLD_QUALITY=0.1
|
|
26
|
+
|
|
27
|
+
# --- perf defaults ---
|
|
28
|
+
PERF_EVENTS_DEFAULT="cycles,cache-misses,branch-misses"
|
|
29
|
+
|
|
30
|
+
# --- Binary discovery (customize for your project) ---
|
|
31
|
+
find_program() {
|
|
32
|
+
local paths=(
|
|
33
|
+
"$PROJECT_ROOT/bin/release/program"
|
|
34
|
+
"$PROJECT_ROOT/build/program"
|
|
35
|
+
"$PROJECT_ROOT/program"
|
|
36
|
+
)
|
|
37
|
+
for p in "${paths[@]}"; do
|
|
38
|
+
if [[ -x "$p" ]]; then
|
|
39
|
+
echo "$p"
|
|
40
|
+
return 0
|
|
41
|
+
fi
|
|
42
|
+
done
|
|
43
|
+
echo ""
|
|
44
|
+
return 1
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# --- Timestamp ---
|
|
48
|
+
timestamp() {
|
|
49
|
+
date +%Y%m%dT%H%M%S
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# --- Logging ---
|
|
53
|
+
log_info() { echo "[INFO] $*"; }
|
|
54
|
+
log_warn() { echo "[WARN] $*" >&2; }
|
|
55
|
+
log_error() { echo "[ERROR] $*" >&2; }
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Compare two benchmark JSON files, output Δ% table with regression detection.
|
|
3
|
+
# Usage: ./compare.sh <baseline.json> <candidate.json>
|
|
4
|
+
|
|
5
|
+
source "$(dirname "$0")/common.sh"
|
|
6
|
+
|
|
7
|
+
if [[ $# -ne 2 ]]; then
|
|
8
|
+
log_error "Usage: $0 <baseline.json> <candidate.json>"
|
|
9
|
+
exit 1
|
|
10
|
+
fi
|
|
11
|
+
|
|
12
|
+
BASELINE="$1"
|
|
13
|
+
CANDIDATE="$2"
|
|
14
|
+
|
|
15
|
+
if [[ ! -f "$BASELINE" ]]; then log_error "Baseline not found: $BASELINE"; exit 1; fi
|
|
16
|
+
if [[ ! -f "$CANDIDATE" ]]; then log_error "Candidate not found: $CANDIDATE"; exit 1; fi
|
|
17
|
+
|
|
18
|
+
python3 -c "
|
|
19
|
+
import json, sys
|
|
20
|
+
|
|
21
|
+
with open('$BASELINE') as f: b = json.load(f)
|
|
22
|
+
with open('$CANDIDATE') as f: c = json.load(f)
|
|
23
|
+
|
|
24
|
+
def avg(items, key):
|
|
25
|
+
vals = [i.get(key) for i in items if i.get(key) is not None]
|
|
26
|
+
if not vals:
|
|
27
|
+
return None
|
|
28
|
+
return sum(vals) / len(vals)
|
|
29
|
+
|
|
30
|
+
def pct(b_val, c_val):
|
|
31
|
+
if b_val is None or c_val is None or b_val == 0:
|
|
32
|
+
return None
|
|
33
|
+
return ((c_val - b_val) / b_val) * 100
|
|
34
|
+
|
|
35
|
+
b_t = avg(b['iterations'], 'wall_time_ms')
|
|
36
|
+
c_t = avg(c['iterations'], 'wall_time_ms')
|
|
37
|
+
|
|
38
|
+
print(f\"{'Metric':<25} {'Baseline':<14} {'Candidate':<14} {'Δ%':<10} {'Status'}\")
|
|
39
|
+
print(f\"{'-'*25} {'-'*14} {'-'*14} {'-'*10} {'-'*10}\")
|
|
40
|
+
|
|
41
|
+
def fmt(v, unit=''):
|
|
42
|
+
if v is None: return 'N/A'
|
|
43
|
+
return f'{v:.2f}{unit}'
|
|
44
|
+
|
|
45
|
+
regression = False
|
|
46
|
+
|
|
47
|
+
# Wall time: negative Δ% = good (faster)
|
|
48
|
+
dt = pct(b_t, c_t)
|
|
49
|
+
status = '✓' if dt is not None and dt < $THRESHOLD_TIME_PCT else ''
|
|
50
|
+
if dt is not None and dt >= $THRESHOLD_TIME_PCT:
|
|
51
|
+
status = '⚠ REGRESSION'
|
|
52
|
+
regression = True
|
|
53
|
+
print(f\"{'Wall time (ms)':<25} {fmt(b_t):<14} {fmt(c_t):<14} {fmt(dt, '%'):<10} {status}\")
|
|
54
|
+
|
|
55
|
+
print()
|
|
56
|
+
if regression:
|
|
57
|
+
print('⚠ REGRESSION DETECTED: time exceeded threshold.')
|
|
58
|
+
else:
|
|
59
|
+
print('✓ PASS: no regression detected.')
|
|
60
|
+
" 2>&1 || {
|
|
61
|
+
log_error "Comparison failed. Ensure both files are valid benchmark JSON."
|
|
62
|
+
exit 1
|
|
63
|
+
}
|