1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
use byteorder::{BigEndian, LittleEndian};
use linear_map::LinearMap;
use linux_perf_event_reader::{CpuMode, Endianness};

use std::collections::HashMap;
use std::ops::Deref;

use super::build_id_event::BuildIdEvent;
use super::dso_info::DsoInfo;
use super::dso_key::DsoKey;
use super::error::Error;
use super::feature_sections::{AttributeDescription, NrCpus, PmuMappings, SampleTimeRange};
use super::features::{Feature, FeatureSet};

/// Contains the information from the perf.data file header and feature sections.
pub struct PerfFile {
    pub(crate) endian: Endianness,
    pub(crate) features: FeatureSet,
    pub(crate) feature_sections: LinearMap<Feature, Vec<u8>>,
    /// Guaranteed to have at least one element
    pub(crate) attributes: Vec<AttributeDescription>,
}

impl PerfFile {
    /// The attributes which were requested for each perf event, along with the IDs.
    pub fn event_attributes(&self) -> &[AttributeDescription] {
        &self.attributes
    }
    /// Returns a map of build ID entries. `perf record` creates these records for any DSOs
    /// which it thinks have been "hit" in the profile. They supplement Mmap records, which
    /// usually don't come with build IDs.
    ///
    /// This method returns a HashMap so that you can easily look up the right build ID from
    /// the DsoKey in an Mmap event. For some DSOs, the path in the raw Mmap event can be
    /// different from the path in the build ID record; for example, the Mmap event for the
    /// kernel ("vmlinux") image could have the path "[kernel.kallsyms]_text", whereas the
    /// corresponding build ID record might have the path "[kernel.kallsyms]" (without the
    /// trailing "_text"), or it could even have the full absolute path to a vmlinux file.
    /// The DsoKey canonicalizes those differences away.
    ///
    /// Having the build ID for a DSO allows you to do the following:
    ///
    ///  - If the DSO file has changed in the time since the perf.data file was captured,
    ///    you can detect this change because the new file will have a different build ID.
    ///  - If debug symbols are installed for the DSO, you can sometimes find the debug symbol
    ///    file using the build ID. For example, you might find it at
    ///    /usr/lib/debug/.build-id/b8/037b6260865346802321dd2256b8ad1d857e63.debug
    ///  - If the original DSO file is gone, or you're trying to read the perf.data file on
    ///    an entirely different machine, you can sometimes retrieve the original DSO file just
    ///    from its build ID, for example from a debuginfod server.
    ///  - This also works for DSOs which are not present on the file system at all;
    ///    specifically, the vDSO file is a bit of a pain to obtain. With the build ID you can
    ///    instead obtain it from, say,
    ///    <https://debuginfod.elfutils.org/buildid/0d82ee4bd7f9609c367095ba0bedf155b71cb058/executable>
    ///
    /// This method is a bit lossy. We discard the pid, because it seems to be always -1 in
    /// the files I've tested. We also discard any entries for which we fail to create a `DsoKey`.
    pub fn build_ids(&self) -> Result<HashMap<DsoKey, DsoInfo>, Error> {
        let section_data = match self.feature_section_data(Feature::BUILD_ID) {
            Some(section) => section,
            None => return Ok(HashMap::new()),
        };
        let mut cursor = section_data;
        let mut build_ids = HashMap::new();
        loop {
            let event = match self.endian {
                Endianness::LittleEndian => BuildIdEvent::parse::<_, LittleEndian>(&mut cursor),
                Endianness::BigEndian => BuildIdEvent::parse::<_, BigEndian>(&mut cursor),
            };
            let event = match event {
                Ok(e) => e,
                Err(_) => break,
            };
            let misc = event.header.misc;
            let path = event.file_path;
            let build_id = event.build_id;
            let dso_key = match DsoKey::detect(&path, CpuMode::from_misc(misc)) {
                Some(dso_key) => dso_key,
                None => continue,
            };
            build_ids.insert(dso_key, DsoInfo { path, build_id });
        }
        Ok(build_ids)
    }

