Skip to content

Commit

Permalink
servo: Merge #17536 - Add cli options to write profiler output to Inf…
Browse files Browse the repository at this point in the history
…luxDB (from ferjm:influxdb.profiler); r=jdm

- [X] `./mach build -d` does not report any errors
- [X] `./mach test-tidy` does not report any errors

This patch adds the command line options and associated code to write the output of running the profiler to an InfluxDB instance, so we can create graphs like [1] with Grafana.

This is part of the work required to record and watch PWM results during CI to catch performance regressions.

[1] https://screenshots.firefox.com/j6sSZrN7pTuPK2kX/localhost

Source-Repo: https://github.com/servo/servo
Source-Revision: 901525c9116ee0945781811c97fd3395db7c5cf9
  • Loading branch information
ferjm committed Jul 6, 2017
1 parent d775a19 commit 4e867cb
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 6 deletions.
11 changes: 11 additions & 0 deletions servo/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 21 additions & 4 deletions servo/components/config/opts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,13 @@ pub struct Opts {
/// platform default setting.
pub device_pixels_per_px: Option<f32>,

/// `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`).
/// `None` to disable the time profiler or `Some` to enable it with:
/// - an interval in seconds to cause it to produce output on that interval.
/// (`i.e. -p 5`).
/// - a file path to write profiling info to a TSV file upon Servo's termination.
/// (`i.e. -p out.tsv`).
/// - an InfluxDB hostname to store profiling info upon Servo's termination.
/// (`i.e. -p http://localhost:8086`)
pub time_profiling: Option<OutputOptions>,

/// When the profiler is enabled, this is an optional path to dump a self-contained HTML file
Expand Down Expand Up @@ -419,8 +424,10 @@ fn print_debug_usage(app: &str) -> ! {

#[derive(Clone, Deserialize, Serialize)]
pub enum OutputOptions {
/// Database connection config (hostname, name, user, pass)
DB(ServoUrl, Option<String>, Option<String>, Option<String>),
FileName(String),
Stdout(f64)
Stdout(f64),
}

fn args_fail(msg: &str) -> ! {
Expand Down Expand Up @@ -598,6 +605,9 @@ pub fn from_cmdline_args(args: &[String]) -> ArgumentParsingResult {
"config directory following xdg spec on linux platform", "");
opts.optflag("v", "version", "Display servo version information");
opts.optflag("", "unminify-js", "Unminify Javascript");
opts.optopt("", "profiler-db-user", "Profiler database user", "");
opts.optopt("", "profiler-db-pass", "Profiler database password", "");
opts.optopt("", "profiler-db-name", "Profiler database name", "");

let opt_match = match opts.parse(args) {
Ok(m) => m,
Expand Down Expand Up @@ -666,7 +676,14 @@ pub fn from_cmdline_args(args: &[String]) -> ArgumentParsingResult {
match opt_match.opt_str("p") {
Some(argument) => match argument.parse::<f64>() {
Ok(interval) => Some(OutputOptions::Stdout(interval)) ,
Err(_) => Some(OutputOptions::FileName(argument)),
Err(_) => {
match ServoUrl::parse(&argument) {
Ok(url) => Some(OutputOptions::DB(url, opt_match.opt_str("profiler-db-name"),
opt_match.opt_str("profiler-db-user"),
opt_match.opt_str("profiler-db-pass"))),
Err(_) => Some(OutputOptions::FileName(argument)),
}
}
},
None => Some(OutputOptions::Stdout(5.0 as f64)),
}
Expand Down
1 change: 1 addition & 0 deletions servo/components/profile/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ path = "lib.rs"

[dependencies]
profile_traits = {path = "../profile_traits"}
influent = "0.4"
ipc-channel = "0.8"
heartbeats-simple = "0.4"
log = "0.3.5"
Expand Down
1 change: 1 addition & 0 deletions servo/components/profile/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#[cfg(not(target_os = "windows"))]
extern crate alloc_jemalloc;
extern crate heartbeats_simple;
extern crate influent;
extern crate ipc_channel;
#[cfg(not(target_os = "windows"))]
extern crate libc;
Expand Down
55 changes: 53 additions & 2 deletions servo/components/profile/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
//! Timing functions.
use heartbeats;
use influent::client::{Client, Credentials};
use influent::create_client;
use influent::measurement::{Measurement, Value};
use ipc_channel::ipc::{self, IpcReceiver};
use profile_traits::energy::{energy_interval_ms, read_energy_uj};
use profile_traits::time::{ProfilerCategory, ProfilerChan, ProfilerMsg, TimerMetadata};
Expand Down Expand Up @@ -183,7 +186,8 @@ impl Profiler {
}).expect("Thread spawning failed");
// decide if we need to spawn the timer thread
match option {
&OutputOptions::FileName(_) => { /* no timer thread needed */ },
&OutputOptions::FileName(_) |
&OutputOptions::DB(_, _, _, _) => { /* no timer thread needed */ },
&OutputOptions::Stdout(period) => {
// Spawn a timer thread
let chan = chan.clone();
Expand Down Expand Up @@ -391,7 +395,54 @@ impl Profiler {
}
writeln!(&mut lock, "").unwrap();
},
None => { /* Do nothing if not output option has been set */ },
Some(OutputOptions::DB(ref hostname, ref dbname, ref user, ref password)) => {
// Unfortunately, influent does not like hostnames ending with "/"
let mut hostname = hostname.to_string();
if hostname.ends_with("/") {
hostname.pop();
}

let empty = String::from("");
let username = user.as_ref().unwrap_or(&empty);
let password = password.as_ref().unwrap_or(&empty);
let database = dbname.as_ref().unwrap_or(&empty);
let credentials = Credentials {
username: username,
password: password,
database: database,
};

let hosts = vec![hostname.as_str()];
let client = create_client(credentials, hosts);

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) = Self::get_statistics(data);
let category = category.format(&self.output);
let mut measurement = Measurement::new(&category);
measurement.add_field("mean", Value::Float(mean));
measurement.add_field("median", Value::Float(median));
measurement.add_field("min", Value::Float(min));
measurement.add_field("max", Value::Float(max));
if let Some(ref meta) = *meta {
measurement.add_tag("host", meta.url.as_str());
};
if client.write_one(measurement, None).is_err() {
warn!("Could not write measurement to profiler db");
}
}
}

},
None => { /* Do nothing if no output option has been set */ },
};
}
}
Expand Down

0 comments on commit 4e867cb

Please sign in to comment.