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}