linux_perf_data/
perf_file.rs

1use byteorder::{BigEndian, LittleEndian};
2use linear_map::LinearMap;
3use linux_perf_event_reader::{CpuMode, Endianness};
4
5use std::collections::HashMap;
6use std::ops::Deref;
7
8use super::build_id_event::BuildIdEvent;
9use super::dso_info::DsoInfo;
10use super::dso_key::DsoKey;
11use super::error::Error;
12use super::feature_sections::{
13    AttributeDescription, ClockData, NrCpus, PmuMappings, SampleTimeRange,
14};
15use super::features::{Feature, FeatureSet};
16use super::simpleperf;
17
18/// Contains the information from the perf.data file header and feature sections.
19pub struct PerfFile {
20    pub(crate) endian: Endianness,
21    pub(crate) features: FeatureSet,
22    pub(crate) feature_sections: LinearMap<Feature, Vec<u8>>,
23    /// Guaranteed to have at least one element
24    pub(crate) attributes: Vec<AttributeDescription>,
25}
26
27impl PerfFile {
28    /// The attributes which were requested for each perf event, along with the IDs.
29    pub fn event_attributes(&self) -> &[AttributeDescription] {
30        &self.attributes
31    }
32    /// Returns a map of build ID entries. `perf record` creates these records for any DSOs
33    /// which it thinks have been "hit" in the profile. They supplement Mmap records, which
34    /// usually don't come with build IDs.
35    ///
36    /// This method returns a HashMap so that you can easily look up the right build ID from
37    /// the DsoKey in an Mmap event. For some DSOs, the path in the raw Mmap event can be
38    /// different from the path in the build ID record; for example, the Mmap event for the
39    /// kernel ("vmlinux") image could have the path "[kernel.kallsyms]_text", whereas the
40    /// corresponding build ID record might have the path "[kernel.kallsyms]" (without the
41    /// trailing "_text"), or it could even have the full absolute path to a vmlinux file.
42    /// The DsoKey canonicalizes those differences away.
43    ///
44    /// Having the build ID for a DSO allows you to do the following:
45    ///
46    ///  - If the DSO file has changed in the time since the perf.data file was captured,
47    ///    you can detect this change because the new file will have a different build ID.
48    ///  - If debug symbols are installed for the DSO, you can sometimes find the debug symbol
49    ///    file using the build ID. For example, you might find it at
50    ///    /usr/lib/debug/.build-id/b8/037b6260865346802321dd2256b8ad1d857e63.debug
51    ///  - If the original DSO file is gone, or you're trying to read the perf.data file on
52    ///    an entirely different machine, you can sometimes retrieve the original DSO file just
53    ///    from its build ID, for example from a debuginfod server.
54    ///  - This also works for DSOs which are not present on the file system at all;
55    ///    specifically, the vDSO file is a bit of a pain to obtain. With the build ID you can
56    ///    instead obtain it from, say,
57    ///    <https://debuginfod.elfutils.org/buildid/0d82ee4bd7f9609c367095ba0bedf155b71cb058/executable>
58    ///
59    /// This method is a bit lossy. We discard the pid, because it seems to be always -1 in
60    /// the files I've tested. We also discard any entries for which we fail to create a `DsoKey`.
61    pub fn build_ids(&self) -> Result<HashMap<DsoKey, DsoInfo>, Error> {
62        let section_data = match self.feature_section_data(Feature::BUILD_ID) {
63            Some(section) => section,
64            None => return Ok(HashMap::new()),
65        };
66        let mut cursor = section_data;
67        let mut build_ids = HashMap::new();
68        loop {
69            let event = match self.endian {
70                Endianness::LittleEndian => BuildIdEvent::parse::<_, LittleEndian>(&mut cursor),
71                Endianness::BigEndian => BuildIdEvent::parse::<_, BigEndian>(&mut cursor),
72            };
73            let event = match event {
74                Ok(e) => e,
75                Err(_) => break,
76            };
77            let misc = event.header.misc;
78            let path = event.file_path;
79            let build_id = event.build_id;
80            let dso_key = match DsoKey::detect(&path, CpuMode::from_misc(misc)) {
81                Some(dso_key) => dso_key,
82                None => continue,
83            };
84            build_ids.insert(dso_key, DsoInfo { path, build_id });
85        }
86        Ok(build_ids)
87    }
88
89    /// The timestamp of the first and the last sample in this file.
90    pub fn sample_time_range(&self) -> Result<Option<SampleTimeRange>, Error> {
91        let section_data = match self.feature_section_data(Feature::SAMPLE_TIME) {
92            Some(section) => section,
93            None => return Ok(None),
94        };
95        let time_range = match self.endian {
96            Endianness::LittleEndian => SampleTimeRange::parse::<_, LittleEndian>(section_data)?,
97            Endianness::BigEndian => SampleTimeRange::parse::<_, BigEndian>(section_data)?,
98        };
99        Ok(Some(time_range))
100    }
101
102    /// Only call this for features whose section is just a perf_header_string.
103    fn feature_string(&self, feature: Feature) -> Result<Option<&str>, Error> {
104        match self.feature_section_data(feature) {
105            Some(section) => Ok(Some(self.read_string(section)?.0)),
106            None => Ok(None),
107        }
108    }
109
110    /// The hostname where the data was collected (`uname -n`).
111    pub fn hostname(&self) -> Result<Option<&str>, Error> {
112        self.feature_string(Feature::HOSTNAME)
113    }
114
115    /// The OS release where the data was collected (`uname -r`).
116    pub fn os_release(&self) -> Result<Option<&str>, Error> {
117        self.feature_string(Feature::OSRELEASE)
118    }
119
120    /// The perf user tool version where the data was collected. This is the same
121    /// as the version of the Linux source tree the perf tool was built from.
122    pub fn perf_version(&self) -> Result<Option<&str>, Error> {
123        self.feature_string(Feature::VERSION)
124    }
125
126    /// The CPU architecture (`uname -m`).
127    pub fn arch(&self) -> Result<Option<&str>, Error> {
128        self.feature_string(Feature::ARCH)
129    }
130
131    /// A structure defining the number of CPUs.
132    pub fn nr_cpus(&self) -> Result<Option<NrCpus>, Error> {
133        self.feature_section_data(Feature::NRCPUS)
134            .map(|section| {
135                Ok(match self.endian {
136                    Endianness::LittleEndian => NrCpus::parse::<_, LittleEndian>(section),
137                    Endianness::BigEndian => NrCpus::parse::<_, BigEndian>(section),
138                }?)
139            })
140            .transpose()
141    }
142
143    /// The description of the CPU. On x86 this is the model name
144    /// from `/proc/cpuinfo`.
145    pub fn cpu_desc(&self) -> Result<Option<&str>, Error> {
146        self.feature_string(Feature::CPUDESC)
147    }
148
149    /// The exact CPU type. On x86 this is `vendor,family,model,stepping`.
150    /// For example: `GenuineIntel,6,69,1`
151    pub fn cpu_id(&self) -> Result<Option<&str>, Error> {
152        self.feature_string(Feature::CPUID)
153    }
154
155    /// If true, the data section contains data recorded from `perf stat record`.
156    pub fn is_stats(&self) -> bool {
157        self.features.has_feature(Feature::STAT)
158    }
159
160    /// The perf arg-vector used to collect the data.
161    pub fn cmdline(&self) -> Result<Option<Vec<&str>>, Error> {
162        match self.feature_section_data(Feature::CMDLINE) {
163            Some(section) => Ok(Some(self.read_string_list(section)?.0)),
164            None => Ok(None),
165        }
166    }
167
168    /// The total memory in kilobytes. (MemTotal from /proc/meminfo)
169    pub fn total_mem(&self) -> Result<Option<u64>, Error> {
170        let data = match self.feature_section_data(Feature::TOTAL_MEM) {
171            Some(data) => data,
172            None => return Ok(None),
173        };
174        if data.len() < 8 {
175            return Err(Error::FeatureSectionTooSmall);
176        }
177        let b = data;
178        let data = [b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]];
179        let mem = match self.endian {
180            Endianness::LittleEndian => u64::from_le_bytes(data),
181            Endianness::BigEndian => u64::from_be_bytes(data),
182        };
183        Ok(Some(mem))
184    }
185
186    /// The clock frequency for the clock in [`clock_data`] expressed as ns per tick.
187    pub fn clock_frequency(&self) -> Result<Option<u64>, Error> {
188        let data = match self.feature_section_data(Feature::CLOCKID) {
189            Some(data) => data,
190            None => return Ok(None),
191        };
192        if data.len() < 8 {
193            return Err(Error::FeatureSectionTooSmall);
194        }
195        let b = data;
196        let data = [b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]];
197        let mem = match self.endian {
198            Endianness::LittleEndian => u64::from_le_bytes(data),
199            Endianness::BigEndian => u64::from_be_bytes(data),
200        };
201        Ok(Some(mem))
202    }
203
204    /// A structure containing information about the clock
205    pub fn clock_data(&self) -> Result<Option<ClockData>, Error> {
206        self.feature_section_data(Feature::CLOCK_DATA)
207            .map(|section| {
208                Ok(match self.endian {
209                    Endianness::LittleEndian => ClockData::parse::<_, LittleEndian>(section),
210                    Endianness::BigEndian => ClockData::parse::<_, BigEndian>(section),
211                }?)
212            })
213            .transpose()
214    }
215
216    /// The meta info map, if this is a Simpleperf profile.
217    pub fn simpleperf_meta_info(&self) -> Result<Option<HashMap<&str, &str>>, Error> {
218        match self.feature_section_data(Feature::SIMPLEPERF_META_INFO) {
219            Some(section) => Ok(Some(simpleperf::parse_meta_info_map(section)?)),
220            None => Ok(None),
221        }
222    }
223
224    /// Symbol tables from Simpleperf.
225    ///
226    /// `perf.data` files from simpleperf come with a `FILE2` section which contains,
227    /// for each DSO that was hit by a stack frame, the symbol table from the file
228    /// as present on the device.
229    pub fn simpleperf_symbol_tables(
230        &self,
231    ) -> Result<Option<Vec<simpleperf::SimpleperfFileRecord>>, Error> {
232        if let Some(section) = self.feature_section_data(Feature::SIMPLEPERF_FILE2) {
233            return Ok(Some(simpleperf::parse_file2_section(section, self.endian)?));
234        }
235        if let Some(section) = self.feature_section_data(Feature::SIMPLEPERF_FILE) {
236            return Ok(Some(simpleperf::parse_file_section(section, self.endian)?));
237        }
238        Ok(None)
239    }
240
241    /// The names of the dynamic PMU types used in [`PerfEventType::DynamicPmu`](linux_perf_event_reader::PerfEventType::DynamicPmu).
242    ///
243    /// This mapping allows you to interpret the perf event type field of the perf event
244    /// attributes returned by [`PerfFile::event_attributes`].
245    ///
246    /// For example, let's say you observed a kprobe or a uprobe. The perf event will be
247    /// of type `DynamicPmu`, and its dynamic PMU type ID might be 6 or 7.
248    ///
249    /// Just by seeing this 6 or 7 you don't know for sure what type of event it is.
250    /// But the `pmu_mappings()` map will have a 6 => "kprobe" and a 7 => "uprobe" entry.
251    /// Once you see those entries, you can be sure what you're dealing with.
252    ///
253    /// This map also contains the values "software", "tracepoint", and "breakpoint"; those
254    /// always have the IDs 1, 2 and 5, respectively.
255    ///
256    /// Additionally, the map contains the CPU-specific dynamic entries. For example, an Intel
257    /// CPU might have IDs for the names "cpu", "intel_bts", "intel_pt", "msr", "uncore_imc",
258    /// "uncore_cbox_0", ..., "uncore_cbox_7", "uncore_arb", "cstate_core", "cstate_pkg", "power",
259    /// "i915".
260    pub fn pmu_mappings(&self) -> Result<Option<PmuMappings>, Error> {
261        self.feature_section_data(Feature::PMU_MAPPINGS)
262            .map(|section| {
263                Ok(match self.endian {
264                    Endianness::LittleEndian => PmuMappings::parse::<_, LittleEndian>(section),
265                    Endianness::BigEndian => PmuMappings::parse::<_, BigEndian>(section),
266                }?)
267            })
268            .transpose()
269    }
270
271    /// The set of features used in this perf file.
272    pub fn features(&self) -> FeatureSet {
273        self.features
274    }
275
276    /// The raw data of a feature section.
277    pub fn feature_section_data(&self, feature: Feature) -> Option<&[u8]> {
278        self.feature_sections.get(&feature).map(Deref::deref)
279    }
280
281    /// The file endian.
282    pub fn endian(&self) -> Endianness {
283        self.endian
284    }
285
286    fn read_string<'s>(&self, s: &'s [u8]) -> Result<(&'s str, &'s [u8]), Error> {
287        if s.len() < 4 {
288            return Err(Error::NotEnoughSpaceForStringLen);
289        }
290        let (len_bytes, rest) = s.split_at(4);
291        let len_bytes = [len_bytes[0], len_bytes[1], len_bytes[2], len_bytes[3]];
292        let len = match self.endian {
293            Endianness::LittleEndian => u32::from_le_bytes(len_bytes),
294            Endianness::BigEndian => u32::from_be_bytes(len_bytes),
295        };
296        let len = usize::try_from(len).map_err(|_| Error::StringLengthBiggerThanUsize)?;
297        if rest.len() < len {
298            return Err(Error::StringLengthTooLong);
299        }
300        let (s, rest) = rest.split_at(len);
301        let actual_len = memchr::memchr(0, s).unwrap_or(s.len());
302        let s = std::str::from_utf8(&s[..actual_len])?;
303        Ok((s, rest))
304    }
305
306    fn read_string_list<'s>(&self, s: &'s [u8]) -> Result<(Vec<&'s str>, &'s [u8]), Error> {
307        if s.len() < 4 {
308            return Err(Error::NotEnoughSpaceForStringListLen);
309        }
310        let (len_bytes, mut rest) = s.split_at(4);
311        let len_bytes = [len_bytes[0], len_bytes[1], len_bytes[2], len_bytes[3]];
312        let len = match self.endian {
313            Endianness::LittleEndian => u32::from_le_bytes(len_bytes),
314            Endianness::BigEndian => u32::from_be_bytes(len_bytes),
315        };
316        let len = usize::try_from(len).map_err(|_| Error::StringListLengthBiggerThanUsize)?;
317        let mut vec = Vec::with_capacity(len);
318        for _ in 0..len {
319            let s;
320            (s, rest) = self.read_string(rest)?;
321            vec.push(s);
322        }
323
324        Ok((vec, rest))
325    }
326}