mirror of
https://github.com/servo/servo.git
synced 2025-08-03 20:50:07 +01:00
Auto merge of #17536 - ferjm:influxdb.profiler, r=jdm
Add cli options to write profiler output to InfluxDB - [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 <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/17536) <!-- Reviewable:end -->
This commit is contained in:
commit
901525c911
5 changed files with 87 additions and 6 deletions
11
Cargo.lock
generated
11
Cargo.lock
generated
|
@ -1309,6 +1309,15 @@ name = "inflate"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
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]]
|
[[package]]
|
||||||
name = "io-surface"
|
name = "io-surface"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
|
@ -2164,6 +2173,7 @@ name = "profile"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heartbeats-simple 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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)",
|
"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)",
|
"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)",
|
"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 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 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 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 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 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"
|
"checksum ipc-channel 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a38ad662f104525ac57012e0b79aad07507e3fc11e3bae668bf416264e760ebb"
|
||||||
|
|
|
@ -39,8 +39,13 @@ pub struct Opts {
|
||||||
/// platform default setting.
|
/// platform default setting.
|
||||||
pub device_pixels_per_px: Option<f32>,
|
pub device_pixels_per_px: Option<f32>,
|
||||||
|
|
||||||
/// `None` to disable the time profiler or `Some` with an interval in seconds to enable it and
|
/// `None` to disable the time profiler or `Some` to enable it with:
|
||||||
/// cause it to produce output on that interval (`-p`).
|
/// - 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>,
|
pub time_profiling: Option<OutputOptions>,
|
||||||
|
|
||||||
/// When the profiler is enabled, this is an optional path to dump a self-contained HTML file
|
/// 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)]
|
#[derive(Clone, Deserialize, Serialize)]
|
||||||
pub enum OutputOptions {
|
pub enum OutputOptions {
|
||||||
|
/// Database connection config (hostname, name, user, pass)
|
||||||
|
DB(ServoUrl, Option<String>, Option<String>, Option<String>),
|
||||||
FileName(String),
|
FileName(String),
|
||||||
Stdout(f64)
|
Stdout(f64),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn args_fail(msg: &str) -> ! {
|
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", "");
|
"config directory following xdg spec on linux platform", "");
|
||||||
opts.optflag("v", "version", "Display servo version information");
|
opts.optflag("v", "version", "Display servo version information");
|
||||||
opts.optflag("", "unminify-js", "Unminify Javascript");
|
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) {
|
let opt_match = match opts.parse(args) {
|
||||||
Ok(m) => m,
|
Ok(m) => m,
|
||||||
|
@ -666,7 +676,14 @@ pub fn from_cmdline_args(args: &[String]) -> ArgumentParsingResult {
|
||||||
match opt_match.opt_str("p") {
|
match opt_match.opt_str("p") {
|
||||||
Some(argument) => match argument.parse::<f64>() {
|
Some(argument) => match argument.parse::<f64>() {
|
||||||
Ok(interval) => Some(OutputOptions::Stdout(interval)) ,
|
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)),
|
None => Some(OutputOptions::Stdout(5.0 as f64)),
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ path = "lib.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
profile_traits = {path = "../profile_traits"}
|
profile_traits = {path = "../profile_traits"}
|
||||||
|
influent = "0.4"
|
||||||
ipc-channel = "0.8"
|
ipc-channel = "0.8"
|
||||||
heartbeats-simple = "0.4"
|
heartbeats-simple = "0.4"
|
||||||
log = "0.3.5"
|
log = "0.3.5"
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
extern crate alloc_jemalloc;
|
extern crate alloc_jemalloc;
|
||||||
extern crate heartbeats_simple;
|
extern crate heartbeats_simple;
|
||||||
|
extern crate influent;
|
||||||
extern crate ipc_channel;
|
extern crate ipc_channel;
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
extern crate libc;
|
extern crate libc;
|
||||||
|
|
|
@ -5,6 +5,9 @@
|
||||||
//! Timing functions.
|
//! Timing functions.
|
||||||
|
|
||||||
use heartbeats;
|
use heartbeats;
|
||||||
|
use influent::client::{Client, Credentials};
|
||||||
|
use influent::create_client;
|
||||||
|
use influent::measurement::{Measurement, Value};
|
||||||
use ipc_channel::ipc::{self, IpcReceiver};
|
use ipc_channel::ipc::{self, IpcReceiver};
|
||||||
use profile_traits::energy::{energy_interval_ms, read_energy_uj};
|
use profile_traits::energy::{energy_interval_ms, read_energy_uj};
|
||||||
use profile_traits::time::{ProfilerCategory, ProfilerChan, ProfilerMsg, TimerMetadata};
|
use profile_traits::time::{ProfilerCategory, ProfilerChan, ProfilerMsg, TimerMetadata};
|
||||||
|
@ -183,7 +186,8 @@ impl Profiler {
|
||||||
}).expect("Thread spawning failed");
|
}).expect("Thread spawning failed");
|
||||||
// decide if we need to spawn the timer thread
|
// decide if we need to spawn the timer thread
|
||||||
match option {
|
match option {
|
||||||
&OutputOptions::FileName(_) => { /* no timer thread needed */ },
|
&OutputOptions::FileName(_) |
|
||||||
|
&OutputOptions::DB(_, _, _, _) => { /* no timer thread needed */ },
|
||||||
&OutputOptions::Stdout(period) => {
|
&OutputOptions::Stdout(period) => {
|
||||||
// Spawn a timer thread
|
// Spawn a timer thread
|
||||||
let chan = chan.clone();
|
let chan = chan.clone();
|
||||||
|
@ -391,7 +395,54 @@ impl Profiler {
|
||||||
}
|
}
|
||||||
writeln!(&mut lock, "").unwrap();
|
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 */ },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue