1use 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 pub port: GenericReceiver<ProfilerMsg>,
26
27 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 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 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 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 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 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 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}