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