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}