Skip to content

Commit a0f9ab7

Browse files
committed
feat: support perf pipe data format parsing
1 parent 031dd29 commit a0f9ab7

File tree

8 files changed

+629
-53
lines changed

8 files changed

+629
-53
lines changed

README.md

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,16 @@ records" from perf / simpleperf.
1515
This crate also contains parsing code for jitdump files, which are used
1616
in conjunction with perf.data files when profiling JIT runtimes.
1717

18-
# Example
18+
## File Modes
19+
20+
This crate supports two modes for reading perf.data files:
21+
22+
- **File mode** (`parse_file`) - For reading regular perf.data files from disk. Requires `Read + Seek`.
23+
- **Pipe mode** (`parse_pipe`) - For streaming perf.data from pipes, stdin, or network streams. Only requires `Read`.
24+
25+
# Examples
26+
27+
## File Mode Example
1928

2029
```rust
2130
use linux_perf_data::{AttributeDescription, PerfFileReader, PerfFileRecord};
@@ -43,6 +52,40 @@ while let Some(record) = record_iter.next_record(&mut perf_file)? {
4352
}
4453
```
4554

55+
## Pipe Mode Example
56+
57+
Read perf.data from stdin or a pipe (no seeking required):
58+
59+
```rust
60+
use linux_perf_data::{PerfFileReader, PerfFileRecord};
61+
62+
// Read from stdin
63+
let stdin = std::io::stdin();
64+
let PerfFileReader { mut perf_file, mut record_iter } = PerfFileReader::parse_pipe(stdin)?;
65+
66+
println!("Events: {}", perf_file.event_attributes().len());
67+
68+
while let Some(record) = record_iter.next_record(&mut perf_file)? {
69+
match record {
70+
PerfFileRecord::EventRecord { attr_index, record } => {
71+
// Process event records
72+
}
73+
PerfFileRecord::UserRecord(record) => {
74+
// Process user records
75+
}
76+
}
77+
}
78+
```
79+
80+
**Command-line usage:**
81+
```bash
82+
# Stream directly from perf record
83+
perf record -o - sleep 1 | cargo run --example perfpipeinfo
84+
85+
# Or pipe an existing file
86+
cat perf.data | cargo run --example perfpipeinfo
87+
```
88+
4689
## Jitdump example
4790

