update health to be more helpful

This commit is contained in:
41666 2022-12-10 22:01:27 -05:00
parent 89d115b61d
commit 004def8fbb
7 changed files with 135 additions and 15 deletions

27
Cargo.lock generated
View file

@ -48,6 +48,7 @@ dependencies = [
"async-graphql", "async-graphql",
"async-graphql-axum", "async-graphql-axum",
"axum", "axum",
"chrono",
"lazy_static", "lazy_static",
"reqwest", "reqwest",
"serde", "serde",
@ -76,6 +77,7 @@ dependencies = [
"async-trait", "async-trait",
"base64", "base64",
"bytes", "bytes",
"chrono",
"fast_chemail", "fast_chemail",
"fnv", "fnv",
"futures-util", "futures-util",
@ -329,8 +331,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
dependencies = [ dependencies = [
"iana-time-zone", "iana-time-zone",
"js-sys",
"num-integer", "num-integer",
"num-traits", "num-traits",
"time",
"wasm-bindgen",
"winapi", "winapi",
] ]
@ -744,7 +749,7 @@ checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"wasi", "wasi 0.11.0+wasi-snapshot-preview1",
] ]
[[package]] [[package]]
@ -1141,7 +1146,7 @@ checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de"
dependencies = [ dependencies = [
"libc", "libc",
"log", "log",
"wasi", "wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.42.0", "windows-sys 0.42.0",
] ]
@ -1843,6 +1848,7 @@ dependencies = [
"bitflags", "bitflags",
"byteorder", "byteorder",
"bytes", "bytes",
"chrono",
"crc", "crc",
"crossbeam-queue", "crossbeam-queue",
"dirs", "dirs",
@ -2038,6 +2044,17 @@ dependencies = [
"once_cell", "once_cell",
] ]
[[package]]
name = "time"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
dependencies = [
"libc",
"wasi 0.10.0+wasi-snapshot-preview1",
"winapi",
]
[[package]] [[package]]
name = "tinyvec" name = "tinyvec"
version = "1.6.0" version = "1.6.0"
@ -2419,6 +2436,12 @@ dependencies = [
"try-lock", "try-lock",
] ]
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.11.0+wasi-snapshot-preview1" version = "0.11.0+wasi-snapshot-preview1"

View file

@ -8,11 +8,12 @@ edition = "2021"
[dependencies] [dependencies]
serde_json = "1.0.89" serde_json = "1.0.89"
serde = "1.0.149" serde = "1.0.149"
async-graphql = { version = "5.0.3" } async-graphql = { version = "5.0.3", features = ["chrono"] }
async-graphql-axum = "5.0.3" async-graphql-axum = "5.0.3"
axum = "0.6.1" axum = "0.6.1"
sqlx = { version = "0.6.2", features = [ "runtime-tokio-native-tls", "postgres" ] } sqlx = { version = "0.6.2", features = [ "runtime-tokio-native-tls", "postgres", "chrono" ] }
tokio = { version = "1.23.0", features = [ "full" ] } tokio = { version = "1.23.0", features = [ "full" ] }
tower-http = { version = "0.3.5", features = ["cors"] } tower-http = { version = "0.3.5", features = ["cors"] }
lazy_static = "1.4.0" lazy_static = "1.4.0"
reqwest = "0.11.13" reqwest = "0.11.13"
chrono = "0.4.23"

View file

