Firefox timeline integration #4957

This commit is contained in:
Guro Bokum 2015-03-28 23:27:54 +07:00
parent 1e45d025b3
commit 97714ec5ed
15 changed files with 761 additions and 14 deletions

View file

@ -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<String, Box<Actor+Send>>,
new_actors: RefCell<Vec<Box<Actor+Send>>>,
old_actors: RefCell<Vec<String>>,
script_actors: RefCell<HashMap<String, String>>,
shareable: Option<Arc<Mutex<ActorRegistry>>>,
next: Cell<u32>,
}
@ -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<Mutex<ActorRegistry>>{
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<Mutex<ActorRegistry>> {
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);
}
}

View file

@ -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<u64>,
is_recording: bool,
ticks: Vec<u64>,
}
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<bool, ()> {
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<u64> {
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();
}
}

View file

@ -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,

View file

@ -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<bool, ()> {
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,
}
}
}

View file

@ -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(),
}
}
}

View file

@ -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<DevtoolScriptControlMsg>,
marker_types: Vec<TimelineMarkerType>,
pipeline: PipelineId,
is_recording: Arc<Mutex<bool>>,
stream: RefCell<Option<TcpStream>>,
framerate_actor: RefCell<Option<String>>,
memory_actor: RefCell<Option<String>>,
}
struct Emitter {
from: String,
stream: TcpStream,
markers: Vec<TimelineMarkerReply>,
registry: Arc<Mutex<ActorRegistry>>,
framerate_actor: Option<String>,
memory_actor: Option<String>,
}
#[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<Vec<()>>,
endStack: Option<Vec<()>>,
}
#[derive(RustcEncodable)]
struct MarkersEmitterReply {
__type__: String,
markers: Vec<TimelineMarkerReply>,
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<u64>,
}
static DEFAULT_TIMELINE_DATA_PULL_TIMEOUT: usize = 200; //ms
impl TimelineActor {
pub fn new(name: String,
pipeline: PipelineId,
script_sender: Sender<DevtoolScriptControlMsg>) -> 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<TimelineMarker>, 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<TimelineMarker>, depth: usize,
start_payload: Option<TimelineMarker>, 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<bool, ()> {
Ok(match msg_type {
"start" => {
**self.is_recording.lock().as_mut().unwrap() = true;
let (tx, rx) = channel::<TimelineMarker>();
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<Mutex<ActorRegistry>>,
stream: TcpStream,
memory_actor_name: Option<String>,
framerate_actor_name: Option<String>) -> 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::<FramerateActor>(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::<MemoryActor>(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);
}
}
}

View file

@ -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,

View file

@ -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<DevtoolsControlMsg>,
registry.register(root);
registry.find::<RootActor>("root");
let actors = Arc::new(Mutex::new(registry));
let actors = registry.create_shareable();
let mut accepted_connections: Vec<TcpStream> = Vec::new();
@ -122,9 +126,8 @@ fn run_server(sender: Sender<DevtoolsControlMsg>,
'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<DevtoolsControlMsg>,
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<DevtoolsControlMsg>,
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<DevtoolsControlMsg>,
url: url.serialize(),
console: console.name(),
inspector: inspector.name(),
timeline: timeline.name(),
};
let root = actors.find_mut::<RootActor>("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<DevtoolsControlMsg>,
actors.register(box tab);
actors.register(box console);
actors.register(box inspector);
actors.register(box timeline);
}
fn handle_console_message(actors: Arc<Mutex<ActorRegistry>>,