profile_traits/
mem.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! APIs for memory profiling.
6
7#![deny(missing_docs)]
8
9use std::cell::{LazyCell, RefCell};
10use std::collections::HashSet;
11use std::ffi::c_void;
12use std::marker::Send;
13
14use base::generic_channel::GenericSender;
15use crossbeam_channel::Sender;
16use ipc_channel::ipc::{self, IpcSender};
17use ipc_channel::router::ROUTER;
18use log::warn;
19use malloc_size_of::MallocSizeOfOps;
20use serde::{Deserialize, Serialize};
21
22/// A trait to abstract away the various kinds of message senders we use.
23pub trait OpaqueSender<T> {
24    /// Send a message.
25    fn send(&self, message: T);
26}
27
28impl<T> OpaqueSender<T> for Sender<T> {
29    fn send(&self, message: T) {
30        if let Err(e) = Sender::send(self, message) {
31            warn!(
32                "Error communicating with the target thread from the profiler: {:?}",
33                e
34            );
35        }
36    }
37}
38
39impl<T> OpaqueSender<T> for IpcSender<T>
40where
41    T: serde::Serialize,
42{
43    fn send(&self, message: T) {
44        if let Err(e) = IpcSender::send(self, message) {
45            warn!(
46                "Error communicating with the target thread from the profiler: {}",
47                e
48            );
49        }
50    }
51}
52
53impl<T> OpaqueSender<T> for GenericSender<T>
54where
55    T: serde::Serialize,
56{
57    fn send(&self, message: T) {
58        if let Err(e) = GenericSender::send(self, message) {
59            warn!(
60                "Error communicating with the target thread from the profiler: {}",
61                e
62            );
63        }
64    }
65}
66
67/// Front-end representation of the profiler used to communicate with the
68/// profiler.
69#[derive(Clone, Debug, Deserialize, Serialize)]
70pub struct ProfilerChan(pub IpcSender<ProfilerMsg>);
71
72/// A handle that encompasses a registration with the memory profiler.
73/// The registration is tied to the lifetime of this type; the memory
74/// profiler unregister the reporter when this object is dropped.
75pub struct ProfilerRegistration {
76    sender: ProfilerChan,
77    reporter_name: String,
78}
79
80impl Drop for ProfilerRegistration {
81    fn drop(&mut self) {
82        self.sender
83            .send(ProfilerMsg::UnregisterReporter(self.reporter_name.clone()));
84    }
85}
86
87impl ProfilerChan {
88    /// Send `msg` on this `IpcSender`.
89    ///
90    /// Warns if the send fails.
91    pub fn send(&self, msg: ProfilerMsg) {
92        if let Err(e) = self.0.send(msg) {
93            warn!("Error communicating with the memory profiler thread: {}", e);
94        }
95    }
96
97    /// Register a new reporter and return a handle to automatically
98    /// unregister it in the future.
99    pub fn prepare_memory_reporting<M, T, C>(
100        &self,
101        reporter_name: String,
102        channel_for_reporter: C,
103        msg: M,
104    ) -> ProfilerRegistration
105    where
106        M: Fn(ReportsChan) -> T + Send + 'static,
107        T: Send + 'static,
108        C: OpaqueSender<T> + Send + 'static,
109    {
110        // Register the memory reporter.
111        let (reporter_sender, reporter_receiver) = ipc::channel().unwrap();
112        ROUTER.add_typed_route(
113            reporter_receiver,
114            Box::new(move |message| {
115                // Just injects an appropriate event into the paint thread's queue.
116                let request: ReporterRequest = message.unwrap();
117                channel_for_reporter.send(msg(request.reports_channel));
118            }),
119        );
120        self.send(ProfilerMsg::RegisterReporter(
121            reporter_name.clone(),
122            Reporter(reporter_sender),
123        ));
124
125        ProfilerRegistration {
126            sender: self.clone(),
127            reporter_name,
128        }
129    }
130
131    /// Runs `f()` with memory profiling.
132    pub fn run_with_memory_reporting<F, M, T, C>(
133        &self,
134        f: F,
135        reporter_name: String,
136        channel_for_reporter: C,
137        msg: M,
138    ) where
139        F: FnOnce(),
140        M: Fn(ReportsChan) -> T + Send + 'static,
141        T: Send + 'static,
142        C: OpaqueSender<T> + Send + 'static,
143    {
144        let _registration = self.prepare_memory_reporting(reporter_name, channel_for_reporter, msg);
145
146        f();
147    }
148}
149
150/// The various kinds of memory measurement.
151///
152/// Here "explicit" means explicit memory allocations done by the application. It includes
153/// allocations made at the OS level (via functions such as VirtualAlloc, vm_allocate, and mmap),
154/// allocations made at the heap allocation level (via functions such as malloc, calloc, realloc,
155/// memalign, operator new, and operator new[]) and where possible, the overhead of the heap
156/// allocator itself. It excludes memory that is mapped implicitly such as code and data segments,
157/// and thread stacks. "explicit" is not guaranteed to cover every explicit allocation, but it does
158/// cover most (including the entire heap), and therefore it is the single best number to focus on
159/// when trying to reduce memory usage.
160#[derive(Debug, Deserialize, Serialize)]
161pub enum ReportKind {
162    /// A size measurement for an explicit allocation on the jemalloc heap. This should be used
163    /// for any measurements done via the `MallocSizeOf` trait.
164    ExplicitJemallocHeapSize,
165
166    /// A size measurement for an explicit allocation on the system heap. Only likely to be used
167    /// for external C or C++ libraries that don't use jemalloc.
168    ExplicitSystemHeapSize,
169
170    /// A size measurement for an explicit allocation not on the heap, e.g. via mmap().
171    ExplicitNonHeapSize,
172
173    /// A size measurement for an explicit allocation whose location is unknown or uncertain.
174    ExplicitUnknownLocationSize,
175
176    /// A size measurement for a non-explicit allocation. This kind is used for global
177    /// measurements such as "resident" and "vsize", and also for measurements that cross-cut the
178    /// measurements grouped under "explicit", e.g. by grouping those measurements in a way that's
179    /// different to how they are grouped under "explicit".
180    NonExplicitSize,
181}
182
183/// A single memory-related measurement.
184#[derive(Debug, Deserialize, Serialize)]
185pub struct Report {
186    /// The identifying path for this report.
187    pub path: Vec<String>,
188
189    /// The report kind.
190    pub kind: ReportKind,
191
192    /// The size, in bytes.
193    pub size: usize,
194}
195
196/// A set of reports belonging to a process.
197#[derive(Debug, Deserialize, Serialize)]
198pub struct ProcessReports {
199    /// The set of reports.
200    pub reports: Vec<Report>,
201
202    /// The process id.
203    pub pid: u32,
204}
205
206impl ProcessReports {
207    /// Adopt these reports and configure the process pid.
208    pub fn new(reports: Vec<Report>) -> Self {
209        Self {
210            reports,
211            pid: std::process::id(),
212        }
213    }
214}
215
216/// A channel through which memory reports can be sent.
217#[derive(Clone, Debug, Deserialize, Serialize)]
218pub struct ReportsChan(pub IpcSender<ProcessReports>);
219
220impl ReportsChan {
221    /// Send `report` on this `IpcSender`.
222    ///
223    /// Panics if the send fails.
224    pub fn send(&self, reports: ProcessReports) {
225        self.0.send(reports).unwrap();
226    }
227}
228
229/// The protocol used to send reporter requests.
230#[derive(Debug, Deserialize, Serialize)]
231pub struct ReporterRequest {
232    /// The channel on which reports are to be sent.
233    pub reports_channel: ReportsChan,
234}
235
236/// A memory reporter is capable of measuring some data structure of interest. It's structured as
237/// an IPC sender that a `ReporterRequest` in transmitted over. `ReporterRequest` objects in turn
238/// encapsulate the channel on which the memory profiling information is to be sent.
239///
240/// In many cases, clients construct `Reporter` objects by creating an IPC sender/receiver pair and
241/// registering the receiving end with the router so that messages from the memory profiler end up
242/// injected into the client's event loop.
243#[derive(Debug, Deserialize, Serialize)]
244pub struct Reporter(pub IpcSender<ReporterRequest>);
245
246impl Reporter {
247    /// Collect one or more memory reports. Returns true on success, and false on failure.
248    pub fn collect_reports(&self, reports_channel: ReportsChan) {
249        self.0.send(ReporterRequest { reports_channel }).unwrap()
250    }
251}
252
253/// An easy way to build a path for a report.
254#[macro_export]
255macro_rules! path {
256    ($($x:expr),*) => {{
257        use std::borrow::ToOwned;
258        vec![$( $x.to_owned() ),*]
259    }}
260}
261
262/// The results produced by the memory reporter.
263#[derive(Debug, Deserialize, Serialize)]
264pub struct MemoryReportResult {
265    /// All the results from the MemoryReports
266    pub results: Vec<MemoryReport>,
267}
268
269#[derive(Debug, Deserialize, Serialize)]
270/// A simple memory report
271pub struct MemoryReport {
272    /// The pid of the report
273    pub pid: u32,
274    /// Is this the main process
275    pub is_main_process: bool,
276    /// All the reports for this pid
277    pub reports: Vec<Report>,
278}
279
280/// Messages that can be sent to the memory profiler thread.
281#[derive(Debug, Deserialize, Serialize)]
282pub enum ProfilerMsg {
283    /// Register a Reporter with the memory profiler. The String is only used to identify the
284    /// reporter so it can be unregistered later. The String must be distinct from that used by any
285    /// other registered reporter otherwise a panic will occur.
286    RegisterReporter(String, Reporter),
287
288    /// Unregister a Reporter with the memory profiler. The String must match the name given when
289    /// the reporter was registered. If the String does not match the name of a registered reporter
290    /// a panic will occur.
291    UnregisterReporter(String),
292
293    /// Tells the memory profiler to shut down.
294    Exit,
295
296    /// Triggers sending back the memory profiling metrics,
297    Report(IpcSender<MemoryReportResult>),
298}
299
300thread_local!(static SEEN_POINTERS: LazyCell<RefCell<HashSet<*const c_void>>> = const {
301    LazyCell::new(Default::default)
302});
303
304/// Invoke the provided function after initializing the memory profile tools.
305/// The function is expected to call all the desired [MallocSizeOf::size_of]
306/// for allocations reachable from the current thread.
307pub fn perform_memory_report<F: FnOnce(&mut MallocSizeOfOps)>(f: F) {
308    let seen_pointer = move |ptr| SEEN_POINTERS.with(|pointers| !pointers.borrow_mut().insert(ptr));
309    let mut ops = MallocSizeOfOps::new(
310        servo_allocator::usable_size,
311        servo_allocator::enclosing_size,
312        Some(Box::new(seen_pointer)),
313    );
314    f(&mut ops);
315    SEEN_POINTERS.with(|pointers| {
316        let mut pointers = pointers.borrow_mut();
317        pointers.clear();
318        pointers.shrink_to_fit();
319    });
320}