зеркало из https://github.com/mozilla/gecko-dev.git
servo: Merge #10995 - Interval + CSV time-profiling (from rzambre:master); r=larsbergstrom
This PR fixes #10886. The -p option can be followed by either an interval number or a CSV filename. * In the interval profiling, the profiler output would be spitted out to the terminal periodically. Example usage: **./mach run -p 1 http://www.google.com** will print the time-profiling output to the terminal every second. * In the CSV file profiling, a CSV file will be generate upon termination of servo. Example usage: **./mach run -x -o out.png -p out.csv http://www.google.com** will generate out.csv upon termination of Servo. Source-Repo: https://github.com/servo/servo Source-Revision: d4315855aaddb9d9ed26cd730e9b75e15500c3bd
This commit is contained in:
Родитель
0ca1c50fb0
Коммит
e8e9371fba
|
@ -509,7 +509,13 @@ impl<Window: WindowMethods> IOCompositor<Window> {
|
|||
while self.port.try_recv_compositor_msg().is_some() {}
|
||||
|
||||
// Tell the profiler, memory profiler, and scrolling timer to shut down.
|
||||
self.time_profiler_chan.send(time::ProfilerMsg::Exit);
|
||||
match ipc::channel() {
|
||||
Ok((sender, receiver)) => {
|
||||
self.time_profiler_chan.send(time::ProfilerMsg::Exit(sender));
|
||||
receiver.recv();
|
||||
},
|
||||
Err(_) => {},
|
||||
}
|
||||
self.mem_profiler_chan.send(mem::ProfilerMsg::Exit);
|
||||
self.delayed_composition_timer.shutdown();
|
||||
|
||||
|
|
|
@ -12,43 +12,72 @@ use profile_traits::time::{TimerMetadataReflowType, TimerMetadataFrameType};
|
|||
use std::borrow::ToOwned;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::BTreeMap;
|
||||
use std::error::Error;
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Write};
|
||||
use std::path;
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
use std::{thread, f64};
|
||||
use std_time::precise_time_ns;
|
||||
use trace_dump::TraceDump;
|
||||
use util::opts::OutputOptions;
|
||||
use util::thread::spawn_named;
|
||||
use util::time::duration_from_seconds;
|
||||
|
||||
pub trait Formattable {
|
||||
fn format(&self) -> String;
|
||||
fn format(&self, output: &Option<OutputOptions>) -> String;
|
||||
}
|
||||
|
||||
impl Formattable for Option<TimerMetadata> {
|
||||
fn format(&self) -> String {
|
||||
fn format(&self, output: &Option<OutputOptions>) -> String {
|
||||
match *self {
|
||||
// TODO(cgaebel): Center-align in the format strings as soon as rustc supports it.
|
||||
Some(ref meta) => {
|
||||
let url = &*meta.url;
|
||||
let url = if url.len() > 30 {
|
||||
&url[..30]
|
||||
} else {
|
||||
url
|
||||
};
|
||||
let incremental = match meta.incremental {
|
||||
TimerMetadataReflowType::Incremental => " yes",
|
||||
TimerMetadataReflowType::FirstReflow => " no ",
|
||||
};
|
||||
let iframe = match meta.iframe {
|
||||
TimerMetadataFrameType::RootWindow => " yes",
|
||||
TimerMetadataFrameType::IFrame => " no ",
|
||||
};
|
||||
format!(" {:14} {:9} {:30}", incremental, iframe, url)
|
||||
match *output {
|
||||
Some(OutputOptions::FileName(_)) => {
|
||||
/* The profiling output is a CSV file */
|
||||
let incremental = match meta.incremental {
|
||||
TimerMetadataReflowType::Incremental => "yes",
|
||||
TimerMetadataReflowType::FirstReflow => "no",
|
||||
};
|
||||
let iframe = match meta.iframe {
|
||||
TimerMetadataFrameType::RootWindow => "yes",
|
||||
TimerMetadataFrameType::IFrame => "no",
|
||||
};
|
||||
format!(" {}, {}, {}", incremental, iframe, url)
|
||||
},
|
||||
_ => {
|
||||
/* The profiling output is the terminal */
|
||||
let url = if url.len() > 30 {
|
||||
&url[..30]
|
||||
} else {
|
||||
url
|
||||
};
|
||||
let incremental = match meta.incremental {
|
||||
TimerMetadataReflowType::Incremental => " yes",
|
||||
TimerMetadataReflowType::FirstReflow => " no ",
|
||||
};
|
||||
let iframe = match meta.iframe {
|
||||
TimerMetadataFrameType::RootWindow => " yes",
|
||||
TimerMetadataFrameType::IFrame => " no ",
|
||||
};
|
||||
format!(" {:14} {:9} {:30}", incremental, iframe, url)
|
||||
},
|
||||
}
|
||||
},
|
||||
None =>
|
||||
format!(" {:14} {:9} {:30}", " N/A", " N/A", " N/A")
|
||||
None => {
|
||||
match *output {
|
||||
Some(OutputOptions::FileName(_)) => {
|
||||
format!(" {}, {}, {}", " N/A", " N/A", " N/A")
|
||||
},
|
||||
_ => {
|
||||
format!(" {:14} {:9} {:30}", " N/A", " N/A", " N/A")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -56,7 +85,7 @@ impl Formattable for Option<TimerMetadata> {
|
|||
impl Formattable for ProfilerCategory {
|
||||
// some categories are subcategories of LayoutPerformCategory
|
||||
// and should be printed to indicate this
|
||||
fn format(&self) -> String {
|
||||
fn format(&self, output: &Option<OutputOptions>) -> String {
|
||||
let padding = match *self {
|
||||
ProfilerCategory::LayoutStyleRecalc |
|
||||
ProfilerCategory::LayoutRestyleDamagePropagation |
|
||||
|
@ -128,45 +157,71 @@ type ProfilerBuckets = BTreeMap<(ProfilerCategory, Option<TimerMetadata>), Vec<f
|
|||
pub struct Profiler {
|
||||
pub port: IpcReceiver<ProfilerMsg>,
|
||||
buckets: ProfilerBuckets,
|
||||
output: Option<OutputOptions>,
|
||||
pub last_msg: Option<ProfilerMsg>,
|
||||
trace: Option<TraceDump>,
|
||||
}
|
||||
|
||||
impl Profiler {
|
||||
pub fn create(period: Option<f64>, file_path: Option<String>) -> ProfilerChan {
|
||||
pub fn create(output: &Option<OutputOptions>, file_path: Option<String>) -> ProfilerChan {
|
||||
let (chan, port) = ipc::channel().unwrap();
|
||||
if let Some(period) = period {
|
||||
let chan = chan.clone();
|
||||
spawn_named("Time profiler timer".to_owned(), move || {
|
||||
loop {
|
||||
thread::sleep(duration_from_seconds(period));
|
||||
if chan.send(ProfilerMsg::Print).is_err() {
|
||||
break;
|
||||
}
|
||||
match *output {
|
||||
Some(ref option) => {
|
||||
// Spawn the time profiler thread
|
||||
let outputoption = option.clone();
|
||||
spawn_named("Time profiler".to_owned(), move || {
|
||||
let trace = file_path.as_ref()
|
||||
.map(path::Path::new)
|
||||
.map(fs::File::create)
|
||||
.map(|res| TraceDump::new(res.unwrap()));
|
||||
let mut profiler = Profiler::new(port, trace, Some(outputoption));
|
||||
profiler.start();
|
||||
});
|
||||
// decide if we need to spawn the timer thread
|
||||
match option {
|
||||
&OutputOptions::FileName(_) => {/* no timer thread needed */},
|
||||
&OutputOptions::Stdout(period) => {
|
||||
// Spawn a timer thread
|
||||
let chan = chan.clone();
|
||||
spawn_named("Time profiler timer".to_owned(), move || {
|
||||
loop {
|
||||
thread::sleep(duration_from_seconds(period));
|
||||
if chan.send(ProfilerMsg::Print).is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if period.is_some() || file_path.is_some() {
|
||||
// Spawn the time profiler.
|
||||
spawn_named("Time profiler".to_owned(), move || {
|
||||
let trace = file_path.as_ref()
|
||||
.map(path::Path::new)
|
||||
.map(fs::File::create)
|
||||
.map(|res| TraceDump::new(res.unwrap()));
|
||||
let mut profiler = Profiler::new(port, trace);
|
||||
profiler.start();
|
||||
});
|
||||
} else {
|
||||
// No-op to handle messages when the time profiler is inactive.
|
||||
spawn_named("Time profiler".to_owned(), move || {
|
||||
loop {
|
||||
match port.recv() {
|
||||
Err(_) | Ok(ProfilerMsg::Exit) => break,
|
||||
_ => {}
|
||||
}
|
||||
},
|
||||
None => {
|
||||
// this is when the -p option hasn't been specified
|
||||
if file_path.is_some() {
|
||||
// Spawn the time profiler
|
||||
spawn_named("Time profiler".to_owned(), move || {
|
||||
let trace = file_path.as_ref()
|
||||
.map(path::Path::new)
|
||||
.map(fs::File::create)
|
||||
.map(|res| TraceDump::new(res.unwrap()));
|
||||
let mut profiler = Profiler::new(port, trace, None);
|
||||
profiler.start();
|
||||
});
|
||||
} else {
|
||||
// No-op to handle messages when the time profiler is not printing:
|
||||
spawn_named("Time profiler".to_owned(), move || {
|
||||
loop {
|
||||
match port.recv() {
|
||||
Err(_) => break,
|
||||
Ok(ProfilerMsg::Exit(chan)) => {
|
||||
chan.send(());
|
||||
break;
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
heartbeats::init();
|
||||
|
@ -216,10 +271,11 @@ impl Profiler {
|
|||
profiler_chan
|
||||
}
|
||||
|
||||
pub fn new(port: IpcReceiver<ProfilerMsg>, trace: Option<TraceDump>) -> Profiler {
|
||||
pub fn new(port: IpcReceiver<ProfilerMsg>, trace: Option<TraceDump>, output: Option<OutputOptions>) -> Profiler {
|
||||
Profiler {
|
||||
port: port,
|
||||
buckets: BTreeMap::new(),
|
||||
output: output,
|
||||
last_msg: None,
|
||||
trace: trace,
|
||||
}
|
||||
|
@ -256,8 +312,10 @@ impl Profiler {
|
|||
// only print if more data has arrived since the last printout
|
||||
self.print_buckets();
|
||||
},
|
||||
ProfilerMsg::Exit => {
|
||||
ProfilerMsg::Exit(chan) => {
|
||||
heartbeats::cleanup();
|
||||
self.print_buckets();
|
||||
chan.send(());
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
@ -266,34 +324,70 @@ impl Profiler {
|
|||
}
|
||||
|
||||
fn print_buckets(&mut self) {
|
||||
let stdout = io::stdout();
|
||||
let mut lock = stdout.lock();
|
||||
|
||||
writeln!(&mut lock, "{:35} {:14} {:9} {:30} {:15} {:15} {:-15} {:-15} {:-15}",
|
||||
"_category_", "_incremental?_", "_iframe?_",
|
||||
" _url_", " _mean (ms)_", " _median (ms)_",
|
||||
" _min (ms)_", " _max (ms)_", " _events_").unwrap();
|
||||
for (&(ref category, ref meta), ref mut data) in &mut self.buckets {
|
||||
data.sort_by(|a, b| {
|
||||
if a < b {
|
||||
Ordering::Less
|
||||
} else {
|
||||
Ordering::Greater
|
||||
match self.output {
|
||||
Some(OutputOptions::FileName(ref filename)) => {
|
||||
let path = Path::new(&filename);
|
||||
let mut file = match File::create(&path) {
|
||||
Err(e) => panic!("Couldn't create {}: {}",
|
||||
path.display(),
|
||||
Error::description(&e)),
|
||||
Ok(file) => file,
|
||||
};
|
||||
write!(file, "_category_, _incremental?_, _iframe?_, _url_, _mean (ms)_, _median (ms)_, _min (ms)_, \
|
||||
_max (ms)_, _events_\n");
|
||||
for (&(ref category, ref meta), ref mut data) in &mut self.buckets {
|
||||
data.sort_by(|a, b| {
|
||||
if a < b {
|
||||
Ordering::Less
|
||||
} else {
|
||||
Ordering::Greater
|
||||
}
|
||||
});
|
||||
let data_len = data.len();
|
||||
if data_len > 0 {
|
||||
let (mean, median, min, max) =
|
||||
(data.iter().sum::<f64>() / (data_len as f64),
|
||||
data[data_len / 2],
|
||||
data.iter().fold(f64::INFINITY, |a, &b| a.min(b)),
|
||||
data.iter().fold(-f64::INFINITY, |a, &b| a.max(b)));
|
||||
write!(file, "{}, {}, {:15.4}, {:15.4}, {:15.4}, {:15.4}, {:15}\n",
|
||||
category.format(&self.output), meta.format(&self.output),
|
||||
mean, median, min, max, data_len);
|
||||
}
|
||||
}
|
||||
});
|
||||
let data_len = data.len();
|
||||
if data_len > 0 {
|
||||
let (mean, median, min, max) =
|
||||
(data.iter().sum::<f64>() / (data_len as f64),
|
||||
data[data_len / 2],
|
||||
data.iter().fold(f64::INFINITY, |a, &b| a.min(b)),
|
||||
data.iter().fold(-f64::INFINITY, |a, &b| a.max(b)));
|
||||
writeln!(&mut lock, "{:-35}{} {:15.4} {:15.4} {:15.4} {:15.4} {:15}",
|
||||
category.format(), meta.format(), mean, median, min, max,
|
||||
data_len).unwrap();
|
||||
}
|
||||
}
|
||||
writeln!(&mut lock, "").unwrap();
|
||||
},
|
||||
Some(OutputOptions::Stdout(ref period)) => {
|
||||
let stdout = io::stdout();
|
||||
let mut lock = stdout.lock();
|
||||
|
||||
writeln!(&mut lock, "{:35} {:14} {:9} {:30} {:15} {:15} {:-15} {:-15} {:-15}",
|
||||
"_category_", "_incremental?_", "_iframe?_",
|
||||
" _url_", " _mean (ms)_", " _median (ms)_",
|
||||
" _min (ms)_", " _max (ms)_", " _events_").unwrap();
|
||||
for (&(ref category, ref meta), ref mut data) in &mut self.buckets {
|
||||
data.sort_by(|a, b| {
|
||||
if a < b {
|
||||
Ordering::Less
|
||||
} else {
|
||||
Ordering::Greater
|
||||
}
|
||||
});
|
||||
let data_len = data.len();
|
||||
if data_len > 0 {
|
||||
let (mean, median, min, max) =
|
||||
(data.iter().sum::<f64>() / (data_len as f64),
|
||||
data[data_len / 2],
|
||||
data.iter().fold(f64::INFINITY, |a, &b| a.min(b)),
|
||||
data.iter().fold(-f64::INFINITY, |a, &b| a.max(b)));
|
||||
writeln!(&mut lock, "{:-35}{} {:15.4} {:15.4} {:15.4} {:15.4} {:15}",
|
||||
category.format(&self.output), meta.format(&self.output), mean, median, min, max,
|
||||
data_len).unwrap();
|
||||
}
|
||||
}
|
||||
writeln!(&mut lock, "").unwrap();
|
||||
},
|
||||
None => {/* Do nothing if not output option has been set */},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ pub enum ProfilerMsg {
|
|||
/// Message used to force print the profiling metrics
|
||||
Print,
|
||||
/// Tells the profiler to shut down.
|
||||
Exit,
|
||||
Exit(IpcSender<()>),
|
||||
}
|
||||
|
||||
#[repr(u32)]
|
||||
|
|
|
@ -1706,6 +1706,7 @@ dependencies = [
|
|||
name = "profile_tests"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"ipc-channel 0.2.2 (git+https://github.com/servo/ipc-channel)",
|
||||
"profile 0.0.1",
|
||||
"profile_traits 0.0.1",
|
||||
]
|
||||
|
|
|
@ -117,7 +117,7 @@ impl Browser {
|
|||
let (compositor_proxy, compositor_receiver) =
|
||||
window.create_compositor_channel();
|
||||
let supports_clipboard = window.supports_clipboard();
|
||||
let time_profiler_chan = profile_time::Profiler::create(opts.time_profiler_period,
|
||||
let time_profiler_chan = profile_time::Profiler::create(&opts.time_profiling,
|
||||
opts.time_profiler_trace_path.clone());
|
||||
let mem_profiler_chan = profile_mem::Profiler::create(opts.mem_profiler_period);
|
||||
let devtools_chan = opts.devtools_port.map(|port| {
|
||||
|
|
|
@ -48,7 +48,7 @@ pub struct Opts {
|
|||
|
||||
/// `None` to disable the time profiler or `Some` with an interval in seconds to enable it and
|
||||
/// cause it to produce output on that interval (`-p`).
|
||||
pub time_profiler_period: Option<f64>,
|
||||
pub time_profiling: Option<OutputOptions>,
|
||||
|
||||
/// When the profiler is enabled, this is an optional path to dump a self-contained HTML file
|
||||
/// visualizing the traces as a timeline.
|
||||
|
@ -383,6 +383,12 @@ pub fn print_debug_usage(app: &str) -> ! {
|
|||
process::exit(0)
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
pub enum OutputOptions {
|
||||
FileName(String),
|
||||
Stdout(f64)
|
||||
}
|
||||
|
||||
fn args_fail(msg: &str) -> ! {
|
||||
let mut stderr = io::stderr();
|
||||
stderr.write_all(msg.as_bytes()).unwrap();
|
||||
|
@ -472,7 +478,7 @@ pub fn default_opts() -> Opts {
|
|||
gpu_painting: false,
|
||||
tile_size: 512,
|
||||
device_pixels_per_px: None,
|
||||
time_profiler_period: None,
|
||||
time_profiling: None,
|
||||
time_profiler_trace_path: None,
|
||||
mem_profiler_period: None,
|
||||
layout_threads: 1,
|
||||
|
@ -533,7 +539,9 @@ pub fn from_cmdline_args(args: &[String]) -> ArgumentParsingResult {
|
|||
opts.optopt("s", "size", "Size of tiles", "512");
|
||||
opts.optopt("", "device-pixel-ratio", "Device pixels per px", "");
|
||||
opts.optopt("t", "threads", "Number of paint threads", "1");
|
||||
opts.optflagopt("p", "profile", "Profiler flag and output interval", "10");
|
||||
opts.optflagopt("p", "profile", "Time profiler flag and either a CSV output filename \
|
||||
OR an interval for output to Stdout (blank for Stdout with interval of 5s)", "10 \
|
||||
OR time.csv");
|
||||
opts.optflagopt("", "profiler-trace-path",
|
||||
"Path to dump a self-contained HTML timeline of profiler traces",
|
||||
"");
|
||||
|
@ -659,10 +667,19 @@ pub fn from_cmdline_args(args: &[String]) -> ArgumentParsingResult {
|
|||
None => cmp::max(num_cpus::get() * 3 / 4, 1),
|
||||
};
|
||||
|
||||
// If only the flag is present, default to a 5 second period for both profilers.
|
||||
let time_profiler_period = opt_match.opt_default("p", "5").map(|period| {
|
||||
period.parse().unwrap_or_else(|err| args_fail(&format!("Error parsing option: -p ({})", err)))
|
||||
});
|
||||
// If only the flag is present, default to a 5 second period for both profilers
|
||||
let time_profiling = if opt_match.opt_present("p") {
|
||||
match opt_match.opt_str("p") {
|
||||
Some(argument) => match argument.parse::<f64>() {
|
||||
Ok(interval) => Some(OutputOptions::Stdout(interval)) ,
|
||||
Err(_) => Some(OutputOptions::FileName(argument)),
|
||||
},
|
||||
None => Some(OutputOptions::Stdout(5 as f64)),
|
||||
}
|
||||
} else {
|
||||
// if the p option doesn't exist:
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(ref time_profiler_trace_path) = opt_match.opt_str("profiler-trace-path") {
|
||||
let mut path = PathBuf::from(time_profiler_trace_path);
|
||||
|
@ -771,7 +788,7 @@ pub fn from_cmdline_args(args: &[String]) -> ArgumentParsingResult {
|
|||
gpu_painting: gpu_painting,
|
||||
tile_size: tile_size,
|
||||
device_pixels_per_px: device_pixels_per_px,
|
||||
time_profiler_period: time_profiler_period,
|
||||
time_profiling: time_profiling,
|
||||
time_profiler_trace_path: opt_match.opt_str("profiler-trace-path"),
|
||||
mem_profiler_period: mem_profiler_period,
|
||||
layout_threads: layout_threads,
|
||||
|
|
|
@ -11,3 +11,4 @@ doctest = false
|
|||
[dependencies]
|
||||
profile = {path = "../../../components/profile"}
|
||||
profile_traits = {path = "../../../components/profile_traits"}
|
||||
ipc-channel = {git = "https://github.com/servo/ipc-channel"}
|
||||
|
|
|
@ -2,6 +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/. */
|
||||
|
||||
extern crate ipc_channel;
|
||||
extern crate profile;
|
||||
extern crate profile_traits;
|
||||
|
||||
|
|
|
@ -2,14 +2,16 @@
|
|||
* 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 ipc_channel::ipc;
|
||||
use profile::time;
|
||||
use profile_traits::time::ProfilerMsg;
|
||||
|
||||
#[test]
|
||||
fn time_profiler_smoke_test() {
|
||||
let chan = time::Profiler::create(None, None);
|
||||
let chan = time::Profiler::create(&None, None);
|
||||
assert!(true, "Can create the profiler thread");
|
||||
|
||||
chan.send(ProfilerMsg::Exit);
|
||||
let (ipcchan, ipcport) = ipc::channel().unwrap();
|
||||
chan.send(ProfilerMsg::Exit(ipcchan));
|
||||
assert!(true, "Can tell the profiler thread to exit");
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче