diff --git a/Cargo.lock b/Cargo.lock index 4f51d85..17bde09 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -619,6 +619,19 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d0165d2900ae6778e36e80bbc4da3b5eefccee9ba939761f9c2882a5d9af3ff" +[[package]] +name = "crossbeam-epoch" +version = "0.9.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a" +dependencies = [ + "autocfg", + "cfg-if 1.0.0", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + [[package]] name = "crossbeam-queue" version = "0.3.8" @@ -871,7 +884,7 @@ checksum = "a604f7a68fbf8103337523b1fadc8ade7361ee3f112f7c680ad179651616aed5" dependencies = [ "futures-core", "lock_api", - "parking_lot", + "parking_lot 0.11.2", ] [[package]] @@ -1250,6 +1263,12 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "ipnet" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" + [[package]] name = "itertools" version = "0.10.5" @@ -1297,6 +1316,9 @@ dependencies = [ "clap", "cookie", "csv", + "futures", + "metrics", + "metrics-exporter-prometheus", "mime_guess", "recipes", "rust-embed", @@ -1394,6 +1416,15 @@ dependencies = [ "value-bag", ] +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + [[package]] name = "matchit" version = "0.5.0" @@ -1406,6 +1437,73 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "metrics" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b9b8653cec6897f73b519a43fba5ee3d50f62fe9af80b428accdcc093b4a849" +dependencies = [ + "ahash", + "metrics-macros", + "portable-atomic", +] + +[[package]] +name = "metrics-exporter-prometheus" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8603921e1f54ef386189335f288441af761e0fc61bcb552168d9cedfe63ebc70" +dependencies = [ + "hyper", + "indexmap", + "ipnet", + "metrics", + "metrics-util", + "parking_lot 0.12.1", + "portable-atomic", + "quanta", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "metrics-macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "731f8ecebd9f3a4aa847dfe75455e4757a45da40a7793d2f0b1f9b6ed18b23f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "metrics-util" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d24dc2dbae22bff6f1f9326ffce828c9f07ef9cc1e8002e5279f845432a30a" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", + "hashbrown", + "metrics", + "num_cpus", + "parking_lot 0.12.1", + "portable-atomic", + "quanta", + "sketches-ddsketch", +] + [[package]] name = "mime" version = "0.3.16" @@ -1550,7 +1648,17 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", - "parking_lot_core", + "parking_lot_core 0.8.6", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.6", ] [[package]] @@ -1567,6 +1675,19 @@ dependencies = [ "winapi", ] +[[package]] +name = "parking_lot_core" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + [[package]] name = "password-hash" version = "0.4.2" @@ -1642,6 +1763,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "portable-atomic" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26f6a7b87c2e435a3241addceeeff740ff8b7e76b74c13bf9acb17fa454ea00b" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -1657,6 +1784,22 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quanta" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e31331286705f455e56cca62e0e717158474ff02b7936c1fa596d983f4ae27" +dependencies = [ + "crossbeam-utils", + "libc", + "mach", + "once_cell", + "raw-cpuid", + "wasi 0.10.0+wasi-snapshot-preview1", + "web-sys", + "winapi", +] + [[package]] name = "quote" version = "1.0.23" @@ -1696,6 +1839,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "raw-cpuid" +version = "10.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6823ea29436221176fe662da99998ad3b4db2c7f31e7b6f5fe43adccd6320bb" +dependencies = [ + "bitflags", +] + [[package]] name = "recipes" version = "0.2.16" @@ -1958,6 +2110,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "sketches-ddsketch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ceb945e54128e09c43d8e4f1277851bd5044c6fc540bbaa2ad888f60b3da9ae7" + [[package]] name = "slab" version = "0.4.7" diff --git a/kitchen/Cargo.toml b/kitchen/Cargo.toml index 7f1585c..f00e6d8 100644 --- a/kitchen/Cargo.toml +++ b/kitchen/Cargo.toml @@ -21,6 +21,10 @@ tower = "0.4.13" serde = "1.0.144" cookie = "0.16.0" chrono = "0.4.22" +metrics = "0.20.1" +metrics-exporter-prometheus = "0.11.0" +futures = "0.3" +#prometheus = "0.13.3" [dependencies.argon2] version = "0.4.1" diff --git a/kitchen/src/web/metrics.rs b/kitchen/src/web/metrics.rs new file mode 100644 index 0000000..e92ca6a --- /dev/null +++ b/kitchen/src/web/metrics.rs @@ -0,0 +1,165 @@ +// Copyright 2023 Jeremy Wall (Jeremy@marzhilsltudios.com) +// +// 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 std::sync::{ + atomic::{AtomicU64, Ordering}, + Arc, Mutex, +}; + +use axum::{body::Bytes, http::Request, http::Response}; +use metrics::{Key, Label, Recorder}; +use metrics_exporter_prometheus::{PrometheusBuilder, PrometheusRecorder}; +use tower_http::{ + classify::{ServerErrorsAsFailures, SharedClassifier}, + trace::{ + DefaultMakeSpan, DefaultOnEos, OnBodyChunk, OnEos, OnFailure, OnRequest, OnResponse, + TraceLayer, + }, +}; + +// We want to track requeste count, request latency, request size minimally. + +pub type MetricsTraceLayer = TraceLayer< + SharedClassifier, + DefaultMakeSpan, + MetricsRecorder, + MetricsRecorder, + MetricsRecorder, + DefaultOnEos, + MetricsRecorder, +>; + +pub fn get_recorder() -> PrometheusRecorder { + let builder = PrometheusBuilder::new(); + builder.build_recorder() +} + +#[derive(Clone)] +pub struct MetricsRecorder { + rec: Arc, + labels: Arc>>, + size: Arc, +} + +impl MetricsRecorder { + pub fn new_with_rec(rec: Arc) -> Self { + Self { + rec, + labels: Arc::new(Mutex::new(Vec::new())), + size: Arc::new(AtomicU64::new(0)), + } + } +} + +impl OnBodyChunk for MetricsRecorder { + fn on_body_chunk( + &mut self, + chunk: &Bytes, + _latency: std::time::Duration, + _span: &tracing::Span, + ) { + let _ = self.size.fetch_add(chunk.len() as u64, Ordering::SeqCst); + } +} + +impl OnEos for MetricsRecorder { + fn on_eos( + self, + _trailers: Option<&axum::http::HeaderMap>, + _stream_duration: std::time::Duration, + _span: &tracing::Span, + ) { + } +} + +impl OnFailure for MetricsRecorder { + fn on_failure( + &mut self, + _failure_classification: FailureClass, + _latency: std::time::Duration, + _span: &tracing::Span, + ) { + let labels = self.labels.lock().expect("Failed to unlock labels").clone(); + self.rec + .as_ref() + .register_histogram(&Key::from_parts("http_request_failure_counter", labels)); + } +} + +impl OnResponse for MetricsRecorder { + fn on_response( + self, + _response: &Response, + latency: std::time::Duration, + _span: &tracing::Span, + ) { + let labels = self.labels.lock().expect("Failed to unlock labels").clone(); + self.rec + .as_ref() + .register_histogram(&Key::from_parts("http_request_time_ms", labels.clone())) + // If we somehow end up having requests overflow from u128 into f64 then we have + // much bigger problems than this cast. + .record(latency.as_micros() as f64); + self.rec + .as_ref() + .register_histogram(&Key::from_parts("http_request_size_bytes", labels)) + .record(self.size.as_ref().load(Ordering::SeqCst) as f64); + } +} + +fn make_request_lables(path: String, host: String, method: String) -> Vec