perf_event/
group.rs

1use std::io;
2
3use crate::events::Software;
4use crate::{Builder, Counter, GroupData, ReadFormat};
5
6/// A group of counters that can be managed as a unit.
7///
8/// A `Group` represents a group of [`Counter`]s that can be enabled,
9/// disabled, reset, or read as a single atomic operation. This is necessary if
10/// you want to compare counter values, produce ratios, and so on, since those
11/// operations are only meaningful on counters that cover exactly the same
12/// period of execution.
13///
14/// A `Counter` is placed in a group when it is created via the
15/// [`Builder::build_with_group`] method. A `Group`'s [`read`] method returns
16/// values of all its member counters at once as a [`GroupData`] value, which
17/// can be indexed by `Counter` to retrieve a specific value.
18///
19/// The lifetime of a `Group` and its associated `Counter`s are independent:
20/// you can drop them in any order and they will continue to work. A `Counter`
21/// will continue to work after the `Group` is dropped. If a `Counter` is
22/// dropped first then it will simply be removed from the `Group`.
23///
24/// Enabling or disabling a `Group` affects each `Counter` that belongs to it.
25/// Subsequent reads from the `Counter` will not reflect activity while the
26/// `Group` was disabled, unless the `Counter` is re-enabled individually.
27///
28/// ## Limits on group size
29///
30/// Hardware counters are implemented using special-purpose registers on the
31/// processor, of which there are only a fixed number. (For example, an Intel
32/// high-end laptop processor from 2015 has four such registers per virtual
33/// processor.) Without using groups, if you request more hardware counters than
34/// the processor can actually support, a complete count isn't possible, but the
35/// kernel will rotate the processor's real registers amongst the measurements
36/// you've requested to at least produce a sample.
37///
38/// But since the point of a counter group is that its members all cover exactly
39/// the same period of time, this tactic can't be applied to support large
40/// groups. If the kernel cannot schedule a group, its counters remain zero. I
41/// think you can detect this situation by comparing the group's
42/// [`time_enabled`] and [`time_running`] values. If the [`pinned`] option is
43/// set then you will also be able to detect this by [`read`] returning an error
44/// with kind [`UnexpectedEof`].
45///
46/// According to the `perf_list(1)` man page, you may be able to free up a
47/// hardware counter by disabling the kernel's NMI watchdog, which reserves one
48/// for detecting kernel hangs:
49///
50/// ```text
51/// $ echo 0 > /proc/sys/kernel/nmi_watchdog
52/// ```
53///
54/// You can reenable the watchdog when you're done like this:
55///
56/// ```text
57/// $ echo 1 > /proc/sys/kernel/nmi_watchdog
58/// ```
59///
60/// [`read`]: Self::read
61/// [`pinned`]: Builder::pinned
62/// [`UnexpectedEof`]: io::ErrorKind::UnexpectedEof
63///
64/// # Examples
65/// Compute the average cycles-per-instruction (CPI) for a call to `println!`:
66/// ```
67/// use perf_event::events::Hardware;
68/// use perf_event::{Builder, Group};
69///
70/// let mut group = Group::new()?;
71/// let cycles = group.add(&Builder::new(Hardware::CPU_CYCLES))?;
72/// let insns = group.add(&Builder::new(Hardware::INSTRUCTIONS))?;
73///
74/// let vec = (0..=51).collect::<Vec<_>>();
75///
76/// group.enable()?;
77/// println!("{:?}", vec);
78/// group.disable()?;
79///
80/// let counts = group.read()?;
81/// println!(
82///     "cycles / instructions: {} / {} ({:.2} cpi)",
83///     counts[&cycles],
84///     counts[&insns],
85///     (counts[&cycles] as f64 / counts[&insns] as f64)
86/// );
87/// # std::io::Result::Ok(())
88/// ```
89///
90/// [`read`]: Group::read
91/// [`time_enabled`]: GroupData::time_enabled
92/// [`time_running`]: GroupData::time_running
93pub struct Group(pub(crate) Counter);
94
95impl Group {
96    /// Construct a new, empty `Group`.
97    ///
98    /// The resulting `Group` is only suitable for observing the current process
99    /// on any CPU. If you need to build a `Group` with different settings you
100    /// will need to use [`Builder::build_group`].
101    pub fn new() -> io::Result<Group> {
102        Self::builder().build_group()
103    }
104
105    /// Construct a [Builder] preconfigured for creating a `Group`.
106    ///
107    /// Specifically, this creates a builder with the [`Software::DUMMY`] event
108    /// and with [`read_format`] set to `GROUP | ID | TOTAL_TIME_ENABLED |
109    /// TOTAL_TIME_RUNNING`. If you override [`read_format`] you will need to
110    /// ensure that [`ReadFormat::GROUP`] is set, otherwise [`build_group`] will
111    /// return an error.
112    ///
113    /// Note that any counter added to this group must observe the same set of
114    /// CPUs and processes as the group itself. That means if you configure the
115    /// group to observe a single CPU then the members of the group must also be
116    /// configured to only observe a single CPU, the same applies when choosing
117    /// target processes. Failing to follow this will result in an error when
118    /// adding the counter to the group.
119    ///
120    /// [`read_format`]: Builder::read_format
121    /// [`build_group`]: Builder::build_group
122    pub fn builder() -> Builder<'static> {
123        let mut builder = Builder::new(Software::DUMMY);
124        builder.read_format(
125            ReadFormat::GROUP
126                | ReadFormat::TOTAL_TIME_ENABLED
127                | ReadFormat::TOTAL_TIME_RUNNING
128                | ReadFormat::ID,
129        );
130
131        builder
132    }
133
134    /// Access the internal counter for this group.
135    pub fn as_counter(&self) -> &Counter {
136        &self.0
137    }
138
139    /// Mutably access the internal counter for this group.
140    pub fn as_counter_mut(&mut self) -> &mut Counter {
141        &mut self.0
142    }
143
144    /// Convert this `Group` into its internal counter.
145    pub fn into_counter(self) -> Counter {
146        self.0
147    }
148
149    /// Return this group's kernel-assigned unique id.
150    pub fn id(&self) -> u64 {
151        self.0.id()
152    }
153
154    /// Enable all counters in this `Group`.
155    pub fn enable(&mut self) -> io::Result<()> {
156        self.0.enable_group()
157    }
158
159    /// Disable all counters in this `Group`
160    pub fn disable(&mut self) -> io::Result<()> {
161        self.0.disable_group()
162    }
163
164    /// Reset the value of all counters in this `Group` to zero.
165    pub fn reset(&mut self) -> io::Result<()> {
166        self.0.reset_group()
167    }
168
169    /// Construct a new counter as a part of this group.
170    ///
171    /// # Example
172    /// ```
173    /// use perf_event::events::Hardware;
174    /// use perf_event::{Builder, Group};
175    ///
176    /// let mut group = Group::new()?;
177    /// let counter = group.add(&Builder::new(Hardware::INSTRUCTIONS).any_cpu());
178    /// #
179    /// # std::io::Result::Ok(())
180    /// ```
181    pub fn add(&mut self, builder: &Builder) -> io::Result<Counter> {
182        builder.build_with_group(self)
183    }
184
185    /// Return the values of all the `Counter`s in this `Group` as a
186    /// [`GroupData`] value.
187    ///
188    /// A [`GroupData`] value is a map from specific `Counter`s to their values.
189    /// You can find a specific `Counter`'s value by indexing:
190    ///
191    /// ```
192    /// # use perf_event::events::Software;
193    /// # const RHOMBUS_INCLINATIONS: Software = Software::DUMMY;
194    /// # const TAXI_MEDALLIONS: Software = Software::DUMMY;
195    /// #
196    /// use perf_event::{Builder, Group};
197    ///
198    /// let mut group = Group::new()?;
199    /// let counter1 = Builder::new(RHOMBUS_INCLINATIONS).build_with_group(&mut group)?;
200    /// let counter2 = Builder::new(TAXI_MEDALLIONS).build_with_group(&mut group)?;
201    /// // ...
202    /// let counts = group.read()?;
203    /// println!(
204    ///     "Rhombus inclinations per taxi medallion: {} / {} ({:.0}%)",
205    ///     counts[&counter1],
206    ///     counts[&counter2],
207    ///     (counts[&counter1] as f64 / counts[&counter2] as f64) * 100.0
208    /// );
209    /// # std::io::Result::Ok(())
210    /// ```
211    pub fn read(&mut self) -> io::Result<GroupData> {
212        let mut data = self.0.read_group()?;
213        data.skip_group();
214        Ok(data)
215    }
216}
217
218impl AsRef<Counter> for &'_ Group {
219    fn as_ref(&self) -> &Counter {
220        &self.0
221    }
222}
223
224impl AsMut<Counter> for &'_ mut Group {
225    fn as_mut(&mut self) -> &mut Counter {
226        &mut self.0
227    }
228}