linux_perf_data/
thread_map.rs

1use std::fmt;
2
3use byteorder::{ByteOrder, NativeEndian};
4
5use linux_perf_event_reader::{is_swapped_endian, RawData};
6
7/// A list of threads, usually without names.
8///
9/// It's not clear to me what the point of this list is. It doesn't even give you the
10/// pid of the process that each thread belongs to. And unless you use `perf stat`,
11/// it doesn't seem to have thread names either.
12///
13/// So it seems like all the useful information is instead in the PERF_RECORD_COMM
14/// records which get synthesized at the start of a file for `perf record -p <pid>`.
15/// It seems you're better of just reading those, instead of looking at the thread map.
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct ThreadMap<'a> {
18    swap_endian: bool,
19    data: RawData<'a>,
20}
21
22const THREAD_ENTRY_SIZE: usize = 8 + 16;
23
24impl<'a> ThreadMap<'a> {
25    pub fn parse<T: ByteOrder>(mut data: RawData<'a>) -> Result<Self, std::io::Error> {
26        let len = data.read_u64::<T>()?;
27        let len = usize::try_from(len).map_err(|_| std::io::ErrorKind::InvalidData)?;
28        let datalen = len
29            .checked_mul(THREAD_ENTRY_SIZE)
30            .ok_or(std::io::ErrorKind::InvalidData)?;
31        let data = data.split_off_prefix(datalen)?;
32        Ok(Self {
33            swap_endian: is_swapped_endian::<T>(),
34            data,
35        })
36    }
37
38    pub fn len(&self) -> usize {
39        self.data.len() / THREAD_ENTRY_SIZE
40    }
41
42    pub fn is_empty(&self) -> bool {
43        self.len() == 0
44    }
45
46    pub fn iter(&self) -> ThreadMapIter<'a> {
47        ThreadMapIter {
48            swap_endian: self.swap_endian,
49            index: 0,
50            len: self.len(),
51            data: self.data,
52        }
53    }
54}
55
56#[derive(Clone, Copy, PartialEq, Eq)]
57pub struct ThreadMapEntry<'a> {
58    /// The tid of this thread.
59    pub tid: u64,
60    /// The name is usually empty, unfortunately. It looks like `thread_map__read_comms`
61    /// only gets called by `perf stat`, not by `perf record`.
62    pub name: RawData<'a>,
63}
64
65impl fmt::Debug for ThreadMapEntry<'_> {
66    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
67        use std::str;
68
69        let mut map = fmt.debug_map();
70        map.entry(&"tid", &self.tid);
71
72        if let Ok(string) = str::from_utf8(&self.name.as_slice()) {
73            map.entry(&"name", &string);
74        } else {
75            map.entry(&"name", &self.name);
76        }
77
78        map.finish()
79    }
80}
81
82pub struct ThreadMapIter<'a> {
83    swap_endian: bool,
84    data: RawData<'a>,
85    index: usize,
86    len: usize,
87}
88
89impl<'a> Iterator for ThreadMapIter<'a> {
90    type Item = ThreadMapEntry<'a>;
91
92    fn next(&mut self) -> Option<Self::Item> {
93        if self.index >= self.len {
94            return None;
95        }
96
97        let mut tid = self.data.read_u64::<NativeEndian>().unwrap();
98        if self.swap_endian {
99            tid = tid.swap_bytes();
100        }
101        let mut name = self.data.split_off_prefix(16).unwrap();
102        let name = name.read_string().unwrap_or(name);
103        self.index += 1;
104        Some(ThreadMapEntry { tid, name })
105    }
106
107    fn size_hint(&self) -> (usize, Option<usize>) {
108        (0, Some(self.len))
109    }
110}
111
112#[cfg(test)]
113mod test {
114    use byteorder::LittleEndian;
115    use linux_perf_event_reader::RawData;
116
117    use super::ThreadMap;
118
119    #[test]
120    fn parse_one() {
121        let data = RawData::Single(&[
122            1, 0, 0, 0, 0, 0, 0, 0, 108, 71, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
123            0, 0, 0, 0,
124        ]);
125        let map = ThreadMap::parse::<LittleEndian>(data).unwrap();
126        assert_eq!(map.len(), 1);
127        let vec: Vec<_> = map.iter().collect();
128        assert_eq!(vec.len(), 1);
129        assert_eq!(vec[0].tid, 542572);
130        assert_eq!(&vec[0].name.as_slice()[..], b"");
131    }
132
133    #[test]
134    fn parse_big() {
135        let data = RawData::Single(&[
136            12, 0, 0, 0, 0, 0, 0, 0, 165, 115, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
137            0, 0, 0, 0, 0, 169, 115, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
138            0, 171, 115, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 172,
139            115, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 187, 115, 1, 0,
140            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 188, 115, 1, 0, 0, 0, 0, 0,
141            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 189, 115, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
142            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 190, 115, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
143            0, 0, 0, 0, 0, 0, 0, 0, 191, 115, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
144            0, 0, 0, 0, 194, 115, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
145            197, 115, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 199, 115,
146            1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
147        ]);
148        let map = ThreadMap::parse::<LittleEndian>(data).unwrap();
149        assert_eq!(map.len(), 12);
150        let vec: Vec<_> = map.iter().collect();
151        assert_eq!(vec.len(), 12);
152        assert_eq!(vec[8].tid, 95167);
153        assert_eq!(&vec[8].name.as_slice()[..], b"");
154    }
155}