diff --git a/site/src/api.rs b/site/src/api.rs index 4abace9c9..a663b791b 100644 --- a/site/src/api.rs +++ b/site/src/api.rs @@ -42,6 +42,28 @@ pub mod dashboard { } pub mod graph { + use super::graphs::{GraphKind, Series}; + use collector::Bound; + use serde::{Deserialize, Serialize}; + + #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] + pub struct Request { + pub benchmark: String, + pub profile: String, + pub scenario: String, + pub metric: String, + pub start: Bound, + pub end: Bound, + pub kind: GraphKind, + } + + #[derive(Debug, PartialEq, Clone, Serialize)] + pub struct Response { + pub series: Series, + } +} + +pub mod graphs { use collector::Bound; use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; diff --git a/site/src/load.rs b/site/src/load.rs index 80d86d55d..f4cf3b324 100644 --- a/site/src/load.rs +++ b/site/src/load.rs @@ -79,7 +79,7 @@ pub struct SiteCtxt { /// Site configuration pub config: Config, /// Cached site landing page - pub landing_page: ArcSwap>>, + pub landing_page: ArcSwap>>, /// Index of various common queries pub index: ArcSwap, /// Database connection pool diff --git a/site/src/request_handlers.rs b/site/src/request_handlers.rs index 5cbc16007..f300b8840 100644 --- a/site/src/request_handlers.rs +++ b/site/src/request_handlers.rs @@ -9,7 +9,7 @@ mod status_page; pub use bootstrap::handle_bootstrap; pub use dashboard::handle_dashboard; pub use github::handle_github; -pub use graph::handle_graphs; +pub use graph::{handle_graph, handle_graphs}; pub use next_commit::handle_next_commit; pub use self_profile::{ handle_self_profile, handle_self_profile_processed_download, handle_self_profile_raw, diff --git a/site/src/request_handlers/graph.rs b/site/src/request_handlers/graph.rs index f8df06ca9..ee0faf493 100644 --- a/site/src/request_handlers/graph.rs +++ b/site/src/request_handlers/graph.rs @@ -2,25 +2,34 @@ use collector::Bound; use std::collections::HashMap; use std::sync::Arc; -use crate::api::graph::GraphKind; -use crate::api::{graph, ServerResult}; +use crate::api::graphs::GraphKind; +use crate::api::{graph, graphs, ServerResult}; use crate::db::{self, ArtifactId, Benchmark, Profile, Scenario}; use crate::interpolate::IsInterpolated; use crate::load::SiteCtxt; use crate::selector::{Query, Selector, SeriesResponse, Tag}; +pub async fn handle_graph( + request: graph::Request, + ctxt: Arc, +) -> ServerResult { + log::info!("handle_graph({:?})", request); + + create_graph(request, ctxt).await +} + pub async fn handle_graphs( - body: graph::Request, - ctxt: &SiteCtxt, -) -> ServerResult> { - log::info!("handle_graph({:?})", body); + request: graphs::Request, + ctxt: Arc, +) -> ServerResult> { + log::info!("handle_graphs({:?})", request); - let is_default_query = body - == graph::Request { + let is_default_query = request + == graphs::Request { start: Bound::None, end: Bound::None, stat: String::from("instructions:u"), - kind: graph::GraphKind::Raw, + kind: graphs::GraphKind::Raw, }; if is_default_query { @@ -30,7 +39,7 @@ pub async fn handle_graphs( } } - let resp = graph_response(body, ctxt).await?; + let resp = create_graphs(request, &ctxt).await?; if is_default_query { ctxt.landing_page.store(Arc::new(Some(resp.clone()))); @@ -39,12 +48,36 @@ pub async fn handle_graphs( Ok(resp) } -async fn graph_response( - body: graph::Request, +async fn create_graph( + request: graph::Request, + ctxt: Arc, +) -> ServerResult { + let artifact_ids = artifact_ids_for_range(&ctxt, request.start, request.end); + let mut series_iterator = ctxt + .statistic_series( + Query::new() + .set::(Tag::Benchmark, Selector::One(request.benchmark)) + .set::(Tag::Profile, Selector::One(request.profile)) + .set::(Tag::Scenario, Selector::One(request.scenario)) + .set::(Tag::Metric, Selector::One(request.metric)), + Arc::new(artifact_ids), + ) + .await? + .into_iter() + .map(SeriesResponse::interpolate); + + let result = series_iterator.next().unwrap(); + let graph_series = graph_series(result.series, request.kind); + Ok(graph::Response { + series: graph_series, + }) +} + +async fn create_graphs( + request: graphs::Request, ctxt: &SiteCtxt, -) -> ServerResult> { - let range = ctxt.data_range(body.start..=body.end); - let commits: Arc> = Arc::new(range.into_iter().map(|c| c.into()).collect()); +) -> ServerResult> { + let artifact_ids = Arc::new(artifact_ids_for_range(ctxt, request.start, request.end)); let mut benchmarks = HashMap::new(); let interpolated_responses: Vec<_> = ctxt @@ -53,15 +86,15 @@ async fn graph_response( .set::(Tag::Benchmark, Selector::All) .set::(Tag::Profile, Selector::All) .set::(Tag::Scenario, Selector::All) - .set::(Tag::Metric, Selector::One(body.stat)), - commits.clone(), + .set::(Tag::Metric, Selector::One(request.stat)), + artifact_ids.clone(), ) .await? .into_iter() .map(|sr| sr.interpolate().map(|series| series.collect::>())) .collect(); - let summary_benchmark = create_summary(ctxt, &interpolated_responses, body.kind)?; + let summary_benchmark = create_summary(ctxt, &interpolated_responses, request.kind)?; benchmarks.insert("Summary".to_string(), summary_benchmark); @@ -69,7 +102,7 @@ async fn graph_response( let benchmark = response.path.get::()?.to_string(); let profile = *response.path.get::()?; let scenario = response.path.get::()?.to_string(); - let graph_series = graph_series(response.series.into_iter(), body.kind); + let graph_series = graph_series(response.series.into_iter(), request.kind); benchmarks .entry(benchmark) @@ -79,8 +112,8 @@ async fn graph_response( .insert(scenario, graph_series); } - Ok(Arc::new(graph::Response { - commits: Arc::try_unwrap(commits) + Ok(Arc::new(graphs::Response { + commits: Arc::try_unwrap(artifact_ids) .unwrap() .into_iter() .map(|c| match c { @@ -92,13 +125,18 @@ async fn graph_response( })) } +fn artifact_ids_for_range(ctxt: &SiteCtxt, start: Bound, end: Bound) -> Vec { + let range = ctxt.data_range(start..=end); + range.into_iter().map(|c| c.into()).collect() +} + /// Creates a summary "benchmark" that averages the results of all other /// test cases per profile type fn create_summary( ctxt: &SiteCtxt, interpolated_responses: &[SeriesResponse), IsInterpolated)>>], graph_kind: GraphKind, -) -> ServerResult>> { +) -> ServerResult>> { let mut baselines = HashMap::new(); let mut summary_benchmark = HashMap::new(); let summary_query_cases = iproduct!( @@ -152,8 +190,8 @@ fn create_summary( fn graph_series( points: impl Iterator), IsInterpolated)>, kind: GraphKind, -) -> graph::Series { - let mut graph_series = graph::Series { +) -> graphs::Series { + let mut graph_series = graphs::Series { points: Vec::new(), interpolated_indices: Default::default(), }; diff --git a/site/src/server.rs b/site/src/server.rs index fcc084cf2..de4c403e0 100644 --- a/site/src/server.rs +++ b/site/src/server.rs @@ -18,7 +18,7 @@ use serde::de::DeserializeOwned; use serde::Serialize; pub use crate::api::{ - self, bootstrap, comparison, dashboard, github, graph, info, self_profile, self_profile_raw, + self, bootstrap, comparison, dashboard, github, graphs, info, self_profile, self_profile_raw, status, triage, ServerResult, }; use crate::db::{self, ArtifactId}; @@ -131,6 +131,39 @@ impl Server { .unwrap()) } + async fn handle_fallible_get_async( + &self, + req: &Request, + handler: F, + ) -> Result + where + F: FnOnce(Arc) -> R, + R: std::future::Future> + Send, + S: Serialize, + E: Into>, + { + check_http_method!(*req.method(), http::Method::GET); + let ctxt = self.ctxt.clone(); + let ctxt = ctxt.read().as_ref().unwrap().clone(); + let result = handler(ctxt).await; + let response = match result { + Ok(result) => { + let response = http::Response::builder() + .header_typed(ContentType::json()) + .header_typed(CacheControl::new().with_no_cache().with_no_store()); + let body = serde_json::to_vec(&result).unwrap(); + response.body(hyper::Body::from(body)).unwrap() + } + Err(err) => http::Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .header_typed(ContentType::text_utf8()) + .header_typed(CacheControl::new().with_no_cache().with_no_store()) + .body(hyper::Body::from(err.into())) + .unwrap(), + }; + Ok(response) + } + fn check_auth(&self, req: &http::request::Parts) -> bool { if let Some(auth) = req .headers @@ -317,6 +350,18 @@ async fn serve_req(server: Server, req: Request) -> Result { + let query = check!(parse_query_string(req.uri())); + return server + .handle_fallible_get_async(&req, |c| request_handlers::handle_graph(query, c)) + .await; + } + "/perf/graphs" => { + let query = check!(parse_query_string(req.uri())); + return server + .handle_fallible_get_async(&req, |c| request_handlers::handle_graphs(query, c)) + .await; + } "/perf/metrics" => { return Ok(server.handle_metrics(req).await); } @@ -403,23 +448,6 @@ async fn serve_req(server: Server, req: Request) -> Result Ok(to_response( request_handlers::handle_self_profile_raw(check!(parse_body(&body)), &ctxt).await, )), - "/perf/graphs" => Ok( - match request_handlers::handle_graphs(check!(parse_body(&body)), &ctxt).await { - Ok(result) => { - let response = http::Response::builder() - .header_typed(ContentType::json()) - .header_typed(CacheControl::new().with_no_cache().with_no_store()); - let body = serde_json::to_vec(&result).unwrap(); - response.body(hyper::Body::from(body)).unwrap() - } - Err(err) => http::Response::builder() - .status(StatusCode::INTERNAL_SERVER_ERROR) - .header_typed(ContentType::text_utf8()) - .header_typed(CacheControl::new().with_no_cache().with_no_store()) - .body(hyper::Body::from(err)) - .unwrap(), - }, - ), "/perf/bootstrap" => Ok( match request_handlers::handle_bootstrap(check!(parse_body(&body)), &ctxt).await { Ok(result) => { diff --git a/site/static/index.html b/site/static/index.html index d7514437b..af6cb3e5c 100644 --- a/site/static/index.html +++ b/site/static/index.html @@ -387,11 +387,9 @@

This may take a while!

document.querySelector("#loading").style.display = 'none'; } - function post_json(path, body) { - return fetch(BASE_URL + path, { - method: "POST", - body: JSON.stringify(body), - }).then(r => r.json()); + function get_json(path, query) { + let q = Object.entries(query).map(e => `${e[0]}=${e[1]}`).join("&"); + return fetch(BASE_URL + path + "?" + q).then(r => r.json()); } function prepData(data) { @@ -439,7 +437,7 @@

This may take a while!

stat: "instructions:u", kind: "raw", }, state); - post_json("/graphs", values).then(prepData).then(data => + get_json("/graphs", values).then(prepData).then(data => renderPlots(data, values)); });