use crate::file_summary::FileSummary;
use hashbrown::HashMap;
#[derive(Debug, Clone)]
pub struct UtilizationProfile {
file_summaries: HashMap<u32, FileSummary>,
modified: bool,
}
impl UtilizationProfile {
pub fn new() -> Self {
Self { file_summaries: HashMap::new(), modified: false }
}
pub fn get_file_summary(&self, file_number: u32) -> Option<&FileSummary> {
self.file_summaries.get(&file_number)
}
pub fn get_file_summary_mut(
&mut self,
file_number: u32,
) -> Option<&mut FileSummary> {
self.modified = true;
self.file_summaries.get_mut(&file_number)
}
pub fn populate(&mut self, summaries: HashMap<u32, FileSummary>) {
self.file_summaries = summaries;
self.modified = true;
}
pub fn update_file_summary(
&mut self,
file_number: u32,
delta: &FileSummary,
) {
self.file_summaries.entry(file_number).or_default().add(delta);
self.modified = true;
}
pub fn remove_file_summary(
&mut self,
file_number: u32,
) -> Option<FileSummary> {
self.modified = true;
self.file_summaries.remove(&file_number)
}
pub fn get_best_file_for_cleaning(
&self,
min_utilization: f64,
) -> Option<(u32, f64)> {
self.file_summaries
.iter()
.filter(|(_, summary)| !summary.is_empty())
.map(|(&file_num, summary)| {
let util = summary.get_utilization();
let benefit = summary.get_obsolete_size() as f64;
let cost = (summary.get_active_size() as f64).max(1.0);
let score = benefit / cost;
(file_num, util, score)
})
.filter(|&(_, util, _)| util < min_utilization)
.max_by(|a, b| {
a.2.partial_cmp(&b.2).unwrap_or(std::cmp::Ordering::Equal)
})
.map(|(file_num, util, _)| (file_num, util))
}
pub fn get_files_at_utilization(&self, threshold: f64) -> Vec<(u32, f64)> {
let mut files: Vec<(u32, f64)> = self
.file_summaries
.iter()
.filter(|(_, summary)| !summary.is_empty())
.map(|(&file_num, summary)| (file_num, summary.get_utilization()))
.filter(|&(_, util)| util < threshold)
.collect();
files.sort_by(|a, b| {
a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal)
});
files
}
pub fn count_and_sort(&self) -> Vec<(u32, f64)> {
let mut files: Vec<(u32, f64)> = self
.file_summaries
.iter()
.filter(|(_, summary)| !summary.is_empty())
.map(|(&file_num, summary)| (file_num, summary.get_utilization()))
.collect();
files.sort_by(|a, b| {
a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal)
});
files
}
pub fn get_total_log_size(&self) -> i64 {
self.file_summaries.values().map(|s| s.total_size as i64).sum()
}
pub fn get_active_log_size(&self) -> i64 {
self.file_summaries.values().map(|s| s.get_active_size() as i64).sum()
}
pub fn get_obsolete_log_size(&self) -> i64 {
self.file_summaries.values().map(|s| s.get_obsolete_size() as i64).sum()
}
pub fn get_overall_utilization(&self) -> f64 {
let total = self.get_total_log_size();
if total == 0 {
return 0.0;
}
let active = self.get_active_log_size();
active as f64 / total as f64
}
pub fn is_modified(&self) -> bool {
self.modified
}
pub fn clear_modified(&mut self) {
self.modified = false;
}
pub fn get_file_count(&self) -> usize {
self.file_summaries.len()
}
pub fn get_file_numbers(&self) -> Vec<u32> {
let mut files: Vec<u32> = self.file_summaries.keys().copied().collect();
files.sort_unstable();
files
}
pub fn clear(&mut self) {
self.file_summaries.clear();
self.modified = true;
}
}
impl Default for UtilizationProfile {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_summary(total_size: i32, obsolete_size: i32) -> FileSummary {
let mut summary = FileSummary::new();
summary.total_count = 10;
summary.total_size = total_size;
summary.total_ln_count = 10;
summary.total_ln_size = total_size;
summary.obsolete_ln_count = (obsolete_size * 10) / total_size;
summary.obsolete_ln_size = obsolete_size;
summary.obsolete_ln_size_counted = summary.obsolete_ln_count;
summary
}
#[test]
fn test_new() {
let profile = UtilizationProfile::new();
assert_eq!(profile.get_file_count(), 0);
assert!(!profile.is_modified());
}
#[test]
fn test_populate() {
let mut profile = UtilizationProfile::new();
let mut summaries = HashMap::new();
summaries.insert(1, create_summary(1000, 500));
summaries.insert(2, create_summary(2000, 1000));
profile.populate(summaries);
assert_eq!(profile.get_file_count(), 2);
assert!(profile.is_modified());
}
#[test]
fn test_update_file_summary_new() {
let mut profile = UtilizationProfile::new();
let delta = create_summary(1000, 500);
profile.update_file_summary(1, &delta);
let summary = profile.get_file_summary(1).unwrap();
assert_eq!(summary.total_size, 1000);
assert!(profile.is_modified());
}
#[test]
fn test_update_file_summary_existing() {
let mut profile = UtilizationProfile::new();
profile.update_file_summary(1, &create_summary(1000, 500));
profile.clear_modified();
profile.update_file_summary(1, &create_summary(500, 250));
let summary = profile.get_file_summary(1).unwrap();
assert_eq!(summary.total_size, 1500);
assert!(profile.is_modified());
}
#[test]
fn test_remove_file_summary() {
let mut profile = UtilizationProfile::new();
profile.update_file_summary(1, &create_summary(1000, 500));
let removed = profile.remove_file_summary(1);
assert!(removed.is_some());
assert_eq!(profile.get_file_count(), 0);
}
#[test]
fn test_get_best_file_for_cleaning() {
let mut profile = UtilizationProfile::new();
profile.update_file_summary(1, &create_summary(1000, 900)); profile.update_file_summary(2, &create_summary(1000, 500)); profile.update_file_summary(3, &create_summary(1000, 700));
let result = profile.get_best_file_for_cleaning(0.6);
assert_eq!(result.map(|(f, _)| f), Some(1)); }
#[test]
fn test_get_best_file_no_qualification() {
let mut profile = UtilizationProfile::new();
profile.update_file_summary(1, &create_summary(1000, 100));
let result = profile.get_best_file_for_cleaning(0.5);
assert_eq!(result, None);
}
#[test]
fn test_get_files_at_utilization() {
let mut profile = UtilizationProfile::new();
profile.update_file_summary(1, &create_summary(1000, 900)); profile.update_file_summary(2, &create_summary(1000, 500)); profile.update_file_summary(3, &create_summary(1000, 700)); profile.update_file_summary(4, &create_summary(1000, 100));
let files = profile.get_files_at_utilization(0.6);
assert_eq!(files.len(), 3);
assert_eq!(files[0].0, 1); assert_eq!(files[1].0, 3); assert_eq!(files[2].0, 2); }
#[test]
fn test_count_and_sort() {
let mut profile = UtilizationProfile::new();
profile.update_file_summary(1, &create_summary(1000, 500)); profile.update_file_summary(2, &create_summary(1000, 900)); profile.update_file_summary(3, &create_summary(1000, 700));
let files = profile.count_and_sort();
assert_eq!(files.len(), 3);
assert_eq!(files[0].0, 2); assert_eq!(files[1].0, 3); assert_eq!(files[2].0, 1); }
#[test]
fn test_get_total_log_size() {
let mut profile = UtilizationProfile::new();
profile.update_file_summary(1, &create_summary(1000, 500));
profile.update_file_summary(2, &create_summary(2000, 1000));
assert_eq!(profile.get_total_log_size(), 3000);
}
#[test]
fn test_get_active_log_size() {
let mut profile = UtilizationProfile::new();
profile.update_file_summary(1, &create_summary(1000, 500)); profile.update_file_summary(2, &create_summary(2000, 1000));
assert_eq!(profile.get_active_log_size(), 1500);
}
#[test]
fn test_get_obsolete_log_size() {
let mut profile = UtilizationProfile::new();
profile.update_file_summary(1, &create_summary(1000, 500)); profile.update_file_summary(2, &create_summary(2000, 1000));
assert_eq!(profile.get_obsolete_log_size(), 1500);
}
#[test]
fn test_get_overall_utilization() {
let mut profile = UtilizationProfile::new();
profile.update_file_summary(1, &create_summary(1000, 500)); profile.update_file_summary(2, &create_summary(1000, 500));
assert_eq!(profile.get_overall_utilization(), 0.5);
}
#[test]
fn test_get_overall_utilization_empty() {
let profile = UtilizationProfile::new();
assert_eq!(profile.get_overall_utilization(), 0.0);
}
#[test]
fn test_modified_flag() {
let mut profile = UtilizationProfile::new();
assert!(!profile.is_modified());
profile.update_file_summary(1, &create_summary(1000, 500));
assert!(profile.is_modified());
profile.clear_modified();
assert!(!profile.is_modified());
}
#[test]
fn test_get_file_numbers() {
let mut profile = UtilizationProfile::new();
profile.update_file_summary(3, &create_summary(1000, 500));
profile.update_file_summary(1, &create_summary(1000, 500));
profile.update_file_summary(2, &create_summary(1000, 500));
let files = profile.get_file_numbers();
assert_eq!(files, vec![1, 2, 3]); }
#[test]
fn test_clear() {
let mut profile = UtilizationProfile::new();
profile.update_file_summary(1, &create_summary(1000, 500));
profile.update_file_summary(2, &create_summary(1000, 500));
profile.clear();
assert_eq!(profile.get_file_count(), 0);
assert!(profile.is_modified());
}
#[test]
fn test_get_file_summary_mut_sets_modified() {
let mut profile = UtilizationProfile::new();
profile.update_file_summary(1, &create_summary(1000, 500));
profile.clear_modified();
{
let _summary = profile.get_file_summary_mut(1);
}
assert!(profile.is_modified());
}
#[test]
fn test_clone() {
let mut profile1 = UtilizationProfile::new();
profile1.update_file_summary(1, &create_summary(1000, 500));
let profile2 = profile1.clone();
assert_eq!(profile2.get_file_count(), 1);
assert!(profile2.get_file_summary(1).is_some());
}
#[test]
fn test_default() {
let profile = UtilizationProfile::default();
assert_eq!(profile.get_file_count(), 0);
}
}