    /// The timestamp of the first and the last sample in this file.
    pub fn sample_time_range(&self) -> Result<Option<SampleTimeRange>, Error> {
        let section_data = match self.feature_section_data(Feature::SAMPLE_TIME) {
            Some(section) => section,
            None => return Ok(None),
        };
        let time_range = match self.endian {
            Endianness::LittleEndian => SampleTimeRange::parse::<_, LittleEndian>(section_data)?,
            Endianness::BigEndian => SampleTimeRange::parse::<_, BigEndian>(section_data)?,
        };
        Ok(Some(time_range))
    }

    /// Only call this for features whose section is just a perf_header_string.
    fn feature_string(&self, feature: Feature) -> Result<Option<&str>, Error> {
        match self.feature_section_data(feature) {
            Some(section) => Ok(Some(self.read_string(section)?.0)),
            None => Ok(None),
        }
    }

    /// The hostname where the data was collected (`uname -n`).
    pub fn hostname(&self) -> Result<Option<&str>, Error> {
        self.feature_string(Feature::HOSTNAME)
    }

    /// The OS release where the data was collected (`uname -r`).
    pub fn os_release(&self) -> Result<Option<&str>, Error> {
        self.feature_string(Feature::OSRELEASE)
    }

    /// The perf user tool version where the data was collected. This is the same
    /// as the version of the Linux source tree the perf tool was built from.
    pub fn perf_version(&self) -> Result<Option<&str>, Error> {
        self.feature_string(Feature::VERSION)
    }

    /// The CPU architecture (`uname -m`).
    pub fn arch(&self) -> Result<Option<&str>, Error> {
        self.feature_string(Feature::ARCH)
    }

    /// A structure defining the number of CPUs.
    pub fn nr_cpus(&self) -> Result<Option<NrCpus>, Error> {
        self.feature_section_data(Feature::NRCPUS)
            .map(|section| {
                Ok(match self.endian {
                    Endianness::LittleEndian => NrCpus::parse::<_, LittleEndian>(section),
                    Endianness::BigEndian => NrCpus::parse::<_, BigEndian>(section),
                }?)
            })
            .transpose()
    }

    /// The description of the CPU. On x86 this is the model name
    /// from `/proc/cpuinfo`.
    pub fn cpu_desc(&self) -> Result<Option<&str>, Error> {
        self.feature_string(Feature::CPUDESC)
    }

    /// The exact CPU type. On x86 this is `vendor,family,model,stepping`.
    /// For example: `GenuineIntel,6,69,1`
    pub fn cpu_id(&self) -> Result<Option<&str>, Error> {
        self.feature_string(Feature::CPUID)
    }

    /// If true, the data section contains data recorded from `perf stat record`.
    pub fn is_stats(&self) -> bool {
        self.features.has_feature(Feature::STAT)
    }

    /// The perf arg-vector used to collect the data.
    pub fn cmdline(&self) -> Result<Option<Vec<&str>>, Error> {
        match self.feature_section_data(Feature::CMDLINE) {
            Some(section) => Ok(Some(self.read_string_list(section)?.0)),
            None => Ok(None),
        }
    }

