From e79000c3018dde1ab282fe5c665291b3d1edc0d3 Mon Sep 17 00:00:00 2001 From: Filipe Rodrigues Date: Tue, 6 Jun 2023 03:37:47 +0100 Subject: [PATCH] Separated other graphs into their own functions. --- ftmemsim-graphs/src/args.rs | 2 + ftmemsim-graphs/src/main.rs | 513 ++++++++++++++++++------------------ 2 files changed, 263 insertions(+), 252 deletions(-) diff --git a/ftmemsim-graphs/src/args.rs b/ftmemsim-graphs/src/args.rs index ba2633d..b83f483 100644 --- a/ftmemsim-graphs/src/args.rs +++ b/ftmemsim-graphs/src/args.rs @@ -30,9 +30,11 @@ pub enum SubCmd { #[clap(name = "page-migrations")] PageMigrations(PageMigrations), + // TODO: This is no longer a histogram, rename it? #[clap(name = "page-migrations-hist")] PageMigrationsHist(PageMigrationsHist), + // TODO: This is no longer a histogram, rename it? #[clap(name = "page-migrations-hist-multiple")] PageMigrationsHistMultiple(PageMigrationsHistMultiple), diff --git a/ftmemsim-graphs/src/main.rs b/ftmemsim-graphs/src/main.rs index ad1895a..bf37be4 100644 --- a/ftmemsim-graphs/src/main.rs +++ b/ftmemsim-graphs/src/main.rs @@ -34,258 +34,10 @@ fn main() -> Result<(), anyhow::Error> { // Then check the sub-command match args.sub_cmd { args::SubCmd::PageMigrations(cmd_args) => self::draw_page_migrations(&cmd_args)?, - - // TODO: This is no longer a histogram, rename it? - args::SubCmd::PageMigrationsHist(cmd_args) => { - // Parse and build the data - let data = self::read_data(&cmd_args.input_file)?; - let data = self::page_migrations_hist_data(&data); - - // Finally create and save the plot - let mut fg = gnuplot::Figure::new(); - fg.axes2d() - .lines(0..data.len(), &data, &[ - PlotOption::Caption("Migration count"), - PlotOption::Color("black"), - ]) - .set_x_range(AutoOption::Fix(0.0), AutoOption::Fix(data.len() as f64)) - .set_y_range(AutoOption::Fix(0.0), AutoOption::Auto) - .set_x_label("Migrations (flattened)", &[]) - .set_y_label("Migrations", &[]); - - // Then output the plot - self::handle_output(&cmd_args.output, &mut fg).context("Unable to handle output")?; - }, - - // TODO: This is no longer a histogram, rename it? - args::SubCmd::PageMigrationsHistMultiple(cmd_args) => { - // Create the figure - // Note: We do this before parsing the data since we parse - // the files in parallel (with a limit, to not load too - // many at once) - let mut fg = gnuplot::Figure::new(); - let fg_axes2d = fg.axes2d(); - - // Then process all input files - // TODO: Process them in parallel with `rayon`? Issue is that we need to add each - // plot in order so that the legend stays consistent. - for (data_idx, input_file) in cmd_args.input_files.iter().enumerate() { - // Parse and build the data - let data = self::read_data(input_file).with_context(|| format!("Unable to read {input_file:?}"))?; - let data = self::page_migrations_hist_data(&data); - - // Then render the lines - let progress = data_idx as f64 / (cmd_args.input_files.len() as f64 - 1.0); - - let color = LinSrgb::new(1.0, 0.0, 0.0).mix(LinSrgb::new(0.0, 1.0, 0.0), progress); - let color = format!("#{:x}", color.into_format::()); - - fg_axes2d.lines(0..data.len(), &data, &[ - PlotOption::Caption(&format!("Migration count ({})", input_file.display())), - PlotOption::Color(&color), - ]); - } - - // Finally finish building the graph - fg_axes2d - .set_x_range(AutoOption::Fix(0.0), AutoOption::Auto) - .set_y_range(AutoOption::Fix(0.0), AutoOption::Auto) - .set_x_label("Migrations (flattened)", &[]) - .set_y_label("Migrations", &[]); - - // Then output the plot - self::handle_output(&cmd_args.output, &mut fg).context("Unable to handle output")?; - }, - - args::SubCmd::PageTemperature(cmd_args) => { - // Parse the input file - let data = self::read_data(&cmd_args.input_file)?; - - let (min_time, max_time) = data - .hemem - .page_accesses - .accesses - .iter() - .map(|page_access| page_access.time) - .minmax() - .into_option() - .unwrap_or((0, 1)); - - // Get all the points - struct Point { - x: f64, - y_avg: f64, - y_err: f64, - global_cooled: bool, - } - let mut cur_temps = HashMap::new(); - let points = data - .hemem - .page_accesses - .accesses - .iter() - .map(|page_access| { - // Update the temperatures - // TODO: Optimize global cooling? - *cur_temps.entry(page_access.page_ptr).or_insert(0) = page_access.prev_temp; - if page_access.caused_cooling { - for temp in cur_temps.values_mut() { - *temp /= 2; - } - } - - // TODO: Optimize this with a moving average? - let average_temp = &cur_temps - .values() - .map(|&temp| temp as f64) - .collect::(); - Point { - x: (page_access.time - min_time) as f64 / (max_time - min_time) as f64, - y_avg: average_temp.mean(), - y_err: average_temp.error(), - global_cooled: page_access.caused_cooling, - } - }) - .collect::>(); - - let max_y = points.iter().map(|p| p.y_avg).max_by(f64::total_cmp).unwrap_or(0.0); - - // Finally create and save the plot - let mut fg = gnuplot::Figure::new(); - fg.axes2d() - .boxes_set_width( - points.iter().map(|p| p.x), - points.iter().map(|p| if p.global_cooled { max_y } else { 0.0 }), - (0..points.len()).map(|_| 0.1 / (points.len() as f64)), - &[ - PlotOption::Caption("Global cooling"), - PlotOption::Color("red"), - PlotOption::LineWidth(0.1), - ], - ) - .lines(points.iter().map(|p| p.x), points.iter().map(|p| p.y_avg), &[ - PlotOption::Caption("Page migrations (Avg)"), - PlotOption::Color("black"), - PlotOption::LineStyle(DashType::Solid), - PlotOption::LineWidth(1.0), - ]) - .fill_between( - points.iter().map(|p| p.x), - points.iter().map(|p| p.y_avg - p.y_err), - points.iter().map(|p| p.y_avg + p.y_err), - &[ - PlotOption::Caption("Page migrations (Error)"), - PlotOption::Color("green"), - PlotOption::FillAlpha(0.3), - PlotOption::FillRegion(FillRegionType::Below), - ], - ) - .set_x_label("Time (normalized)", &[]) - .set_y_label("Temperature", &[]) - .set_x_range(AutoOption::Fix(0.0), AutoOption::Fix(1.0)) - .set_y_range(AutoOption::Fix(0.0), AutoOption::Fix(max_y)); - - // Then output the plot - self::handle_output(&cmd_args.output, &mut fg).context("Unable to handle output")?; - }, - - args::SubCmd::PageTemperatureDensity(cmd_args) => { - // Parse the input file - let data = self::read_data(&cmd_args.input_file)?; - - let (min_time, max_time) = data - .hemem - .page_accesses - .accesses - .iter() - .map(|page_access| page_access.time) - .minmax() - .into_option() - .unwrap_or((0, 1)); - - // Then index the page pointers. - // Note: We do this because the page pointers are very far away, value-wise, which - // causes them to display far away in the graph. Since the actual values of the - // pages don't matter to us, we just index them by order of appearance. - let page_ptr_idxs = data - .hemem - .page_migrations - .migrations - .iter() - .enumerate() - .map(|(idx, (page_ptr, _))| (page_ptr, idx)) - .collect::>(); - - - // Get all the points - let mut cur_temps = HashMap::::new(); - let points = data - .hemem - .page_accesses - .accesses - .iter() - .group_by(|page_access| page_access.page_ptr) - .into_iter() - .map(|(page_ptr, page_accesses)| { - // Note: The page accesses will already be sorted by time - let mut temps = page_accesses - .map(|page_access| { - let cur_temp = cur_temps.entry(page_ptr).or_insert(0.0); - match page_access.kind { - ftmemsim::data::PageAccessKind::Read => *cur_temp += cmd_args.temp_read_weight, - ftmemsim::data::PageAccessKind::Write => *cur_temp += cmd_args.temp_write_weight, - }; - - let time = (page_access.time - min_time) as f64 / (max_time - min_time) as f64; - (time, *cur_temp) - }) - .collect::>(); - - // Add a temperature at the start and end to ensure that all rectangles are drawn from both ends - let last_temp = temps.back().map_or(0.0, |&(_, temp)| temp); - temps.push_front((0.0, 0.0)); - temps.push_back((1.0, last_temp)); - - (page_ptr, temps) - }) - .collect::>(); - - let max_temp = points - .values() - .flat_map(|temps| temps.iter().map(|&(_, temp)| temp)) - .max_by(f64::total_cmp) - .unwrap_or(0.0); - - // Finally create and save the plot - let mut fg = gnuplot::Figure::new(); - let fg_axes2d: &mut gnuplot::Axes2D = fg.axes2d(); - for (&page_ptr, temps) in &points { - let page_ptr_idx = *page_ptr_idxs.get(&page_ptr).expect("Page ptr had no index"); - - for ((prev_time, prev_temp), (cur_time, cur_temp)) in temps.iter().copied().tuple_windows() { - let progress = (prev_temp + cur_temp) / (2.0 * max_temp); - let progress = progress.powf(cmd_args.temp_exponent); - - let color = LinSrgb::new(1.0, 0.0, 0.0).mix(LinSrgb::new(0.0, 1.0, 0.0), progress); - let color = format!("#{:x}", color.into_format::()); - - fg_axes2d.fill_between([prev_time, cur_time], [page_ptr_idx; 2], [page_ptr_idx + 1; 2], &[ - PlotOption::Color(&color), - PlotOption::FillAlpha(1.0), - PlotOption::FillRegion(FillRegionType::Below), - ]); - } - } - - fg_axes2d - .set_x_label("Time (normalized)", &[]) - .set_y_label("Page (indexed)", &[]) - .set_x_range(AutoOption::Fix(0.0), AutoOption::Fix(1.0)) - .set_y_range(AutoOption::Fix(0.0), AutoOption::Fix(page_ptr_idxs.len() as f64)); - - // Then output the plot - self::handle_output(&cmd_args.output, &mut fg).context("Unable to handle output")?; - }, + args::SubCmd::PageMigrationsHist(cmd_args) => self::draw_page_migrations_hist(cmd_args)?, + args::SubCmd::PageMigrationsHistMultiple(cmd_args) => self::draw_page_migrations_hist_multiple(cmd_args)?, + args::SubCmd::PageTemperature(cmd_args) => self::draw_page_temperature(cmd_args)?, + args::SubCmd::PageTemperatureDensity(cmd_args) => self::draw_page_temperature_density(cmd_args)?, } Ok(()) @@ -414,6 +166,263 @@ fn draw_page_migrations(cmd_args: &args::PageMigrations) -> Result<(), anyhow::E Ok(()) } +fn draw_page_migrations_hist(cmd_args: args::PageMigrationsHist) -> Result<(), anyhow::Error> { + // Parse and build the data + let data = self::read_data(&cmd_args.input_file)?; + let data = self::page_migrations_hist_data(&data); + + // Finally create and save the plot + let mut fg = gnuplot::Figure::new(); + fg.axes2d() + .lines(0..data.len(), &data, &[ + PlotOption::Caption("Migration count"), + PlotOption::Color("black"), + ]) + .set_x_range(AutoOption::Fix(0.0), AutoOption::Fix(data.len() as f64)) + .set_y_range(AutoOption::Fix(0.0), AutoOption::Auto) + .set_x_label("Migrations (flattened)", &[]) + .set_y_label("Migrations", &[]); + + // Then output the plot + self::handle_output(&cmd_args.output, &mut fg).context("Unable to handle output")?; + + Ok(()) +} + +fn draw_page_migrations_hist_multiple(cmd_args: args::PageMigrationsHistMultiple) -> Result<(), anyhow::Error> { + // Create the figure + // Note: We do this before parsing the data since we parse + // the files in parallel (with a limit, to not load too + // many at once) + let mut fg = gnuplot::Figure::new(); + let fg_axes2d = fg.axes2d(); + + // Then process all input files + // TODO: Process them in parallel with `rayon`? Issue is that we need to add each + // plot in order so that the legend stays consistent. + for (data_idx, input_file) in cmd_args.input_files.iter().enumerate() { + // Parse and build the data + let data = self::read_data(input_file).with_context(|| format!("Unable to read {input_file:?}"))?; + let data = self::page_migrations_hist_data(&data); + + // Then render the lines + let progress = data_idx as f64 / (cmd_args.input_files.len() as f64 - 1.0); + + let color = LinSrgb::new(1.0, 0.0, 0.0).mix(LinSrgb::new(0.0, 1.0, 0.0), progress); + let color = format!("#{:x}", color.into_format::()); + + fg_axes2d.lines(0..data.len(), &data, &[ + PlotOption::Caption(&format!("Migration count ({})", input_file.display())), + PlotOption::Color(&color), + ]); + } + + // Finally finish building the graph + fg_axes2d + .set_x_range(AutoOption::Fix(0.0), AutoOption::Auto) + .set_y_range(AutoOption::Fix(0.0), AutoOption::Auto) + .set_x_label("Migrations (flattened)", &[]) + .set_y_label("Migrations", &[]); + + // Then output the plot + self::handle_output(&cmd_args.output, &mut fg).context("Unable to handle output")?; + + Ok(()) +} + +fn draw_page_temperature(cmd_args: args::PageTemperature) -> Result<(), anyhow::Error> { + // Parse the input file + let data = self::read_data(&cmd_args.input_file)?; + + let (min_time, max_time) = data + .hemem + .page_accesses + .accesses + .iter() + .map(|page_access| page_access.time) + .minmax() + .into_option() + .unwrap_or((0, 1)); + + // Get all the points + struct Point { + x: f64, + y_avg: f64, + y_err: f64, + global_cooled: bool, + } + let mut cur_temps = HashMap::new(); + let points = data + .hemem + .page_accesses + .accesses + .iter() + .map(|page_access| { + // Update the temperatures + // TODO: Optimize global cooling? + *cur_temps.entry(page_access.page_ptr).or_insert(0) = page_access.prev_temp; + if page_access.caused_cooling { + for temp in cur_temps.values_mut() { + *temp /= 2; + } + } + + // TODO: Optimize this with a moving average? + let average_temp = &cur_temps + .values() + .map(|&temp| temp as f64) + .collect::(); + Point { + x: (page_access.time - min_time) as f64 / (max_time - min_time) as f64, + y_avg: average_temp.mean(), + y_err: average_temp.error(), + global_cooled: page_access.caused_cooling, + } + }) + .collect::>(); + + let max_y = points.iter().map(|p| p.y_avg).max_by(f64::total_cmp).unwrap_or(0.0); + + // Finally create and save the plot + let mut fg = gnuplot::Figure::new(); + fg.axes2d() + .boxes_set_width( + points.iter().map(|p| p.x), + points.iter().map(|p| if p.global_cooled { max_y } else { 0.0 }), + (0..points.len()).map(|_| 0.1 / (points.len() as f64)), + &[ + PlotOption::Caption("Global cooling"), + PlotOption::Color("red"), + PlotOption::LineWidth(0.1), + ], + ) + .lines(points.iter().map(|p| p.x), points.iter().map(|p| p.y_avg), &[ + PlotOption::Caption("Page migrations (Avg)"), + PlotOption::Color("black"), + PlotOption::LineStyle(DashType::Solid), + PlotOption::LineWidth(1.0), + ]) + .fill_between( + points.iter().map(|p| p.x), + points.iter().map(|p| p.y_avg - p.y_err), + points.iter().map(|p| p.y_avg + p.y_err), + &[ + PlotOption::Caption("Page migrations (Error)"), + PlotOption::Color("green"), + PlotOption::FillAlpha(0.3), + PlotOption::FillRegion(FillRegionType::Below), + ], + ) + .set_x_label("Time (normalized)", &[]) + .set_y_label("Temperature", &[]) + .set_x_range(AutoOption::Fix(0.0), AutoOption::Fix(1.0)) + .set_y_range(AutoOption::Fix(0.0), AutoOption::Fix(max_y)); + + // Then output the plot + self::handle_output(&cmd_args.output, &mut fg).context("Unable to handle output")?; + + Ok(()) +} + +fn draw_page_temperature_density(cmd_args: args::PageTemperatureDensity) -> Result<(), anyhow::Error> { + // Parse the input file + let data = self::read_data(&cmd_args.input_file)?; + + let (min_time, max_time) = data + .hemem + .page_accesses + .accesses + .iter() + .map(|page_access| page_access.time) + .minmax() + .into_option() + .unwrap_or((0, 1)); + + // Then index the page pointers. + // Note: We do this because the page pointers are very far away, value-wise, which + // causes them to display far away in the graph. Since the actual values of the + // pages don't matter to us, we just index them by order of appearance. + let page_ptr_idxs = data + .hemem + .page_migrations + .migrations + .iter() + .enumerate() + .map(|(idx, (page_ptr, _))| (page_ptr, idx)) + .collect::>(); + + + // Get all the points + let mut cur_temps = HashMap::::new(); + let points = data + .hemem + .page_accesses + .accesses + .iter() + .group_by(|page_access| page_access.page_ptr) + .into_iter() + .map(|(page_ptr, page_accesses)| { + // Note: The page accesses will already be sorted by time + let mut temps = page_accesses + .map(|page_access| { + let cur_temp = cur_temps.entry(page_ptr).or_insert(0.0); + match page_access.kind { + ftmemsim::data::PageAccessKind::Read => *cur_temp += cmd_args.temp_read_weight, + ftmemsim::data::PageAccessKind::Write => *cur_temp += cmd_args.temp_write_weight, + }; + + let time = (page_access.time - min_time) as f64 / (max_time - min_time) as f64; + (time, *cur_temp) + }) + .collect::>(); + + // Add a temperature at the start and end to ensure that all rectangles are drawn from both ends + let last_temp = temps.back().map_or(0.0, |&(_, temp)| temp); + temps.push_front((0.0, 0.0)); + temps.push_back((1.0, last_temp)); + + (page_ptr, temps) + }) + .collect::>(); + + let max_temp = points + .values() + .flat_map(|temps| temps.iter().map(|&(_, temp)| temp)) + .max_by(f64::total_cmp) + .unwrap_or(0.0); + + // Finally create and save the plot + let mut fg = gnuplot::Figure::new(); + let fg_axes2d: &mut gnuplot::Axes2D = fg.axes2d(); + for (&page_ptr, temps) in &points { + let page_ptr_idx = *page_ptr_idxs.get(&page_ptr).expect("Page ptr had no index"); + + for ((prev_time, prev_temp), (cur_time, cur_temp)) in temps.iter().copied().tuple_windows() { + let progress = (prev_temp + cur_temp) / (2.0 * max_temp); + let progress = progress.powf(cmd_args.temp_exponent); + + let color = LinSrgb::new(1.0, 0.0, 0.0).mix(LinSrgb::new(0.0, 1.0, 0.0), progress); + let color = format!("#{:x}", color.into_format::()); + + fg_axes2d.fill_between([prev_time, cur_time], [page_ptr_idx; 2], [page_ptr_idx + 1; 2], &[ + PlotOption::Color(&color), + PlotOption::FillAlpha(1.0), + PlotOption::FillRegion(FillRegionType::Below), + ]); + } + } + + fg_axes2d + .set_x_label("Time (normalized)", &[]) + .set_y_label("Page (indexed)", &[]) + .set_x_range(AutoOption::Fix(0.0), AutoOption::Fix(1.0)) + .set_y_range(AutoOption::Fix(0.0), AutoOption::Fix(page_ptr_idxs.len() as f64)); + + // Then output the plot + self::handle_output(&cmd_args.output, &mut fg).context("Unable to handle output")?; + + Ok(()) +} /// Computes the data to use fr the `page-migrations-hist` graph fn page_migrations_hist_data(data: &ftmemsim::data::Data) -> Vec {