profile/
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//! Memory profiling functions.
6
7use std::borrow::ToOwned;
8use std::collections::HashMap;
9use std::fs::File;
10use std::thread;
11
12use log::debug;
13use profile_traits::mem::{
14    MemoryReport, MemoryReportResult, ProfilerChan, ProfilerMsg, Report, Reporter, ReporterRequest,
15    ReportsChan,
16};
17use servo_base::generic_channel::{self, GenericCallback, GenericReceiver};
18
19use crate::system_reporter;
20
21const LOG_FILE_VAR: &str = "UNTRACKED_LOG_FILE";
22
23pub struct Profiler {
24    /// The port through which messages are received.
25    pub port: GenericReceiver<ProfilerMsg>,
26
27    /// Registered memory reporters.
28    reporters: HashMap<String, Reporter>,
29}
30
31impl Profiler {
32    pub fn create() -> ProfilerChan {
33        let (chan, port) = generic_channel::channel().unwrap();
34
35        if servo_allocator::is_tracking_unmeasured() && std::env::var(LOG_FILE_VAR).is_err() {
36            eprintln!("Allocation tracking is enabled but {LOG_FILE_VAR} is unset.");
37        }
38
39        // Always spawn the memory profiler. If there is no timer thread it won't receive regular
40        // `Print` events, but it will still receive the other events.
41        thread::Builder::new()
42            .name("MemoryProfiler".to_owned())
43            .spawn(move || {
44                let mut mem_profiler = Profiler::new(port);
45                mem_profiler.start();
46            })
47            .expect("Thread spawning failed");
48
49        let mem_profiler_chan = ProfilerChan(chan);
50
51        // Register the system memory reporter, which will run on its own thread. It never needs to
52        // be unregistered, because as long as the memory profiler is running the system memory
53        // reporter can make measurements.
54        let callback = GenericCallback::new(|message| {
55            let request: ReporterRequest = message.unwrap();
56            system_reporter::collect_reports(request)
57        })
58        .expect("Could not create system reporter callback");
59        mem_profiler_chan.send(ProfilerMsg::RegisterReporter(
60            "system-main".to_owned(),
61            Reporter(callback),
62        ));
63
64        mem_profiler_chan
65    }
66
67    pub fn new(port: GenericReceiver<ProfilerMsg>) -> Profiler {
68        Profiler {
69            port,
70            reporters: HashMap::new(),
71        }
72    }
73
74    pub fn start(&mut self) {
75        while let Ok(msg) = self.port.recv() {
76            if !self.handle_msg(msg) {
77                break;
78            }
79        }
80    }
81
82    fn handle_msg(&mut self, msg: ProfilerMsg) -> bool {
83        match msg {
84            ProfilerMsg::RegisterReporter(name, reporter) => {
85                debug!("Registering memory reporter: {}", name);
86                // Panic if it has already been registered.
87                let name_clone = name.clone();
88                match self.reporters.insert(name, reporter) {
89                    None => true,
90                    Some(_) => panic!("RegisterReporter: '{}' name is already in use", name_clone),
91                }
92            },
93
94            ProfilerMsg::UnregisterReporter(name) => {
95                debug!("Unregistering memory reporter: {}", name);
96                // Panic if it hasn't previously been registered.
97                match self.reporters.remove(&name) {
98                    Some(_) => true,
99                    None => panic!("UnregisterReporter: '{}' name is unknown", &name),
100                }
101            },
102
103            ProfilerMsg::Report(sender) => {
104                let main_pid = std::process::id();
105
106                let reports = self.collect_reports();
107                // Turn the pid -> reports map into a vector and add the
108                // hint to find the main process.
109                let results: Vec<MemoryReport> = reports
110                    .into_iter()
111                    .map(|(pid, reports)| MemoryReport {
112                        pid,
113                        reports,
114                        is_main_process: pid == main_pid,
115                    })
116                    .collect();
117                let _ = sender.send(MemoryReportResult { results });
118
119                if let Ok(value) = std::env::var(LOG_FILE_VAR) {
120                    match File::create(&value) {
121                        Ok(file) => {
122                            servo_allocator::dump_unmeasured(file);
123                        },
124                        Err(error) => {
125                            log::error!("Error creating log file: {error:?}");
126                        },
127                    }
128                }
129                true
130            },
131            ProfilerMsg::Exit => false,
132        }
133    }
134
135    /// Returns a map of pid -> reports
136    fn collect_reports(&self) -> HashMap<u32, Vec<Report>> {
137        let mut result = HashMap::new();
138
139        for reporter in self.reporters.values() {
140            let (chan, port) = generic_channel::channel().unwrap();
141            reporter.collect_reports(ReportsChan(chan));
142            if let Ok(mut reports) = port.recv() {
143                result
144                    .entry(reports.pid)
145                    .or_insert(vec![])
146                    .append(&mut reports.reports);
147            }
148        }
149        result
150    }
151}