Path: blob/main/audio_streams_conformance_test/src/performance_data.rs
5392 views
// Copyright 2022 The ChromiumOS Authors1// Use of this source code is governed by a BSD-style license that can be2// found in the LICENSE file.34use std::fmt;5use std::time::Duration;67use serde::Serialize;89use crate::args::Args;10use crate::error::*;1112const NANOS_PER_MICROS: f32 = 1_000_000.0;1314/// `PerformanceReport` is the estimated buffer consumption rate and error term15/// derived by the linear regression of `BufferConsumptionRecord`.16#[derive(Debug, Serialize)]17pub struct PerformanceReport {18args: Args,19cold_start_latency: Duration,20record_count: usize,21rate: EstimatedRate,22/// {min, max, avg, stddev}_time for per "next_buffer + zero write + commit" call23min_time: Duration,24max_time: Duration,25avg_time: Duration,26stddev_time: Duration,27/// How many times that consumed frames are different from buffer_frames.28mismatched_frame_count: u32,29}3031impl fmt::Display for PerformanceReport {32#[allow(clippy::print_in_format_impl)]33fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {34if self.mismatched_frame_count != 0 {35eprint!(36"[Error] {} consumed buffers size != {} frames",37self.mismatched_frame_count, self.args.buffer_frames38);39}40write!(41f,42r#"{}43Cold start latency: {:?}44Records count: {}45[Step] min: {:.2} ms, max: {:.2} ms, average: {:.2} ms, standard deviation: {:.2} ms.46{}47"#,48self.args,49self.cold_start_latency,50self.record_count,51to_micros(self.min_time),52to_micros(self.max_time),53to_micros(self.avg_time),54to_micros(self.stddev_time),55self.rate,56)57}58}5960/// `BufferConsumptionRecord` records the timestamp and the61/// accumulated number of consumed frames at every stream buffer commit.62/// It is used to compute the buffer consumption rate.63#[derive(Debug, Default)]64pub struct BufferConsumptionRecord {65pub ts: Duration,66pub frames: usize,67}6869impl BufferConsumptionRecord {70pub fn new(frames: usize, ts: Duration) -> Self {71Self { ts, frames }72}73}7475#[derive(Debug, Serialize, PartialEq)]76pub struct EstimatedRate {77/// linear coefficients of LINEST(frames,timestamps).78rate: f64,79/// STEYX(frames, timestamps).80error: f64,81}8283impl EstimatedRate {84fn new(rate: f64, error: f64) -> Self {85Self { rate, error }86}87}8889impl fmt::Display for EstimatedRate {90fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {91write!(92f,93"[Linear Regression] rate: {:.2} frames/s, standard error: {:.2} ",94self.rate, self.error95)96}97}9899#[derive(Debug, Default)]100pub struct PerformanceData {101pub cold_start: Duration,102pub records: Vec<BufferConsumptionRecord>,103}104105fn to_micros(t: Duration) -> f32 {106t.as_nanos() as f32 / NANOS_PER_MICROS107}108109fn linear_regression(x: &[f64], y: &[f64]) -> Result<EstimatedRate> {110if x.len() != y.len() {111return Err(Error::MismatchedSamples);112}113114if x.len() <= 2 {115return Err(Error::NotEnoughSamples);116}117118/* hat(y_i) = b(x_i) + a */119let x_sum: f64 = x.iter().sum();120let x_average = x_sum / x.len() as f64;121// sum(x_i * x_i)122let x_square_sum: f64 = x.iter().map(|&xi| xi * xi).sum();123// sum(x_i * y_i)124let x_y_sum: f64 = x.iter().zip(y.iter()).map(|(&xi, &yi)| xi * yi).sum();125126let y_sum: f64 = y.iter().sum();127128let y_square_sum: f64 = y.iter().map(|yi| yi * yi).sum();129/* b = (n * sum(x * y) - sum(x) * sum(y)) / (n * sum(x ^ 2) - sum(x) ^ 2)130= (sum(x * y) - avg(x) * sum(y)) / (sum(x ^ 2) - avg(x) * sum(x)) */131let b = (x_y_sum - x_average * y_sum) / (x_square_sum - x_average * x_sum);132let n = y.len() as f64;133/* err = sqrt(sum((y_i - hat(y_i)) ^ 2) / n) */134let err: f64 =135((n * y_square_sum - y_sum * y_sum - b * b * (n * x_square_sum - x_sum * x_sum))136/ (n * (n - 2.0)))137.sqrt();138139Ok(EstimatedRate::new(b, err))140}141142impl PerformanceData {143pub fn print_records(&self) {144println!("TS\t\tTS_DIFF\t\tPLAYED");145let mut previous_ts = 0.0;146for record in &self.records {147println!(148"{:.6}\t{:.6}\t{}",149record.ts.as_secs_f64(),150record.ts.as_secs_f64() - previous_ts,151record.frames152);153previous_ts = record.ts.as_secs_f64();154}155}156pub fn gen_report(&self, args: Args) -> Result<PerformanceReport> {157let time_records: Vec<f64> = self158.records159.iter()160.map(|record| record.ts.as_secs_f64())161.collect();162163let frames: Vec<f64> = self164.records165.iter()166.map(|record| record.frames as f64)167.collect();168169let mut steps = Vec::new();170let mut mismatched_frame_count = 0;171for i in 1..frames.len() {172let time_diff = self.records[i].ts - self.records[i - 1].ts;173steps.push(time_diff);174175let frame_diff = self.records[i].frames - self.records[i - 1].frames;176if frame_diff != args.buffer_frames {177mismatched_frame_count += 1;178}179}180let avg_time = steps181.iter()182.sum::<Duration>()183.checked_div(steps.len() as u32)184.ok_or(Error::NotEnoughSamples)?;185let stddev_time = (steps186.iter()187.map(|x| {188(x.as_nanos().abs_diff(avg_time.as_nanos())189* x.as_nanos().abs_diff(avg_time.as_nanos())) as f64190})191.sum::<f64>()192/ steps.len() as f64)193.sqrt();194195let rate = linear_regression(&time_records, &frames)?;196let min_time = steps.iter().min().unwrap().to_owned();197let max_time = steps.iter().max().unwrap().to_owned();198199Ok(PerformanceReport {200args,201cold_start_latency: self.cold_start,202record_count: self.records.len(),203rate,204min_time,205max_time,206avg_time,207stddev_time: Duration::from_nanos(stddev_time as u64),208mismatched_frame_count,209})210}211}212213#[cfg(test)]214mod tests {215use super::*;216217#[test]218fn test1() {219let xs: Vec<f64> = vec![1.0, 2.0, 3.0, 4.0, 5.0];220let ys: Vec<f64> = vec![1.0, 2.0, 3.0, 4.0, 5.0];221222assert_eq!(223EstimatedRate::new(1.0, 0.0),224linear_regression(&xs, &ys).expect("test1 should pass")225);226}227228#[test]229fn test2() {230let xs: Vec<f64> = vec![1.0, 2.0, 3.0, 4.0, 5.0];231let ys: Vec<f64> = vec![2.0, 4.0, 5.0, 4.0, 5.0];232233assert_eq!(234EstimatedRate::new(0.6, 0.8944271909999159),235linear_regression(&xs, &ys).expect("test2 should pass")236);237}238}239240241