use chrono::{DateTime, Duration, Utc};
use ratatui::style::Color;
use std::collections::HashMap;
use crate::custom::app::debug_log;
pub fn get_duration_text(duration: Duration) -> String {
return if duration.num_weeks() > 104 {
format!("{} years", duration.num_days() / 365)
} else if duration.num_weeks() > 4 {
format!("{} weeks", duration.num_weeks())
} else if duration.num_hours() > 48 {
format!("{} days", duration.num_days())
} else if duration.num_hours() > 2 {
format!("{} hrs", duration.num_hours())
} else if duration.num_minutes() > 5 {
format!("{} min", duration.num_minutes())
} else {
format!("{} sec", duration.num_seconds())
};
}
pub fn get_max_buckets_value(buckets: &Vec<u64>) -> u64 {
let mut max: u64 = 0;
for i in 0..buckets.len() - 1 {
if buckets[i] > max {
max = buckets[i];
}
}
return max;
}
pub fn get_min_buckets_value(buckets: &Vec<u64>) -> u64 {
let mut min: u64 = u64::MAX;
for i in 0..buckets.len() - 1 {
if buckets[i] > 0 && buckets[i] < min {
min = buckets[i];
}
}
return min;
}
#[derive(Default)]
pub enum MinMeanMax {
#[default]
Min = 1,
Mean = 2,
Max = 3,
}
use serde::{Deserialize, Serialize};
use serde_with;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Timeline {
pub name: String,
pub units_text: String,
pub is_mmm: bool,
pub is_cumulative: bool,
pub colour: Color,
pub last_non_zero_value: u64,
buckets: HashMap<String, Buckets>,
}
impl Timeline {
pub fn new(
name: String,
units_text: String,
is_mmm: bool,
is_cumulative: bool,
colour: Color,
) -> Timeline {
Timeline {
name,
units_text,
is_mmm,
is_cumulative,
buckets: HashMap::<String, Buckets>::new(),
last_non_zero_value: 0,
colour,
}
}
pub fn get_name(&self) -> &String {
&self.name
}
pub fn add_bucket_set(&mut self, name: &'static str, duration: Duration, num_buckets: usize) {
self.buckets.insert(
name.to_string(),
Buckets::new(duration, num_buckets, self.is_mmm),
);
}
pub fn get_bucket_set(&self, timescale_name: &str) -> Option<&Buckets> {
return self.buckets.get(timescale_name);
}
pub fn get_bucket_set_mut(&mut self, timescale_name: &str) -> Option<&mut Buckets> {
return self.buckets.get_mut(timescale_name);
}
pub fn get_buckets_mut(
&mut self,
timescale_name: &str,
mmm_ui_mode: Option<&MinMeanMax>,
) -> Option<&Vec<u64>> {
if let Some(bucket_set) = self.buckets.get(timescale_name) {
return Some(bucket_set.buckets(mmm_ui_mode));
} else {
return None;
}
}
pub fn get_buckets(
&self,
timescale_name: &str,
mmm_ui_mode: Option<&MinMeanMax>,
) -> Option<&Vec<u64>> {
if let Some(bucket_set) = self.buckets.get(timescale_name) {
return Some(bucket_set.buckets(mmm_ui_mode));
} else {
return None;
}
}
pub fn update_current_time(&mut self, new_time: &DateTime<Utc>) {
for (_name, bs) in self.buckets.iter_mut() {
bs.update_current_time(new_time, self.is_cumulative);
}
}
pub fn update_value(&mut self, time: &DateTime<Utc>, value: u64) {
if value > 0 {
self.last_non_zero_value = value;
}
for (_name, bs) in self.buckets.iter_mut() {
let mut index = Some(bs.num_buckets() - 1);
if let Some(bucket_time) = bs.bucket_time {
if time.lt(&bucket_time) {
let time_difference = (bucket_time - *time).num_nanoseconds();
let bucket_duration = bs.bucket_duration.num_nanoseconds();
if time_difference.and(bucket_duration).is_some() {
let buckets_behind = time_difference.unwrap() / bucket_duration.unwrap();
if buckets_behind as usize >= bs.num_buckets() {
debug_log!(
format!("increment DISCARDED buckets_behind: {}", buckets_behind).as_str()
);
index = None;
} else {
if bs.num_buckets() > 1 {
index = Some(bs.num_buckets() - 1 - buckets_behind as usize);
}
}
}
}
}
if let Some(index) = index {
bs.bucket_update_value(index, value, self.is_cumulative);
}
}
}
}
use serde_with::serde_as;
use serde_with::DurationSeconds;
#[serde_as]
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Buckets {
pub bucket_time: Option<DateTime<Utc>>, pub earliest_time: Option<DateTime<Utc>>, pub latest_time: Option<DateTime<Utc>>,
#[serde_as(as = "DurationSeconds<i64>")]
pub total_duration: Duration,
#[serde_as(as = "DurationSeconds<i64>")]
pub bucket_duration: Duration,
pub num_buckets: usize,
pub values_total: u64,
pub values_min: u64,
pub values_max: u64,
pub is_mmm: bool,
pub buckets: Vec<u64>,
pub buckets_count: Vec<u64>, pub buckets_total: Vec<u64>, pub buckets_min: Vec<u64>, pub buckets_mean: Vec<u64>, pub buckets_max: Vec<u64>,
pub buckets_need_init: Vec<u64>, }
impl Buckets {
pub fn new(bucket_duration: Duration, num_buckets: usize, is_mmm: bool) -> Buckets {
let value_buckets_size = if is_mmm { 1 } else { num_buckets };
let mmm_buckets_size = if is_mmm { num_buckets } else { 1 };
return Buckets {
bucket_time: None,
earliest_time: None,
latest_time: None,
bucket_duration,
num_buckets,
values_total: 0,
values_min: u64::MAX,
values_max: 0,
total_duration: bucket_duration * num_buckets as i32,
is_mmm: is_mmm,
buckets: vec![0; value_buckets_size],
buckets_count: vec![0; mmm_buckets_size],
buckets_total: vec![0; mmm_buckets_size],
buckets_min: vec![0; mmm_buckets_size],
buckets_mean: vec![0; mmm_buckets_size],
buckets_max: vec![0; mmm_buckets_size],
buckets_need_init: vec![1; mmm_buckets_size],
};
}
pub fn update_current_time(&mut self, new_time: &DateTime<Utc>, is_cumulative: bool) {
if let Some(mut bucket_time) = self.bucket_time {
let mut end_time = bucket_time + self.bucket_duration;
while end_time.lt(&new_time) {
self.bucket_time = Some(end_time);
bucket_time = end_time;
end_time = bucket_time + self.bucket_duration;
if self.is_mmm {
for buckets in &mut vec![
&mut self.buckets_count,
&mut self.buckets_total,
&mut self.buckets_min,
&mut self.buckets_mean,
&mut self.buckets_max,
]
.iter_mut()
{
buckets.push(0);
if buckets.len() > self.num_buckets {
buckets.remove(0);
}
}
self.buckets_need_init.push(1);
if self.buckets_need_init.len() > self.num_buckets {
self.buckets_need_init.remove(0);
}
} else {
self.buckets.push(0);
if self.buckets.len() > self.num_buckets {
if is_cumulative {
self.values_total -= self.buckets[0];
}
self.buckets.remove(0);
}
}
}
} else {
self.bucket_time = Some(*new_time);
}
if let Some(earliest_time) = self.earliest_time {
if new_time.lt(&earliest_time) {
self.earliest_time = Some(*new_time);
}
} else {
self.earliest_time = Some(*new_time);
};
if let Some(latest_time) = self.latest_time {
if new_time.gt(&latest_time) {
self.latest_time = Some(*new_time);
}
} else {
self.latest_time = Some(*new_time);
};
}
pub fn bucket_update_value(&mut self, index: usize, value: u64, is_cumulative: bool) {
if self.is_mmm {
debug_log!(format!(
"is_mmm: bucket_update_value(index:{}, value:{}, is_cum:{})",
index, value, is_cumulative
)
.as_str());
if self.buckets_need_init[index] == 1 {
self.buckets_need_init[index] = 0;
self.buckets_count[index] = 0;
self.buckets_total[index] = 0;
self.buckets_min[index] = u64::MAX;
self.buckets_mean[index] = 0;
self.buckets_max[index] = 0;
}
self.buckets_count[index] += 1;
self.buckets_total[index] += value;
self.buckets_mean[index] = self.buckets_total[index] / self.buckets_count[index];
if value < self.buckets_min[index] {
self.buckets_min[index] = value
}
if value > self.buckets_max[index] {
self.buckets_max[index] = value
}
if value < self.values_min {
self.values_min = value
}
if value > self.values_max {
self.values_max = value
}
} else {
if is_cumulative {
self.buckets[index] += value;
if self.buckets[index] < self.values_min {
self.values_min = self.buckets[index]
}
if self.buckets[index] > self.values_max {
self.values_max = self.buckets[index]
}
self.values_total += value;
} else {
self.buckets[index] = value;
if value < self.values_min {
self.values_min = value
}
if value > self.values_max {
self.values_max = value
}
}
}
}
pub fn get_duration_text(&self) -> String {
let mut duration = self.total_duration;
if let Some(earliest_time) = self.earliest_time {
if let Some(latest_time) = self.latest_time {
if (latest_time - earliest_time).lt(&duration)
&& (latest_time - earliest_time).num_seconds() > 0
{
duration = latest_time - earliest_time;
} else if latest_time.eq(&earliest_time) {
duration = self.bucket_duration;
}
};
return get_duration_text(duration);
};
return String::from("(zero duration)");
}
pub fn num_buckets(&self) -> usize {
return self.num_buckets;
}
pub fn buckets(&self, mmm_ui_mode: Option<&MinMeanMax>) -> &Vec<u64> {
if self.is_mmm {
return match mmm_ui_mode {
None => &self.buckets,
Some(MinMeanMax::Min) => &self.buckets_min,
Some(MinMeanMax::Mean) => &self.buckets_mean,
Some(MinMeanMax::Max) => &self.buckets_max,
};
} else {
return &self.buckets;
}
}
}