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"
|
||||
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"
|
||||
|
|
|
@ -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
|
||||
|
@ -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) -> ! {
|
||||
|
@ -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::<f64>() {
|
||||
Ok(interval) => Some(OutputOptions::Stdout(interval)) ,
|
||||
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)),
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 */ },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue