Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,14 +179,18 @@ Runs a single observation.

### Stats

`cardamon stats [scenario_name]`
`cardamon stats [-o text | json] [scenario_name]`

Shows the stats for previous runs of scenarios.
Output formats:
- text: Ascii table (default)
- json: Json format

**_Options_**

- **\*scenario_name**: An optional argument for the scenario you want to show stats for\*
- **\*previous_runs**: The number of previous runs to show\*
- **\*output: The output format

### Ui

Expand Down
2 changes: 1 addition & 1 deletion src/carbon_intensity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ pub async fn fetch_ci(code: &str, date: &DateTime<Utc>) -> anyhow::Result<f64> {

#[cfg(test)]
mod tests {
use chrono::NaiveDate;
use chrono::{Datelike, NaiveDate};

use super::*;

Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub mod metrics_logger;
pub mod migrations;
pub mod models;
pub mod server;
pub mod stats;

use crate::{
config::{Config, ExecutionMode},
Expand Down
93 changes: 22 additions & 71 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ use cardamon::{
carbon_intensity::{fetch_ci, fetch_region_code, valid_region_code, GLOBAL_CI},
cleanup_stdout_stderr,
config::{self, Config, ExecutionPlan, ProcessToObserve},
data::{dataset::LiveDataFilter, dataset_builder::DatasetBuilder, Data},
data::{dataset::LiveDataFilter, Data},
db_connect, db_migrate, init_config,
models::rab_model,
run, server,
run, server, stats::{stats_output_json, stats_output_text},
};
use chrono::{TimeZone, Utc};
use clap::{Parser, Subcommand};
use chrono::Utc;
use clap::{arg, Parser, Subcommand, ValueEnum};
use colored::Colorize;
use dotenvy::dotenv;
use itertools::Itertools;
Expand All @@ -28,6 +28,15 @@ pub struct Cli {
pub command: Commands,
}

#[derive(Clone, Debug, ValueEnum)]
pub enum StatsOutputFormat {
#[value(alias("text"))]
Text,

#[value(alias("json"))]
Json,
}

#[derive(Subcommand, Debug)]
pub enum Commands {
#[command(about = "Runs a single observation")]
Expand Down Expand Up @@ -61,6 +70,8 @@ pub enum Commands {

#[arg(value_name = "NUMBER OF PREVIOUS", short = 'n')]
previous_runs: Option<u64>,
#[arg(value_enum, default_value_t=StatsOutputFormat::Text, short ='o', long = "output")]
output: StatsOutputFormat,
},

#[command(about = "Start the Cardamon UI server")]
Expand Down Expand Up @@ -303,75 +314,15 @@ async fn main() -> anyhow::Result<()> {
Commands::Stats {
scenario_name,
previous_runs,
output,
} => {
// build dataset
let dataset_builder = DatasetBuilder::new();
let dataset_rows = match scenario_name {
Some(scenario_name) => dataset_builder.scenario(&scenario_name).all(),
None => dataset_builder.scenarios_all().all(),
};
let dataset_cols = match previous_runs {
Some(n) => dataset_rows.last_n_runs(n).all(),
None => dataset_rows.runs_all().all(),
};
let dataset = dataset_cols.build(&db_conn).await?;

println!("\n{}", " Cardamon Stats \n".reversed().green());
if dataset.is_empty() {
println!("\nno data found!");
}

for scenario_dataset in dataset.by_scenario(LiveDataFilter::IncludeLive) {
println!(
"Scenario {}:",
scenario_dataset.scenario_name().to_string().green()
);

let mut table = Table::builder()
.rows(rows![row![
TableCell::builder("Datetime (Utc)".bold()).build(),
TableCell::builder("Region".bold()).build(),
TableCell::builder("Duration (s)".bold()).build(),
TableCell::builder("Power (Wh)".bold()).build(),
TableCell::builder("CI (gWh)".bold()).build(),
TableCell::builder("CO2 (g)".bold()).build()
]])
.style(TableStyle::rounded())
.build();

// let mut points: Vec<(f32, f32)> = vec![];
// let mut run = 0.0;
for run_dataset in scenario_dataset.by_run() {
let run_data = run_dataset.apply_model(&db_conn, &rab_model).await?;
let run_region = run_data.region;
let run_ci = run_data.ci;
let run_start_time = Utc.timestamp_opt(run_data.start_time / 1000, 0).unwrap();
let run_duration = (run_data.stop_time - run_data.start_time) as f64 / 1000.0;
let _per_min_factor = 60.0 / run_duration;

table.add_row(row![
TableCell::new(run_start_time.format("%d/%m/%y %H:%M")),
TableCell::new(run_region.unwrap_or_default()),
TableCell::new(format!("{:.3}s", run_duration)),
TableCell::new(format!("{:.4}Wh", run_data.data.pow)),
TableCell::new(format!("{:.4}gWh", run_ci)),
TableCell::new(format!("{:.4}g", run_data.data.co2)),
]);
// points.push((run, run_data.data.pow as f32));
// run += 1.0;
match output {
StatsOutputFormat::Text => {
stats_output_text(scenario_name, previous_runs, &db_conn).await?;
}
StatsOutputFormat::Json => {
stats_output_json(scenario_name, previous_runs, &db_conn).await?;
}
println!("{}", table.render());

// let x_max = points.len() as f32;
// let y_data = points.iter().map(|(_, y)| *y);
// let y_min = y_data.clone().reduce(f32::min).unwrap_or(0.0);
// let y_max = y_data.clone().reduce(f32::max).unwrap_or(0.0);
//
// Chart::new_with_y_range(128, 64, 0.0, x_max, y_min, y_max)
// .x_axis_style(textplots::LineStyle::Solid)
// .y_tick_display(TickDisplay::Sparse)
// .lineplot(&Shape::Lines(&points))
// .nice();
}
}

Expand Down
147 changes: 147 additions & 0 deletions src/stats.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
use chrono::{TimeZone, Utc};
use colored::Colorize;
use sea_orm::DatabaseConnection;
use serde::Serialize;
use term_table::table_cell::TableCell;
use term_table::{row, rows, Table, TableStyle};
use term_table::row::Row;

use crate::data::{dataset::LiveDataFilter, dataset_builder::DatasetBuilder};
use crate::models::rab_model;


pub async fn stats_output_text(scenario_name: Option<String>,previous_runs: Option<u64>, db_conn: &DatabaseConnection)->anyhow::Result<()> {
// build dataset
let dataset_builder = DatasetBuilder::new();
let dataset_rows = match scenario_name {
Some(scenario_name) => dataset_builder.scenario(&scenario_name).all(),
None => dataset_builder.scenarios_all().all(),
};
let dataset_cols = match previous_runs {
Some(n) => dataset_rows.last_n_runs(n).all(),
None => dataset_rows.runs_all().all(),
};
let dataset = dataset_cols.build(&db_conn).await?;

println!("\n{}", " Cardamon Stats \n".reversed().green());
if dataset.is_empty() {
println!("\nno data found!");
}

for scenario_dataset in dataset.by_scenario(LiveDataFilter::IncludeLive) {
println!(
"Scenario {}:",
scenario_dataset.scenario_name().to_string().green()
);

let mut table = Table::builder()
.rows(rows![row![
TableCell::builder("Datetime (Utc)".bold()).build(),
TableCell::builder("Region".bold()).build(),
TableCell::builder("Duration (s)".bold()).build(),
TableCell::builder("Power (Wh)".bold()).build(),
TableCell::builder("CI (gWh)".bold()).build(),
TableCell::builder("CO2 (g)".bold()).build()
]])
.style(TableStyle::rounded())
.build();

for run_dataset in scenario_dataset.by_run() {
let run_data = run_dataset.apply_model(&db_conn, &rab_model).await?;
let run_region = run_data.region;
let run_ci = run_data.ci;
let run_start_time = Utc.timestamp_opt(run_data.start_time / 1000, 0).unwrap();
let run_duration = (run_data.stop_time - run_data.start_time) as f64 / 1000.0;
let _per_min_factor = 60.0 / run_duration;

table.add_row(row![
TableCell::new(run_start_time.format("%d/%m/%y %H:%M")),
TableCell::new(run_region.unwrap_or_default()),
TableCell::new(format!("{:.3}s", run_duration)),
TableCell::new(format!("{:.4}Wh", run_data.data.pow)),
TableCell::new(format!("{:.4}gWh", run_ci)),
TableCell::new(format!("{:.4}g", run_data.data.co2)),
]);
// points.push((run, run_data.data.pow as f32));
// run += 1.0;
}
println!("{}", table.render());

// let x_max = points.len() as f32;
// let y_data = points.iter().map(|(_, y)| *y);
// let y_min = y_data.clone().reduce(f32::min).unwrap_or(0.0);
// let y_max = y_data.clone().reduce(f32::max).unwrap_or(0.0);
//
// Chart::new_with_y_range(128, 64, 0.0, x_max, y_min, y_max)
// .x_axis_style(textplots::LineStyle::Solid)
// .y_tick_display(TickDisplay::Sparse)
// .lineplot(&Shape::Lines(&points))
// .nice();
}
Ok(())

}

#[derive(Debug, Clone, Serialize)]
struct ScenarioOutput {
name: String,
region: String,
duration: f64,
power: f64,
ci: f64,
co2: f64,
}

#[derive(Debug, Clone, Serialize)]
struct RunOutput {
name: String,
outputs: Vec<ScenarioOutput>,
}

pub async fn stats_output_json(scenario_name: Option<String>,previous_runs: Option<u64>, db_conn: &DatabaseConnection)->anyhow::Result<()> {
// build dataset
let dataset_builder = DatasetBuilder::new();
let dataset_rows = match scenario_name {
Some(scenario_name) => dataset_builder.scenario(&scenario_name).all(),
None => dataset_builder.scenarios_all().all(),
};
let dataset_cols = match previous_runs {
Some(n) => dataset_rows.last_n_runs(n).all(),
None => dataset_rows.runs_all().all(),
};
let dataset = dataset_cols.build(&db_conn).await?;
let mut run_outputs: Vec<RunOutput> = vec![];
for scenario_dataset in dataset.by_scenario(LiveDataFilter::IncludeLive) {
let mut scenario_outputs: Vec<ScenarioOutput> = vec![];
let scenario_name = scenario_dataset.scenario_name().to_string();
for run_dataset in scenario_dataset.by_run() {
let run_data = run_dataset.apply_model(&db_conn, &rab_model).await?;
let run_region = run_data.region;
let run_ci = run_data.ci;
let _run_start_time = Utc.timestamp_opt(run_data.start_time / 1000, 0).unwrap();
let run_duration = (run_data.stop_time - run_data.start_time) as f64 / 1000.0;
let _per_min_factor = 60.0 / run_duration;
let stats_output = ScenarioOutput {
name: scenario_name.clone(),
region: run_region.unwrap_or_default(),
duration: run_duration,
power: run_data.data.pow,
ci: run_ci,
co2: run_data.data.co2,
};
scenario_outputs.push(stats_output);
}
let scenario_output = RunOutput {
name: scenario_name,
outputs: scenario_outputs,
};
run_outputs.push(scenario_output);
}
println!(
"{}",
serde_json::to_string_pretty(&run_outputs)?
);

Ok(())

}