diff options
Diffstat (limited to 'vendor/prodash/src/throughput.rs')
-rw-r--r-- | vendor/prodash/src/throughput.rs | 124 |
1 files changed, 124 insertions, 0 deletions
diff --git a/vendor/prodash/src/throughput.rs b/vendor/prodash/src/throughput.rs new file mode 100644 index 000000000..6e8eebbd1 --- /dev/null +++ b/vendor/prodash/src/throughput.rs @@ -0,0 +1,124 @@ +use std::{ + collections::VecDeque, + sync::atomic::Ordering, + time::{Duration, SystemTime}, +}; + +use crate::{progress, unit}; + +const THROTTLE_INTERVAL: Duration = Duration::from_secs(1); +const ONCE_A_SECOND: Duration = Duration::from_secs(1); + +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)] +struct State { + observed: Duration, + last_value: progress::Step, + elapsed_values: VecDeque<(Duration, progress::Step)>, + + last_update_duration: Duration, + precomputed_throughput: Option<progress::Step>, +} + +impl State { + fn new(value: progress::Step, elapsed: Duration) -> Self { + State { + observed: elapsed, + last_value: value, + elapsed_values: { + let mut v = VecDeque::with_capacity(6); // default frames per second + v.push_back((elapsed, value)); + v + }, + + last_update_duration: elapsed, + precomputed_throughput: None, + } + } + + fn compute_throughput(&mut self) -> progress::Step { + let mut observed: Duration = self.elapsed_values.iter().map(|e| e.0).sum(); + while !self.elapsed_values.is_empty() && observed > ONCE_A_SECOND { + let candidate = self + .elapsed_values + .front() + .map(|e| e.0) + .expect("at least one item as we are in the checked loop"); + if observed.checked_sub(candidate).unwrap_or_default() <= ONCE_A_SECOND { + break; + } + observed -= candidate; + self.elapsed_values.pop_front(); + } + let observed_value: progress::Step = self.elapsed_values.iter().map(|e| e.1).sum(); + ((observed_value as f64 / observed.as_secs_f64()) * ONCE_A_SECOND.as_secs_f64()) as progress::Step + } + + fn update(&mut self, value: progress::Step, elapsed: Duration) -> Option<unit::display::Throughput> { + self.observed += elapsed; + self.elapsed_values + .push_back((elapsed, value.saturating_sub(self.last_value))); + self.last_value = value; + if self.observed - self.last_update_duration > THROTTLE_INTERVAL { + self.precomputed_throughput = Some(self.compute_throughput()); + self.last_update_duration = self.observed; + } + self.throughput() + } + + fn throughput(&self) -> Option<unit::display::Throughput> { + self.precomputed_throughput.map(|tp| unit::display::Throughput { + value_change_in_timespan: tp, + timespan: ONCE_A_SECOND, + }) + } +} + +/// A utility to compute throughput of a set of progress values usually available to a renderer. +#[derive(Default)] +pub struct Throughput { + sorted_by_key: Vec<(progress::Key, State)>, + updated_at: Option<SystemTime>, + elapsed: Option<Duration>, +} + +impl Throughput { + /// Called at the beginning of the drawing of a renderer to remember at which time progress values are + /// going to be updated with [`update_and_get(…)`][Throughput::update_and_get()]. + pub fn update_elapsed(&mut self) { + let now = SystemTime::now(); + self.elapsed = self.updated_at.and_then(|then| now.duration_since(then).ok()); + self.updated_at = Some(now); + } + + /// Lookup or create the progress value at `key` and set its current `progress`, returning its computed + /// throughput. + pub fn update_and_get( + &mut self, + key: &progress::Key, + progress: Option<&progress::Value>, + ) -> Option<unit::display::Throughput> { + progress.and_then(|progress| { + self.elapsed + .and_then(|elapsed| match self.sorted_by_key.binary_search_by_key(key, |t| t.0) { + Ok(index) => self.sorted_by_key[index] + .1 + .update(progress.step.load(Ordering::SeqCst), elapsed), + Err(index) => { + let state = State::new(progress.step.load(Ordering::SeqCst), elapsed); + let tp = state.throughput(); + self.sorted_by_key.insert(index, (*key, state)); + tp + } + }) + }) + } + + /// Compare the keys in `sorted_values` with our internal state and remove all missing tasks from it. + /// + /// This should be called after [`update_and_get(…)`][Throughput::update_and_get()] to pick up removed/finished + /// progress. + pub fn reconcile(&mut self, sorted_values: &[(progress::Key, progress::Task)]) { + self.sorted_by_key + .retain(|(key, _)| sorted_values.binary_search_by_key(key, |e| e.0).is_ok()); + } +} |