devtools/actors/
timeline.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#![expect(dead_code)]
6
7use std::sync::{Arc, Mutex};
8use std::thread;
9use std::time::Duration;
10
11use atomic_refcell::AtomicRefCell;
12use devtools_traits::DevtoolScriptControlMsg::{DropTimelineMarkers, SetTimelineMarkers};
13use devtools_traits::{DevtoolScriptControlMsg, TimelineMarker, TimelineMarkerType};
14use malloc_size_of_derive::MallocSizeOf;
15use serde::{Serialize, Serializer};
16use serde_json::{Map, Value};
17use servo_base::cross_process_instant::CrossProcessInstant;
18use servo_base::generic_channel::{self, GenericReceiver, GenericSender};
19use servo_base::id::PipelineId;
20
21use crate::StreamId;
22use crate::actor::{Actor, ActorError, ActorRegistry};
23use crate::actors::framerate::FramerateActor;
24use crate::actors::memory::{MemoryActor, TimelineMemoryReply};
25use crate::protocol::{ClientRequest, DevtoolsConnection, JsonPacketStream};
26
27#[derive(MallocSizeOf)]
28pub(crate) struct TimelineActor {
29    name: String,
30    script_sender: GenericSender<DevtoolScriptControlMsg>,
31    marker_types: Vec<TimelineMarkerType>,
32    pipeline_id: PipelineId,
33    #[conditional_malloc_size_of]
34    is_recording: Arc<Mutex<bool>>,
35    framerate_actor: AtomicRefCell<Option<String>>,
36    memory_actor: AtomicRefCell<Option<String>>,
37    #[conditional_malloc_size_of]
38    registry: Arc<Mutex<ActorRegistry>>,
39    start_stamp: CrossProcessInstant,
40}
41
42struct Emitter {
43    from: String,
44    stream: DevtoolsConnection,
45    registry: Arc<Mutex<ActorRegistry>>,
46    start_stamp: CrossProcessInstant,
47
48    framerate_actor: Option<String>,
49    memory_actor: Option<String>,
50}
51
52#[derive(Serialize)]
53struct IsRecordingReply {
54    from: String,
55    value: bool,
56}
57
58#[derive(Serialize)]
59struct StartReply {
60    from: String,
61    value: HighResolutionStamp,
62}
63
64#[derive(Serialize)]
65struct StopReply {
66    from: String,
67    value: HighResolutionStamp,
68}
69
70#[derive(Serialize)]
71#[serde(rename_all = "camelCase")]
72struct TimelineMarkerReply {
73    name: String,
74    start: HighResolutionStamp,
75    end: HighResolutionStamp,
76    stack: Option<Vec<()>>,
77    end_stack: Option<Vec<()>>,
78}
79
80#[derive(Serialize)]
81#[serde(rename_all = "camelCase")]
82struct MarkersEmitterReply {
83    #[serde(rename = "type")]
84    type_: String,
85    markers: Vec<TimelineMarkerReply>,
86    from: String,
87    end_time: HighResolutionStamp,
88}
89
90#[derive(Serialize)]
91struct MemoryEmitterReply {
92    #[serde(rename = "type")]
93    type_: String,
94    from: String,
95    delta: HighResolutionStamp,
96    measurement: TimelineMemoryReply,
97}
98
99#[derive(Serialize)]
100struct FramerateEmitterReply {
101    #[serde(rename = "type")]
102    type_: String,
103    from: String,
104    delta: HighResolutionStamp,
105    timestamps: Vec<HighResolutionStamp>,
106}
107
108/// HighResolutionStamp is struct that contains duration in milliseconds
109/// with accuracy to microsecond that shows how much time has passed since
110/// actor registry inited
111/// analog <https://w3c.github.io/hr-time/#sec-DOMHighResTimeStamp>
112#[derive(MallocSizeOf)]
113pub(crate) struct HighResolutionStamp(f64);
114
115impl HighResolutionStamp {
116    pub fn new(start_stamp: CrossProcessInstant, time: CrossProcessInstant) -> HighResolutionStamp {
117        let duration = (time - start_stamp).whole_microseconds();
118        HighResolutionStamp(duration as f64 / 1000_f64)
119    }
120
121    pub fn wrap(time: f64) -> HighResolutionStamp {
122        HighResolutionStamp(time)
123    }
124}
125
126impl Serialize for HighResolutionStamp {
127    fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
128        self.0.serialize(s)
129    }
130}
131
132static DEFAULT_TIMELINE_DATA_PULL_TIMEOUT: u64 = 200; // ms
133
134impl TimelineActor {
135    pub fn new(
136        name: String,
137        pipeline_id: PipelineId,
138        script_sender: GenericSender<DevtoolScriptControlMsg>,
139        registry: Arc<Mutex<ActorRegistry>>,
140    ) -> TimelineActor {
141        let marker_types = vec![TimelineMarkerType::Reflow, TimelineMarkerType::DOMEvent];
142
143        TimelineActor {
144            name,
145            pipeline_id,
146            marker_types,
147            script_sender,
148            is_recording: Arc::new(Mutex::new(false)),
149            framerate_actor: AtomicRefCell::new(None),
150            memory_actor: AtomicRefCell::new(None),
151            start_stamp: CrossProcessInstant::now(),
152            registry,
153        }
154    }
155
156    fn pull_timeline_data(
157        &self,
158        receiver: GenericReceiver<Option<TimelineMarker>>,
159        mut emitter: Emitter,
160    ) {
161        let is_recording = self.is_recording.clone();
162
163        if !*is_recording.lock().unwrap() {
164            return;
165        }
166
167        thread::Builder::new()
168            .name("PullTimelineData".to_owned())
169            .spawn(move || {
170                loop {
171                    if !*is_recording.lock().unwrap() {
172                        break;
173                    }
174
175                    let mut markers = vec![];
176                    while let Ok(Some(marker)) = receiver.try_recv() {
177                        markers.push(emitter.marker(marker));
178                    }
179                    if emitter.send(markers).is_err() {
180                        break;
181                    }
182
183                    thread::sleep(Duration::from_millis(DEFAULT_TIMELINE_DATA_PULL_TIMEOUT));
184                }
185            })
186            .expect("Thread spawning failed");
187    }
188}
189
190impl Actor for TimelineActor {
191    fn name(&self) -> String {
192        self.name.clone()
193    }
194
195    fn handle_message(
196        &self,
197        request: ClientRequest,
198        registry: &ActorRegistry,
199        msg_type: &str,
200        msg: &Map<String, Value>,
201        _id: StreamId,
202    ) -> Result<(), ActorError> {
203        match msg_type {
204            "start" => {
205                **self.is_recording.lock().as_mut().unwrap() = true;
206
207                let (tx, rx) = generic_channel::channel::<Option<TimelineMarker>>().unwrap();
208                self.script_sender
209                    .send(SetTimelineMarkers(
210                        self.pipeline_id,
211                        self.marker_types.clone(),
212                        tx,
213                    ))
214                    .unwrap();
215
216                // init memory actor
217                if let Some(with_memory) = msg.get("withMemory") {
218                    if let Some(true) = with_memory.as_bool() {
219                        *self.memory_actor.borrow_mut() = Some(MemoryActor::create(registry));
220                    }
221                }
222
223                // init framerate actor
224                if let Some(with_ticks) = msg.get("withTicks") {
225                    if let Some(true) = with_ticks.as_bool() {
226                        let framerate_actor = Some(FramerateActor::create(
227                            registry,
228                            self.pipeline_id,
229                            self.script_sender.clone(),
230                        ));
231                        *self.framerate_actor.borrow_mut() = framerate_actor;
232                    }
233                }
234
235                let emitter = Emitter::new(
236                    self.name(),
237                    self.registry.clone(),
238                    self.start_stamp,
239                    request.stream(),
240                    self.memory_actor.borrow().clone(),
241                    self.framerate_actor.borrow().clone(),
242                );
243
244                self.pull_timeline_data(rx, emitter);
245
246                let msg = StartReply {
247                    from: self.name(),
248                    value: HighResolutionStamp::new(self.start_stamp, CrossProcessInstant::now()),
249                };
250                request.reply_final(&msg)?
251            },
252
253            "stop" => {
254                let msg = StopReply {
255                    from: self.name(),
256                    value: HighResolutionStamp::new(self.start_stamp, CrossProcessInstant::now()),
257                };
258
259                self.script_sender
260                    .send(DropTimelineMarkers(
261                        self.pipeline_id,
262                        self.marker_types.clone(),
263                    ))
264                    .unwrap();
265
266                // TODO: move this to the cleanup method.
267                if let Some(ref actor_name) = *self.framerate_actor.borrow() {
268                    registry.remove(actor_name.clone());
269                }
270
271                if let Some(ref actor_name) = *self.memory_actor.borrow() {
272                    registry.remove(actor_name.clone());
273                }
274
275                **self.is_recording.lock().as_mut().unwrap() = false;
276                request.reply_final(&msg)?
277            },
278
279            "isRecording" => {
280                let msg = IsRecordingReply {
281                    from: self.name(),
282                    value: *self.is_recording.lock().unwrap(),
283                };
284
285                request.reply_final(&msg)?
286            },
287
288            _ => return Err(ActorError::UnrecognizedPacketType),
289        };
290        Ok(())
291    }
292}
293
294impl Emitter {
295    pub fn new(
296        name: String,
297        registry: Arc<Mutex<ActorRegistry>>,
298        start_stamp: CrossProcessInstant,
299        stream: DevtoolsConnection,
300        memory_actor_name: Option<String>,
301        framerate_actor_name: Option<String>,
302    ) -> Emitter {
303        Emitter {
304            from: name,
305            stream,
306            registry,
307            start_stamp,
308
309            framerate_actor: framerate_actor_name,
310            memory_actor: memory_actor_name,
311        }
312    }
313
314    fn marker(&self, payload: TimelineMarker) -> TimelineMarkerReply {
315        TimelineMarkerReply {
316            name: payload.name,
317            start: HighResolutionStamp::new(self.start_stamp, payload.start_time),
318            end: HighResolutionStamp::new(self.start_stamp, payload.end_time),
319            stack: payload.start_stack,
320            end_stack: payload.end_stack,
321        }
322    }
323
324    fn send(&mut self, markers: Vec<TimelineMarkerReply>) -> Result<(), ActorError> {
325        let end_time = CrossProcessInstant::now();
326        let reply = MarkersEmitterReply {
327            type_: "markers".to_owned(),
328            markers,
329            from: self.from.clone(),
330            end_time: HighResolutionStamp::new(self.start_stamp, end_time),
331        };
332        self.stream.write_json_packet(&reply)?;
333
334        if let Some(ref actor_name) = self.framerate_actor {
335            let mut lock = self.registry.lock();
336            let registry = lock.as_mut().unwrap();
337            let framerate_actor = registry.find::<FramerateActor>(actor_name);
338            let framerate_reply = FramerateEmitterReply {
339                type_: "framerate".to_owned(),
340                from: framerate_actor.name(),
341                delta: HighResolutionStamp::new(self.start_stamp, end_time),
342                timestamps: framerate_actor.take_pending_ticks(),
343            };
344            self.stream.write_json_packet(&framerate_reply)?;
345        }
346
347        if let Some(ref actor_name) = self.memory_actor {
348            let registry = self.registry.lock().unwrap();
349            let memory_actor = registry.find::<MemoryActor>(actor_name);
350            let memory_reply = MemoryEmitterReply {
351                type_: "memory".to_owned(),
352                from: memory_actor.name(),
353                delta: HighResolutionStamp::new(self.start_stamp, end_time),
354                measurement: memory_actor.measure(),
355            };
356            self.stream.write_json_packet(&memory_reply)?;
357        }
358
359        Ok(())
360    }
361}