prometheus
This commit is contained in:
parent
a6a4d68651
commit
c7e317985f
6 changed files with 157 additions and 11 deletions
30
Cargo.lock
generated
30
Cargo.lock
generated
|
@ -52,6 +52,7 @@ dependencies = [
|
|||
"env_logger",
|
||||
"futures-util",
|
||||
"log",
|
||||
"prometheus-client",
|
||||
"qrcode",
|
||||
"rand",
|
||||
"sailfish",
|
||||
|
@ -466,6 +467,12 @@ version = "0.15.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
||||
|
||||
[[package]]
|
||||
name = "dtoa"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653"
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.16"
|
||||
|
@ -1416,6 +1423,29 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prometheus-client"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "510c4f1c9d81d556458f94c98f857748130ea9737bbd6053da497503b26ea63c"
|
||||
dependencies = [
|
||||
"dtoa",
|
||||
"itoa",
|
||||
"parking_lot",
|
||||
"prometheus-client-derive-encode",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prometheus-client-derive-encode"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "qrcode"
|
||||
version = "0.12.0"
|
||||
|
|
|
@ -24,3 +24,4 @@ rand = "0.8"
|
|||
qrcode = "0.12"
|
||||
tower = "0.4.13"
|
||||
tower-sessions = "0.4.1"
|
||||
prometheus-client = "0.22.0"
|
||||
|
|
18
flake.lock
18
flake.lock
|
@ -7,11 +7,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1699030822,
|
||||
"narHash": "sha256-a25bCHvTPJfAvK3qLoi5uI2pvwnOYhMQLRpJYNEt55o=",
|
||||
"lastModified": 1699548976,
|
||||
"narHash": "sha256-xnpxms0koM8mQpxIup9JnT0F7GrKdvv0QvtxvRuOYR4=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "2c89c36bffac32d8267e719f73b0d06e313ede30",
|
||||
"rev": "6849911446e18e520970cc6b7a691e64ee90d649",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -40,11 +40,11 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1698924604,
|
||||
"narHash": "sha256-GCFbkl2tj8fEZBZCw3Tc0AkGo0v+YrQlohhEGJ/X4s0=",
|
||||
"lastModified": 1699099776,
|
||||
"narHash": "sha256-X09iKJ27mGsGambGfkKzqvw5esP1L/Rf8H3u3fCqIiU=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "fa804edfb7869c9fb230e174182a8a1a7e512c40",
|
||||
"rev": "85f1ba3e51676fa8cc604a3d863d729026a6b8eb",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -72,11 +72,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1698977568,
|
||||
"narHash": "sha256-bnbCqPDFdOUcSANJv9Br3q/b1LyK9vyB1I7os5T4jXI=",
|
||||
"lastModified": 1699582387,
|
||||
"narHash": "sha256-sPmUXPDl+cEi+zFtM5lnAs7dWOdRn0ptZ4a/qHwvNDk=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "321affd863e3e4e669990a1db5fdabef98387b95",
|
||||
"rev": "41f7b0618052430d3a050e8f937030d00a2fcced",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
@ -46,6 +46,9 @@ pub enum Error {
|
|||
|
||||
#[error("invalid input")]
|
||||
InvalidInput,
|
||||
|
||||
#[error("Prometheus: {0:?}")]
|
||||
Prometheus(std::fmt::Error),
|
||||
}
|
||||
|
||||
impl IntoResponse for Error {
|
||||
|
|
70
src/game.rs
70
src/game.rs
|
@ -1,10 +1,19 @@
|
|||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
fmt::Debug,
|
||||
ops::Deref,
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use prometheus_client::{
|
||||
collector::Collector,
|
||||
encoding::{EncodeLabelSet, EncodeMetric},
|
||||
metrics::{
|
||||
family::Family,
|
||||
gauge::{ConstGauge, Gauge},
|
||||
},
|
||||
};
|
||||
use qrcode::{render::svg, QrCode};
|
||||
use rand::{distributions, Rng};
|
||||
use sailfish::TemplateOnce;
|
||||
|
@ -256,3 +265,64 @@ impl Game {
|
|||
.render_once()?)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(EncodeLabelSet, PartialEq, Eq, Hash, Clone, Debug)]
|
||||
pub struct GameLabels {
|
||||
game: String,
|
||||
}
|
||||
|
||||
pub struct GameCollector {
|
||||
games: Arc<RwLock<HashMap<GameId, Game>>>,
|
||||
}
|
||||
|
||||
impl GameCollector {
|
||||
pub fn new(games: Arc<RwLock<HashMap<GameId, Game>>>) -> Self {
|
||||
Self { games }
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for GameCollector {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("GameCollector").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Collector for GameCollector {
|
||||
fn encode(
|
||||
&self,
|
||||
mut encoder: prometheus_client::encoding::DescriptorEncoder,
|
||||
) -> Result<(), std::fmt::Error> {
|
||||
let games = self.games.blocking_read();
|
||||
let running_games = ConstGauge::new(games.len() as i64);
|
||||
|
||||
let participants = Family::<GameLabels, Gauge>::default();
|
||||
|
||||
games.iter().for_each(|(id, game)| {
|
||||
participants
|
||||
.get_or_create(&GameLabels {
|
||||
game: id.0.to_string(),
|
||||
})
|
||||
.set(game.players.len() as i64);
|
||||
});
|
||||
|
||||
drop(games);
|
||||
|
||||
let running_games_encoder = encoder.encode_descriptor(
|
||||
"ars_running_games",
|
||||
"number of running games",
|
||||
None,
|
||||
running_games.metric_type(),
|
||||
)?;
|
||||
running_games.encode(running_games_encoder)?;
|
||||
|
||||
let participants_encoder = encoder.encode_descriptor(
|
||||
"ars_game_participants",
|
||||
"number of participants for a game",
|
||||
None,
|
||||
participants.metric_type(),
|
||||
)?;
|
||||
participants.encode(participants_encoder)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
46
src/main.rs
46
src/main.rs
|
@ -10,7 +10,7 @@ use std::{
|
|||
use axum::{
|
||||
error_handling::HandleErrorLayer,
|
||||
extract::{Multipart, Path, Query, State},
|
||||
http::{StatusCode, Uri},
|
||||
http::{header::CONTENT_TYPE, HeaderMap, HeaderValue, StatusCode, Uri},
|
||||
response::{
|
||||
sse::{Event, KeepAlive},
|
||||
Html, IntoResponse, Redirect, Sse,
|
||||
|
@ -23,8 +23,13 @@ use axum_oidc::{
|
|||
error::MiddlewareError, EmptyAdditionalClaims, OidcAuthLayer, OidcClaims, OidcLoginLayer,
|
||||
};
|
||||
use futures_util::Stream;
|
||||
use game::{Game, GameId, PlayerId};
|
||||
use game::{Game, GameCollector, GameId, PlayerId};
|
||||
use garbage_collector::{start_gc, GarbageCollectorItem};
|
||||
use prometheus_client::{
|
||||
encoding::EncodeLabelSet,
|
||||
metrics::{counter::Counter, family::Family, gauge::Gauge},
|
||||
registry::Registry,
|
||||
};
|
||||
use question::{single_choice::SingleChoiceQuestion, Question};
|
||||
use sailfish::TemplateOnce;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -50,6 +55,13 @@ pub struct AppState {
|
|||
games: Arc<RwLock<HashMap<GameId, Game>>>,
|
||||
game_expiry: Arc<RwLock<BinaryHeap<GarbageCollectorItem>>>,
|
||||
application_base: &'static str,
|
||||
prometheus_registry: Arc<Registry>,
|
||||
metrics: Arc<AppMetrics>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct AppMetrics {
|
||||
arc_games_total: Counter,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
|
@ -100,12 +112,25 @@ pub async fn main() {
|
|||
Arc::new(RwLock::new(BinaryHeap::new()));
|
||||
let games = Arc::new(RwLock::new(HashMap::new()));
|
||||
|
||||
let app_metrics = Arc::new(AppMetrics::default());
|
||||
let mut registry = Registry::default();
|
||||
|
||||
registry.register(
|
||||
"arc_games_total",
|
||||
"number of games created",
|
||||
app_metrics.arc_games_total.clone(),
|
||||
);
|
||||
|
||||
registry.register_collector(Box::new(GameCollector::new(games.clone())));
|
||||
|
||||
start_gc(game_expiry.clone(), games.clone());
|
||||
|
||||
let app_state = AppState {
|
||||
games,
|
||||
game_expiry,
|
||||
application_base: Box::leak(application_base.into()),
|
||||
prometheus_registry: Arc::new(registry),
|
||||
metrics: app_metrics,
|
||||
};
|
||||
|
||||
let app = Router::new()
|
||||
|
@ -115,6 +140,7 @@ pub async fn main() {
|
|||
.layer(oidc_login_service)
|
||||
.route("/:id", get(handle_player).post(handle_player_answer))
|
||||
.route("/:id/events", get(sse_player))
|
||||
.route("/metrics", get(metrics))
|
||||
.nest_service("/static", ServeDir::new("static"))
|
||||
.with_state(app_state)
|
||||
.layer(oidc_auth_service)
|
||||
|
@ -157,6 +183,8 @@ pub async fn handle_create(
|
|||
let mut game_expiry = state.game_expiry.write().await;
|
||||
game_expiry.push(GarbageCollectorItem::new_in(game_id, 24 * 3600));
|
||||
|
||||
state.metrics.arc_games_total.inc();
|
||||
|
||||
Ok((HxRedirect(Uri::from_maybe_shared(url.clone())?), "Ok"))
|
||||
}
|
||||
|
||||
|
@ -284,6 +312,20 @@ pub async fn sse_player(
|
|||
Ok(Sse::new(stream).keep_alive(KeepAlive::default()))
|
||||
}
|
||||
|
||||
async fn metrics(State(app_state): State<AppState>) -> HandlerResult<impl IntoResponse> {
|
||||
let mut buffer = String::new();
|
||||
prometheus_client::encoding::text::encode(&mut buffer, &app_state.prometheus_registry)
|
||||
.map_err(Error::Prometheus)?;
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
CONTENT_TYPE,
|
||||
HeaderValue::from_static("text/plain; version=0.0.4"),
|
||||
);
|
||||
|
||||
Ok((headers, buffer))
|
||||
}
|
||||
|
||||
#[derive(TemplateOnce)]
|
||||
#[template(path = "index.stpl")]
|
||||
struct IndexTemplate {}
|
||||
|
|
Loading…
Reference in a new issue