Skip to content

Commit 6db6330

Browse files
authored
Merge pull request #28 from tinythings/isbm-perfetto-types
Cover all perfetto types
2 parents 6816d2d + ac5fcca commit 6db6330

14 files changed

Lines changed: 1021 additions & 263 deletions

File tree

demo/perfetto/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44

55
```bash
66
# From workspace root — downloads deps and builds all needed tools.
7-
./scripts/build-perfetto.sh
7+
./demo/perfetto/build-perfetto.sh
88
```
99

1010
Or pass a custom source path:
1111
```bash
12-
./scripts/build-perfetto.sh /path/to/perfetto
12+
./demo/perfetto/build-perfetto.sh /path/to/perfetto
1313
```
1414

1515
The script installs missing system packages (git, python3, curl, tar), downloads

demo/perfetto/linux-data-record/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ the trace processor for interactive inspection.
88
Build the Perfetto tools (from workspace root):
99

1010
```bash
11-
./scripts/build-perfetto.sh
11+
./demo/perfetto/build-perfetto.sh
1212
```
1313

1414
The script finds tools via `PERFETTO_OUT` (default: `perfetto/out/linux_release`).

demo/perfetto/linux-data-record/run-demo.sh

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ echo "Recording 5s of ftrace to $PERFETTO_TRACE_OUT..."
4545
CONFIG_FILE="$SCRIPT_DIR/trace-config.txt"
4646
cat > "$CONFIG_FILE" <<'ENDCONFIG'
4747
buffers: {
48-
size_kb: 4096
48+
size_kb: 8192
4949
fill_policy: RING_BUFFER
5050
}
5151
data_sources: {
@@ -54,6 +54,9 @@ data_sources: {
5454
ftrace_config {
5555
ftrace_events: "sched/sched_switch"
5656
ftrace_events: "sched/sched_waking"
57+
ftrace_events: "sched/sched_process_exec"
58+
ftrace_events: "sched/sched_process_fork"
59+
ftrace_events: "power/cpu_frequency"
5760
}
5861
}
5962
}
-1.09 KB
Binary file not shown.

demo/perfetto/perfetto-to-logjet/README.md

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ plugin into a `.logjet` spool, and view the result in `ljx view`.
88
```bash
99
# From workspace root
1010
make dev
11-
./scripts/build-perfetto.sh
11+
./demo/perfetto/build-perfetto.sh
1212
```
1313

1414
## Run
@@ -23,25 +23,30 @@ Requires sudo for ftrace access.
2323
## What Happens
2424

2525
1. `traced` + `traced_probes` start in the background.
26-
2. `tracebox` records 5s of scheduler events (CPU switches) via ftrace.
26+
2. `tracebox` records 5s of scheduler events (CPU switches, process lifecycle,
27+
CPU frequency, interrupts) via ftrace.
2728
3. `ljd` loads the perfetto-ingest plugin, which spawns `trace_processor`,
28-
exports the trace as SQLite, maps `sched_slice` rows to OTel log records
29-
with CPU/state/duration, and streams them into a `.logjet` spool.
30-
4. `ljx view` opens the spool — each CPU scheduling event appears as one line.
29+
exports the trace as SQLite, maps every Perfetto table to OTel log records
30+
(sched slices, thread states, ftrace events, spurious wakeups, instant
31+
events, counters), and streams them into a `.logjet` spool.
32+
4. `ljx view` opens the spool.
3133

3234
## What You Should See
3335

34-
- Thousands of log lines, each showing a CPU scheduling event:
36+
- Thousands of log lines across multiple types:
3537
```
36-
May 7 10:43:15 I cpu=7 dur=7.2us state=R utid=19 ts=...
37-
May 7 10:43:15 I cpu=7 dur=2.0us state=R utid=21 ts=...
38+
cpu=7 state=R utid=19 dur=7.2us ← sched_slice
39+
state=S dur=12.3us utid=3 cpu=1 ← thread_state
40+
sched_switch cpu=5 ← ftrace_event
41+
spurious_wakeup utid=1 ← spurious_wakeup
3842
```
39-
- Press `Enter` to see full OTel attributes (perfetto.sched.id, cpu, end_state).
40-
- Press `F` for field filter, `/` to search, `q` to quit.
43+
- Press `Enter` to see full OTel attributes for each record.
44+
- Press `F` for field filter (e.g. filter by `perfetto.sched.cpu` to see only one CPU).
45+
- `/` to search, `q` to quit.
4146

