diff --git a/components/devtools/actor.rs b/components/devtools/actor.rs index 2ea8e75c061..125080328f0 100644 --- a/components/devtools/actor.rs +++ b/components/devtools/actor.rs @@ -10,6 +10,7 @@ use std::cell::{Cell, RefCell}; use std::mem::{replace, transmute}; use std::net::TcpStream; use std::raw::TraitObject; +use std::sync::{Arc, Mutex}; use rustc_serialize::json; /// A common trait for all devtools actors that encompasses an immutable name @@ -77,7 +78,9 @@ impl Actor { pub struct ActorRegistry { actors: HashMap>, new_actors: RefCell>>, + old_actors: RefCell>, script_actors: RefCell>, + shareable: Option>>, next: Cell, } @@ -87,11 +90,31 @@ impl ActorRegistry { ActorRegistry { actors: HashMap::new(), new_actors: RefCell::new(vec!()), + old_actors: RefCell::new(vec!()), script_actors: RefCell::new(HashMap::new()), + shareable: None, next: Cell::new(0), } } + /// Creating shareable registry + pub fn create_shareable(self) -> Arc>{ + if self.shareable.is_some() { + return self.shareable.unwrap(); + } + + let shareable = Arc::new(Mutex::new(self)); + let mut lock = shareable.lock(); + let registry = lock.as_mut().unwrap(); + registry.shareable = Some(shareable.clone()); + shareable.clone() + } + + /// Get shareable registry through threads + pub fn get_shareable(&self) -> Arc> { + self.shareable.as_ref().unwrap().clone() + } + pub fn register_script_actor(&self, script_id: String, actor: String) { println!("registering {} ({})", actor, script_id); let mut script_actors = self.script_actors.borrow_mut(); @@ -155,6 +178,7 @@ impl ActorRegistry { stream: &mut TcpStream) -> Result<(), ()> { let to = msg.get("to").unwrap().as_string().unwrap(); + match self.actors.get(&to.to_string()) { None => println!("message received for unknown actor \"{}\"", to), Some(actor) => { @@ -169,6 +193,20 @@ impl ActorRegistry { for actor in new_actors.into_iter() { self.actors.insert(actor.name().to_string(), actor); } + + let old_actors = replace(&mut *self.old_actors.borrow_mut(), vec!()); + for name in old_actors.into_iter() { + self.drop_actor(name); + } Ok(()) } + + pub fn drop_actor(&mut self, name: String) { + self.actors.remove(&name); + } + + pub fn drop_actor_later(&self, name: String) { + let mut actors = self.old_actors.borrow_mut(); + actors.push(name); + } } diff --git a/components/devtools/actors/framerate.rs b/components/devtools/actors/framerate.rs new file mode 100644 index 00000000000..55b6755fd81 --- /dev/null +++ b/components/devtools/actors/framerate.rs @@ -0,0 +1,88 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use rustc_serialize::json; +use std::mem; +use std::net::TcpStream; +use time::precise_time_ns; + +use actor::{Actor, ActorRegistry}; + +pub struct FramerateActor { + name: String, + start_time: Option, + is_recording: bool, + ticks: Vec, +} + +impl Actor for FramerateActor { + fn name(&self) -> String { + self.name.clone() + } + + + fn handle_message(&self, + _registry: &ActorRegistry, + _msg_type: &str, + _msg: &json::Object, + _stream: &mut TcpStream) -> Result { + Ok(false) + } +} + +impl FramerateActor { + /// return name of actor + pub fn create(registry: &ActorRegistry) -> String { + let actor_name = registry.new_name("framerate"); + let mut actor = FramerateActor { + name: actor_name.clone(), + start_time: None, + is_recording: false, + ticks: Vec::new(), + }; + + actor.start_recording(); + registry.register_later(box actor); + actor_name + } + + // callback on request animation frame + pub fn on_refresh_driver_tick(&mut self) { + if !self.is_recording { + return; + } + // TODO: Need implement requesting animation frame + // http://hg.mozilla.org/mozilla-central/file/0a46652bd992/dom/base/nsGlobalWindow.cpp#l5314 + + let start_time = self.start_time.as_ref().unwrap(); + self.ticks.push(*start_time - precise_time_ns()); + } + + pub fn take_pending_ticks(&mut self) -> Vec { + mem::replace(&mut self.ticks, Vec::new()) + } + + fn start_recording(&mut self) { + self.is_recording = true; + self.start_time = Some(precise_time_ns()); + + // TODO: Need implement requesting animation frame + // http://hg.mozilla.org/mozilla-central/file/0a46652bd992/dom/base/nsGlobalWindow.cpp#l5314 + } + + fn stop_recording(&mut self) { + if !self.is_recording { + return; + } + self.is_recording = false; + self.start_time = None; + } + +} + +impl Drop for FramerateActor { + fn drop(&mut self) { + self.stop_recording(); + } +} diff --git a/components/devtools/actors/inspector.rs b/components/devtools/actors/inspector.rs index a01d18f79bf..5519652cf06 100644 --- a/components/devtools/actors/inspector.rs +++ b/components/devtools/actors/inspector.rs @@ -15,9 +15,9 @@ use collections::BTreeMap; use msg::constellation_msg::PipelineId; use rustc_serialize::json::{self, Json, ToJson}; use std::cell::RefCell; -use std::sync::mpsc::{channel, Sender}; use std::net::TcpStream; use std::num::Float; +use std::sync::mpsc::{channel, Sender}; pub struct InspectorActor { pub name: String, diff --git a/components/devtools/actors/memory.rs b/components/devtools/actors/memory.rs new file mode 100644 index 00000000000..a651f3f3d4d --- /dev/null +++ b/components/devtools/actors/memory.rs @@ -0,0 +1,67 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use rustc_serialize::json; +use std::net::TcpStream; + +use actor::{Actor, ActorRegistry}; + +#[derive(RustcEncodable)] +pub struct TimelineMemoryReply { + jsObjectSize: u64, + jsStringSize: u64, + jsOtherSize: u64, + domSize: u64, + styleSize: u64, + otherSize: u64, + totalSize: u64, + jsMilliseconds: f64, + nonJSMilliseconds: f64, +} + +pub struct MemoryActor { + pub name: String, +} + +impl Actor for MemoryActor { + fn name(&self) -> String { + self.name.clone() + } + + fn handle_message(&self, + _registry: &ActorRegistry, + _msg_type: &str, + _msg: &json::Object, + _stream: &mut TcpStream) -> Result { + Ok(false) + } +} + +impl MemoryActor { + /// return name of actor + pub fn create(registry: &ActorRegistry) -> String { + let actor_name = registry.new_name("memory"); + let actor = MemoryActor { + name: actor_name.clone() + }; + + registry.register_later(box actor); + actor_name + } + + pub fn measure(&self) -> TimelineMemoryReply { + //TODO: + TimelineMemoryReply { + jsObjectSize: 1, + jsStringSize: 1, + jsOtherSize: 1, + domSize: 1, + styleSize: 1, + otherSize: 1, + totalSize: 1, + jsMilliseconds: 1.1, + nonJSMilliseconds: 1.1, + } + } +} diff --git a/components/devtools/actors/tab.rs b/components/devtools/actors/tab.rs index f5bba52a022..7450ae45143 100644 --- a/components/devtools/actors/tab.rs +++ b/components/devtools/actors/tab.rs @@ -60,6 +60,7 @@ pub struct TabActorMsg { outerWindowID: u32, consoleActor: String, inspectorActor: String, + timelineActor: String, } pub struct TabActor { @@ -68,6 +69,7 @@ pub struct TabActor { pub url: String, pub console: String, pub inspector: String, + pub timeline: String, } impl Actor for TabActor { @@ -143,6 +145,7 @@ impl TabActor { outerWindowID: 0, //FIXME: this should probably be the pipeline id consoleActor: self.console.clone(), inspectorActor: self.inspector.clone(), + timelineActor: self.timeline.clone(), } } } diff --git a/components/devtools/actors/timeline.rs b/components/devtools/actors/timeline.rs new file mode 100644 index 00000000000..62f05c2fa91 --- /dev/null +++ b/components/devtools/actors/timeline.rs @@ -0,0 +1,352 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use core::iter::FromIterator; +use msg::constellation_msg::PipelineId; +use rustc_serialize::json; +use std::cell::RefCell; +use std::collections::{HashMap, VecDeque}; +use std::net::TcpStream; +use std::old_io::timer::sleep; +use std::sync::{Arc, Mutex}; +use std::time::duration::Duration; +use std::sync::mpsc::{channel, Sender, Receiver, TryRecvError}; +use time::precise_time_ns; + +use actor::{Actor, ActorRegistry}; +use actors::memory::{MemoryActor, TimelineMemoryReply}; +use actors::framerate::FramerateActor; +use devtools_traits::DevtoolScriptControlMsg; +use devtools_traits::DevtoolScriptControlMsg::{SetTimelineMarkers, DropTimelineMarkers}; +use devtools_traits::{TimelineMarker, TracingMetadata, TimelineMarkerType}; +use protocol::JsonPacketStream; +use util::task; + +pub struct TimelineActor { + name: String, + script_sender: Sender, + marker_types: Vec, + pipeline: PipelineId, + is_recording: Arc>, + stream: RefCell>, + + framerate_actor: RefCell>, + memory_actor: RefCell>, +} + +struct Emitter { + from: String, + stream: TcpStream, + markers: Vec, + registry: Arc>, + + framerate_actor: Option, + memory_actor: Option, +} + +#[derive(RustcEncodable)] +struct IsRecordingReply { + from: String, + value: bool +} + +#[derive(RustcEncodable)] +struct StartReply { + from: String, + value: u64 +} + +#[derive(RustcEncodable)] +struct StopReply { + from: String, + value: u64 +} + +#[derive(RustcEncodable)] +struct TimelineMarkerReply { + name: String, + start: u64, + end: u64, + stack: Option>, + endStack: Option>, +} + +#[derive(RustcEncodable)] +struct MarkersEmitterReply { + __type__: String, + markers: Vec, + from: String, + endTime: u64, +} + +#[derive(RustcEncodable)] +struct MemoryEmitterReply { + __type__: String, + from: String, + delta: u64, + measurement: TimelineMemoryReply, +} + +#[derive(RustcEncodable)] +struct FramerateEmitterReply { + __type__: String, + from: String, + delta: u64, + timestamps: Vec, +} + +static DEFAULT_TIMELINE_DATA_PULL_TIMEOUT: usize = 200; //ms + +impl TimelineActor { + pub fn new(name: String, + pipeline: PipelineId, + script_sender: Sender) -> TimelineActor { + + let marker_types = vec!(TimelineMarkerType::Reflow, + TimelineMarkerType::DOMEvent); + + TimelineActor { + name: name, + pipeline: pipeline, + marker_types: marker_types, + script_sender: script_sender, + is_recording: Arc::new(Mutex::new(false)), + stream: RefCell::new(None), + + framerate_actor: RefCell::new(None), + memory_actor: RefCell::new(None), + } + } + + fn pull_timeline_data(&self, receiver: Receiver, mut emitter: Emitter) { + let is_recording = self.is_recording.clone(); + + if !*is_recording.lock().unwrap() { + return; + } + + /// Select root(with depth 0) TimelineMarker pair (IntervalStart + IntervalEnd) + /// from queue and add marker to emitter + /// Return true if closed (IntervalStart + IntervalEnd) pair was founded + fn group(queue: &mut VecDeque, depth: usize, + start_payload: Option, emitter: &mut Emitter) -> bool { + + if let Some(start_payload) = start_payload { + if start_payload.metadata != TracingMetadata::IntervalStart { + panic!("Start payload doesn't have metadata IntervalStart"); + } + + if let Some(end_payload) = queue.pop_front() { + match end_payload.metadata { + TracingMetadata::IntervalEnd => { + if depth == 0 { + // Emit TimelineMarkerReply, pair was found + emitter.add_marker(start_payload, end_payload); + } + return true; + } + TracingMetadata::IntervalStart => { + if group(queue, depth + 1, Some(end_payload), emitter) { + return group(queue, depth, Some(start_payload), emitter); + } else { + queue.push_front(start_payload); + } + } + _ => panic!("Unknown tracingMetadata") + } + } else { + queue.push_front(start_payload); + } + } + + false + } + + task::spawn_named("PullTimelineMarkers".to_string(), move || { + let mut queues = HashMap::new(); + queues.insert("Reflow".to_string(), VecDeque::new()); + queues.insert("DOMEvent".to_string(), VecDeque::new()); + + loop { + if !*is_recording.lock().unwrap() { + break; + } + + // Creating queues by marker.name + loop { + match receiver.try_recv() { + Ok(marker) => { + if let Some(list) = queues.get_mut(&marker.name) { + list.push_back(marker); + } + } + + Err(TryRecvError) => break + } + } + + // Emit all markers + for (_, queue) in queues.iter_mut() { + let start_payload = queue.pop_front(); + group(queue, 0, start_payload, &mut emitter); + } + emitter.send(); + + sleep(Duration::milliseconds(DEFAULT_TIMELINE_DATA_PULL_TIMEOUT as i64)); + } + }); + } +} + +impl Actor for TimelineActor { + fn name(&self) -> String { + self.name.clone() + } + + fn handle_message(&self, + registry: &ActorRegistry, + msg_type: &str, + msg: &json::Object, + stream: &mut TcpStream) -> Result { + Ok(match msg_type { + "start" => { + **self.is_recording.lock().as_mut().unwrap() = true; + + let (tx, rx) = channel::(); + self.script_sender.send(SetTimelineMarkers(self.pipeline, self.marker_types.clone(), tx)); + + *self.stream.borrow_mut() = stream.try_clone().ok(); + + // init memory actor + if let Some(with_memory) = msg.get("withMemory") { + if let Some(true) = with_memory.as_boolean() { + *self.memory_actor.borrow_mut() = Some(MemoryActor::create(registry)); + } + } + + // init framerate actor + if let Some(with_ticks) = msg.get("withTicks") { + if let Some(true) = with_ticks.as_boolean() { + *self.framerate_actor.borrow_mut() = Some(FramerateActor::create(registry)); + } + } + + let emitter = Emitter::new(self.name(), registry.get_shareable(), + stream.try_clone().unwrap(), + self.memory_actor.borrow().clone(), + self.framerate_actor.borrow().clone()); + + self.pull_timeline_data(rx, emitter); + + let msg = StartReply { + from: self.name(), + value: precise_time_ns(), + }; + stream.write_json_packet(&msg); + true + } + + "stop" => { + let msg = StopReply { + from: self.name(), + value: precise_time_ns() + }; + + stream.write_json_packet(&msg); + self.script_sender.send(DropTimelineMarkers(self.pipeline, self.marker_types.clone())); + + if let Some(ref actor_name) = *self.framerate_actor.borrow() { + registry.drop_actor_later(actor_name.clone()); + } + + if let Some(ref actor_name) = *self.memory_actor.borrow() { + registry.drop_actor_later(actor_name.clone()); + } + + **self.is_recording.lock().as_mut().unwrap() = false; + self.stream.borrow_mut().take(); + true + } + + "isRecording" => { + let msg = IsRecordingReply { + from: self.name(), + value: self.is_recording.lock().unwrap().clone() + }; + + stream.write_json_packet(&msg); + true + } + + _ => { + false + } + }) + } +} + +impl Emitter { + pub fn new(name: String, + registry: Arc>, + stream: TcpStream, + memory_actor_name: Option, + framerate_actor_name: Option) -> Emitter { + + Emitter { + from: name, + stream: stream, + markers: Vec::new(), + registry: registry, + + framerate_actor: framerate_actor_name, + memory_actor: memory_actor_name, + } + } + + fn add_marker(&mut self, start_payload: TimelineMarker, end_payload: TimelineMarker) -> () { + self.markers.push(TimelineMarkerReply { + name: start_payload.name, + start: start_payload.time, + end: end_payload.time, + stack: start_payload.stack, + endStack: end_payload.stack, + }); + } + + fn send(&mut self) -> () { + let end_time = precise_time_ns(); + let reply = MarkersEmitterReply { + __type__: "markers".to_string(), + markers: Vec::from_iter(self.markers.drain()), + from: self.from.clone(), + endTime: end_time, + }; + self.stream.write_json_packet(&reply); + + if let Some(ref actor_name) = self.framerate_actor { + let mut lock = self.registry.lock(); + let registry = lock.as_mut().unwrap(); + let mut framerate_actor = registry.find_mut::(actor_name); + let framerateReply = FramerateEmitterReply { + __type__: "framerate".to_string(), + from: framerate_actor.name(), + delta: end_time, + timestamps: framerate_actor.take_pending_ticks(), + }; + self.stream.write_json_packet(&framerateReply); + } + + if let Some(ref actor_name) = self.memory_actor { + let registry = self.registry.lock().unwrap(); + let memory_actor = registry.find::(actor_name); + let memoryReply = MemoryEmitterReply { + __type__: "memory".to_string(), + from: memory_actor.name(), + delta: end_time, + measurement: memory_actor.measure(), + }; + self.stream.write_json_packet(&memoryReply); + } + } +} diff --git a/components/devtools/actors/worker.rs b/components/devtools/actors/worker.rs index 947d43523d3..a1e6fe71540 100644 --- a/components/devtools/actors/worker.rs +++ b/components/devtools/actors/worker.rs @@ -3,9 +3,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use actor::{Actor, ActorRegistry}; +use msg::constellation_msg::WorkerId; use rustc_serialize::json; use std::net::TcpStream; -use msg::constellation_msg::WorkerId; pub struct WorkerActor { pub name: String, diff --git a/components/devtools/lib.rs b/components/devtools/lib.rs index 29947540205..c5057d79f90 100644 --- a/components/devtools/lib.rs +++ b/components/devtools/lib.rs @@ -34,6 +34,7 @@ use actors::worker::WorkerActor; use actors::inspector::InspectorActor; use actors::root::RootActor; use actors::tab::TabActor; +use actors::timeline::TimelineActor; use protocol::JsonPacketStream; use devtools_traits::{ConsoleMessage, DevtoolsControlMsg}; @@ -53,9 +54,12 @@ mod actor; /// Corresponds to http://mxr.mozilla.org/mozilla-central/source/toolkit/devtools/server/actors/ mod actors { pub mod console; + pub mod framerate; + pub mod memory; pub mod inspector; pub mod root; pub mod tab; + pub mod timeline; pub mod worker; } mod protocol; @@ -103,7 +107,7 @@ fn run_server(sender: Sender, registry.register(root); registry.find::("root"); - let actors = Arc::new(Mutex::new(registry)); + let actors = registry.create_shareable(); let mut accepted_connections: Vec = Vec::new(); @@ -122,9 +126,8 @@ fn run_server(sender: Sender, 'outer: loop { match stream.read_json_packet() { Ok(json_packet) => { - let mut actors = actors.lock().unwrap(); - match actors.handle_message(json_packet.as_object().unwrap(), - &mut stream) { + match actors.lock().unwrap().handle_message(json_packet.as_object().unwrap(), + &mut stream) { Ok(()) => {}, Err(()) => { println!("error: devtools actor stopped responding"); @@ -156,7 +159,7 @@ fn run_server(sender: Sender, let mut actor_workers: HashMap<(PipelineId, WorkerId), String> = HashMap::new(); //TODO: move all this actor creation into a constructor method on TabActor - let (tab, console, inspector) = { + let (tab, console, inspector, timeline) = { let console = ConsoleActor { name: actors.new_name("console"), script_chan: scriptSender.clone(), @@ -168,10 +171,14 @@ fn run_server(sender: Sender, walker: RefCell::new(None), pageStyle: RefCell::new(None), highlighter: RefCell::new(None), - script_chan: scriptSender, + script_chan: scriptSender.clone(), pipeline: pipeline, }; + let timeline = TimelineActor::new(actors.new_name("timeline"), + pipeline, + scriptSender); + let DevtoolsPageInfo { title, url } = page_info; let tab = TabActor { name: actors.new_name("tab"), @@ -179,11 +186,12 @@ fn run_server(sender: Sender, url: url.serialize(), console: console.name(), inspector: inspector.name(), + timeline: timeline.name(), }; let root = actors.find_mut::("root"); root.tabs.push(tab.name.clone()); - (tab, console, inspector) + (tab, console, inspector, timeline) }; if let Some(id) = worker_id { @@ -199,6 +207,7 @@ fn run_server(sender: Sender, actors.register(box tab); actors.register(box console); actors.register(box inspector); + actors.register(box timeline); } fn handle_console_message(actors: Arc>, diff --git a/components/devtools_traits/Cargo.toml b/components/devtools_traits/Cargo.toml index 465f751e852..0788d874c3a 100644 --- a/components/devtools_traits/Cargo.toml +++ b/components/devtools_traits/Cargo.toml @@ -15,4 +15,7 @@ path = "../util" [dependencies] url = "0.2.16" + +[dependencies] +time = "*" rustc-serialize = "0.3" diff --git a/components/devtools_traits/lib.rs b/components/devtools_traits/lib.rs index fd1bb9263ff..851a8e6da1b 100644 --- a/components/devtools_traits/lib.rs +++ b/components/devtools_traits/lib.rs @@ -17,6 +17,7 @@ extern crate msg; extern crate "rustc-serialize" as rustc_serialize; extern crate url; extern crate util; +extern crate time; use rustc_serialize::{Decodable, Decoder}; use msg::constellation_msg::{PipelineId, WorkerId}; @@ -83,6 +84,28 @@ pub struct NodeInfo { pub incompleteValue: bool, } +#[derive(PartialEq, Eq)] +pub enum TracingMetadata { + Default, + IntervalStart, + IntervalEnd, + Event, + EventBacktrace, +} + +pub struct TimelineMarker { + pub name: String, + pub metadata: TracingMetadata, + pub time: u64, + pub stack: Option>, +} + +#[derive(PartialEq, Eq, Hash, Clone)] +pub enum TimelineMarkerType { + Reflow, + DOMEvent, +} + /// Messages to process in a particular script task, as instructed by a devtools client. pub enum DevtoolScriptControlMsg { EvaluateJS(PipelineId, String, Sender), @@ -92,6 +115,8 @@ pub enum DevtoolScriptControlMsg { GetLayout(PipelineId, String, Sender<(f32, f32)>), ModifyAttribute(PipelineId, String, Vec), WantsLiveNotifications(PipelineId, bool), + SetTimelineMarkers(PipelineId, Vec, Sender), + DropTimelineMarkers(PipelineId, Vec), } /// Messages to instruct devtools server to update its state relating to a particular @@ -128,3 +153,14 @@ pub enum ConsoleMessage { LogMessage(String, String, u32, u32), //WarnMessage(String), } + +impl TimelineMarker { + pub fn new(name: String, metadata: TracingMetadata) -> TimelineMarker { + TimelineMarker { + name: name, + metadata: metadata, + time: time::precise_time_ns(), + stack: None, + } + } +} diff --git a/components/script/devtools.rs b/components/script/devtools.rs index 8b9bb84e830..271cc39ab35 100644 --- a/components/script/devtools.rs +++ b/components/script/devtools.rs @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use devtools_traits::{EvaluateJSReply, NodeInfo, Modification}; +use devtools_traits::{EvaluateJSReply, NodeInfo, Modification, TimelineMarker, TimelineMarkerType}; use dom::bindings::conversions::FromJSValConvertible; use dom::bindings::conversions::StringificationBehavior; use dom::bindings::js::{JSRef, Temporary, OptionalRootable}; @@ -16,7 +16,7 @@ use dom::element::Element; use dom::document::DocumentHelpers; use page::Page; use msg::constellation_msg::PipelineId; -use script_task::get_page; +use script_task::{get_page, ScriptTask}; use std::sync::mpsc::Sender; use std::rc::Rc; @@ -114,3 +114,36 @@ pub fn handle_wants_live_notifications(page: &Rc, pipeline_id: PipelineId, let window = page.window().root(); window.r().set_devtools_wants_updates(send_notifications); } + +pub fn handle_set_timeline_markers(page: &Rc, + script_task: &ScriptTask, + marker_types: Vec, + reply: Sender) { + for marker_type in &marker_types { + match *marker_type { + TimelineMarkerType::Reflow => { + let window = page.window().root(); + window.r().set_devtools_timeline_marker(TimelineMarkerType::Reflow, reply.clone()); + } + TimelineMarkerType::DOMEvent => { + script_task.set_devtools_timeline_marker(TimelineMarkerType::DOMEvent, reply.clone()); + } + } + } +} + +pub fn handle_drop_timeline_markers(page: &Rc, + script_task: &ScriptTask, + marker_types: Vec) { + let window = page.window().root(); + for marker_type in &marker_types { + match *marker_type { + TimelineMarkerType::Reflow => { + window.r().drop_devtools_timeline_markers(); + } + TimelineMarkerType::DOMEvent => { + script_task.drop_devtools_timeline_markers(); + } + } + } +} diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index 4b5dffc1875..9d4e548e161 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -241,6 +241,7 @@ no_jsmanaged_fields!(ImageCacheTask, ScriptControlChan); no_jsmanaged_fields!(Atom, Namespace, Timer); no_jsmanaged_fields!(Trusted); no_jsmanaged_fields!(PropertyDeclarationBlock); +no_jsmanaged_fields!(HashSet); // These three are interdependent, if you plan to put jsmanaged data // in one of these make sure it is propagated properly to containing structs no_jsmanaged_fields!(SubpageId, WindowSizeData, PipelineId); diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index 9fe135eb53f..5bd20138550 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -34,7 +34,7 @@ use script_task::ScriptMsg; use script_traits::ScriptControlChan; use timers::{IsInterval, TimerId, TimerManager, TimerCallback}; -use devtools_traits::DevtoolsControlChan; +use devtools_traits::{DevtoolsControlChan, TimelineMarker, TimelineMarkerType, TracingMetadata}; use msg::compositor_msg::ScriptListener; use msg::constellation_msg::{LoadData, PipelineId, SubpageId, ConstellationChan, WindowSizeData, WorkerId}; use net_traits::ResourceTask; @@ -54,13 +54,15 @@ use url::{Url, UrlParser}; use libc; use rustc_serialize::base64::{FromBase64, ToBase64, STANDARD}; -use std::cell::{Cell, Ref, RefMut}; +use std::borrow::ToOwned; +use std::cell::{Cell, Ref, RefMut, RefCell}; +use std::collections::HashSet; use std::default::Default; use std::ffi::CString; use std::mem; use std::num::Float; use std::rc::Rc; -use std::sync::mpsc::{channel, Receiver}; +use std::sync::mpsc::{channel, Receiver, Sender}; use std::sync::mpsc::TryRecvError::{Empty, Disconnected}; use time; @@ -102,6 +104,10 @@ pub struct Window { /// For providing instructions to an optional devtools server. devtools_chan: Option, + /// For sending timeline markers. Will be ignored if + /// no devtools server + devtools_markers: RefCell>, + devtools_marker_sender: RefCell>>, /// A flag to indicate whether the developer tools have requested live updates of /// page changes. @@ -477,6 +483,10 @@ pub trait WindowHelpers { fn IndexedGetter(self, _index: u32, _found: &mut bool) -> Option>; fn thaw(self); fn freeze(self); + fn need_emit_timeline_marker(self, timeline_type: TimelineMarkerType) -> bool; + fn emit_timeline_marker(self, marker: TimelineMarker); + fn set_devtools_timeline_marker(self, marker: TimelineMarkerType, reply: Sender); + fn drop_devtools_timeline_markers(self); } pub trait ScriptHelpers { @@ -547,6 +557,11 @@ impl<'a> WindowHelpers for JSRef<'a, Window> { None => return, }; + if self.need_emit_timeline_marker(TimelineMarkerType::Reflow) { + let marker = TimelineMarker::new("Reflow".to_owned(), TracingMetadata::IntervalStart); + self.emit_timeline_marker(marker); + } + // Layout will let us know when it's done. let (join_chan, join_port) = channel(); @@ -583,6 +598,11 @@ impl<'a> WindowHelpers for JSRef<'a, Window> { debug!("script: layout forked"); self.join_layout(); + + if self.need_emit_timeline_marker(TimelineMarkerType::Reflow) { + let marker = TimelineMarker::new("Reflow".to_owned(), TracingMetadata::IntervalEnd); + self.emit_timeline_marker(marker); + } } // FIXME(cgaebel): join_layout is racey. What if the compositor triggers a @@ -776,6 +796,27 @@ impl<'a> WindowHelpers for JSRef<'a, Window> { fn freeze(self) { self.timers.suspend(); } + + fn need_emit_timeline_marker(self, timeline_type: TimelineMarkerType) -> bool { + let markers = self.devtools_markers.borrow(); + markers.contains(&timeline_type) + } + + fn emit_timeline_marker(self, marker: TimelineMarker) { + let sender = self.devtools_marker_sender.borrow(); + let sender = sender.as_ref().expect("There is no marker sender"); + sender.send(marker); + } + + fn set_devtools_timeline_marker(self, marker: TimelineMarkerType, reply: Sender) { + *self.devtools_marker_sender.borrow_mut() = Some(reply); + self.devtools_markers.borrow_mut().insert(marker); + } + + fn drop_devtools_timeline_markers(self) { + self.devtools_markers.borrow_mut().clear(); + *self.devtools_marker_sender.borrow_mut() = None; + } } impl Window { @@ -836,6 +877,9 @@ impl Window { layout_rpc: layout_rpc, layout_join_port: DOMRefCell::new(None), window_size: Cell::new(window_size), + + devtools_marker_sender: RefCell::new(None), + devtools_markers: RefCell::new(HashSet::new()), devtools_wants_updates: Cell::new(false), }; diff --git a/components/script/script_task.rs b/components/script/script_task.rs index 40eae7e0f74..134c629b091 100644 --- a/components/script/script_task.rs +++ b/components/script/script_task.rs @@ -47,6 +47,7 @@ use devtools; use devtools_traits::{DevtoolsControlChan, DevtoolsControlPort, DevtoolsPageInfo}; use devtools_traits::{DevtoolsControlMsg, DevtoolScriptControlMsg}; +use devtools_traits::{TimelineMarker, TimelineMarkerType, TracingMetadata}; use script_traits::CompositorEvent; use script_traits::CompositorEvent::{ResizeEvent, ReflowEvent, ClickEvent}; use script_traits::CompositorEvent::{MouseDownEvent, MouseUpEvent}; @@ -86,6 +87,7 @@ use std::ascii::AsciiExt; use std::any::Any; use std::borrow::ToOwned; use std::cell::{Cell, RefCell}; +use std::collections::HashSet; use std::num::ToPrimitive; use std::option::Option; use std::ptr; @@ -279,6 +281,10 @@ pub struct ScriptTask { /// no such server exists. devtools_port: DevtoolsControlPort, devtools_sender: Sender, + /// For sending timeline markers. Will be ignored if + /// no devtools server + devtools_markers: RefCell>, + devtools_marker_sender: RefCell>>, /// The JavaScript runtime. js_runtime: js::rust::rt, @@ -447,6 +453,8 @@ impl ScriptTask { devtools_chan: devtools_chan, devtools_port: devtools_receiver, devtools_sender: devtools_sender, + devtools_markers: RefCell::new(HashSet::new()), + devtools_marker_sender: RefCell::new(None), js_runtime: js_runtime, js_context: DOMRefCell::new(Some(js_context)), @@ -700,6 +708,10 @@ impl ScriptTask { devtools::handle_modify_attribute(&page, id, node_id, modifications), DevtoolScriptControlMsg::WantsLiveNotifications(pipeline_id, to_send) => devtools::handle_wants_live_notifications(&page, pipeline_id, to_send), + DevtoolScriptControlMsg::SetTimelineMarkers(_pipeline_id, marker_types, reply) => + devtools::handle_set_timeline_markers(&page, self, marker_types, reply), + DevtoolScriptControlMsg::DropTimelineMarkers(_pipeline_id, marker_types) => + devtools::handle_drop_timeline_markers(&page, self, marker_types), } } @@ -1150,8 +1162,14 @@ impl ScriptTask { /// /// TODO: Actually perform DOM event dispatch. fn handle_event(&self, pipeline_id: PipelineId, event: CompositorEvent) { + match event { ResizeEvent(new_size) => { + let _marker; + if self.need_emit_timeline_marker(TimelineMarkerType::DOMEvent) { + _marker = AutoDOMEventMarker::new(self); + } + self.handle_resize_event(pipeline_id, new_size); } @@ -1179,6 +1197,10 @@ impl ScriptTask { } ClickEvent(button, point) => { + let _marker; + if self.need_emit_timeline_marker(TimelineMarkerType::DOMEvent) { + _marker = AutoDOMEventMarker::new(self); + } let page = get_page(&self.root_page(), pipeline_id); let document = page.document().root(); document.r().handle_click_event(self.js_runtime.ptr, button, point); @@ -1187,6 +1209,10 @@ impl ScriptTask { MouseDownEvent(..) => {} MouseUpEvent(..) => {} MouseMoveEvent(point) => { + let _marker; + if self.need_emit_timeline_marker(TimelineMarkerType::DOMEvent) { + _marker = AutoDOMEventMarker::new(self); + } let page = get_page(&self.root_page(), pipeline_id); let document = page.document().root(); let mouse_over_targets = &mut *self.mouse_over_targets.borrow_mut(); @@ -1195,6 +1221,10 @@ impl ScriptTask { } KeyEvent(key, state, modifiers) => { + let _marker; + if self.need_emit_timeline_marker(TimelineMarkerType::DOMEvent) { + _marker = AutoDOMEventMarker::new(self); + } let page = get_page(&self.root_page(), pipeline_id); let document = page.document().root(); document.r().dispatch_key_event( @@ -1311,6 +1341,26 @@ impl ScriptTask { self.incomplete_loads.borrow_mut().push(incomplete); } + + fn need_emit_timeline_marker(&self, timeline_type: TimelineMarkerType) -> bool { + self.devtools_markers.borrow().contains(&timeline_type) + } + + fn emit_timeline_marker(&self, marker: TimelineMarker) { + let sender = self.devtools_marker_sender.borrow(); + let sender = sender.as_ref().expect("There is no marker sender"); + sender.send(marker); + } + + pub fn set_devtools_timeline_marker(&self, marker: TimelineMarkerType, reply: Sender) { + *self.devtools_marker_sender.borrow_mut() = Some(reply); + self.devtools_markers.borrow_mut().insert(marker); + } + + pub fn drop_devtools_timeline_markers(&self) { + self.devtools_markers.borrow_mut().clear(); + *self.devtools_marker_sender.borrow_mut() = None; + } } impl Drop for ScriptTask { @@ -1321,6 +1371,28 @@ impl Drop for ScriptTask { } } +struct AutoDOMEventMarker<'a> { + script_task: &'a ScriptTask +} + +impl<'a> AutoDOMEventMarker<'a> { + fn new(script_task: &'a ScriptTask) -> AutoDOMEventMarker<'a> { + let marker = TimelineMarker::new("DOMEvent".to_owned(), TracingMetadata::IntervalStart); + script_task.emit_timeline_marker(marker); + AutoDOMEventMarker { + script_task: script_task + } + } +} + +#[unsafe_destructor] +impl<'a> Drop for AutoDOMEventMarker<'a> { + fn drop(&mut self) { + let marker = TimelineMarker::new("DOMEvent".to_owned(), TracingMetadata::IntervalEnd); + self.script_task.emit_timeline_marker(marker); + } +} + /// Shuts down layout for the given page tree. fn shut_down_layout(page_tree: &Rc, exit_type: PipelineExitType) { let mut channels = vec!(); diff --git a/components/servo/Cargo.lock b/components/servo/Cargo.lock index 4d332f65ad2..f53a156d553 100644 --- a/components/servo/Cargo.lock +++ b/components/servo/Cargo.lock @@ -173,6 +173,7 @@ version = "0.0.1" dependencies = [ "msg 0.0.1", "rustc-serialize 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)", "url 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", "util 0.0.1", ]