@ -1,5 +1,7 @@
use async_graphql::{Context, Enum, Object}; use crate::utils::ID_TO_WORLD;
use async_graphql::{Context, Enum, Object, SimpleObject};
use axum::{http::StatusCode, response::IntoResponse, Extension, Json}; use axum::{http::StatusCode, response::IntoResponse, Extension, Json};
use chrono::{DateTime, Utc};
use sqlx::{query, Pool, Postgres, Row}; use sqlx::{query, Pool, Postgres, Row};
pub async fn get_health(Extension(pool): Extension<Pool<Postgres>>) -> impl IntoResponse { pub async fn get_health(Extension(pool): Extension<Pool<Postgres>>) -> impl IntoResponse {
@ -53,6 +55,37 @@ enum UpDown {
pub struct Health {} pub struct Health {}
impl Health {
async fn most_recent_event_time<'ctx>(
&self,
ctx: &Context<'ctx>,
world_id: i32,
) -> (UpDown, Option<DateTime<Utc>>) {
let pool = ctx.data::<Pool<Postgres>>().unwrap();
let events_resp =
query("SELECT time FROM analytics WHERE world_id = $1 ORDER BY time DESC LIMIT 1")
.bind(world_id)
.fetch_one(pool)
.await;
match events_resp {
Ok(row) => {
let last_event: DateTime<Utc> = row.get(0);
if last_event < Utc::now() - chrono::Duration::minutes(5) {
return (UpDown::Down, None);
} else {
return (UpDown::Up, Some(last_event));
}
}
Err(_) => {
return (UpDown::Down, None);
}
}
}
}
/// Reports on the health of Saerro Listening Post /// Reports on the health of Saerro Listening Post
#[Object] #[Object]
impl Health { impl Health {
@ -104,6 +137,34 @@ impl Health {
.map(|_| UpDown::Up) .map(|_| UpDown::Up)
.unwrap_or(UpDown::Down) .unwrap_or(UpDown::Down)
} }
/// Shows a disclaimer for the worlds check
async fn worlds_disclaimer(&self) -> String {
"This is a best-effort check. A world reports `DOWN` when it doesn't have new events for 5 minutes. It could be broken, it could be the reality of the game state.".to_string()
}
/// Checks if a world has had any events for the last 5 minutes
async fn worlds<'ctx>(&self, ctx: &Context<'ctx>) -> Vec<WorldUpDown> {
let mut worlds = Vec::new();
for (id, name) in ID_TO_WORLD.iter() {
let (status, last_event) = self.most_recent_event_time(ctx, *id).await;
worlds.push(WorldUpDown {
id: *id,
name: name.to_string(),
status,
last_event,
});
}
worlds
}
}
#[derive(SimpleObject)]
struct WorldUpDown {
id: i32,
name: String,
status: UpDown,
last_event: Option<DateTime<Utc>>,
} }
#[derive(Default)] #[derive(Default)]

View file

@ -48,7 +48,7 @@
<li> <li>
<a <a
id="status_query_link" id="status_query_link"
href="/graphql?query={ health { database ingest ingestReachable } }" href="/graphql?query={ health { database ingest ingestReachable worldsDisclaimer worlds { name status lastEvent } } }"
>Current system status</a >Current system status</a
> >
(<a (<a
@ -61,7 +61,13 @@
health { health {
database database
ingest ingest
ingestReachable ingestReachable
worldsDisclaimer
worlds {
name
status
lastEvent
}
} }
}</code></pre> }</code></pre>
<a <a
@ -236,10 +242,10 @@
</p> </p>
<p>For help, please contact us in #api-dev on the PlanetSide 2 Discord.</p> <p>For help, please contact us in #api-dev on the PlanetSide 2 Discord.</p>
<p> <p>
[<a href="https://github.com/genudine/saerro">github</a>] [<a [<a href="/ingest.html">ingest stats</a>] [<a
href="https://pstop.harasse.rs" href="https://github.com/genudine/saerro"
>pstop</a >github</a
>] >] [<a href="https://pstop.harasse.rs">pstop</a>]
</p> </p>
<script> <script>
const runQuery = async (linkId, resultId) => { const runQuery = async (linkId, resultId) => {

View file

@ -0,0 +1,19 @@
<!DOCTYPE html>
<title>Ingest Stats - Saerro Listening Post</title>
<meta charset="utf-8" />
<style>
body {
font-family: monospace;
background-color: #010101;
color: #e0e0e0;
font-size: 1.25rem;
line-height: 1.6;
}
a {
color: #cead42;
text-decoration: none;
}
</style>
<h1>Ingest Stats</h1>
<p>sorry wip</p>

View file

@ -27,6 +27,10 @@ async fn index() -> Html<&'static str> {
Html(include_str!("html/index.html")) Html(include_str!("html/index.html"))
} }
async fn ingest() -> Html<&'static str> {
Html(include_str!("html/ingest.html"))
}
async fn handle_404() -> Html<&'static str> { async fn handle_404() -> Html<&'static str> {
Html(include_str!("html/404.html")) Html(include_str!("html/404.html"))
} }
@ -70,6 +74,7 @@ async fn main() {
let app = Router::new() let app = Router::new()
.route("/", get(index)) .route("/", get(index))
.route("/ingest", get(ingest))
.route("/health", get(health::get_health)) .route("/health", get(health::get_health))
.route( .route(
"/graphql", "/graphql",

View file

@ -316,9 +316,14 @@ async fn main() {
let fused_reader = read let fused_reader = read
.for_each(|msg| async move { .for_each(|msg| async move {
let body = &msg.unwrap().to_string(); let body = &msg.unwrap().to_string();
let data: Payload = serde_json::from_str(body).unwrap_or(Payload {
payload: Event::default(), let data: Payload = match serde_json::from_str(body) {
}); Ok(data) => data,
Err(_) => {
// println!("Error: {}; body: {}", e, body.clone());
return;
}
};
if data.payload.event_name == "" { if data.payload.event_name == "" {
return; return;