- Rust 67.7%
- Svelte 18.5%
- TypeScript 10.3%
- CSS 3%
- GDB 0.3%
- Other 0.2%
|
|
||
|---|---|---|
| .config | ||
| .forgejo/workflows | ||
| config | ||
| examples | ||
| ffdc-api | ||
| ffdc-core | ||
| ffdc-tui | ||
| web-ui | ||
| .dockerignore | ||
| .gitattributes | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| compose.gdb.yml | ||
| compose.yml | ||
| Dockerfile | ||
| Dockerfile.gdb | ||
| README.md | ||
FFDC Parser
Parses OpenBMC First Failure Data Capture (FFDC) dump archives. Extracts, classifies, and renders structured data from obmcdump tarballs.
Architecture
modular crate:
| Crate | Description |
|---|---|
ffdc-core |
Core library: compression detection, archive extraction, file classification, structured parsers, journal parser, coredump analysis, SQLite metadata store |
ffdc-api |
Axum REST API with upload, job tracking, search, compare, OIDC session auth |
ffdc-tui |
Terminal UI (ratatui) with Browse, View, and Search modes |
web-ui |
SvelteKit static SPA frontend |
Usage (API)
ffdc-api --db-path /path/to/ffdc.db
| Flag / Env | Default | Description |
|---|---|---|
--bind / FFDC_BIND |
0.0.0.0:3000 |
Listen address |
--db-path / FFDC_DB_PATH |
ffdc.db |
SQLite database path |
--cache-dir / FFDC_CACHE_DIR |
cache |
Upload extraction cache |
--max-upload-bytes / FFDC_MAX_UPLOAD_BYTES |
8388608 | Max upload size |
--auth-enabled / FFDC_AUTH_ENABLED |
false | Require authentication |
--session-ttl-secs / FFDC_SESSION_TTL_SECS |
86400 | Session cookie TTL |
--oidc-issuer / FFDC_OIDC_ISSUER |
- | OIDC issuer URL |
--oidc-client-id / FFDC_OIDC_CLIENT_ID |
- | OIDC client ID |
--oidc-client-secret / FFDC_OIDC_CLIENT_SECRET |
- | OIDC client secret |
--oidc-redirect-url / FFDC_OIDC_REDIRECT_URL |
- | OIDC callback URL |
--static-dir / FFDC_STATIC_DIR |
- | SPA static files dir |
Usage (TUI)
ffdc-tui /path/to/dumps/
Browse, inspect, and search FFDC dump archives interactively:
- Browse mode - list dumps in the left panel, select files in the right panel, view parsed content or raw hex in the main area
- View mode - scroll through file content with
j/k, search with/when browsing a file - Search mode - press
/to enter a regex query,n/Nto cycle hits, results highlighted across all dumps and files - Save a file with
s, quit withqorEsc
| Key | Mode | Action |
|---|---|---|
Tab |
Browse | Focus toggle: dump list / file list / content |
↑/↓ / j/k |
Any | Move selection or scroll content |
→ / l |
Browse | Enter selected dump to expand |
← / h |
Browse | Collapse or go up |
Enter |
Browse | View selected file |
/ |
Browse | Start search |
n / N |
Search | Next / previous match |
Esc |
Any | Cancel search or go back |
s |
Browse | Save current file to disk |
q |
Any | Quit |
Web UI
cd web-ui && npm install && npm run build
Serve the built files from web-ui/build/ - either set FFDC_STATIC_DIR on the API or serve with any HTTP server.
E2E Tests
Uses Playwright against the real Rust API server with the built SvelteKit UI.
cd web-ui
npm install
npx playwright install chromium # one-time: download browser binaries
npm run build
npm run test:e2e
The playwright.config.ts automatically builds the UI, starts the Rust API
server on port 3000 (serving both the API and the static UI), runs tests, and
shuts down.
To run against an externally managed API (e.g. already running on port 3000 with the UI on a SvelteKit dev server at port 5173):
PLAYWRIGHT_BASE_URL=http://localhost:5173 npx playwright test
This skips the built-in webServer and points the tests at your UI origin.
The API is expected to be already running (at any port your UI proxies to).
Authentication
When --auth-enabled is set, the API uses OIDC (Authorization Code + PKCE) with httpOnly session cookies.
Flow:
- User clicks "Sign in with SSO" on the login page
- Browser is redirected to
/api/v1/auth/oidc/start, which generates a PKCE challenge and redirects to the OIDC provider - Provider authenticates the user and redirects back to
/api/v1/auth/oidc/callback - Server exchanges the authorization code (with PKCE verifier), fetches userinfo, creates a session, sets an httpOnly cookie, and redirects to
/ - All subsequent requests include the session cookie automatically
When --auth-enabled is false (default), all routes are accessible without authentication.
Docker
docker compose up --build
Runs the API on port 3000 with persistent storage. Set FFDC_AUTH_ENABLED=true and OIDC environment variables in compose.yml for auth.
GDB coredump analysis
The gdb Cargo feature adds automated coredump analysis: backtrace, crashing
thread, signal, and register state. Analysis requires a Yocto cross-GDB
binary and the matching target sysroot for debug-symbol resolution.
Because BMC hardware families use different CPU architectures (ARM 32-bit, AArch64, RISC-V, …), each with its own SDK, the server supports multiple SDKs simultaneously. The correct SDK is selected automatically based on the archive filename.
How SDK selection works
After the archive is parsed, the server reads the dump's own metadata to pick the right SDK, not the archive filename (which is unreliable)!
| Dump field | Source file | Example value |
|---|---|---|
hostnamectl.architecture |
hostnamectl output |
"arm", "arm64" |
hostnamectl.hostname |
hostnamectl output |
"rainier-bmc" |
os-release.TARGET_MACHINE |
os-release |
"ast2600" |
Selection rules:
- Each SDK entry is checked in order. Entries with at least one non-empty criterion are checked first (specific match).
architecturesmust matchhostnamectl.architecture(substring, case-insensitive).machinesmust matchTARGET_MACHINEorhostname(substring, case-insensitive).- Both criteria are ANDed. An empty list means "any" for that criterion.
- If no specific entry matches, the first catch-all entry (both lists empty) is used.
- If still no match, the
on_no_matchpolicy applies:"skip"(default),"warn","error".
If automatic selection picks the wrong SDK (e.g. because metadata is missing), the user can override it explicitly. See Manual SDK override below.
1. Create an SDK config file
Copy the example and fill in the real paths for your SDKs:
cp config/sdks.example.json config/sdks.json
{
"sdks": [
{
"name": "ARM Cortex-A (AST2600 / AST2500)",
"gdb_path": "/sdks/arm/sysroots/x86_64-pokysdk-linux/usr/bin/arm-poky-linux-gnueabi/arm-poky-linux-gnueabi-gdb",
"sysroot": "/sdks/arm/sysroots/cortexa7t2hf-neon-poky-linux-gnueabi",
"architectures": ["arm"],
"machines": ["ast2600", "ast2500", "rainier", "everest"]
},
{
"name": "AArch64 (AST2700)",
"gdb_path": "/sdks/aarch64/sysroots/x86_64-pokysdk-linux/usr/bin/aarch64-poky-linux/aarch64-poky-linux-gdb",
"sysroot": "/sdks/aarch64/sysroots/cortexa35-poky-linux",
"architectures": ["arm64", "aarch64"],
"machines": ["ast2700", "bletchley"]
}
],
"on_no_match": "skip"
}
Fields:
| Field | Required | Description |
|---|---|---|
name |
yes | Unique label; used in logs and for manual override |
gdb_path |
yes | Absolute path to the cross-GDB binary inside the SDK |
sysroot |
no | Absolute path to the target sysroot (enables debug-symbol loading) |
architectures |
no | hostnamectl.architecture substrings; empty means any arch |
machines |
no | TARGET_MACHINE or hostname substrings; empty means any machine |
on_no_match values: "skip" (default) · "warn" · "error"
Manual SDK override
If no SDK matched (GDB analysis was skipped) or the wrong SDK was selected, trigger a re-parse with an explicit SDK name:
# 1. List available SDKs
curl http://localhost:3000/api/v1/sdks
# 2. Re-parse with a specific SDK
curl -X POST http://localhost:3000/api/v1/ffdc/<uuid>/reparse \
-H 'Content-Type: application/json' \
-d '{"sdk": "ARM Cortex-A (AST2600 / AST2500)"}'
The sdk field must match the name of an entry in your SDK config exactly.
Omit the body (or omit the sdk field) to use automatic selection.
2. Build and run
Place your Yocto SDK installer scripts in the repo root, then use the
compose.gdb.yml overlay which switches the build to Dockerfile.gdb:
cp config/sdks.example.json config/sdks.json # fill in your paths
docker compose -f compose.yml -f compose.gdb.yml up --build
compose.gdb.yml sets FFDC_SDK_CONFIG=/sdks/sdks.json. Bake your
sdks.json into the image by placing it in the repo root and adding a
COPY sdks.json /sdks/sdks.json line to Dockerfile.gdb, or mount it at
runtime:
docker run -p 3000:3000 \
-v /path/to/data:/data \
-v $(pwd)/config/sdks.json:/sdks/sdks.json:ro \
-e FFDC_SDK_CONFIG=/sdks/sdks.json \
ffdc-api-gdb
See the comments inside Dockerfile.gdb for SDK installer filenames and
install-path constraints.
Single-target setup
If you only have one architecture, create a sdks.json with a single entry
and leave both architectures and machines empty so it acts as a catch-all:
{
"sdks": [
{
"name": "ARM Cortex-A",
"gdb_path": "/sdks/arm/sysroots/x86_64-pokysdk-linux/usr/bin/arm-poky-linux-gnueabi/arm-poky-linux-gnueabi-gdb",
"sysroot": "/sdks/arm/sysroots/cortexa7t2hf-neon-poky-linux-gnueabi",
"architectures": [],
"machines": []
}
],
"on_no_match": "skip"
}
Development
Build
Each crate builds independently:
cargo build -p ffdc-core
cargo build -p ffdc-api
cargo build --workspace
Test & Lint
cargo test
cargo clippy --workspace
cargo doc --no-deps --document-private-items