mirror of
https://github.com/zaphar/kitchen.git
synced 2025-07-22 19:40:14 -04:00
Add prometheus metrics
This commit is contained in:
parent
80e93fa476
commit
99261fb35a
162
Cargo.lock
generated
162
Cargo.lock
generated
@ -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"
|
||||
|
@ -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"
|
||||
|
165
kitchen/src/web/metrics.rs
Normal file
165
kitchen/src/web/metrics.rs
Normal file
@ -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<ServerErrorsAsFailures>,
|
||||
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<PrometheusRecorder>,
|
||||
labels: Arc<Mutex<Vec<Label>>>,
|
||||
size: Arc<AtomicU64>,
|
||||
}
|
||||
|
||||
impl MetricsRecorder {
|
||||
pub fn new_with_rec(rec: Arc<PrometheusRecorder>) -> Self {
|
||||
Self {
|
||||
rec,
|
||||
labels: Arc::new(Mutex::new(Vec::new())),
|
||||
size: Arc::new(AtomicU64::new(0)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OnBodyChunk<Bytes> 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<FailureClass> OnFailure<FailureClass> 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<B> OnResponse<B> for MetricsRecorder {
|
||||
fn on_response(
|
||||
self,
|
||||
_response: &Response<B>,
|
||||
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<Label> {
|
||||
vec![
|
||||
Label::new("path", path),
|
||||
Label::new("host", host),
|
||||
Label::new("method", method),
|
||||
]
|
||||
}
|
||||
|
||||
impl<B> OnRequest<B> for MetricsRecorder {
|
||||
fn on_request(&mut self, request: &Request<B>, _span: &tracing::Span) {
|
||||
let rec = self.rec.as_ref();
|
||||
let path = request.uri().path().to_lowercase();
|
||||
let host = request.uri().host().unwrap_or("").to_lowercase();
|
||||
let method = request.method().to_string();
|
||||
|
||||
let labels = make_request_lables(path, host, method);
|
||||
let mut labels_lock = self.labels.lock().expect("Failed to unlock labels");
|
||||
(*labels_lock.as_mut()) = labels.clone();
|
||||
rec.register_counter(&Key::from_parts("http_request_counter", labels.clone()))
|
||||
.increment(1);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_trace_layer(rec: Arc<PrometheusRecorder>) -> MetricsTraceLayer {
|
||||
let metrics_recorder = MetricsRecorder::new_with_rec(rec);
|
||||
let layer = TraceLayer::new_for_http()
|
||||
.on_body_chunk(metrics_recorder.clone())
|
||||
.on_request(metrics_recorder.clone())
|
||||
.on_response(metrics_recorder.clone())
|
||||
.on_failure(metrics_recorder.clone());
|
||||
layer
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn test_construction() {
|
||||
let metrics_recorder = MetricsRecorder::new_with_rec(std::sync::Arc::new(get_recorder()));
|
||||
let _trace_layer = TraceLayer::new_for_http()
|
||||
.on_body_chunk(metrics_recorder.clone())
|
||||
.on_request(metrics_recorder.clone())
|
||||
.on_response(metrics_recorder.clone())
|
||||
.on_failure(metrics_recorder.clone());
|
||||
}
|
||||
}
|
@ -24,17 +24,17 @@ use axum::{
|
||||
routing::{get, Router},
|
||||
};
|
||||
use chrono::NaiveDate;
|
||||
use client_api as api;
|
||||
use mime_guess;
|
||||
use recipes::{IngredientKey, RecipeEntry};
|
||||
use rust_embed::RustEmbed;
|
||||
use storage::{APIStore, AuthStore};
|
||||
use tower::ServiceBuilder;
|
||||
use tower_http::trace::TraceLayer;
|
||||
use tracing::{debug, info, instrument};
|
||||
|
||||
use client_api as api;
|
||||
use storage::{APIStore, AuthStore};
|
||||
|
||||
mod auth;
|
||||
mod metrics;
|
||||
mod storage;
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
@ -546,6 +546,9 @@ fn mk_v2_routes() -> Router {
|
||||
|
||||
#[instrument(fields(recipe_dir=?recipe_dir_path), skip_all)]
|
||||
pub async fn make_router(recipe_dir_path: PathBuf, store_path: PathBuf) -> Router {
|
||||
let recorder = std::sync::Arc::new(metrics::get_recorder());
|
||||
let handle = recorder.handle();
|
||||
let metrics_trace_layer = metrics::make_trace_layer(recorder);
|
||||
let store = Arc::new(storage::file_store::AsyncFileStore::new(
|
||||
recipe_dir_path.clone(),
|
||||
));
|
||||
@ -570,15 +573,20 @@ pub async fn make_router(recipe_dir_path: PathBuf, store_path: PathBuf) -> Route
|
||||
.nest("/v1", mk_v1_routes())
|
||||
.nest("/v2", mk_v2_routes()),
|
||||
)
|
||||
// NOTE(jwall): Note that the layers are applied to the preceding routes not
|
||||
.route(
|
||||
"/metrics/prometheus",
|
||||
get(|| async move { handle.render() }),
|
||||
)
|
||||
// NOTE(jwall): Note that this layer is applied to the preceding routes not
|
||||
// the following routes.
|
||||
.layer(
|
||||
// NOTE(jwall): However service builder will apply the layers from top
|
||||
// NOTE(jwall): However service builder will apply these layers from top
|
||||
// to bottom.
|
||||
ServiceBuilder::new()
|
||||
.layer(TraceLayer::new_for_http())
|
||||
.layer(metrics_trace_layer)
|
||||
.layer(Extension(store))
|
||||
.layer(Extension(app_store)),
|
||||
.layer(Extension(app_store)), //.layer(prometheus_layer)
|
||||
)
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user