pub struct ClientStats {
pub total_bloops: u64,
pub per_minute_bloops: [u8; 1440],
pub per_minute_dates: [NaiveDate; 1440],
pub bloops_per_hour: [u32; 24],
pub bloops_per_day: HashMap<NaiveDate, u32>,
}Expand description
Stats collected per client to track “bloops” over time.
This structure holds aggregated counters for the last 24 hours and beyond, to efficiently support queries like “how many bloops in the last hour” or “bloops per day”. It is designed to be seeded from a database on startup.
§Seeding from PostgreSQL
To efficiently load these stats from a Postgres database with potentially thousands of bloops per client, the following SQL queries are recommended.
§Recommended index:
CREATE INDEX idx_bloops_client_recorded_at ON bloops (
client_id,
recorded_at
);This index speeds up queries filtering by client and time range.
§Total bloops per client:
SELECT client_id, COUNT(*) as total_bloops
FROM bloops
GROUP BY client_id;§Bloops per minute for the last 24 hours (UTC):
SELECT client_id,
DATE_TRUNC(
'minute',
recorded_at AT TIME ZONE 'UTC'
) AS minute_bucket,
COUNT(*) AS count
FROM bloops
WHERE recorded_at >= NOW() - INTERVAL '24 hours'
GROUP BY client_id, minute_bucket;minute_bucketcontains the UTC timestamp truncated to the minute.- You can map
minute_bucketto your in-memory bucket index like this:
§Mapping to In-Memory Buckets:
You can map the minute_bucket to your in-memory buckets like this:
use chrono::{NaiveDateTime, Timelike};
use bloop_server_framework::statistics::{minute_index, ClientStats};
use bloop_server_framework::test_utils::Utc;
// Load these from your database
let minute_bucket = Utc::now().naive_utc();
let count = 0;
let mut client_stats = ClientStats::new();
let idx = minute_index(minute_bucket);
let date = minute_bucket.date();
client_stats.per_minute_dates[idx] = date;
client_stats.per_minute_bloops[idx] = count as u8;§Bloops per hour for the last 24 hours (in local time):
SELECT client_id,
EXTRACT(hour FROM recorded_at AT TIME ZONE 'your_timezone') AS hour,
COUNT(*) AS count
FROM bloops
WHERE recorded_at >= NOW() - INTERVAL '24 hours'
GROUP BY client_id, hour;§Bloops per day (in local time):
SELECT client_id,
DATE(recorded_at AT TIME ZONE 'your_timezone') AS day,
COUNT(*) AS count
FROM bloops
GROUP BY client_id, day;After executing these queries, aggregate the counts per client into the fields of this struct.
§Example usage:
use std::collections::HashMap;
use bloop_server_framework::statistics::ClientStats;
let mut stats_map: HashMap<String, ClientStats> = HashMap::new();
// For each row of minute-buckets query:
// 1. Parse `minute_bucket` into NaiveDate and index.
// 2. Insert count into `per_minute_bloops` and `per_minute_dates`.
// Similarly for other queries...Fields§
§total_bloops: u64Total bloops observed for this client.
per_minute_bloops: [u8; 1440]Count of bloops per minute in a rolling 24-hour buffer.
Indexed by minute of day (0..1439).
per_minute_dates: [NaiveDate; 1440]The date associated with each minute bucket, used to invalidate stale data.
bloops_per_hour: [u32; 24]Count of bloops per hour in the local timezone.
bloops_per_day: HashMap<NaiveDate, u32>Count of bloops per day, keyed by date in local timezone.
Implementations§
Source§impl ClientStats
impl ClientStats
Trait Implementations§
Source§impl Debug for ClientStats
impl Debug for ClientStats
Auto Trait Implementations§
impl Freeze for ClientStats
impl RefUnwindSafe for ClientStats
impl Send for ClientStats
impl Sync for ClientStats
impl Unpin for ClientStats
impl UnwindSafe for ClientStats
Blanket Implementations§
§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> Erasable for T
impl<T> Erasable for T
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more