diff --git a/Cargo.lock b/Cargo.lock index 2014c9c..b410bff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,6 +21,12 @@ dependencies = [ "pretty", ] +[[package]] +name = "adler32" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b077b825e468cc974f0020d4082ee6e03132512f207ef1a02fd5d00d1f32d" + [[package]] name = "aho-corasick" version = "0.7.13" @@ -255,6 +261,15 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" +[[package]] +name = "crc32fast" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" +dependencies = [ + "cfg-if", +] + [[package]] name = "dhall" version = "0.5.3" @@ -776,6 +791,24 @@ version = "0.2.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9f8082297d534141b30c8d39e9b1773713ab50fdbe4ff30f750d063b3bfd701" +[[package]] +name = "libflate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9bac9023e1db29c084f9f8cd9d3852e5e8fddf98fb47c4964a0ea4663d95949" +dependencies = [ + "adler32", + "crc32fast", + "libflate_lz77", + "rle-decode-fast", +] + +[[package]] +name = "libflate_lz77" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3286f09f7d4926fc486334f28d8d2e6ebe4f7f9994494b6dab27ddfad2c9b11b" + [[package]] name = "linked-hash-map" version = "0.5.3" @@ -1237,6 +1270,35 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "procfs" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c434e93ef69c216e68e4f417c927b4f31502c3560b72cfdb6827e2321c5c6b3e" +dependencies = [ + "bitflags", + "byteorder", + "hex", + "lazy_static", + "libc", + "libflate", +] + +[[package]] +name = "prometheus" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0ced56dee39a6e960c15c74dc48849d614586db2eaada6497477af7c7811cd" +dependencies = [ + "cfg-if", + "fnv", + "lazy_static", + "libc", + "procfs", + "spin", + "thiserror", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -1475,6 +1537,12 @@ dependencies = [ "winreg", ] +[[package]] +name = "rle-decode-fast" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cabe4fa914dec5870285fa7f71f602645da47c486e68486d2b4ceb4a343e90ac" + [[package]] name = "ructe" version = "0.11.4" @@ -1712,6 +1780,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "static_assertions" version = "1.1.0" @@ -2262,9 +2336,12 @@ dependencies = [ "comrak", "envy", "glob", + "hyper", + "lazy_static", "log 0.4.8", "mime 0.3.16", "pretty_env_logger", + "prometheus", "rand 0.7.3", "ructe", "serde", diff --git a/Cargo.toml b/Cargo.toml index be96384..15b59b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,9 +13,12 @@ chrono = "0.4" comrak = "0.8" envy = "0.4" glob = "0.3" +hyper = "0.13" +lazy_static = "1.4" log = "0" mime = "0.3.0" pretty_env_logger = "0" +prometheus = { version = "0.9", default-features = false, features = ["process"] } rand = "0" ructe = "0.11" serde_dhall = "0.5.3" diff --git a/src/handlers/blog.rs b/src/handlers/blog.rs index 9bcd423..e494e04 100644 --- a/src/handlers/blog.rs +++ b/src/handlers/blog.rs @@ -4,9 +4,17 @@ use crate::{ post::Post, templates::{self, Html, RenderRucte}, }; +use lazy_static::lazy_static; +use prometheus::{IntCounterVec, register_int_counter_vec, opts}; use std::sync::Arc; use warp::{http::Response, Rejection, Reply}; +lazy_static! { + static ref HIT_COUNTER: IntCounterVec = + register_int_counter_vec!(opts!("blogpost_hits", "Number of hits to blogposts"), &["name"]) + .unwrap(); +} + pub async fn index(state: Arc) -> Result { let state = state.clone(); Response::builder().html(|o| templates::blogindex_html(o, state.blog.clone())) @@ -61,6 +69,7 @@ pub async fn post_view(name: String, state: Arc) -> Result Err(PostNotFound("blog".into(), name).into()), Some(post) => { + HIT_COUNTER.with_label_values(&[name.clone().as_str()]).inc(); let body = Html(post.body_html.clone()); Response::builder().html(|o| templates::blogpost_html(o, post, body)) } diff --git a/src/handlers/gallery.rs b/src/handlers/gallery.rs index ee8f836..2094ab2 100644 --- a/src/handlers/gallery.rs +++ b/src/handlers/gallery.rs @@ -4,9 +4,17 @@ use crate::{ post::Post, templates::{self, Html, RenderRucte}, }; +use lazy_static::lazy_static; +use prometheus::{IntCounterVec, register_int_counter_vec, opts}; use std::sync::Arc; use warp::{http::Response, Rejection, Reply}; +lazy_static! { + static ref HIT_COUNTER: IntCounterVec = + register_int_counter_vec!(opts!("gallery_hits", "Number of hits to gallery images"), &["name"]) + .unwrap(); +} + pub async fn index(state: Arc) -> Result { let state = state.clone(); Response::builder().html(|o| templates::galleryindex_html(o, state.gallery.clone())) @@ -22,8 +30,9 @@ pub async fn post_view(name: String, state: Arc) -> Result Err(PostNotFound("blog".into(), name).into()), + None => Err(PostNotFound("gallery".into(), name).into()), Some(post) => { + HIT_COUNTER.with_label_values(&[name.clone().as_str()]).inc(); let body = Html(post.body_html.clone()); Response::builder().html(|o| templates::gallerypost_html(o, post, body)) } diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index eab42bd..6c9c054 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -2,40 +2,55 @@ use crate::{ app::State, templates::{self, Html, RenderRucte}, }; +use lazy_static::lazy_static; +use prometheus::{IntCounterVec, register_int_counter_vec, opts}; use std::{convert::Infallible, fmt, sync::Arc}; use warp::{ http::{Response, StatusCode}, Rejection, Reply, }; +lazy_static! { + static ref HIT_COUNTER: IntCounterVec = + register_int_counter_vec!(opts!("hits", "Number of hits to various pages"), &["page"]) + .unwrap(); +} + pub async fn index() -> Result { + HIT_COUNTER.with_label_values(&["index"]).inc(); Response::builder().html(|o| templates::index_html(o)) } pub async fn contact() -> Result { + HIT_COUNTER.with_label_values(&["contact"]).inc(); Response::builder().html(|o| templates::contact_html(o)) } pub async fn feeds() -> Result { + HIT_COUNTER.with_label_values(&["feeds"]).inc(); Response::builder().html(|o| templates::feeds_html(o)) } pub async fn resume(state: Arc) -> Result { + HIT_COUNTER.with_label_values(&["resume"]).inc(); let state = state.clone(); Response::builder().html(|o| templates::resume_html(o, Html(state.resume.clone()))) } pub async fn signalboost(state: Arc) -> Result { + HIT_COUNTER.with_label_values(&["signalboost"]).inc(); let state = state.clone(); Response::builder().html(|o| templates::signalboost_html(o, state.signalboost.clone())) } pub async fn not_found() -> Result { + HIT_COUNTER.with_label_values(&["not_found"]).inc(); Response::builder().html(|o| templates::notfound_html(o, "some path".into())) } pub mod blog; pub mod gallery; +pub mod talks; #[derive(Debug, thiserror::Error)] struct PostNotFound(String, String); @@ -71,24 +86,34 @@ impl From for warp::reject::Rejection { } } +lazy_static! { + static ref REJECTION_COUNTER: IntCounterVec = + register_int_counter_vec!(opts!("rejections", "Number of rejections by kind"), &["kind"]) + .unwrap(); +} + pub async fn rejection(err: Rejection) -> Result { let path: String; let code; if err.is_not_found() { + REJECTION_COUNTER.with_label_values(&["404"]).inc(); path = "".into(); code = StatusCode::NOT_FOUND; } else if let Some(SeriesNotFound(series)) = err.find() { + REJECTION_COUNTER.with_label_values(&["SeriesNotFound"]).inc(); log::error!("invalid series {}", series); path = format!("/blog/series/{}", series); code = StatusCode::NOT_FOUND; } else if let Some(PostNotFound(kind, name)) = err.find() { + REJECTION_COUNTER.with_label_values(&["PostNotFound"]).inc(); log::error!("unknown post {}/{}", kind, name); path = format!("/{}/{}", kind, name); code = StatusCode::NOT_FOUND; } else { + REJECTION_COUNTER.with_label_values(&["Other"]).inc(); log::error!("unhandled rejection: {:?}", err); - path = "wut".into(); + path = format!("weird rejection: {:?}", err); code = StatusCode::INTERNAL_SERVER_ERROR; } diff --git a/src/handlers/talks.rs b/src/handlers/talks.rs new file mode 100644 index 0000000..54f1e64 --- /dev/null +++ b/src/handlers/talks.rs @@ -0,0 +1,40 @@ +use super::PostNotFound; +use crate::{ + app::State, + post::Post, + templates::{self, Html, RenderRucte}, +}; +use lazy_static::lazy_static; +use prometheus::{IntCounterVec, register_int_counter_vec, opts}; +use std::sync::Arc; +use warp::{http::Response, Rejection, Reply}; + +lazy_static! { + static ref HIT_COUNTER: IntCounterVec = + register_int_counter_vec!(opts!("talks_hits", "Number of hits to talks images"), &["name"]) + .unwrap(); +} + +pub async fn index(state: Arc) -> Result { + let state = state.clone(); + Response::builder().html(|o| templates::talkindex_html(o, state.talks.clone())) +} + +pub async fn post_view(name: String, state: Arc) -> Result { + let mut want: Option = None; + + for post in &state.talks { + if post.link == format!("talks/{}", name) { + want = Some(post.clone()); + } + } + + match want { + None => Err(PostNotFound("talks".into(), name).into()), + Some(post) => { + HIT_COUNTER.with_label_values(&[name.clone().as_str()]).inc(); + let body = Html(post.body_html.clone()); + Response::builder().html(|o| templates::talkpost_html(o, post, body)) + } + } +} diff --git a/src/main.rs b/src/main.rs index dfb626a..307572c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,6 @@ use anyhow::Result; +use hyper::{header::CONTENT_TYPE, Body, Response}; +use prometheus::{Encoder, TextEncoder}; use std::sync::Arc; use warp::{path, Filter}; @@ -72,6 +74,22 @@ async fn main() -> Result<()> { index.or(post_view) }; + let talks = { + let base = warp::path!("talks" / ..); + let index = base + .and(warp::path::end()) + .and(with_state(state.clone())) + .and_then(handlers::talks::index); + let post_view = base.and( + warp::path!(String) + .and(with_state(state.clone())) + .and(warp::get()) + .and_then(handlers::talks::post_view), + ); + + index.or(post_view) + }; + let static_pages = { let contact = warp::path!("contact").and_then(handlers::contact); let feeds = warp::path!("feeds").and_then(handlers::feeds); @@ -82,15 +100,9 @@ async fn main() -> Result<()> { .and(with_state(state.clone())) .and_then(handlers::signalboost); - contact.or(feeds.or(resume.or(signalboost))) + contact.or(feeds).or(resume).or(signalboost) }; - let routes = warp::get() - .and(path::end().and_then(handlers::index)) - .or(static_pages) - .or(blog) - .or(gallery); - let files = { let files = warp::path("static").and(warp::fs::dir("./static")); let css = warp::path("css").and(warp::fs::dir("./css")); @@ -100,8 +112,26 @@ async fn main() -> Result<()> { files.or(css).or(sw).or(robots) }; + let metrics_endpoint = warp::path("metrics").and(warp::path::end()).map(move || { + let encoder = TextEncoder::new(); + let metric_families = prometheus::gather(); + let mut buffer = vec![]; + encoder.encode(&metric_families, &mut buffer).unwrap(); + Response::builder() + .status(200) + .header(CONTENT_TYPE, encoder.format_type()) + .body(Body::from(buffer)) + .unwrap() + }); + let site = files - .or(routes) + .or(warp::get().and(path::end().and_then(handlers::index))) + .or(static_pages) + .or(blog) + .or(gallery) + .or(talks) + .or(healthcheck) + .or(metrics_endpoint) .map(|reply| { warp::reply::with_header( reply, @@ -109,7 +139,6 @@ async fn main() -> Result<()> { "If you are reading this, check out /signalboost to find people for your team", ) }) - .or(healthcheck) .map(|reply| warp::reply::with_header(reply, "X-Clacks-Overhead", "GNU Ashlynn")) .with(warp::log(APPLICATION_NAME)) .recover(handlers::rejection); diff --git a/templates/talkindex.rs.html b/templates/talkindex.rs.html new file mode 100644 index 0000000..ce88be0 --- /dev/null +++ b/templates/talkindex.rs.html @@ -0,0 +1,23 @@ + +@use crate::post::Post; +@use super::{header_html, footer_html}; + +@(posts: Vec) + +@:header_html(Some("Talks"), None) + +

Talks

+ +

Here is a link to all of the talks I have done at conferences. Each of these will have links to the slides (PDF) as well as some brief information about them.

+ +

If you have a compatible reader, be sure to check out my RSS Feed for automatic updates. Also check out the JSONFeed.

+ +

+

+

+ +@:footer_html() diff --git a/templates/talkpost.rs.html b/templates/talkpost.rs.html new file mode 100644 index 0000000..1375bca --- /dev/null +++ b/templates/talkpost.rs.html @@ -0,0 +1,118 @@ +@use super::{header_html, footer_html}; +@use crate::post::Post; + +@(post: Post, body: impl ToHtml) + +@:header_html(Some(&post.front_matter.title.clone()), None) + + + + + + + + + + + + + + + + + + + + +@body + +Link to the slides + +
+ + + + +@if post.front_matter.series.is_some() { +

Series: @post.front_matter.series.as_ref().unwrap()

+} + +@if post.front_matter.tags.is_some() { +

Tags: @for tag in post.front_matter.tags.as_ref().unwrap() { @tag }

+} + + + +@:footer_html()