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