use crate::try_lock;
use super::{CompressionType, LifecycleManager, PartitionManager, TimeSeriesIndex};
use crate::{RemDbError, Result, TableDef};
use alloc::{sync::Arc, vec::Vec};
use core::time::Duration;
#[cfg(feature = "std")]
use std::sync::Mutex;
#[cfg(not(feature = "std"))]
use crate::memory::allocator::Mutex;
#[derive(Debug, Clone, Copy)]
pub struct TimeSeriesConfig {
pub partition_duration_secs: u64,
pub retention_period_secs: u64,
pub compression: CompressionType,
pub max_partitions: usize,
}
impl TimeSeriesConfig {
pub const fn new(
partition_duration_secs: u64,
retention_period_secs: u64,
compression: CompressionType,
max_partitions: usize,
) -> Self {
Self {
partition_duration_secs,
retention_period_secs,
compression,
max_partitions,
}
}
pub fn partition_duration(&self) -> Duration {
Duration::from_secs(self.partition_duration_secs)
}
pub fn retention_period(&self) -> Duration {
Duration::from_secs(self.retention_period_secs)
}
}
impl Default for TimeSeriesConfig {
fn default() -> Self {
Self::DEFAULT
}
}
pub const DEFAULT_TIME_SERIES_CONFIG: TimeSeriesConfig = TimeSeriesConfig::new(
3600, 7 * 24 * 3600, CompressionType::DeltaRunLength,
1000,
);
impl TimeSeriesConfig {
pub const DEFAULT: Self = DEFAULT_TIME_SERIES_CONFIG;
}
#[derive(Debug)]
pub struct TimeSeriesTableDef {
pub base: TableDef,
pub time_field: usize,
pub value_field: usize,
pub tag_fields: Box<[usize]>,
pub config: TimeSeriesConfig,
}
#[derive(Debug, Clone, Copy)]
pub struct TimeSeriesRecord {
pub timestamp: u64,
pub value: f64,
pub tag_count: u8,
pub tags: [u64; 8], }
#[derive(Debug, Clone, PartialEq)]
pub struct PreAggregationConfig {
pub interval_seconds: u64,
pub aggregation: String,
}
pub struct PreAggregationStore {
pub configs: Vec<PreAggregationConfig>,
pub data: std::collections::HashMap<(u64, u64), f64>, }
pub struct TimeSeriesTable {
pub def: Arc<TimeSeriesTableDef>,
pub partitions: Arc<Mutex<PartitionManager>>,
pub index: Arc<TimeSeriesIndex>,
pub lifecycle: LifecycleManager,
pub pre_aggregation: Arc<Mutex<PreAggregationStore>>,
}
impl TimeSeriesTable {
pub fn new(def: Arc<TimeSeriesTableDef>, index: Arc<TimeSeriesIndex>) -> Result<Self> {
if def.time_field >= def.base.fields.len() {
return Err(RemDbError::FieldNotFound);
}
if def.value_field >= def.base.fields.len() {
return Err(RemDbError::FieldNotFound);
}
for tag_field in def.tag_fields.iter() {
if *tag_field >= def.base.fields.len() {
return Err(RemDbError::FieldNotFound);
}
}
let partition_manager = Arc::new(Mutex::new(PartitionManager::new(
def.config.partition_duration(),
def.config.max_partitions,
)));
let mut lifecycle_manager = LifecycleManager::new(def.config.retention_period());
let partitions_clone = partition_manager.clone();
let retention_period = def.config.retention_period();
lifecycle_manager.set_cleanup_callback(move || {
let mut partitions_guard = try_lock!(partitions_clone);
let current_time = LifecycleManager::get_current_timestamp();
partitions_guard.cleanup_expired_partitions(current_time, retention_period);
});
let pre_aggregation = Arc::new(Mutex::new(PreAggregationStore {
configs: Vec::new(),
data: std::collections::HashMap::new(),
}));
Ok(Self {
def,
partitions: partition_manager,
index,
lifecycle: lifecycle_manager,
pre_aggregation,
})
}
pub fn add_pre_aggregation(&self, interval_seconds: u64, aggregation: &str) -> Result<()> {
let mut pre_aggregation_guard = try_lock!(self.pre_aggregation);
let existing_config = pre_aggregation_guard.configs.iter().find(|config| {
config.interval_seconds == interval_seconds && config.aggregation == aggregation
});
if existing_config.is_some() {
return Ok(()); }
pre_aggregation_guard.configs.push(PreAggregationConfig {
interval_seconds,
aggregation: aggregation.to_string(),
});
Ok(())
}
pub fn query_pre_aggregated(
&self,
start_time: u64,
end_time: u64,
interval_seconds: u64,
aggregation: &str,
) -> Result<Vec<TimeSeriesRecord>> {
let pre_aggregation_guard = try_lock!(self.pre_aggregation);
let config_exists = pre_aggregation_guard.configs.iter().any(|config| {
config.interval_seconds == interval_seconds && config.aggregation == aggregation
});
if !config_exists {
return Err(RemDbError::ConfigError); }
let interval_nanos = interval_seconds * 1_000_000_000u64;
let start_bucket = start_time / interval_nanos;
let end_bucket = end_time / interval_nanos;
let mut result = Vec::new();
for bucket in start_bucket..=end_bucket {
for ((stored_bucket, _tag_hash), value) in pre_aggregation_guard.data.iter() {
if *stored_bucket == bucket {
result.push(TimeSeriesRecord {
timestamp: bucket * interval_nanos,
value: *value,
tag_count: 0, tags: [0; 8],
});
}
}
}
Ok(result)
}
fn update_pre_aggregations(&self, record: &TimeSeriesRecord) {
let mut pre_aggregation_guard = try_lock!(self.pre_aggregation);
let configs = pre_aggregation_guard.configs.clone();
for config in &configs {
let interval_nanos = config.interval_seconds * 1_000_000_000u64;
let time_bucket = record.timestamp / interval_nanos;
let tag_hash = record.tag_count as u64;
let key = (time_bucket, tag_hash);
match config.aggregation.as_str() {
"avg" => {
let current_value = *pre_aggregation_guard.data.get(&key).unwrap_or(&0.0);
let new_value = (current_value + record.value) / 2.0;
pre_aggregation_guard.data.insert(key, new_value);
}
"sum" => {
let current_value = *pre_aggregation_guard.data.get(&key).unwrap_or(&0.0);
pre_aggregation_guard
.data
.insert(key, current_value + record.value);
}
"min" => {
let current_value = *pre_aggregation_guard.data.get(&key).unwrap_or(&f64::MAX);
pre_aggregation_guard
.data
.insert(key, f64::min(current_value, record.value));
}
"max" => {
let current_value = *pre_aggregation_guard.data.get(&key).unwrap_or(&f64::MIN);
pre_aggregation_guard
.data
.insert(key, f64::max(current_value, record.value));
}
_ => {}
}
}
}
pub unsafe fn batch_write(
&mut self,
records: *const TimeSeriesRecord,
count: usize,
) -> Result<usize> {
if records.is_null() || count == 0 {
return Err(RemDbError::ConfigError);
}
let mut inserted = 0;
for i in 0..count {
let record = *records.add(i);
let mut partitions_guard = try_lock!(self.partitions);
let partition = partitions_guard.get_or_create_partition(record.timestamp);
let mut partition_guard = try_lock!(partition);
partition_guard.records.push(record);
partition_guard.stats.record_count += 1;
self.index.insert(record.timestamp, inserted as usize);
self.update_pre_aggregations(&record);
inserted += 1;
}
Ok(inserted)
}
pub fn write_timeseries_batch(&mut self, data_points: &[TimeSeriesRecord]) -> Result<usize> {
if data_points.is_empty() {
return Err(RemDbError::ConfigError);
}
let has_active_tx = crate::transaction::has_active_tx();
if !has_active_tx {
return Err(RemDbError::TransactionError);
}
let mut inserted = 0;
let table_id = self.def.base.id;
for (i, record) in data_points.iter().enumerate() {
let mut partitions_guard = try_lock!(self.partitions);
let partition = partitions_guard.get_or_create_partition(record.timestamp);
let mut partition_guard = try_lock!(partition);
partition_guard.records.push(*record);
partition_guard.stats.record_count = partition_guard.records.len();
self.index.insert(record.timestamp, inserted as usize);
self.update_pre_aggregations(record);
unsafe {
if let Some(mut tx_ptr) = crate::transaction::get_current_tx() {
let tx_mut = tx_ptr.as_mut();
let data_size = core::mem::size_of::<TimeSeriesRecord>();
let tx_id = tx_mut.id;
let record_slice =
core::slice::from_raw_parts(record as *const _ as *const u8, data_size);
tx_mut.begin_log_item(
tx_id,
crate::transaction::LogOperation::TimeSeriesInsert,
table_id,
i as u16, data_size as u16,
None, Some(record_slice), );
}
}
inserted += 1;
}
Ok(inserted)
}
pub fn query_time_range(
&self,
start_time: u64,
end_time: u64,
) -> Result<Vec<TimeSeriesRecord>> {
let partitions_guard = try_lock!(self.partitions);
let relevant_partitions = partitions_guard.get_partitions_in_range(start_time, end_time);
let mut results = Vec::new();
for partition in relevant_partitions {
let partition_guard = try_lock!(partition);
for record in &partition_guard.records {
if record.timestamp >= start_time && record.timestamp <= end_time {
results.push(*record);
}
}
}
Ok(results)
}
}