4247
## Troubleshooting
4348

4449
- **0 records**: The trace needs ftrace events — they require root. The script
4550
uses `sudo tracebox`. If passwordless sudo isn't configured, run `sudo ./run-demo.sh`.
46-
- **Fewer records than expected in ljx view**: Delete stale index cache:
47-
`rm -rf ~/.cache/ljx && ./run-demo.sh`
51+
- **ljx view shows fewer records than expected**: The ljx index builder bug was fixed
52+
in this PR. If you still see fewer records, delete `~/.cache/ljx/` and re-run.

demo/perfetto/perfetto-to-logjet/run-demo.sh

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ done
2727
for bin in "$TRACED" "$TRACED_PROBES" "$TRACEBOX" "$TP"; do
2828
if [ ! -x "$bin" ]; then
2929
echo "missing $bin"
30-
echo "build perfetto first with: ./scripts/build-perfetto.sh"
30+
echo "build perfetto first with: ./demo/perfetto/build-perfetto.sh"
3131
exit 1
3232
fi
3333
done
@@ -57,7 +57,7 @@ echo "Recording 5s of ftrace to $TRACE_FILE..."
5757
CONFIG_FILE="$SCRIPT_DIR/trace-config.txt"
5858
cat > "$CONFIG_FILE" <<'ENDCONFIG'
5959
buffers: {
60-
size_kb: 4096
60+
size_kb: 8192
6161
fill_policy: RING_BUFFER
6262
}
6363
data_sources: {
@@ -66,7 +66,13 @@ data_sources: {
6666
ftrace_config {
6767
ftrace_events: "sched/sched_switch"
6868
ftrace_events: "sched/sched_waking"
69+
ftrace_events: "sched/sched_process_exec"
70+
ftrace_events: "sched/sched_process_fork"
71+
ftrace_events: "sched/sched_process_exit"
6972
ftrace_events: "power/cpu_frequency"
73+
ftrace_events: "power/cpu_idle"
74+
ftrace_events: "irq/irq_handler_entry"
75+
ftrace_events: "irq/irq_handler_exit"
7076
}
7177
}
7278
}
@@ -119,27 +125,30 @@ LJD_PID=$!
119125
cleanup_ljd() {
120126
kill "$LJD_PID" 2>/dev/null || true
121127
wait "$LJD_PID" 2>/dev/null || true
128+
rm -f "$CONFIG_FILE"
122129
}
123130

124131
trap cleanup_ljd EXIT INT TERM
125132

126-
# Give the plugin time to finish processing (SQLite export + mapping takes a few seconds).
127-
sleep 10
133+
# Poll until records appear (plugin finishes), up to 60s.
134+
echo "Waiting for import..."
135+
elapsed=0
136+
while [ "$elapsed" -lt 60 ]; do
137+
if [ -f "$SPOOL_DIR/perfetto.logjet" ]; then
138+
COUNT=$("$LJX" count "$SPOOL_DIR/perfetto.logjet" 2>/dev/null || echo "0")
139+
if [ "$COUNT" -gt 0 ] 2>/dev/null; then
140+
echo "Imported $COUNT records into $SPOOL_DIR/perfetto.logjet"
141+
break
142+
fi
143+
fi
144+
sleep 1
145+
elapsed=$((elapsed + 1))
146+
done
147+
128148
kill "$LJD_PID" 2>/dev/null || true
129149
wait "$LJD_PID" 2>/dev/null || true
130150
trap - EXIT INT TERM
131151

132-
rm -f "$CONFIG_FILE"
133-
134-
if [ ! -f "$SPOOL_DIR/perfetto.logjet" ]; then
135-
echo "No .logjet file produced."
136-
exit 1
137-
fi
138-
139-
RECORDS=$("$LJX" count "$SPOOL_DIR/perfetto.logjet" | tail -1)
140-
echo "Imported $RECORDS records into $SPOOL_DIR/perfetto.logjet"
141-
echo ""
142-
143152
# ── View the result ───────────────────────────────────────────────────────────
144153

145154
echo "Opening ljx view..."

doc/perfetto-ingest.md

Lines changed: 57 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,111 @@
11
# Perfetto Ingest Plugin (`lj-perfetto-ingest`)
22

33
Imports Perfetto trace files (`.pftrace` / `.perfetto-trace`) into the logjet
4-
ecosystem as OTel traces, metrics, logs, and events.
4+
ecosystem as OTel logs, traces, and metrics.
55

