Skip to content

Commit

Permalink
stats: add more individual stats endpoits
Browse files Browse the repository at this point in the history
  • Loading branch information
MarcelCoding committed May 19, 2024
1 parent 039deb5 commit f71e46a
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 61 deletions.
4 changes: 2 additions & 2 deletions src/routes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ pub(crate) fn route(content_paths: &ContentPaths) -> Router<FoundationState> {
.route("/team/:lang", get(get_team))
.nest_service("/team/assets", ServeDir::new(&content_paths.team))
.route("/mailing_lists/:list", post(add_subscriber))
.route("/stats/traffic", get(get_traffic_stats))
.route("/stats/as112", get(get_as112_stats))
.route("/stats/traffic/:selection", get(get_traffic_stats))
.route("/stats/as112/:selection", get(get_as112_stats))
.route("/peers", get(get_peers_and_supporter))
.route("/bird", get(get_bird))
}
14 changes: 8 additions & 6 deletions src/routes/stats.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
use std::sync::Arc;

use axum::extract::State;
use axum::extract::{Path, State};
use axum::http::StatusCode;
use axum::Json;
use tracing::error;

use crate::state::FoundationState;
use crate::stats::AggregatedStats;
use crate::stats::{Series, TimeSelection};

pub(super) async fn get_traffic_stats(
Path(selection): Path<TimeSelection>,
State(state): State<FoundationState>,
) -> Result<Json<Arc<AggregatedStats>>, StatusCode> {
match state.stats.get_traffic_stats().await {
) -> Result<Json<Arc<Series>>, StatusCode> {
match state.stats.get_traffic_stats(selection).await {
Ok(stats) => Ok(Json(stats)),
Err(err) => {
error!("Error while querying traffic stats: {:?}", err);
Expand All @@ -21,9 +22,10 @@ pub(super) async fn get_traffic_stats(
}

pub(super) async fn get_as112_stats(
Path(selection): Path<TimeSelection>,
State(state): State<FoundationState>,
) -> Result<Json<Arc<AggregatedStats>>, StatusCode> {
match state.stats.get_as112_stats().await {
) -> Result<Json<Arc<Series>>, StatusCode> {
match state.stats.get_as112_stats(selection).await {
Ok(stats) => Ok(Json(stats)),
Err(err) => {
error!("Error while querying as112 stats: {:?}", err);
Expand Down
175 changes: 122 additions & 53 deletions src/stats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,52 @@ use anyhow::anyhow;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use time::{Duration, OffsetDateTime};
use tokio::try_join;
use url::Url;

use crate::cache::{Cache, Updater};

#[derive(Deserialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum TimeSelection {
LastTwoDays,
LastWeek,
LastMonth,
LastThreeMonths,
LastYear,
}

impl From<TimeSelection> for Duration {
fn from(value: TimeSelection) -> Self {
match value {
TimeSelection::LastTwoDays => Duration::days(2),
TimeSelection::LastWeek => Duration::weeks(1),
TimeSelection::LastMonth => Duration::days(30),
TimeSelection::LastThreeMonths => Duration::days(90),
TimeSelection::LastYear => Duration::days(365),
}
}
}

struct TimeSelectionStore<T> {
two_days: T,
week: T,
month: T,
three_months: T,
year: T,
}

impl<T> TimeSelectionStore<T> {
pub(crate) fn get(&self, selection: TimeSelection) -> &T {
match selection {
TimeSelection::LastTwoDays => &self.two_days,
TimeSelection::LastWeek => &self.week,
TimeSelection::LastMonth => &self.month,
TimeSelection::LastThreeMonths => &self.three_months,
TimeSelection::LastYear => &self.year,
}
}
}

#[derive(Serialize)]
struct PrometheusQuery {
query: String,
Expand Down Expand Up @@ -37,55 +78,42 @@ struct PrometheusMetrics {
}

#[derive(Serialize)]
struct Series {
pub(crate) struct Series {
#[serde(with = "time::serde::rfc3339")]
start: OffsetDateTime,
#[serde(with = "time::serde::rfc3339")]
end: OffsetDateTime,
data: Vec<(f64, f64)>,
}

#[derive(Serialize)]
pub(crate) struct AggregatedStats {
two_days: Series,
seven_days: Series,
month: Series,
}

#[derive(Clone)]
pub(crate) struct Stats {
traffic: Arc<Cache<TrafficUpdater>>,
as112: Arc<Cache<As112Updater>>,
traffic: Arc<TimeSelectionStore<Cache<TrafficUpdater>>>,
as112: Arc<TimeSelectionStore<Cache<As112Updater>>>,
}

struct TrafficUpdater {
client: Client,
prometheus_url: Url,
selection: Duration,
}

#[async_trait::async_trait]
impl Updater for TrafficUpdater {
type Output = AggregatedStats;
type Output = Series;
type Error = anyhow::Error;

async fn update(&self) -> Result<Self::Output, Self::Error> {
let now = OffsetDateTime::now_utc();

let (two_days, seven_days, month) = try_join!(
self.query_stats(OffsetDateTime::now_utc() - Duration::days(2), now, 255.0),
self.query_stats(OffsetDateTime::now_utc() - Duration::days(7), now, 255.0),
self.query_stats(OffsetDateTime::now_utc() - Duration::days(30), now, 255.0),
)?;

let metrics = AggregatedStats {
two_days,
seven_days,
month,
};
let data = self
.query_stats(OffsetDateTime::now_utc() - self.selection, now, 255.0)
.await?;

Ok(metrics)
Ok(data)
}
}

impl TrafficUpdater {
async fn query_stats(
&self,
Expand Down Expand Up @@ -128,29 +156,22 @@ impl TrafficUpdater {
struct As112Updater {
client: Client,
prometheus_url: Url,
selection: Duration,
}

#[async_trait::async_trait]
impl Updater for As112Updater {
type Output = AggregatedStats;
type Output = Series;
type Error = anyhow::Error;

async fn update(&self) -> Result<Self::Output, Self::Error> {
let now = OffsetDateTime::now_utc();

let (two_days, seven_days, month) = try_join!(
self.query_stats(OffsetDateTime::now_utc() - Duration::days(2), now, 255.0),
self.query_stats(OffsetDateTime::now_utc() - Duration::days(7), now, 255.0),
self.query_stats(OffsetDateTime::now_utc() - Duration::days(30), now, 255.0),
)?;

let metrics = AggregatedStats {
two_days,
seven_days,
month,
};
let data = self
.query_stats(OffsetDateTime::now_utc() - self.selection, now, 255.0)
.await?;

Ok(metrics)
Ok(data)
}
}
impl As112Updater {
Expand Down Expand Up @@ -196,27 +217,75 @@ impl Stats {
pub(crate) fn new(prometheus_url: Url) -> Self {
let client = Client::new();

let traffic_updater = TrafficUpdater {
client: client.clone(),
prometheus_url: prometheus_url.clone(),
};

let as112 = As112Updater {
client,
prometheus_url,
};

Self {
traffic: Arc::new(Cache::new(traffic_updater)),
as112: Arc::new(Cache::new(as112)),
traffic: Arc::new(TimeSelectionStore {
two_days: Cache::new(TrafficUpdater {
client: client.clone(),
prometheus_url: prometheus_url.clone(),
selection: TimeSelection::LastTwoDays.into(),
}),
week: Cache::new(TrafficUpdater {
client: client.clone(),
prometheus_url: prometheus_url.clone(),
selection: TimeSelection::LastWeek.into(),
}),
month: Cache::new(TrafficUpdater {
client: client.clone(),
prometheus_url: prometheus_url.clone(),
selection: TimeSelection::LastMonth.into(),
}),
three_months: Cache::new(TrafficUpdater {
client: client.clone(),
prometheus_url: prometheus_url.clone(),
selection: TimeSelection::LastThreeMonths.into(),
}),
year: Cache::new(TrafficUpdater {
client: client.clone(),
prometheus_url: prometheus_url.clone(),
selection: TimeSelection::LastYear.into(),
}),
}),
as112: Arc::new(TimeSelectionStore {
two_days: Cache::new(As112Updater {
client: client.clone(),
prometheus_url: prometheus_url.clone(),
selection: TimeSelection::LastTwoDays.into(),
}),
week: Cache::new(As112Updater {
client: client.clone(),
prometheus_url: prometheus_url.clone(),
selection: TimeSelection::LastWeek.into(),
}),
month: Cache::new(As112Updater {
client: client.clone(),
prometheus_url: prometheus_url.clone(),
selection: TimeSelection::LastMonth.into(),
}),
three_months: Cache::new(As112Updater {
client: client.clone(),
prometheus_url: prometheus_url.clone(),
selection: TimeSelection::LastThreeMonths.into(),
}),
year: Cache::new(As112Updater {
client: client.clone(),
prometheus_url: prometheus_url.clone(),
selection: TimeSelection::LastYear.into(),
}),
}),
}
}

pub(crate) async fn get_traffic_stats(&self) -> anyhow::Result<Arc<AggregatedStats>> {
self.traffic.get().await
pub(crate) async fn get_traffic_stats(
&self,
selection: TimeSelection,
) -> anyhow::Result<Arc<Series>> {
self.traffic.get(selection).get().await
}

pub(crate) async fn get_as112_stats(&self) -> anyhow::Result<Arc<AggregatedStats>> {
self.as112.get().await
pub(crate) async fn get_as112_stats(
&self,
selection: TimeSelection,
) -> anyhow::Result<Arc<Series>> {
self.as112.get(selection).get().await
}
}

0 comments on commit f71e46a

Please sign in to comment.