From d722ea9ede4485c1ae30ea7472c474bf92c3b808 Mon Sep 17 00:00:00 2001
From: Christine Dodrill
Date: Tue, 14 Jul 2020 09:10:12 -0400
Subject: [PATCH] talk support + prometheus
---
Cargo.lock | 77 +++++++++++++++++++++++
Cargo.toml | 3 +
src/handlers/blog.rs | 9 +++
src/handlers/gallery.rs | 11 +++-
src/handlers/mod.rs | 27 ++++++++-
src/handlers/talks.rs | 40 ++++++++++++
src/main.rs | 47 +++++++++++---
templates/talkindex.rs.html | 23 +++++++
templates/talkpost.rs.html | 118 ++++++++++++++++++++++++++++++++++++
9 files changed, 344 insertions(+), 11 deletions(-)
create mode 100644 src/handlers/talks.rs
create mode 100644 templates/talkindex.rs.html
create mode 100644 templates/talkpost.rs.html
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
+
+
+
+
+Share on Mastodon
+
+@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()