Skip to main content

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