66
## Architecture
77

88
```
99
.pftrace ──→ trace_processor (spawned as subprocess)
10-
├── export sqlite ──→ sqlite_reader ──→ trace_mapper ──→ OTel spans
10+
├── export sqlite ──→ sqlite_reader ──→ trace_mapper ──→ OTel spans
1111
└── --run-metrics ──→ metrics_reader ──→ metric_mapper ──→ OTel metrics
12-
log_mapper ──→ OTel logs
12+
log_mapper ──→ OTel logs
13+
14+
15+
buffer & sort by ts
1316
1417
1518
ljd spool (.logjet)
1619
```
1720

1821
The plugin is an **active source** (`mode: 1`). ljd calls `lj_ingest_fetch()` once,
19-
which runs the full pipeline and streams OTel payloads through the generic record
20-
callback.
22+
which runs the full pipeline. All records from traces, logs, and metrics are
23+
collected, sorted by timestamp, then streamed through the generic record callback
24+
to guarantee monotonic timestamps in the logjet block format.
2125

2226
## Requirements
2327

2428
- Perfetto trace processor binary (`trace_processor` or `trace_processor_shell`).
2529
Build it from the bundled Perfetto source:
2630
```bash
27-
./scripts/build-perfetto.sh
31+
./demo/perfetto/build-perfetto.sh
2832
```
2933
- A `.pftrace` trace file to import.
3034

3135
## Usage
3236

3337
```bash
3438
# Build the plugin and ljd:
35-
make build
39+
make dev
40+
41+
# Create a config file (ljd uses YAML config, not CLI flags):
42+
cat > /tmp/perfetto.conf <<EOF
43+
output: file
44+
file.path: ./spool
45+
file.size: 10mb
46+
file.name: perfetto.logjet
47+
ingest.protocol: plugin
48+
ingest.plugin-path: ./target/debug/liblj_perfetto_ingest.so
49+
EOF
3650

3751
# Run the import:
3852
LJD_PERFETTO_TRACE_FILE=/path/to/trace.pftrace \
3953
LJD_PERFETTO_TRACE_PROCESSOR=/path/to/trace_processor_shell \
40-
ljd serve \
41-
--ingest-protocol plugin \
42-
--ingest-plugin perfetto \
43-
--storage ./otel-spool
54+
ljd serve --config /tmp/perfetto.conf
4455
```
4556

57+
See `demo/perfetto/perfetto-to-logjet/run-demo.sh` for a complete end-to-end
58+
example that records, imports, and opens the result in `ljx view`.
59+
4660
## Environment Variables
4761

4862
| Variable | Required | Default | Description |
4963
|----------|----------|---------|-------------|
5064
| `LJD_PERFETTO_TRACE_FILE` | **Yes** || Path to the `.pftrace` input file. |
5165
| `LJD_PERFETTO_TRACE_PROCESSOR` | No | PATH search | Path to `trace_processor_shell` binary. |
5266
| `LJD_PERFETTO_TIMESTAMP_POLICY` | No | `best-effort` | `best-effort` or `require-realtime`. |
53-
| `LJD_PERFETTO_METRICS` | No | (none) | Comma-separated metric names to run, e.g. `trace_stats,android_startup`. |
67+
| `LJD_PERFETTO_METRICS` | No | (none) | Comma-separated metric names to run, e.g. `trace_stats`. |
5468

55-
## Output Signals
69+
## Covered Perfetto Types
5670

57-
| Perfetto Source | OTel Signal | Record Type |
58-
|-----------------|-------------|-------------|
59-
| `slice` table | Traces (Spans) | `Traces` |
60-
| Metrics JSON | Metrics (Gauges) | `Metrics` |
61-
| Analysis summary | Logs | `Logs` |
62-
| (reserved) | Events | `Events` |
71+
Every table in the exported SQLite DB has a typed model and DB reader. Types
72+
with data are mapped to OTel log records with structured attributes:
6373

