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::{GenericCallback, GenericSender};
15use crossbeam_channel::Sender;
16use ipc_channel::ipc::IpcSender;
17use log::warn;
18use malloc_size_of::MallocSizeOfOps;
19use malloc_size_of_derive::MallocSizeOf;
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 GenericSender<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 callback = GenericCallback::new(move |message| {
112            // Just injects an appropriate event into the paint thread's queue.
113            let request: ReporterRequest = message.unwrap();
114            channel_for_reporter.send(msg(request.reports_channel));
115        })
116        .expect("Could not create memory reporting callback");
117        self.send(ProfilerMsg::RegisterReporter(
118            reporter_name.clone(),
119            Reporter(callback),
120        ));
121
122        ProfilerRegistration {
123            sender: self.clone(),
124            reporter_name,
125        }
126    }
127
128    /// Runs `f()` with memory profiling.
129    pub fn run_with_memory_reporting<F, M, T, C>(
130        &self,
131        f: F,
132        reporter_name: String,
133        channel_for_reporter: C,
134        msg: M,
135    ) where
136        F: FnOnce(),
137        M: Fn(ReportsChan) -> T + Send + 'static,
138        T: Send + 'static,
139        C: OpaqueSender<T> + Send + 'static,
140    {
141        let _registration = self.prepare_memory_reporting(reporter_name, channel_for_reporter, msg);
142
143        f();
144    }
145}
146
147/// The various kinds of memory measurement.
148///
149/// Here "explicit" means explicit memory allocations done by the application. It includes
150/// allocations made at the OS level (via functions such as VirtualAlloc, vm_allocate, and mmap),
151/// allocations made at the heap allocation level (via functions such as malloc, calloc, realloc,
152/// memalign, operator new, and operator new[]) and where possible, the overhead of the heap
153/// allocator itself. It excludes memory that is mapped implicitly such as code and data segments,
154/// and thread stacks. "explicit" is not guaranteed to cover every explicit allocation, but it does
155/// cover most (including the entire heap), and therefore it is the single best number to focus on
156/// when trying to reduce memory usage.
157#[derive(Debug, Deserialize, Serialize)]
158pub enum ReportKind {
159    /// A size measurement for an explicit allocation on the jemalloc heap. This should be used
160    /// for any measurements done via the `MallocSizeOf` trait.
161    ExplicitJemallocHeapSize,
162
163    /// A size measurement for an explicit allocation on the system heap. Only likely to be used
164    /// for external C or C++ libraries that don't use jemalloc.
165    ExplicitSystemHeapSize,
166
167    /// A size measurement for an explicit allocation not on the heap, e.g. via mmap().
168    ExplicitNonHeapSize,
169
170    /// A size measurement for an explicit allocation whose location is unknown or uncertain.
171    ExplicitUnknownLocationSize,
172
173    /// A size measurement for a non-explicit allocation. This kind is used for global
174    /// measurements such as "resident" and "vsize", and also for measurements that cross-cut the
175    /// measurements grouped under "explicit", e.g. by grouping those measurements in a way that's
176    /// different to how they are grouped under "explicit".
177    NonExplicitSize,
178}
179
180/// A single memory-related measurement.
181#[derive(Debug, Deserialize, Serialize)]
182pub struct Report {
183    /// The identifying path for this report.
184    pub path: Vec<String>,
185
186    /// The report kind.
187    pub kind: ReportKind,
188
189    /// The size, in bytes.
190    pub size: usize,
191}
192
193/// A set of reports belonging to a process.
194#[derive(Debug, Deserialize, Serialize)]
195pub struct ProcessReports {
196    /// The set of reports.
197    pub reports: Vec<Report>,
198
199    /// The process id.
200    pub pid: u32,
201}
202
203impl ProcessReports {
204    /// Adopt these reports and configure the process pid.
205    pub fn new(reports: Vec<Report>) -> Self {
206        Self {
207            reports,
208            pid: std::process::id(),
209        }
210    }
211}
212
213/// A channel through which memory reports can be sent.
214#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
215pub struct ReportsChan(pub GenericSender<ProcessReports>);
216
217impl ReportsChan {
218    /// Send `report` on this `IpcSender`.
219    ///
220    /// Panics if the send fails.
221    pub fn send(&self, reports: ProcessReports) {
222        self.0.send(reports).unwrap();
223    }
224}
225
226/// The protocol used to send reporter requests.
227#[derive(Debug, Deserialize, Serialize)]
228pub struct ReporterRequest {
229    /// The channel on which reports are to be sent.
230    pub reports_channel: ReportsChan,
231}
232
233/// A memory reporter is capable of measuring some data structure of interest. It's structured as
234/// an IPC sender that a `ReporterRequest` in transmitted over. `ReporterRequest` objects in turn
235/// encapsulate the channel on which the memory profiling information is to be sent.
236///
237/// In many cases, clients construct `Reporter` objects by creating an IPC sender/receiver pair and
238/// registering the receiving end with the router so that messages from the memory profiler end up
239/// injected into the client's event loop.
240#[derive(Debug, Deserialize, Serialize)]
241pub struct Reporter(pub GenericCallback<ReporterRequest>);
242
243impl Reporter {
244    /// Collect one or more memory reports. Returns true on success, and false on failure.
245    pub fn collect_reports(&self, reports_channel: ReportsChan) {
246        self.0.send(ReporterRequest { reports_channel }).unwrap()
247    }
248}
249
250/// An easy way to build a path for a report.
251#[macro_export]
252macro_rules! path {
253    ($($x:expr),*) => {{
254        use std::borrow::ToOwned;
255        vec![$( $x.to_owned() ),*]
256    }}
257}
258
259/// The results produced by the memory reporter.
260#[derive(Debug, Deserialize, Serialize)]
261pub struct MemoryReportResult {
262    /// All the results from the MemoryReports
263    pub results: Vec<MemoryReport>,
264}
265
266#[derive(Debug, Deserialize, Serialize)]
267/// A simple memory report
268pub struct MemoryReport {
269    /// The pid of the report
270    pub pid: u32,
271    /// Is this the main process
272    pub is_main_process: bool,
273    /// All the reports for this pid
274    pub reports: Vec<Report>,
275}
276
277/// Messages that can be sent to the memory profiler thread.
278#[derive(Debug, Deserialize, Serialize)]
279pub enum ProfilerMsg {
280    /// Register a Reporter with the memory profiler. The String is only used to identify the
281    /// reporter so it can be unregistered later. The String must be distinct from that used by any
282    /// other registered reporter otherwise a panic will occur.
283    RegisterReporter(String, Reporter),
284
285    /// Unregister a Reporter with the memory profiler. The String must match the name given when
286    /// the reporter was registered. If the String does not match the name of a registered reporter
287    /// a panic will occur.
288    UnregisterReporter(String),
289
290    /// Tells the memory profiler to shut down.
291    Exit,
292
293    /// Triggers sending back the memory profiling metrics,
294    Report(GenericCallback<MemoryReportResult>),
295}
296
297thread_local!(static SEEN_POINTERS: LazyCell<RefCell<HashSet<*const c_void>>> = const {
298    LazyCell::new(Default::default)
299});
300
301/// Invoke the provided function after initializing the memory profile tools.
302/// The function is expected to call all the desired [MallocSizeOf::size_of]
303/// for allocations reachable from the current thread.
304pub fn perform_memory_report<F: FnOnce(&mut MallocSizeOfOps)>(f: F) {
305    let seen_pointer = move |ptr| SEEN_POINTERS.with(|pointers| !pointers.borrow_mut().insert(ptr));
306    let mut ops = MallocSizeOfOps::new(
307        servo_allocator::usable_size,
308        servo_allocator::enclosing_size,
309        Some(Box::new(seen_pointer)),
310    );
311    f(&mut ops);
312    SEEN_POINTERS.with(|pointers| {
313        let mut pointers = pointers.borrow_mut();
314        pointers.clear();
315        pointers.shrink_to_fit();
316    });
317}