diff --git a/Cargo.lock b/Cargo.lock index 09b1d21f74e9..9b96d083cd46 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1309,6 +1309,15 @@ name = "inflate" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "influent" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "hyper 0.10.10 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "io-surface" version = "0.7.0" @@ -2164,6 +2173,7 @@ name = "profile" version = "0.0.1" dependencies = [ "heartbeats-simple 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "influent 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "ipc-channel 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3637,6 +3647,7 @@ dependencies = [ "checksum image 0.12.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d95816db758249fe16f23a4e23f1a3a817fe11892dbfd1c5836f625324702158" "checksum immeta 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0b9260463a221bfe3f02100c56e2d14c050d5ffe7e44a43d0a1b2b1f2b523502" "checksum inflate 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e7e0062d2dc2f17d2f13750d95316ae8a2ff909af0fda957084f5defd87c43bb" +"checksum influent 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a22b311b83431be3ab9af96ca9ea41554bb4a8551ea871ae44c3ce0c57e55f2c" "checksum io-surface 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c35a3278fa52fb070fdc874dfd057163e6c21e0a9295f87f54daee9dd5530b43" "checksum iovec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "29d062ee61fccdf25be172e70f34c9f6efc597e1fb8f6526e8437b2046ab26be" "checksum ipc-channel 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a38ad662f104525ac57012e0b79aad07507e3fc11e3bae668bf416264e760ebb" diff --git a/components/config/opts.rs b/components/config/opts.rs index d257206092e3..b03ed69c17b0 100644 --- a/components/config/opts.rs +++ b/components/config/opts.rs @@ -39,8 +39,13 @@ pub struct Opts { /// platform default setting. pub device_pixels_per_px: Option, - /// `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, /// When the profiler is enabled, this is an optional path to dump a self-contained HTML file @@ -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, Option, Option), FileName(String), - Stdout(f64) + Stdout(f64), } fn args_fail(msg: &str) -> ! { @@ -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, @@ -666,7 +676,14 @@ pub fn from_cmdline_args(args: &[String]) -> ArgumentParsingResult { match opt_match.opt_str("p") { Some(argument) => match argument.parse::() { 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)), } diff --git a/components/profile/Cargo.toml b/components/profile/Cargo.toml index bc7b3e7f173f..7fbe3db8bcac 100644 --- a/components/profile/Cargo.toml +++ b/components/profile/Cargo.toml @@ -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" diff --git a/components/profile/lib.rs b/components/profile/lib.rs index 6a456e1bb839..382edcf31c6c 100644 --- a/components/profile/lib.rs +++ b/components/profile/lib.rs @@ -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; diff --git a/components/profile/time.rs b/components/profile/time.rs index dfe5c37068d0..cc94dd9eb484 100644 --- a/components/profile/time.rs +++ b/components/profile/time.rs @@ -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}; @@ -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(); @@ -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 */ }, }; } }