4891
```rust

examples/perfpipeinfo.rs

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
//! Example: Read and analyze perf.data from stdin in pipe mode
2+
//!
3+
//! This demonstrates the `parse_pipe()` API which works with streams (Read only).
4+
//! Compare with perfdatainfo.rs which uses `parse_file()` (requires Read + Seek).
5+
//!
6+
//! Usage:
7+
//! # Stream directly from perf record:
8+
//! perf record -o - sleep 1 | cargo run --example perfpipeinfo
9+
//!
10+
//! # Or pipe an existing file:
11+
//! cat perf.data | cargo run --example perfpipeinfo
12+
//!
13+
//! # Or from a network stream:
14+
//! nc server 1234 | cargo run --example perfpipeinfo
15+
16+
use std::collections::HashMap;
17+
18+
use linux_perf_data::{PerfFileReader, PerfFileRecord};
19+
#[allow(unused)]
20+
use linux_perf_event_reader::RecordType;
21+
22+
fn main() {
23+
let stdin = std::io::stdin();
24+
let PerfFileReader {
25+
mut perf_file,
26+
mut record_iter,
27+
} = match PerfFileReader::parse_pipe(stdin) {
28+
Ok(reader) => reader,
29+
Err(e) => {
30+
println!("ERROR when creating PerfFileReader: {:?}", e);
31+
return;
32+
}
33+
};
34+
35+
if let Ok(Some(arch)) = perf_file.arch() {
36+
println!("Arch: {arch}");
37+
}
38+
if let Ok(Some(cmdline)) = perf_file.cmdline() {
39+
println!("CmdLine: {cmdline:?}");
40+
}
41+
if let Ok(Some(cpu_desc)) = perf_file.cpu_desc() {
42+
println!("CPU Desc: {cpu_desc}");
43+
}
44+
if let Ok(Some(perf_version)) = perf_file.perf_version() {
45+
println!("Perf version: {perf_version}");
46+
}
47+
if let Ok(Some(frequency)) = perf_file.clock_frequency() {
48+
println!("Clock frequency: {frequency} ns per tick");
49+
}
50+
if let Ok(Some(clock_data)) = perf_file.clock_data() {
51+
println!("Clock data: {clock_data:?}");
52+
}
53+
54+
// Print the feature sections.
55+
let features = perf_file.features();
56+
let features: String = features
57+
.iter()
58+
.map(|f| format!("{f}"))
59+
.collect::<Vec<_>>()
60+
.join(", ");
61+
println!("Features: {features}");
62+
println!();
63+
if let Ok(Some(simpleperf_meta_info)) = perf_file.simpleperf_meta_info() {
64+
println!("Simpleperf meta info:");
65+
for (k, v) in simpleperf_meta_info {
66+
println!(" {k}: {v}");
67+
}
68+
println!();
69+
}
70+
if let Ok(Some(simpleperf_file_symbols)) = perf_file.simpleperf_symbol_tables() {
71+
println!("Simpleperf symbol tables for the following files:");
72+
for f in &simpleperf_file_symbols {
73+
println!(" - {}", f.path);
74+
// println!("{f:#?}");
75+
}
76+
println!();
77+
}
78+
79+
// for event in perf_file.event_attributes() {
80+
// println!("Event: {event:#?}");
81+
// }
82+
83+
let mut event_record_map = HashMap::new();
84+
let mut user_record_map = HashMap::new();
85+
86+
while let Some(record) = record_iter.next_record(&mut perf_file).unwrap() {
87+
match record {
88+
PerfFileRecord::EventRecord { attr_index, record } => {
89+
let record_type = record.record_type;
90+
*event_record_map
91+
.entry(attr_index)
92+
.or_insert_with(HashMap::new)
93+
.entry(record_type)
94+
.or_insert(0) += 1;
95+
match record.parse() {
96+
Ok(parsed_record) => {
97+
// let is_interesting = matches!(record_type, RecordType::FORK | RecordType::COMM | RecordType::MMAP| RecordType::MMAP2);
98+
let is_interesting = false;
99+
if !is_interesting {
100+
continue;
101+
}
102+
103+
if let Some(timestamp) =
104+
record.common_data().ok().and_then(|cd| cd.timestamp)
105+
{
106+
println!(
107+
"{:?} at {} for event {}: {:?}",
108+
record_type, timestamp, attr_index, parsed_record
109+
);
110+
} else {
111+
println!(
112+
"{:?} for event {}: {:?}",
113+
record_type, attr_index, parsed_record
114+
);
115+
}
116+
}
117+
Err(e) => {
118+
println!(
119+
"ERROR when parsing {:?} for event {}: {:?}",
120+
record_type, attr_index, e
121+
);
122+
}
123+
}
124+
}
125+
PerfFileRecord::UserRecord(record) => {
126+
let record_type = record.record_type;
127+
*user_record_map.entry(record_type).or_insert(0) += 1;
128+
match record.parse() {
129+
Ok(_parsed_record) => {
130+
// println!("{:?}: {:?}", record_type, parsed_record);
131+
}
132+
Err(e) => {
133+
println!("ERROR when parsing {:?}: {:?}", record_type, e);
134+
}
135+
}
136+
}
137+
}
138+
}
139+
140+
let mut event_record_map = event_record_map
141+
.into_iter()
142+
.map(|(attr_index, histogram)| {
143+
let sum = histogram.values().sum::<u64>();
144+
(attr_index, histogram, sum)
145+
})
146+
.collect::<Vec<_>>();
147+
event_record_map.sort_by_key(|(_attr_index, _histogram, sum)| -(*sum as i64));
148+
let sum = event_record_map
149+
.iter()
150+
.map(|(_attr_index, _histogram, sum)| sum)
151+
.sum::<u64>();
152+
153+
println!("Event records: {sum} records");
154+
println!();
155+
156+
for (attr_index, record_counts, sum) in event_record_map {
157+
let mut record_counts = record_counts.into_iter().collect::<Vec<_>>();
158+
record_counts.sort_by_key(|(_record_type, count)| -(*count as i64));
159+
println!(
160+
" event {} ({}): {} records",
161+
attr_index,
162+
perf_file.event_attributes()[attr_index]
163+
.name()
164+
.unwrap_or("<no event name found>"),
165+
sum
166+
);
167+
for (record_type, count) in record_counts {
168+
println!(" {:?}: {}", record_type, count);
169+
}
170+
println!();
171+
}
172+
173+
let mut user_record_counts = user_record_map.into_iter().collect::<Vec<_>>();
174+
user_record_counts.sort_by_key(|(_record_type, count)| -(*count as i64));
175+
let sum = user_record_counts
176+
.iter()
177+
.map(|(_record_type, count)| count)
178+
.sum::<u64>();
179+
180+
println!("User records: {sum} records");
181+
println!();
182+
for (record_type, count) in user_record_counts {
183+
println!(" {:?}: {}", record_type, count);
184+
}
185+
}

src/constants.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ pub const PERF_RECORD_EVENT_UPDATE: u32 = 78;
1818
pub const PERF_RECORD_TIME_CONV: u32 = 79;
1919
pub const PERF_RECORD_HEADER_FEATURE: u32 = 80;
2020
pub const PERF_RECORD_COMPRESSED: u32 = 81;
21+
pub const PERF_RECORD_FINISHED_INIT: u32 = 82;
2122

2223
// pub const SIMPLE_PERF_RECORD_TYPE_START: u32 = 32768;
2324

src/features.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pub const HEADER_AUXTRACE: u32 = 18;
2121
pub const HEADER_STAT: u32 = 19;
2222
pub const HEADER_CACHE: u32 = 20;
2323
pub const HEADER_SAMPLE_TIME: u32 = 21;
24-
pub const HEADER_SAMPLE_TOPOLOGY: u32 = 22;
24+
pub const HEADER_MEM_TOPOLOGY: u32 = 22;
2525
pub const HEADER_CLOCKID: u32 = 23;
2626
pub const HEADER_DIR_FORMAT: u32 = 24;
2727
pub const HEADER_BPF_PROG_INFO: u32 = 25;
@@ -31,6 +31,7 @@ pub const HEADER_CPU_PMU_CAPS: u32 = 28;
3131
pub const HEADER_CLOCK_DATA: u32 = 29;
3232
pub const HEADER_HYBRID_TOPOLOGY: u32 = 30;
3333
pub const HEADER_HYBRID_CPU_PMU_CAPS: u32 = 31;
34+
pub const HEADER_PMU_CAPS: u32 = 32;
3435

3536
/// simpleperf `FEAT_FILE`
3637
pub const HEADER_SIMPLEPERF_FILE: u32 = 128;
@@ -75,7 +76,7 @@ impl Feature {
7576
pub const STAT: Self = Self(HEADER_STAT);
7677
pub const CACHE: Self = Self(HEADER_CACHE);
7778
pub const SAMPLE_TIME: Self = Self(HEADER_SAMPLE_TIME);
78-
pub const SAMPLE_TOPOLOGY: Self = Self(HEADER_SAMPLE_TOPOLOGY);
79+
pub const MEM_TOPOLOGY: Self = Self(HEADER_MEM_TOPOLOGY);
7980
pub const CLOCKID: Self = Self(HEADER_CLOCKID);
8081
pub const DIR_FORMAT: Self = Self(HEADER_DIR_FORMAT);
8182
pub const BPF_PROG_INFO: Self = Self(HEADER_BPF_PROG_INFO);
@@ -85,6 +86,7 @@ impl Feature {
8586
pub const CLOCK_DATA: Self = Self(HEADER_CLOCK_DATA);
8687
pub const HYBRID_TOPOLOGY: Self = Self(HEADER_HYBRID_TOPOLOGY);
8788
pub const HYBRID_CPU_PMU_CAPS: Self = Self(HEADER_HYBRID_CPU_PMU_CAPS);
89+
pub const PMU_CAPS: Self = Self(HEADER_PMU_CAPS);
8890
pub const SIMPLEPERF_FILE: Self = Self(HEADER_SIMPLEPERF_FILE);
8991
pub const SIMPLEPERF_META_INFO: Self = Self(HEADER_SIMPLEPERF_META_INFO);
9092
pub const SIMPLEPERF_DEBUG_UNWIND: Self = Self(HEADER_SIMPLEPERF_DEBUG_UNWIND);
@@ -116,7 +118,7 @@ impl fmt::Display for Feature {
116118
Self::STAT => "STAT".fmt(f),
117119
Self::CACHE => "CACHE".fmt(f),
118120
Self::SAMPLE_TIME => "SAMPLE_TIME".fmt(f),
119-
Self::SAMPLE_TOPOLOGY => "SAMPLE_TOPOLOGY".fmt(f),
121+
Self::MEM_TOPOLOGY => "MEM_TOPOLOGY".fmt(f),
120122
Self::CLOCKID => "CLOCKID".fmt(f),
121123
Self::DIR_FORMAT => "DIR_FORMAT".fmt(f),
122124
Self::BPF_PROG_INFO => "BPF_PROG_INFO".fmt(f),
@@ -126,6 +128,7 @@ impl fmt::Display for Feature {
126128
Self::CLOCK_DATA => "CLOCK_DATA".fmt(f),
127129
Self::HYBRID_TOPOLOGY => "HYBRID_TOPOLOGY".fmt(f),
128130
Self::HYBRID_CPU_PMU_CAPS => "HYBRID_CPU_PMU_CAPS".fmt(f),
131+
Self::PMU_CAPS => "PMU_CAPS".fmt(f),
129132
Self::SIMPLEPERF_FILE => "SIMPLEPERF_FILE".fmt(f),
130133
Self::SIMPLEPERF_META_INFO => "SIMPLEPERF_META_INFO".fmt(f),
131134
Self::SIMPLEPERF_DEBUG_UNWIND => "SIMPLEPERF_DEBUG_UNWIND".fmt(f),

0 commit comments

Comments
 (0)