Skip to content

Commit 542baff

Browse files
authored
Merge pull request #613 from EnergySystemsModellingLab/output-metadata
Write metadata to TOML file in output folder
2 parents 8393ff6 + 9b4ab6d commit 542baff

File tree

8 files changed

+542
-6
lines changed

8 files changed

+542
-6
lines changed

Cargo.lock

Lines changed: 387 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@ highs = "1.8.0"
3030
indexmap = "2.9.0"
3131
human-panic = "2.0.2"
3232
clap-markdown = "0.1.5"
33+
platform-info = "2.0.5"
3334

3435
[dev-dependencies]
3536
regex = "1.11.1"
3637
rstest = {version = "0.25.0", default-features = false, features = ["crate-name"]}
38+
39+
[build-dependencies]
40+
built = {version = "0.8.0", features = ["chrono", "git2"]}

build.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/// Write data about build
2+
fn main() {
3+
built::write_built_file().expect("Failed to acquire build-time information");
4+
}

src/input.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,12 @@ pub fn load_model<P: AsRef<Path>>(model_dir: P) -> Result<(Model, AssetPool)> {
196196
let agent_ids = agents.keys().cloned().collect();
197197
let assets = read_assets(model_dir.as_ref(), &agent_ids, &processes, &region_ids)?;
198198

199+
let model_path = model_dir
200+
.as_ref()
201+
.canonicalize()
202+
.context("Could not parse path to model")?;
199203
let model = Model {
204+
model_path,
200205
milestone_years: model_file.milestone_years.years,
201206
agents,
202207
commodities,

src/model.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ use crate::region::{RegionID, RegionMap};
77
use crate::time_slice::TimeSliceInfo;
88
use anyhow::{ensure, Context, Result};
99
use serde::Deserialize;
10-
use std::path::Path;
10+
use std::path::{Path, PathBuf};
1111

1212
const MODEL_FILE_NAME: &str = "model.toml";
1313

1414
/// Model definition
1515
pub struct Model {
16+
/// Path to model folder
17+
pub model_path: PathBuf,
1618
/// Milestone years for the simulation. Sorted.
1719
pub milestone_years: Vec<u32>,
1820
/// Agents for the simulation

src/output.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ use std::fs;
1414
use std::fs::File;
1515
use std::path::{Path, PathBuf};
1616

17+
pub mod metadata;
18+
use metadata::write_metadata;
19+
1720
/// The root folder in which model-specific output folders will be created
1821
const OUTPUT_DIRECTORY_ROOT: &str = "muse2_results";
1922

@@ -233,8 +236,11 @@ impl DataWriter {
233236
/// # Arguments
234237
///
235238
/// * `output_path` - Folder where files will be saved
239+
/// * `model_path` - Path to input model
236240
/// * `save_debug_info` - Whether to include extra CSV files for debugging model
237-
pub fn create(output_path: &Path, save_debug_info: bool) -> Result<Self> {
241+
pub fn create(output_path: &Path, model_path: &Path, save_debug_info: bool) -> Result<Self> {
242+
write_metadata(output_path, model_path).context("Failed to save metadata")?;
243+
238244
let new_writer = |file_name| {
239245
let file_path = output_path.join(file_name);
240246
csv::Writer::from_path(file_path)
@@ -340,7 +346,7 @@ mod tests {
340346

341347
// Write an asset
342348
{
343-
let mut writer = DataWriter::create(dir.path(), false).unwrap();
349+
let mut writer = DataWriter::create(dir.path(), dir.path(), false).unwrap();
344350
writer.write_assets(milestone_year, assets.iter()).unwrap();
345351
writer.flush().unwrap();
346352
}
@@ -367,7 +373,7 @@ mod tests {
367373
// Write a flow
368374
let dir = tempdir().unwrap();
369375
{
370-
let mut writer = DataWriter::create(dir.path(), false).unwrap();
376+
let mut writer = DataWriter::create(dir.path(), dir.path(), false).unwrap();
371377
writer.write_flows(milestone_year, &flow_map).unwrap();
372378
writer.flush().unwrap();
373379
}
@@ -398,7 +404,7 @@ mod tests {
398404

399405
// Write a price
400406
{
401-
let mut writer = DataWriter::create(dir.path(), false).unwrap();
407+
let mut writer = DataWriter::create(dir.path(), dir.path(), false).unwrap();
402408
writer.write_prices(milestone_year, &prices).unwrap();
403409
writer.flush().unwrap();
404410
}

src/output/metadata.rs

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
//! Code for writing metadata to file
2+
use anyhow::Result;
3+
use chrono::prelude::*;
4+
use platform_info::{PlatformInfo, PlatformInfoAPI, UNameAPI};
5+
use serde::Serialize;
6+
use std::fs;
7+
use std::path::Path;
8+
9+
/// The output file name for metadata
10+
const METADATA_FILE_NAME: &str = "metadata.toml";
11+
12+
/// Information about the program build via `built` crate
13+
mod built_info {
14+
// The file has been placed there by the build script.
15+
include!(concat!(env!("OUT_DIR"), "/built.rs"));
16+
}
17+
18+
/// Get information about program version from git
19+
fn get_git_hash() -> String {
20+
let Some(hash) = built_info::GIT_COMMIT_HASH_SHORT else {
21+
return "unknown".into();
22+
};
23+
24+
if built_info::GIT_DIRTY == Some(true) {
25+
format!("{hash}-dirty")
26+
} else {
27+
hash.into()
28+
}
29+
}
30+
31+
/// Metadata about the program run, build and version information for MUSE and the user's system
32+
#[derive(Serialize)]
33+
struct Metadata<'a> {
34+
run: RunMetadata<'a>,
35+
program: ProgramMetadata<'a>,
36+
platform: PlatformMetadata,
37+
}
38+
39+
/// Information about the model run
40+
#[derive(Serialize)]
41+
struct RunMetadata<'a> {
42+
/// Path to the model which was run
43+
model_path: &'a Path,
44+
/// The date and time on which the run started
45+
datetime: String,
46+
}
47+
48+
impl<'a> RunMetadata<'a> {
49+
fn new(model_path: &'a Path) -> Self {
50+
let dt = Local::now();
51+
Self {
52+
model_path,
53+
datetime: dt.to_rfc2822(),
54+
}
55+
}
56+
}
57+
58+
#[derive(Serialize)]
59+
struct ProgramMetadata<'a> {
60+
/// The program name
61+
name: &'a str,
62+
/// The program version as specified in Cargo.toml
63+
version: &'a str,
64+
/// The target architecture for the build (e.g. x86_64-unknown-linux-gnu)
65+
target: &'a str,
66+
/// Whether it is a debug build
67+
is_debug: bool,
68+
/// The version of rustc used to compile MUSE
69+
rustc_version: &'a str,
70+
/// When MUSE was built
71+
build_time_utc: &'a str,
72+
/// The git commit hash for the version of MUSE (if known)
73+
git_commit_hash: String,
74+
}
75+
76+
impl Default for ProgramMetadata<'_> {
77+
fn default() -> Self {
78+
Self {
79+
name: built_info::PKG_NAME,
80+
version: built_info::PKG_VERSION,
81+
target: built_info::TARGET,
82+
is_debug: built_info::DEBUG,
83+
rustc_version: built_info::RUSTC_VERSION,
84+
build_time_utc: built_info::BUILT_TIME_UTC,
85+
git_commit_hash: get_git_hash(),
86+
}
87+
}
88+
}
89+
90+
/// Information about the platform on which MUSE is running.
91+
///
92+
/// The fields correspond to different data available from the [`PlatformInfo`] struct.
93+
#[derive(Serialize)]
94+
struct PlatformMetadata {
95+
sysname: String,
96+
nodename: String,
97+
release: String,
98+
version: String,
99+
machine: String,
100+
osname: String,
101+
}
102+
103+
impl Default for PlatformMetadata {
104+
fn default() -> Self {
105+
let info = PlatformInfo::new().expect("Unable to determine platform info");
106+
Self {
107+
sysname: info.sysname().to_string_lossy().into(),
108+
nodename: info.nodename().to_string_lossy().into(),
109+
release: info.release().to_string_lossy().into(),
110+
version: info.version().to_string_lossy().into(),
111+
machine: info.machine().to_string_lossy().into(),
112+
osname: info.osname().to_string_lossy().into(),
113+
}
114+
}
115+
}
116+
117+
/// Write metadata to the specified output path in TOML format
118+
pub fn write_metadata(output_path: &Path, model_path: &Path) -> Result<()> {
119+
let metadata = Metadata {
120+
run: RunMetadata::new(model_path),
121+
program: ProgramMetadata::default(),
122+
platform: PlatformMetadata::default(),
123+
};
124+
let file_path = output_path.join(METADATA_FILE_NAME);
125+
fs::write(&file_path, toml::to_string(&metadata)?)?;
126+
127+
Ok(())
128+
}

src/simulation.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ pub fn run(
2727
output_path: &Path,
2828
debug_model: bool,
2929
) -> Result<()> {
30-
let mut writer = DataWriter::create(output_path, debug_model)?;
30+
let mut writer = DataWriter::create(output_path, &model.model_path, debug_model)?;
3131

3232
// Iterate over milestone years
3333
let mut year_iter = model.iter_years();

0 commit comments

Comments
 (0)