add backend for history
This commit is contained in:
@@ -1,8 +1,14 @@
|
||||
use crate::telemetry::data_item::TelemetryDataItem;
|
||||
use crate::telemetry::definition::TelemetryDefinition;
|
||||
use crate::telemetry::history::TelemetryHistory;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TelemetryData {
|
||||
pub definition: TelemetryDefinition,
|
||||
pub data: tokio::sync::watch::Sender<Option<TelemetryDataItem>>,
|
||||
}
|
||||
|
||||
pub struct TelemetryDataHistory {
|
||||
pub data: TelemetryData,
|
||||
pub history: TelemetryHistory,
|
||||
}
|
||||
|
||||
195
server/src/telemetry/history.rs
Normal file
195
server/src/telemetry/history.rs
Normal file
@@ -0,0 +1,195 @@
|
||||
use crate::telemetry::data_value::TelemetryDataValue;
|
||||
use chrono::{DateTime, SecondsFormat, TimeDelta, Utc};
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::RwLock;
|
||||
use log::error;
|
||||
use crate::telemetry::data_item::TelemetryDataItem;
|
||||
|
||||
struct SegmentData {
|
||||
values: Vec<TelemetryDataValue>,
|
||||
timestamps: Vec<DateTime<Utc>>
|
||||
}
|
||||
|
||||
struct HistorySegment {
|
||||
start: DateTime<Utc>,
|
||||
end: DateTime<Utc>,
|
||||
data: RwLock<SegmentData>,
|
||||
}
|
||||
|
||||
impl HistorySegment {
|
||||
fn new(start: DateTime<Utc>, end: DateTime<Utc>) -> Self {
|
||||
Self {
|
||||
start,
|
||||
end,
|
||||
data: RwLock::new(SegmentData {
|
||||
values: vec![],
|
||||
timestamps: vec![],
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn insert(&self, value: TelemetryDataValue, timestamp: DateTime<Utc>) {
|
||||
if timestamp < self.start || timestamp >= self.end {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut data = self.data.write().unwrap_or_else(|err| {
|
||||
error!("HistorySegment::insert - data was poisoned: {}", err);
|
||||
let lock = err.into_inner();
|
||||
self.data.clear_poison();
|
||||
lock
|
||||
});
|
||||
|
||||
// Find the point where we should insert this item
|
||||
let index = data.timestamps.partition_point(|item| item <= ×tamp);
|
||||
// Insert the item
|
||||
data.timestamps.insert(index, timestamp);
|
||||
data.values.insert(index, value);
|
||||
}
|
||||
|
||||
fn get(&self, from: DateTime<Utc>, to: DateTime<Utc>, maximum_resolution: TimeDelta) -> (DateTime<Utc>, Vec<TelemetryDataItem>) {
|
||||
let mut result = vec![];
|
||||
|
||||
let mut next_from = from;
|
||||
|
||||
if from < self.end && self.start < to { // If there is overlap with the range
|
||||
let data = self.data.read().unwrap_or_else(|err| {
|
||||
error!("HistorySegment::get - data was poisoned: {}", err);
|
||||
let lock = err.into_inner();
|
||||
self.data.clear_poison();
|
||||
lock
|
||||
});
|
||||
|
||||
let start = data.timestamps.partition_point(|x| x < &from);
|
||||
let end = data.timestamps.partition_point(|x| x < &to);
|
||||
|
||||
if start < data.timestamps.len() {
|
||||
let t = data.timestamps[start];
|
||||
result.push(TelemetryDataItem {
|
||||
value: data.values[start].clone(),
|
||||
timestamp: t.to_rfc3339_opts(SecondsFormat::Millis, true),
|
||||
});
|
||||
for i in (start + 1)..end {
|
||||
let t = data.timestamps[i];
|
||||
if t >= next_from {
|
||||
let time_since_next_from = t - next_from;
|
||||
next_from = match (time_since_next_from.num_nanoseconds(), maximum_resolution.num_nanoseconds()) {
|
||||
(Some(nanos_since_next_from), Some(maximum_resolution_nanos)) => {
|
||||
let nanos_since_next_from = nanos_since_next_from as u64;
|
||||
let maximum_resolution_nanos = maximum_resolution_nanos as u64;
|
||||
let num_steps = nanos_since_next_from.div_ceil(maximum_resolution_nanos);
|
||||
let subsec_nanos = maximum_resolution.subsec_nanos() as u64;
|
||||
// This will break once this can't be represented in 2^63 nanoseconds (over 200 years)
|
||||
let seconds = ((maximum_resolution.num_seconds() as u64) * num_steps) as i64;
|
||||
let nanos = (num_steps * subsec_nanos) as i64;
|
||||
next_from + TimeDelta::seconds(seconds) + TimeDelta::nanoseconds(nanos)
|
||||
},
|
||||
_ => t + maximum_resolution, // If there is a gap so big it can't be represented in 2^63 nanoseconds (over 200 years) just skip forward
|
||||
};
|
||||
result.push(TelemetryDataItem {
|
||||
value: data.values[i].clone(),
|
||||
timestamp: t.to_rfc3339_opts(SecondsFormat::Millis, true),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(next_from, result)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TelemetryHistory {
|
||||
segments: RwLock<VecDeque<HistorySegment>>,
|
||||
}
|
||||
|
||||
impl TelemetryHistory {
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
segments: RwLock::new(VecDeque::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&self, service: &TelemetryHistoryService, value: TelemetryDataValue, timestamp: DateTime<Utc>) {
|
||||
let segments = self.segments.read().unwrap_or_else(|err| {
|
||||
error!("TelemetryHistory::insert - segments was poisoned: {}", err);
|
||||
let lock = err.into_inner();
|
||||
self.segments.clear_poison();
|
||||
lock
|
||||
});
|
||||
|
||||
let segments = if segments.is_empty() || segments[segments.len() - 1].end < timestamp {
|
||||
// We want to insert something that doesn't fit into our history
|
||||
drop(segments);
|
||||
let mut segments = self.segments.write().unwrap_or_else(|err| {
|
||||
error!("TelemetryHistory::insert - segments was poisoned: {}", err);
|
||||
let lock = err.into_inner();
|
||||
self.segments.clear_poison();
|
||||
lock
|
||||
});
|
||||
|
||||
if segments.len() == 0 {
|
||||
segments.push_back(HistorySegment::new(timestamp, timestamp + service.segment_width));
|
||||
} else {
|
||||
while segments[segments.len() - 1].end < timestamp {
|
||||
if segments.len() == service.max_segments {
|
||||
let _ = segments.pop_front();
|
||||
}
|
||||
let start_time = segments[segments.len() - 1].end;
|
||||
segments.push_back(HistorySegment::new(start_time, start_time + service.segment_width));
|
||||
}
|
||||
}
|
||||
|
||||
drop(segments);
|
||||
self.segments.read().unwrap_or_else(|err| {
|
||||
error!("TelemetryHistory::insert - segments was poisoned: {}", err);
|
||||
let lock = err.into_inner();
|
||||
self.segments.clear_poison();
|
||||
lock
|
||||
})
|
||||
} else {
|
||||
segments
|
||||
};
|
||||
|
||||
// Get the index of the first segment which has an end time AFTER the above timestamp
|
||||
let segment_index = segments.partition_point(|segment| segment.end < timestamp);
|
||||
|
||||
segments[segment_index].insert(value, timestamp);
|
||||
}
|
||||
|
||||
pub fn get(&self, from: DateTime<Utc>, to: DateTime<Utc>, maximum_resolution: TimeDelta) -> Vec<TelemetryDataItem> {
|
||||
let mut result = vec![];
|
||||
|
||||
let segments = self.segments.read().unwrap_or_else(|err| {
|
||||
error!("TelemetryHistory::get - segments was poisoned: {}", err);
|
||||
let lock = err.into_inner();
|
||||
self.segments.clear_poison();
|
||||
lock
|
||||
});
|
||||
|
||||
let mut from = from;
|
||||
|
||||
for i in 0..segments.len() {
|
||||
let (new_from, new_data) = segments[i].get(from, to, maximum_resolution);
|
||||
from = new_from;
|
||||
result.extend(new_data);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TelemetryHistoryService {
|
||||
segment_width: TimeDelta,
|
||||
max_segments: usize
|
||||
}
|
||||
|
||||
impl TelemetryHistoryService {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
segment_width: TimeDelta::minutes(1),
|
||||
max_segments: 5
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,23 @@
|
||||
use crate::core::{TelemetryDefinitionRequest, Uuid};
|
||||
use crate::telemetry::data::TelemetryData;
|
||||
use crate::telemetry::data::{TelemetryData, TelemetryDataHistory};
|
||||
use crate::telemetry::definition::TelemetryDefinition;
|
||||
use papaya::HashMap;
|
||||
use papaya::{HashMap, HashMapRef, LocalGuard};
|
||||
use std::error::Error;
|
||||
use std::hash::RandomState;
|
||||
use crate::telemetry::history::{TelemetryHistory, TelemetryHistoryService};
|
||||
|
||||
pub struct TelemetryManagementService {
|
||||
uuid_index: HashMap<String, String>,
|
||||
tlm_data: HashMap<String, TelemetryData>,
|
||||
tlm_data: HashMap<String, TelemetryDataHistory>,
|
||||
telemetry_history_service: TelemetryHistoryService
|
||||
}
|
||||
|
||||
impl TelemetryManagementService {
|
||||
pub fn new() -> Self {
|
||||
pub fn new(telemetry_history_service: TelemetryHistoryService) -> Self {
|
||||
Self {
|
||||
uuid_index: HashMap::new(),
|
||||
tlm_data: HashMap::new(),
|
||||
telemetry_history_service
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,13 +36,16 @@ impl TelemetryManagementService {
|
||||
|
||||
let _ = tlm_data.try_insert(
|
||||
uuid.clone(),
|
||||
TelemetryData {
|
||||
definition: TelemetryDefinition {
|
||||
uuid: uuid.clone(),
|
||||
name: telemetry_definition_request.name.clone(),
|
||||
data_type: telemetry_definition_request.data_type(),
|
||||
TelemetryDataHistory {
|
||||
data: TelemetryData {
|
||||
definition: TelemetryDefinition {
|
||||
uuid: uuid.clone(),
|
||||
name: telemetry_definition_request.name.clone(),
|
||||
data_type: telemetry_definition_request.data_type(),
|
||||
},
|
||||
data: tokio::sync::watch::channel(None).0,
|
||||
},
|
||||
data: tokio::sync::watch::channel(None).0,
|
||||
history: TelemetryHistory::new(),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -53,6 +60,27 @@ impl TelemetryManagementService {
|
||||
|
||||
pub fn get_by_uuid(&self, uuid: &String) -> Option<TelemetryData> {
|
||||
let tlm_data = self.tlm_data.pin();
|
||||
tlm_data.get(uuid).cloned()
|
||||
tlm_data.get(uuid).map(|data_history| &data_history.data).cloned()
|
||||
}
|
||||
|
||||
pub fn pin(&self) -> TelemetryManagementServicePin {
|
||||
TelemetryManagementServicePin {
|
||||
tlm_data: self.tlm_data.pin()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn history_service(&self) -> &TelemetryHistoryService {
|
||||
&self.telemetry_history_service
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TelemetryManagementServicePin<'a> {
|
||||
tlm_data: HashMapRef<'a, String, TelemetryDataHistory, RandomState, LocalGuard<'a>>
|
||||
}
|
||||
|
||||
impl<'a> TelemetryManagementServicePin<'a> {
|
||||
pub fn get_by_uuid(&'a self, uuid: &String) -> Option<&'a TelemetryDataHistory> {
|
||||
self.tlm_data.get(uuid)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,3 +4,4 @@ pub mod data_type;
|
||||
pub mod data_value;
|
||||
pub mod definition;
|
||||
pub mod management_service;
|
||||
pub mod history;
|
||||
|
||||
Reference in New Issue
Block a user