aboutsummaryrefslogtreecommitdiff
path: root/src/util/tranquilizer.rs
blob: f0c2b410bd857d4a9815f209c69ba837921fa76a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
use std::collections::VecDeque;
use std::time::{Duration, Instant};

use tokio::time::sleep;

use crate::background::WorkerStatus;

/// A tranquilizer is a helper object that is used to make
/// background operations not take up too much time.
///
/// Background operations are done in a loop that does the following:
/// - do one step of the background process
/// - tranquilize, i.e. wait some time to not overload the system
///
/// The tranquilizer observes how long the steps take, and keeps
/// in memory a number of observations. The tranquilize operation
/// simply sleeps k * avg(observed step times), where k is
/// the tranquility factor. For instance with a tranquility of 2,
/// the tranquilizer will sleep on average 2 units of time for every
/// 1 unit of time spent doing the background task.
pub struct Tranquilizer {
	n_observations: usize,
	observations: VecDeque<Duration>,
	sum_observations: Duration,
	last_step_begin: Instant,
}

impl Tranquilizer {
	pub fn new(n_observations: usize) -> Self {
		Self {
			n_observations,
			observations: VecDeque::with_capacity(n_observations + 1),
			sum_observations: Duration::ZERO,
			last_step_begin: Instant::now(),
		}
	}

	fn tranquilize_internal(&mut self, tranquility: u32) -> Option<Duration> {
		let observation = Instant::now() - self.last_step_begin;

		self.observations.push_back(observation);
		self.sum_observations += observation;

		while self.observations.len() > self.n_observations {
			self.sum_observations -= self.observations.pop_front().unwrap();
		}

		if !self.observations.is_empty() {
			let delay = (tranquility * self.sum_observations) / (self.observations.len() as u32);
			Some(delay)
		} else {
			None
		}
	}

	pub async fn tranquilize(&mut self, tranquility: u32) {
		if let Some(delay) = self.tranquilize_internal(tranquility) {
			sleep(delay).await;
			self.reset();
		}
	}

	#[must_use]
	pub fn tranquilize_worker(&mut self, tranquility: u32) -> WorkerStatus {
		match self.tranquilize_internal(tranquility) {
			Some(delay) => WorkerStatus::Throttled(delay.as_secs_f32()),
			None => WorkerStatus::Busy,
		}
	}

	pub fn reset(&mut self) {
		self.last_step_begin = Instant::now();
	}
}