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

View file

@ -8,11 +8,12 @@ edition = "2021"
[dependencies]
serde_json = "1.0.89"
serde = "1.0.149"
async-graphql = { version = "5.0.3" }
async-graphql = { version = "5.0.3", features = ["chrono"] }
async-graphql-axum = "5.0.3"
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" ] }
tower-http = { version = "0.3.5", features = ["cors"] }
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 chrono::{DateTime, Utc};
use sqlx::{query, Pool, Postgres, Row};
pub async fn get_health(Extension(pool): Extension<Pool<Postgres>>) -> impl IntoResponse {
@ -53,6 +55,37 @@ enum UpDown {
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
#[Object]
impl Health {
@ -104,6 +137,34 @@ impl Health {
.map(|_| UpDown::Up)
.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)]

View file

@ -48,7 +48,7 @@
<li>
<a
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
>
(<a
@ -61,7 +61,13 @@
health {
database
ingest
ingestReachable
ingestReachable
worldsDisclaimer
worlds {
name
status
lastEvent
}
}
}</code></pre>
<a
@ -236,10 +242,10 @@
</p>
<p>For help, please contact us in #api-dev on the PlanetSide 2 Discord.</p>
<p>
[<a href="https://github.com/genudine/saerro">github</a>] [<a
href="https://pstop.harasse.rs"
>pstop</a
>]
[<a href="/ingest.html">ingest stats</a>] [<a
href="https://github.com/genudine/saerro"
>github</a
>] [<a href="https://pstop.harasse.rs">pstop</a>]
</p>
<script>
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"))
}
async fn ingest() -> Html<&'static str> {
Html(include_str!("html/ingest.html"))
}
async fn handle_404() -> Html<&'static str> {
Html(include_str!("html/404.html"))
}
@ -70,6 +74,7 @@ async fn main() {
let app = Router::new()
.route("/", get(index))
.route("/ingest", get(ingest))
.route("/health", get(health::get_health))
.route(
"/graphql",

View file

@ -316,9 +316,14 @@ async fn main() {
let fused_reader = read
.for_each(|msg| async move {
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 == "" {
return;