#[derive(Debug, Clone)]
pub struct Jitter {
min: i32,
max: i32,
}
impl Jitter {
fn new() -> Self {
Self {
max: 0,
min: i32::MAX,
}
}
fn update(&mut self, num_api_calls_in_a_row: i32) {
self.min = self.min.min(num_api_calls_in_a_row);
self.max = self.max.max(num_api_calls_in_a_row);
}
fn reset(&mut self) {
self.min = i32::MAX;
self.max = 0;
}
pub fn min(&self) -> i32 {
self.min
}
pub fn max(&self) -> i32 {
self.max
}
}
fn time_to_report_metrics(frames_since_last_report: i32) -> bool {
use crate::common::NUM_FRAMES_PER_SECOND;
const REPORTING_INTERVAL_FRAMES: i32 = 10 * NUM_FRAMES_PER_SECOND;
frames_since_last_report == REPORTING_INTERVAL_FRAMES
}
#[derive(Debug)]
pub struct ApiCallJitterMetrics {
render_jitter: Jitter,
capture_jitter: Jitter,
num_api_calls_in_a_row: i32,
frames_since_last_report: i32,
last_call_was_render: bool,
proper_call_observed: bool,
}
impl Default for ApiCallJitterMetrics {
fn default() -> Self {
Self::new()
}
}
impl ApiCallJitterMetrics {
pub fn new() -> Self {
let mut s = Self {
render_jitter: Jitter::new(),
capture_jitter: Jitter::new(),
num_api_calls_in_a_row: 0,
frames_since_last_report: 0,
last_call_was_render: false,
proper_call_observed: false,
};
s.reset();
s
}
pub fn report_render_call(&mut self) {
if !self.last_call_was_render {
if self.proper_call_observed {
self.capture_jitter.update(self.num_api_calls_in_a_row);
}
self.num_api_calls_in_a_row = 0;
}
self.num_api_calls_in_a_row += 1;
self.last_call_was_render = true;
}
pub fn report_capture_call(&mut self) {
if self.last_call_was_render {
if self.proper_call_observed {
self.render_jitter.update(self.num_api_calls_in_a_row);
}
self.num_api_calls_in_a_row = 0;
self.proper_call_observed = true;
}
self.num_api_calls_in_a_row += 1;
self.last_call_was_render = false;
if self.proper_call_observed {
self.frames_since_last_report += 1;
if time_to_report_metrics(self.frames_since_last_report) {
self.frames_since_last_report = 0;
self.reset();
}
}
}
pub fn render_jitter(&self) -> &Jitter {
&self.render_jitter
}
pub fn capture_jitter(&self) -> &Jitter {
&self.capture_jitter
}
pub fn will_report_metrics_at_next_capture(&self) -> bool {
time_to_report_metrics(self.frames_since_last_report + 1)
}
fn reset(&mut self) {
self.render_jitter.reset();
self.capture_jitter.reset();
self.num_api_calls_in_a_row = 0;
self.frames_since_last_report = 0;
self.last_call_was_render = false;
self.proper_call_observed = false;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::common::NUM_BLOCKS_PER_SECOND;
#[test]
fn constant_jitter() {
for jitter in 1..20 {
let mut metrics = ApiCallJitterMetrics::new();
for _ in 0..30 * NUM_BLOCKS_PER_SECOND {
for _ in 0..jitter {
metrics.report_render_call();
}
for _ in 0..jitter {
metrics.report_capture_call();
if metrics.will_report_metrics_at_next_capture() {
assert_eq!(jitter, metrics.render_jitter().min());
assert_eq!(jitter, metrics.render_jitter().max());
assert_eq!(jitter, metrics.capture_jitter().min());
assert_eq!(jitter, metrics.capture_jitter().max());
}
}
}
}
}
#[test]
fn jitter_peak_render() {
const MIN_JITTER: i32 = 2;
const JITTER_PEAK: i32 = 10;
const PEAK_INTERVAL: usize = 100;
let mut metrics = ApiCallJitterMetrics::new();
let mut render_surplus: i32 = 0;
for k in 0..30 * NUM_BLOCKS_PER_SECOND {
let num_render_calls = if k % PEAK_INTERVAL == 0 {
JITTER_PEAK
} else {
MIN_JITTER
};
for _ in 0..num_render_calls {
metrics.report_render_call();
render_surplus += 1;
}
assert!(MIN_JITTER <= render_surplus);
let num_capture_calls = if render_surplus == MIN_JITTER {
MIN_JITTER
} else {
MIN_JITTER + 1
};
for _ in 0..num_capture_calls {
metrics.report_capture_call();
if metrics.will_report_metrics_at_next_capture() {
assert_eq!(MIN_JITTER, metrics.render_jitter().min());
assert_eq!(JITTER_PEAK, metrics.render_jitter().max());
assert_eq!(MIN_JITTER, metrics.capture_jitter().min());
assert_eq!(MIN_JITTER + 1, metrics.capture_jitter().max());
}
render_surplus -= 1;
}
}
}
#[test]
fn jitter_peak_capture() {
const MIN_JITTER: i32 = 2;
const JITTER_PEAK: i32 = 10;
const PEAK_INTERVAL: usize = 100;
let mut metrics = ApiCallJitterMetrics::new();
let mut capture_surplus: i32 = MIN_JITTER;
for k in 0..30 * NUM_BLOCKS_PER_SECOND {
assert!(MIN_JITTER <= capture_surplus);
let num_render_calls = if capture_surplus == MIN_JITTER {
MIN_JITTER
} else {
MIN_JITTER + 1
};
for _ in 0..num_render_calls {
metrics.report_render_call();
capture_surplus -= 1;
}
let num_capture_calls = if k % PEAK_INTERVAL == 0 {
JITTER_PEAK
} else {
MIN_JITTER
};
for _ in 0..num_capture_calls {
metrics.report_capture_call();
if metrics.will_report_metrics_at_next_capture() {
assert_eq!(MIN_JITTER, metrics.render_jitter().min());
assert_eq!(MIN_JITTER + 1, metrics.render_jitter().max());
assert_eq!(MIN_JITTER, metrics.capture_jitter().min());
assert_eq!(JITTER_PEAK, metrics.capture_jitter().max());
}
capture_surplus += 1;
}
}
}
}