1#![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
105pub 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; impl 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 *self.stream.borrow_mut() = request.try_clone_stream().ok();
213
214 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 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 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}