    /// The total memory in kilobytes. (MemTotal from /proc/meminfo)
    pub fn total_mem(&self) -> Result<Option<u64>, Error> {
        let data = match self.feature_section_data(Feature::TOTAL_MEM) {
            Some(data) => data,
            None => return Ok(None),
        };
        if data.len() < 8 {
            return Err(Error::FeatureSectionTooSmall);
        }
        let b = data;
        let data = [b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]];
        let mem = match self.endian {
            Endianness::LittleEndian => u64::from_le_bytes(data),
            Endianness::BigEndian => u64::from_be_bytes(data),
        };
        Ok(Some(mem))
    }

    /// The names of the dynamic PMU types used in [`PerfEventType::DynamicPmu`](linux_perf_event_reader::PerfEventType::DynamicPmu).
    ///
    /// This mapping allows you to interpret the perf event type field of the perf event
    /// attributes returned by [`PerfFile::event_attributes`].
    ///
    /// For example, let's say you observed a kprobe or a uprobe. The perf event will be
    /// of type `DynamicPmu`, and its dynamic PMU type ID might be 6 or 7.
    ///
    /// Just by seeing this 6 or 7 you don't know for sure what type of event it is.
    /// But the `pmu_mappings()` map will have a 6 => "kprobe" and a 7 => "uprobe" entry.
    /// Once you see those entries, you can be sure what you're dealing with.
    ///
    /// This map also contains the values "software", "tracepoint", and "breakpoint"; those
    /// always have the IDs 1, 2 and 5, respectively.
    ///
    /// Additionally, the map contains the CPU-specific dynamic entries. For example, an Intel
    /// CPU might have IDs for the names "cpu", "intel_bts", "intel_pt", "msr", "uncore_imc",
    /// "uncore_cbox_0", ..., "uncore_cbox_7", "uncore_arb", "cstate_core", "cstate_pkg", "power",
    /// "i915".
    pub fn pmu_mappings(&self) -> Result<Option<LinearMap<u32, String>>, Error> {
        self.feature_section_data(Feature::PMU_MAPPINGS)
            .map(|section| {
                Ok(match self.endian {
                    Endianness::LittleEndian => PmuMappings::parse::<_, LittleEndian>(section),
                    Endianness::BigEndian => PmuMappings::parse::<_, BigEndian>(section),
                }?)
            })
            .transpose()
    }

    /// The set of features used in this perf file.
    pub fn features(&self) -> FeatureSet {
        self.features
    }

    /// The raw data of a feature section.
    pub fn feature_section_data(&self, feature: Feature) -> Option<&[u8]> {
        self.feature_sections.get(&feature).map(Deref::deref)
    }

    /// The file endian.
    pub fn endian(&self) -> Endianness {
        self.endian
    }

    fn read_string<'s>(&self, s: &'s [u8]) -> Result<(&'s str, &'s [u8]), Error> {
        if s.len() < 4 {
            return Err(Error::NotEnoughSpaceForStringLen);
        }
        let (len_bytes, rest) = s.split_at(4);
        let len_bytes = [len_bytes[0], len_bytes[1], len_bytes[2], len_bytes[3]];
        let len = match self.endian {
            Endianness::LittleEndian => u32::from_le_bytes(len_bytes),
            Endianness::BigEndian => u32::from_be_bytes(len_bytes),
        };
        let len = usize::try_from(len).map_err(|_| Error::StringLengthBiggerThanUsize)?;
        if rest.len() < len {
            return Err(Error::StringLengthTooLong);
        }
        let (s, rest) = rest.split_at(len);
        let actual_len = memchr::memchr(0, s).unwrap_or(s.len());
        let s = std::str::from_utf8(&s[..actual_len]).map_err(|_| Error::StringUtf8)?;
        Ok((s, rest))
    }

    fn read_string_list<'s>(&self, s: &'s [u8]) -> Result<(Vec<&'s str>, &'s [u8]), Error> {
        if s.len() < 4 {
            return Err(Error::NotEnoughSpaceForStringListLen);
        }
        let (len_bytes, mut rest) = s.split_at(4);
        let len_bytes = [len_bytes[0], len_bytes[1], len_bytes[2], len_bytes[3]];
        let len = match self.endian {
            Endianness::LittleEndian => u32::from_le_bytes(len_bytes),
            Endianness::BigEndian => u32::from_be_bytes(len_bytes),
        };
        let len = usize::try_from(len).map_err(|_| Error::StringListLengthBiggerThanUsize)?;
        let mut vec = Vec::with_capacity(len);
        for _ in 0..len {
            let s;
            (s, rest) = self.read_string(rest)?;
            vec.push(s);
        }

        Ok((vec, rest))
    }
}