64-
Spans are batched in groups of 200 per OTLP export request.
74+
| Perfetto Table | OTel Signal | Attributes |
75+
|---------------|-------------|------------|
76+
| `sched_slice` | Logs | cpu, end_state, dur_ns |
77+
| `thread_state` | Logs | state, dur_ns, cpu, io_wait, blocked_function |
78+
| `ftrace_event` | Logs | name, cpu, utid |
79+
| `spurious_sched_wakeup` | Logs | utid, waker_utid |
80+
| `instant` | Logs | name, track_id |
81+
| `slice` | Traces + Logs | name, dur_ns, depth, parent_id |
82+
| `counter` | Metrics (planned) | value |
83+
| `process`, `thread`, `cpu`, `machine`, `metadata`, `args`, `clock_snapshot` | Metadata | used internally |
84+
| `flow`, `heap_*`, `stack_*`, `memory_*`, `protolog`, `android_logs`, `filedescriptor` | Models ready | not yet mapped |
85+
86+
Each log record carries integer attributes (`perfetto.sched.dur_ns`, etc.) for
87+
structured downstream consumption alongside a human-readable body.
6588

6689
## Timestamp Policy
6790

6891
Perfetto timestamps are trace-clock values (typically `CLOCK_MONOTONIC`). The plugin
6992
converts them to Unix epoch nanoseconds using `clock_snapshot` REALTIME entries.
7093

71-
- **best-effort** (default): Spans without realtime data are skipped. Spans before
72-
the first snapshot are extrapolated backwards.
94+
- **best-effort** (default): Spans without realtime data use extrapolation.
95+
Spans before the first snapshot are extrapolated backwards.
7396
- **require-realtime**: The pipeline fails if any span cannot be converted.
7497

98+
Records from all mappers are collected into a buffer, sorted by timestamp, then
99+
emitted sequentially. This guarantees monotonicity within logjet blocks even when
100+
different mapper types produce interleaved time ranges.
101+
75102
## Limitations
76103

77-
- **No flow-to-link mapping**: `flow` table entries are read but not yet mapped
78-
to OTel span links.
79-
- **No args-to-attributes mapping**: Per-slice key-value arguments are read but
80-
not attached to spans.
81-
- **Thread/process context**: Thread and process names are loaded but not fully
82-
joined to spans via track relationships.
83-
- **Replay/bridge**: Traces and metrics stored in `.logjet` can be exported via
84-
`ljx export` but are not yet forwarded by `ljd bridge/replay` (which currently
85-
only forwards logs).
86-
- **Metrics**: Only scalar metric values are supported (no histograms).
87-
- **Event signal**: The `Events` record type is reserved but not yet generated
88-
by this plugin.
104+
- **Replay/bridge**: Only logs are forwarded by `ljd bridge/replay`. Traces and
105+
metrics stored in `.logjet` can be exported via `ljx export` (parquet).
106+
- **ljx view**: Only decodes `ExportLogsServiceRequest` — trace/metric records
107+
appear as binary data in the detail pane. Trace mapping is disabled by default.
108+
- **Trace span emission**: Currently disabled (ljx can't render it). Enable by
109+
uncommenting `trace_mapper::map_traces` in `lib.rs`.
110+
- **Histograms**: Metrics only support scalar gauge values.
111+
- **Event signal**: The `Events` record type is reserved but not yet generated.

ljx/src/commands/view/render.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -680,7 +680,8 @@ impl ViewApp {
680680
};
681681

682682
let body_height = filtered_sev.len().max(filtered_svc.len()).max(1) as u16;
683-
let popup_h = (body_height + 4).clamp(20, screen.height * 60 / 100);
683+
let max_h = (screen.height * 60 / 100).max(20);
684+
let popup_h = (body_height + 4).min(max_h);
684685
let popup_w = (screen.width * 60 / 100).max(40);
685686
let area = Rect::new(screen.width.saturating_sub(popup_w) / 2, screen.height * 20 / 100, popup_w, popup_h);
686687
frame.render_widget(Clear, area);

logjetd/src/plugin.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -672,7 +672,9 @@ unsafe extern "C" fn on_generic_record(user: *mut c_void, record: *const LjInges
672672
SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_nanos() as u64
673673
};
674674

675-
let wire = WireRecord { record_type, seq: ctx.next_seq.fetch_add(1, Ordering::Relaxed), ts_unix_ns: ts, payload };
675+
let seq = ctx.next_seq.fetch_add(1, Ordering::Relaxed);
676+
677+
let wire = WireRecord { record_type, seq, ts_unix_ns: ts, payload };
676678

677679
if let Err(err) = super::daemon::append_to_spool(&ctx.spool, wire) {
678680
eprintln!("ljd plugin callback spool error: {err}");

0 commit comments

Comments
 (0)