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