From 291c728e57dd21cd3f0425eb5dad3b26a7a27c3b Mon Sep 17 00:00:00 2001 From: "Heinz N. Gies" Date: Fri, 1 Oct 2021 14:31:03 +0200 Subject: [PATCH] Update to include persistance Signed-off-by: Heinz N. Gies --- Cargo.lock | 85 +++++ Cargo.toml | 3 + diesel.toml | 5 + migrations/.gitkeep | 0 .../down.sql | 2 + .../up.sql | 10 + src/error.rs | 68 ++++ src/lib.rs | 263 ---------------- src/main.rs | 163 +++++++--- src/model.rs | 39 +++ src/schema.rs | 11 + src/util.rs | 295 ++++++++++++++++++ ui/index.html | 1 + 13 files changed, 630 insertions(+), 315 deletions(-) create mode 100644 diesel.toml create mode 100644 migrations/.gitkeep create mode 100644 migrations/2021-10-01-103631_create_benchmarks/down.sql create mode 100644 migrations/2021-10-01-103631_create_benchmarks/up.sql create mode 100644 src/error.rs delete mode 100644 src/lib.rs create mode 100644 src/model.rs create mode 100644 src/schema.rs create mode 100644 src/util.rs create mode 100644 ui/index.html diff --git a/Cargo.lock b/Cargo.lock index b916b82..ac1ec3f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -265,6 +265,12 @@ version = "3.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9df67f7bf9ef8498769f994239c45613ef0c5899415fb58e9add412d2c1a538" +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + [[package]] name = "bytes" version = "1.0.1" @@ -426,6 +432,30 @@ dependencies = [ "syn", ] +[[package]] +name = "diesel" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b28135ecf6b7d446b43e27e225622a038cc4e2930a1022f51cdb97ada19b8e4d" +dependencies = [ + "bitflags", + "byteorder", + "diesel_derives", + "libsqlite3-sys", + "pq-sys", +] + +[[package]] +name = "diesel_derives" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45f5098f628d02a7a0f68ddba586fb61e80edec3bdc1be3b921f4ceec60858d3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "diff" version = "0.1.12" @@ -447,6 +477,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + [[package]] name = "encoding_rs" version = "0.8.28" @@ -763,6 +799,23 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-staticfile" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf435f95723ba94d2e44991abf717dd547b73f505830a917dc442a9195df06b" +dependencies = [ + "chrono", + "futures-util", + "http", + "hyper", + "mime_guess", + "percent-encoding", + "tokio", + "url", + "winapi", +] + [[package]] name = "hyper-tls" version = "0.5.0" @@ -877,6 +930,16 @@ version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6" +[[package]] +name = "libsqlite3-sys" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290b64917f8b0cb885d9de0f9959fe1f775d7fa12f1da2db9001c1c8ab60f89d" +dependencies = [ + "pkg-config", + "vcpkg", +] + [[package]] name = "lock_api" version = "0.4.5" @@ -914,6 +977,16 @@ version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +[[package]] +name = "mime_guess" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "miniz_oxide" version = "0.4.4" @@ -1189,6 +1262,15 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +[[package]] +name = "pq-sys" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac25eee5a0582f45a67e837e350d784e7003bd29a5f460796772061ca49ffda" +dependencies = [ + "vcpkg", +] + [[package]] name = "pretty_assertions" version = "0.7.1" @@ -1782,9 +1864,12 @@ dependencies = [ "chrono", "clap", "color-eyre", + "diesel", + "dotenv", "futures-util", "hmac", "hyper", + "hyper-staticfile", "octocrab", "pretty_assertions", "pretty_env_logger", diff --git a/Cargo.toml b/Cargo.toml index f1448a3..e7013ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,9 +14,12 @@ base16 = "*" chrono = "0.4.19" clap = "3.0.0-beta.2" color-eyre = "0.5.10" +diesel = { version = "1.4.4", features = ["postgres", "sqlite"] } +dotenv = "0.15.0" futures-util = "0.3" hmac = "0.11" hyper = { version = "0.14", features = ["full"] } +hyper-staticfile = "*" octocrab = "0.12" pretty_env_logger = "0.4" serde = { version = "1.0.125", features = ["derive"] } diff --git a/diesel.toml b/diesel.toml new file mode 100644 index 0000000..92267c8 --- /dev/null +++ b/diesel.toml @@ -0,0 +1,5 @@ +# For documentation on how to configure this file, +# see diesel.rs/guides/configuring-diesel-cli + +[print_schema] +file = "src/schema.rs" diff --git a/migrations/.gitkeep b/migrations/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/migrations/2021-10-01-103631_create_benchmarks/down.sql b/migrations/2021-10-01-103631_create_benchmarks/down.sql new file mode 100644 index 0000000..168a4cf --- /dev/null +++ b/migrations/2021-10-01-103631_create_benchmarks/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +DROP TABLE benchmarks; \ No newline at end of file diff --git a/migrations/2021-10-01-103631_create_benchmarks/up.sql b/migrations/2021-10-01-103631_create_benchmarks/up.sql new file mode 100644 index 0000000..c84c71e --- /dev/null +++ b/migrations/2021-10-01-103631_create_benchmarks/up.sql @@ -0,0 +1,10 @@ +-- Your SQL goes here +CREATE TABLE benchmarks ( + id VARCHAR NOT NULL PRIMARY KEY, + created_at DATE NOT NULL, + commit_hash CHAR(40) NOT NULL, + bench_name VARCHAR NOT NULL, + mpbs FLOAT8 NOT NULL, + eps FLOAT8 NOT NULL, + hist TEXT NOT NULL +); \ No newline at end of file diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..cb0f8e5 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,68 @@ +use std::fmt::Display; + +// Copyright 2020-2021, The Tremor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use hmac::crypto_mac::InvalidKeyLength; + +impl From for Error { + fn from(e: hyper::Error) -> Self { + Self::Hyper(e) + } +} +impl From<&str> for Error { + fn from(e: &str) -> Self { + Self::Text(e.to_string()) + } +} +impl From for Error { + fn from(_: InvalidKeyLength) -> Self { + Self::Other + } +} + +impl From for Error { + fn from(e: serde_json::Error) -> Self { + Self::BadRequest(format!("Invalid JSON: {}", e)) + } +} +impl From for Error { + fn from(_: std::io::Error) -> Self { + Self::Other + } +} +impl From> for Error { + fn from(_: async_std::channel::SendError) -> Self { + Self::Other + } +} +impl From for Error { + fn from(_: diesel::result::Error) -> Self { + Self::Other + } +} + +#[derive(Debug)] +pub enum Error { + Other, + Text(String), + Hyper(hyper::Error), + BadRequest(String), +} +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +impl std::error::Error for Error {} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index d0b47ae..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,263 +0,0 @@ -use chrono::offset::Utc; -use color_eyre::eyre::Result; -use serde::{Deserialize, Serialize}; - -pub fn update_json(old: &str, to_be_added: &str) -> String { - // old is of the format [{},{},{}] - // to_be_added is of the format {} - // output should be [{},{},{},{}] - if old.is_empty() { - format!("[{}]", to_be_added) - } else { - let last_character_removed = old.trim().strip_suffix(']').unwrap(); - let final_json = format!("{},{}]", last_character_removed, to_be_added); - final_json - } -} - -// TODO The name is horrible here. pls help -#[derive(Deserialize, Debug)] -pub struct WholeReport { - metadata: Metadata, - includes: Vec, - excludes: Vec, - reports: Reports, - stats: Stats, -} - -#[derive(Deserialize, Debug)] -struct Reports { - bench: Vec, -} - -#[derive(Deserialize, Debug)] -struct SingleReport { - description: String, - elements: Element, -} - -#[derive(Deserialize, Debug)] -struct Element { - bench: Bench, -} - -#[derive(Deserialize, Debug)] -struct Bench { - name: String, - description: String, - elements: Vec, - evidence: Evidence, - stats: Stat, - duration: usize, -} - -#[derive(Deserialize, Debug)] -struct Evidence { - #[serde(rename = "test: stdout")] - stdout: String, - #[serde(rename = "test: stderr")] - stderr: String, -} - -#[derive(Deserialize, Debug)] -struct Metadata { - allocator: String, - repository: String, - description: String, - homepage: String, - name: String, - authors: String, - librdkafka: String, - version: String, -} - -#[derive(Deserialize, Debug)] -struct Stats { - command: Stat, - all: Stat, - integration: Stat, - unit: Stat, - bench: Stat, -} - -#[derive(Deserialize, Debug)] -struct Stat { - pass: u16, - fail: u16, - skip: u16, - assert: u16, -} - -// TODO Add test -pub fn deserialize(report: &[u8]) -> Result { - Ok(serde_json::from_slice(report)?) -} - -fn date_and_time() -> String { - Utc::now().to_string() -} - -#[derive(Serialize, Debug)] -pub struct Data { - created_at: String, - commit_hash: String, - benchmarks: Vec, -} - -#[derive(Serialize, Debug)] -struct Benchmark { - name: String, - throughput: f64, - events: f64, -} - -pub fn convert_into_relevant_data(whole_report: WholeReport, commit_hash: &str) -> Result { - let mut benchmarks: Vec = vec![]; - for report in whole_report.reports.bench { - // TODO add a check if benchmark has passed or failed - let name = report.elements.bench.name; - let throughput = extract_throughput(&report.elements.bench.evidence.stdout).unwrap(); - let events = extract_events(&report.elements.bench.evidence.stdout).unwrap(); - benchmarks.push(Benchmark { - name, - throughput, - events, - }); - } - let created_at = date_and_time(); - Ok(Data { - created_at, - commit_hash: commit_hash.into(), - benchmarks, - }) -} - -pub fn serialize(data: &Data) -> Result { - Ok(serde_json::to_string(data)?) -} - -fn extract_throughput(log_string: &str) -> Option { - let start = log_string.find("Throughput (data):").unwrap() + 21; - let end = log_string.find("MB/s").unwrap(); - - log_string - .get(start..end) - .map(|throughput_string| throughput_string.trim().parse().unwrap()) -} - -fn extract_events(log_string: &str) -> Option { - let start = log_string.find("Throughput (events):").unwrap() + 21; - let end = log_string.find("k events/s").unwrap(); - - log_string - .get(start..end) - .map(|throughput_string| throughput_string.trim().parse().unwrap()) -} - -#[cfg(test)] -mod tests { - // importing names from outer (for mod tests) scope. - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn test_extract_throughput() { - let some_log = r#" - Value Percentile TotalCount 1/(1-Percentile) - - 1343 0.00000 1 1.00 - 6495 0.25000 21519175 1.33 - 8895 0.50000 43774741 2.00 - 10111 0.62500 53834372 2.67 - 21247 0.75000 64466758 4.00 - 31615 0.81250 69841533 5.33 - 44031 0.87500 75268579 8.00 - 51199 0.90625 77931574 10.67 - 60159 0.93750 80644651 16.00 - 65279 0.95312 81924551 21.33 - 73215 0.96875 83285642 32.00 - 79871 0.97656 83938098 42.67 - 91135 0.98438 84624143 64.00 - 99839 0.98828 84958398 85.33 - 116735 0.99219 85280988 128.00 - 133119 0.99414 85457548 170.67 - 153599 0.99609 85618379 256.00 - 163839 0.99707 85709966 341.33 - 172031 0.99805 85789891 512.00 - 177151 0.99854 85827174 682.67 - 189439 0.99902 85869533 1024.00 - 203775 0.99927 85889457 1365.33 - 228351 0.99951 85910431 2048.00 - 246783 0.99963 85921094 2730.67 - 270335 0.99976 85931344 4096.00 - 288767 0.99982 85936712 5461.33 - 319487 0.99988 85941757 8192.00 - 342015 0.99991 85944442 10922.67 - 376831 0.99994 85947012 16384.00 - 397311 0.99995 85948245 21845.33 - 448511 0.99997 85949562 32768.00 - 473087 0.99998 85950208 43690.67 - 540671 0.99998 85950877 65536.00 - 561151 0.99999 85951202 87381.33 - 638975 0.99999 85951514 131072.00 - 679935 0.99999 85951685 174762.67 - 724991 1.00000 85951874 262144.00 - 749567 1.00000 85951940 349525.33 - 806911 1.00000 85951999 524288.00 - 815103 1.00000 85952066 699050.67 - 819199 1.00000 85952092 1048576.00 - 831487 1.00000 85952103 1398101.33 - 851967 1.00000 85952128 2097152.00 - 856063 1.00000 85952133 2796202.67 - 880639 1.00000 85952144 4194304.00 - 884735 1.00000 85952151 5592405.33 - 884735 1.00000 85952151 8388608.00 - 888831 1.00000 85952159 11184810.67 - 888831 1.00000 85952159 16777216.00 - 888831 1.00000 85952159 22369621.33 - 888831 1.00000 85952159 33554432.00 - 892927 1.00000 85952161 44739242.67 - 892927 1.00000 85952161 inf -#[Mean = 18564.86, StdDeviation = 23434.73] -#[Max = 892927, Total count = 85952161] -#[Buckets = 30, SubBuckets = 3968] - - -Throughput (data): 58.7 MB/s -Throughput (events): 921.6k events/s - "#; - - let throughput = Some(58.7); - let events = Some(921.6); - assert_eq!(extract_throughput(some_log), throughput); - assert_eq!(extract_events(some_log), events); - } - - #[test] - fn test_update_json() { - let old_json = r#"[{"a":1},{"a":2}]"#; - let to_be_added_json = r#"{"a":3}"#; - let final_json = r#"[{"a":1},{"a":2},{"a":3}]"#; - - assert_eq!(update_json(old_json, to_be_added_json), final_json); - } - - #[ignore] - #[test] - fn test_update_json_2() { - let old_json = "[]"; - let to_be_added_json = r#"{"a":3}"#; - let final_json = r#"[{"a":3}]"#; - - assert_eq!(update_json(old_json, to_be_added_json), final_json); - } - - #[test] - fn test_update_json_3() { - let old_json = ""; - let to_be_added_json = r#"{"a":3}"#; - let final_json = r#"[{"a":3}]"#; - - assert_eq!(update_json(old_json, to_be_added_json), final_json); - } -} diff --git a/src/main.rs b/src/main.rs index f85a2f2..6b7a978 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,16 +1,42 @@ -use async_std::channel::{Sender, bounded}; +// Copyright 2020-2021, The Tremor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[macro_use] +extern crate diesel; + +mod error; +mod model; +pub(crate) mod schema; +mod util; + +use crate::error::Error; +use crate::schema::benchmarks; +use crate::schema::benchmarks::dsl::*; +use crate::util::{convert_into_relevant_data, deserialize}; +use async_std::channel::{bounded, Sender}; use async_std::task; use clap::{crate_authors, crate_version, Clap}; use color_eyre::eyre::Result; +use diesel::prelude::*; +use diesel::{Connection, SqliteConnection}; +use model::Benchmark; use serde_json::Value; -use tremor_benchmark::{convert_into_relevant_data, Data}; use async_std::process::Command; use async_std::sync::Arc; use std::env; -use tremor_benchmark::deserialize; - use hyper::service::{make_service_fn, service_fn}; use hyper::{Body, Method, Request, Response, Server, StatusCode}; @@ -26,9 +52,9 @@ struct Opts { key: Option, } -async fn get_report(commit_hash: &str) -> Result { +async fn get_report(hash: &str) -> Result> { // calculate short commit hash - let short_commit_hash = &commit_hash[..6]; + let short_commit_hash = &hash[..6]; let tag = format!("tremor-benchmark:{}", short_commit_hash); @@ -41,7 +67,7 @@ async fn get_report(commit_hash: &str) -> Result { "-f", "Dockerfile.bench", "--build-arg", - &format!("commithash={}", commit_hash), + &format!("commithash={}", hash), "docker", ]) .output() @@ -53,89 +79,100 @@ async fn get_report(commit_hash: &str) -> Result { .args(&["image", "rm", &tag]) .output() .await?; - convert_into_relevant_data(deserialize(&r.stdout)?, commit_hash) + convert_into_relevant_data(deserialize(&r.stdout)?, hash) } - - /// This is our service handler. It receives a Request, routes on its /// path, and returns a Future of a Response. -async fn run(opts: Arc, tx: Sender, req: Request) -> Result, hyper::Error> { +async fn run( + opts: Arc, + tx: Sender, + req: Request, +) -> Result, Error> { match (req.method(), req.uri().path()) { - // Serve some instructions at / - (&Method::GET, "/") => Ok(Response::new(Body::from( - "Try POSTing data to /echo such as: `curl localhost:3000/bench -XPOST -d ''`", - ))), - + (&Method::GET, "/bench") => { + // FIXME: this is terrible + let connection = establish_connection(); + let res: Vec = benchmarks.limit(100).load(&connection)?; + let res = serde_json::to_string(&res)?; + Ok(Response::new(Body::from(res))) + } // Simply echo the body back to the client. (&Method::POST, "/bench") => { // - let sig = req.headers().get("X-Hub-Signature-256").and_then(|v| v.to_str().ok()).map(ToString::to_string); + let sig = req + .headers() + .get("X-Hub-Signature-256") + .and_then(|v| v.to_str().ok()) + .map(ToString::to_string); let body = hyper::body::to_bytes(req.into_body()).await?; if let Some(key) = &opts.key { - let mut mac = HmacSha256::new_from_slice(key.as_bytes()) .expect("HMAC failed to create"); + let mut mac = HmacSha256::new_from_slice(key.as_bytes())?; mac.update(&body); - let result = base16::encode_lower( &mac.finalize().into_bytes()); - if sig != Some(result) { + let result = base16::encode_lower(&mac.finalize().into_bytes()); + if sig != Some(result) { //FIXME: Error let mut error = Response::new(Body::from("bad hmac")); *error.status_mut() = StatusCode::FORBIDDEN; - return Ok(error) + return Ok(error); }; }; - let body = if let Ok(b) = serde_json::from_slice::(&body) { - b - } else { - let mut error = Response::new(Body::from("invalid payload")); - *error.status_mut() = StatusCode::BAD_REQUEST; - return Ok(error) - }; + let body = serde_json::from_slice::(&body)?; - if Some("push") != - body.get("action").and_then(Value::as_str) { - //FIXME: Error - //FIXME: Error - let mut error = Response::new(Body::from("action missing or not `push`")); - *error.status_mut() = StatusCode::BAD_REQUEST; - return Ok(error) - }; - - let hash = if let Some(s) = body.get("after").and_then(Value::as_str) { - s.to_string() - } else { - //FIXME: Error - let mut error = Response::new(Body::from("after missing")); - *error.status_mut() = StatusCode::FORBIDDEN; - return Ok(error) + if Some("push") != body.get("action").and_then(Value::as_str) { + return Err(Error::BadRequest("action missing or not `push`".into())); }; + let hash = body + .get("after") + .and_then(Value::as_str) + .ok_or_else(|| Error::BadRequest("`after` is missing".into()))? + .to_string(); - tx.send(hash.clone()).await.unwrap(); + tx.send(hash.clone()).await?; - Ok(Response::new(Body::from(format!(r#"{{"hash": "{}"}}"#, hash)))) + Ok(Response::new(Body::from(format!( + r#"{{"hash": "{}"}}"#, + hash + )))) } // Return the 404 Not Found for other routes. _ => { - let mut not_found = Response::default(); - *not_found.status_mut() = StatusCode::NOT_FOUND; - Ok(not_found) + let static_ = hyper_staticfile::Static::new("ui/"); + Ok(static_.serve(req).await?) } } } +fn establish_connection() -> SqliteConnection { + let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); + SqliteConnection::establish(&database_url) + .unwrap_or_else(|_| panic!("Error connecting to {}", database_url)) +} + #[tokio::main] async fn main() -> Result<(), Box> { + dotenv::dotenv().ok(); let (tx, rx) = bounded::(64); task::spawn(async move { + let connection = establish_connection(); while let Ok(hash) = rx.recv().await { - match get_report(&hash).await { + match get_report(&hash).await { Err(e) => eprint!("Report Error {}", e), - Ok(r) => println!("data: {:?}", r) + Ok(r) => { + println!("data: {:?}", r); + for b in r { + diesel::insert_into(benchmarks::table) + .values(&b.as_new()) + .execute(&connection) + .expect("Error saving new post"); + } + } }; } }); @@ -146,7 +183,29 @@ async fn main() -> Result<(), Box> { let service = make_service_fn(move |_| { let o = Arc::new(opts.clone()); let tx = tx.clone(); - async move { Ok::<_, hyper::Error>(service_fn(move |req| run(o.clone(), tx.clone(), req))) } + async move { + Ok::<_, hyper::Error>(service_fn(move |req| { + let o = o.clone(); + let tx = tx.clone(); + + async move { + match run(o, tx, req).await { + Ok(r) => Ok(r), + Err(Error::BadRequest(e)) => { + let mut error = Response::new(Body::from(e)); + *error.status_mut() = StatusCode::BAD_REQUEST; + Ok(error) + } + Err(Error::Hyper(e)) => Err(e), + Err(e) => { + let mut error = Response::new(Body::from(format!("Error: {:?}", e))); + *error.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; + Ok(error) + } + } + } + })) + } }); let server = Server::bind(&addr).serve(service); diff --git a/src/model.rs b/src/model.rs new file mode 100644 index 0000000..2af9c17 --- /dev/null +++ b/src/model.rs @@ -0,0 +1,39 @@ +use super::schema::benchmarks; +use serde::Serialize; + +#[derive(Serialize, Queryable, Debug)] +pub struct Benchmark { + pub id: String, + pub created_at: String, + pub commit_hash: String, + pub bench_name: String, + pub mpbs: f32, + pub eps: f32, + pub hist: String, +} + +impl Benchmark { + pub fn as_new(&self) -> NewBenchmark { + NewBenchmark { + id: &self.id, + created_at: &self.created_at, + commit_hash: &self.commit_hash, + bench_name: &self.bench_name, + mpbs: self.mpbs, + eps: self.eps, + hist: &self.hist, + } + } +} + +#[derive(Insertable)] +#[table_name = "benchmarks"] +pub struct NewBenchmark<'a> { + pub id: &'a str, + pub created_at: &'a str, + pub commit_hash: &'a str, + pub bench_name: &'a str, + pub mpbs: f32, + pub eps: f32, + pub hist: &'a str, +} diff --git a/src/schema.rs b/src/schema.rs new file mode 100644 index 0000000..e60582e --- /dev/null +++ b/src/schema.rs @@ -0,0 +1,11 @@ +table! { + benchmarks (id) { + id -> Text, + created_at -> Date, + commit_hash -> Text, + bench_name -> Text, + mpbs -> Float, + eps -> Float, + hist -> Text, + } +} diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..3401cdf --- /dev/null +++ b/src/util.rs @@ -0,0 +1,295 @@ +// Copyright 2020-2021, The Tremor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use chrono::offset::Utc; +use color_eyre::eyre::Result; +use serde::Deserialize; + +use crate::error::Error; + +// TODO The name is horrible here. pls help +#[derive(Deserialize, Debug)] +pub struct WholeReport { + metadata: Metadata, + includes: Vec, + excludes: Vec, + reports: Reports, + stats: Stats, +} + +#[derive(Deserialize, Debug)] +struct Reports { + bench: Vec, +} + +#[derive(Deserialize, Debug)] +struct SingleReport { + description: String, + elements: Element, +} + +#[derive(Deserialize, Debug)] +struct Element { + bench: Bench, +} + +#[derive(Deserialize, Debug)] +struct Bench { + name: String, + description: String, + elements: Vec, + evidence: Evidence, + stats: Stat, + duration: usize, +} + +#[derive(Deserialize, Debug)] +struct Evidence { + #[serde(rename = "test: stdout")] + stdout: String, + #[serde(rename = "test: stderr")] + stderr: String, +} + +#[derive(Deserialize, Debug)] +struct Metadata { + allocator: String, + repository: String, + description: String, + homepage: String, + name: String, + authors: String, + librdkafka: String, + version: String, +} + +#[derive(Deserialize, Debug)] +struct Stats { + command: Stat, + all: Stat, + integration: Stat, + unit: Stat, + bench: Stat, +} + +#[derive(Deserialize, Debug)] +struct Stat { + pass: u16, + fail: u16, + skip: u16, + assert: u16, +} + +// TODO Add test +pub fn deserialize(report: &[u8]) -> Result { + Ok(serde_json::from_slice(report)?) +} + +fn date_and_time() -> String { + Utc::now().to_string() +} + +pub fn convert_into_relevant_data( + whole_report: WholeReport, + commit_hash: &str, +) -> Result> { + let created_at = date_and_time(); + + whole_report + .reports + .bench + .into_iter() + .map(|report| { + // TODO add a check if benchmark has passed or failed + let bench_name = report.elements.bench.name; + let r = report.elements.bench.evidence.stdout; + let mpbs = extract_throughput(&r).ok_or(Error::Other)?; + let eps = extract_events(&r).ok_or(Error::Other)?; + let hist = extract_hist(&r).ok_or(Error::Other)?.to_string(); + Ok(crate::model::Benchmark { + id: format!("{}-{}-{}", commit_hash, &bench_name, &created_at), + created_at: created_at.clone(), + commit_hash: commit_hash.to_string(), + bench_name, + mpbs, + eps, + hist, + }) + }) + .collect() +} + +fn extract_throughput(log_string: &str) -> Option { + let start = log_string.find("Throughput (data):")? + 21; + let end = log_string.find("MB/s")?; + + log_string + .get(start..end) + .and_then(|throughput_string| throughput_string.trim().parse().ok()) +} + +fn extract_events(log_string: &str) -> Option { + let start = log_string.find("Throughput (events):")? + 21; + let end = log_string.find("k events/s")?; + + log_string + .get(start..end) + .and_then(|throughput_string| throughput_string.trim().parse().ok()) +} + +fn extract_hist(log_string: &str) -> Option<&str> { + let end = log_string.find("\n\n\n")?; + + log_string.get(..end) +} + +#[cfg(test)] +mod tests { + // importing names from outer (for mod tests) scope. + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn test_extract_throughput() { + let some_log = r#" + Value Percentile TotalCount 1/(1-Percentile) + + 1343 0.00000 1 1.00 + 6495 0.25000 21519175 1.33 + 8895 0.50000 43774741 2.00 + 10111 0.62500 53834372 2.67 + 21247 0.75000 64466758 4.00 + 31615 0.81250 69841533 5.33 + 44031 0.87500 75268579 8.00 + 51199 0.90625 77931574 10.67 + 60159 0.93750 80644651 16.00 + 65279 0.95312 81924551 21.33 + 73215 0.96875 83285642 32.00 + 79871 0.97656 83938098 42.67 + 91135 0.98438 84624143 64.00 + 99839 0.98828 84958398 85.33 + 116735 0.99219 85280988 128.00 + 133119 0.99414 85457548 170.67 + 153599 0.99609 85618379 256.00 + 163839 0.99707 85709966 341.33 + 172031 0.99805 85789891 512.00 + 177151 0.99854 85827174 682.67 + 189439 0.99902 85869533 1024.00 + 203775 0.99927 85889457 1365.33 + 228351 0.99951 85910431 2048.00 + 246783 0.99963 85921094 2730.67 + 270335 0.99976 85931344 4096.00 + 288767 0.99982 85936712 5461.33 + 319487 0.99988 85941757 8192.00 + 342015 0.99991 85944442 10922.67 + 376831 0.99994 85947012 16384.00 + 397311 0.99995 85948245 21845.33 + 448511 0.99997 85949562 32768.00 + 473087 0.99998 85950208 43690.67 + 540671 0.99998 85950877 65536.00 + 561151 0.99999 85951202 87381.33 + 638975 0.99999 85951514 131072.00 + 679935 0.99999 85951685 174762.67 + 724991 1.00000 85951874 262144.00 + 749567 1.00000 85951940 349525.33 + 806911 1.00000 85951999 524288.00 + 815103 1.00000 85952066 699050.67 + 819199 1.00000 85952092 1048576.00 + 831487 1.00000 85952103 1398101.33 + 851967 1.00000 85952128 2097152.00 + 856063 1.00000 85952133 2796202.67 + 880639 1.00000 85952144 4194304.00 + 884735 1.00000 85952151 5592405.33 + 884735 1.00000 85952151 8388608.00 + 888831 1.00000 85952159 11184810.67 + 888831 1.00000 85952159 16777216.00 + 888831 1.00000 85952159 22369621.33 + 888831 1.00000 85952159 33554432.00 + 892927 1.00000 85952161 44739242.67 + 892927 1.00000 85952161 inf +#[Mean = 18564.86, StdDeviation = 23434.73] +#[Max = 892927, Total count = 85952161] +#[Buckets = 30, SubBuckets = 3968] + + +Throughput (data): 58.7 MB/s +Throughput (events): 921.6k events/s + "#; + + let hist = r#" + Value Percentile TotalCount 1/(1-Percentile) + + 1343 0.00000 1 1.00 + 6495 0.25000 21519175 1.33 + 8895 0.50000 43774741 2.00 + 10111 0.62500 53834372 2.67 + 21247 0.75000 64466758 4.00 + 31615 0.81250 69841533 5.33 + 44031 0.87500 75268579 8.00 + 51199 0.90625 77931574 10.67 + 60159 0.93750 80644651 16.00 + 65279 0.95312 81924551 21.33 + 73215 0.96875 83285642 32.00 + 79871 0.97656 83938098 42.67 + 91135 0.98438 84624143 64.00 + 99839 0.98828 84958398 85.33 + 116735 0.99219 85280988 128.00 + 133119 0.99414 85457548 170.67 + 153599 0.99609 85618379 256.00 + 163839 0.99707 85709966 341.33 + 172031 0.99805 85789891 512.00 + 177151 0.99854 85827174 682.67 + 189439 0.99902 85869533 1024.00 + 203775 0.99927 85889457 1365.33 + 228351 0.99951 85910431 2048.00 + 246783 0.99963 85921094 2730.67 + 270335 0.99976 85931344 4096.00 + 288767 0.99982 85936712 5461.33 + 319487 0.99988 85941757 8192.00 + 342015 0.99991 85944442 10922.67 + 376831 0.99994 85947012 16384.00 + 397311 0.99995 85948245 21845.33 + 448511 0.99997 85949562 32768.00 + 473087 0.99998 85950208 43690.67 + 540671 0.99998 85950877 65536.00 + 561151 0.99999 85951202 87381.33 + 638975 0.99999 85951514 131072.00 + 679935 0.99999 85951685 174762.67 + 724991 1.00000 85951874 262144.00 + 749567 1.00000 85951940 349525.33 + 806911 1.00000 85951999 524288.00 + 815103 1.00000 85952066 699050.67 + 819199 1.00000 85952092 1048576.00 + 831487 1.00000 85952103 1398101.33 + 851967 1.00000 85952128 2097152.00 + 856063 1.00000 85952133 2796202.67 + 880639 1.00000 85952144 4194304.00 + 884735 1.00000 85952151 5592405.33 + 884735 1.00000 85952151 8388608.00 + 888831 1.00000 85952159 11184810.67 + 888831 1.00000 85952159 16777216.00 + 888831 1.00000 85952159 22369621.33 + 888831 1.00000 85952159 33554432.00 + 892927 1.00000 85952161 44739242.67 + 892927 1.00000 85952161 inf +#[Mean = 18564.86, StdDeviation = 23434.73] +#[Max = 892927, Total count = 85952161] +#[Buckets = 30, SubBuckets = 3968]"#; + let throughput = Some(58.7); + let events = Some(921.6); + assert_eq!(extract_throughput(some_log), throughput); + assert_eq!(extract_events(some_log), events); + assert_eq!(extract_hist(some_log), Some(hist)); + } +} diff --git a/ui/index.html b/ui/index.html new file mode 100644 index 0000000..887f2fe --- /dev/null +++ b/ui/index.html @@ -0,0 +1 @@ +

Wooh wooh!