Add benchmark session metadata to JSON exports

This commit is contained in:
David Peter committed 2026-10-05 16:27:35 +02:00
1 parent d60eba407d
commit 6d0ae4f87c
4 files changed
+100 -5

No files matched your search

Generated
+18
View File
@@ -369,6 +369,12 @@ version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
[[package]]
name = "humantime"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15cdd26707701c53297e2fa6afb323d55fbc1d0810c3aec078ae3ef0424c3c15"
[[package]]
name = "hyperfine"
version = "1.21.0"
@@ -380,12 +386,14 @@ dependencies = [
"clap_complete",
"colored",
"csv",
"humantime",
"indicatif",
"insta",
"insta-cmd",
"libc",
"nix",
"once_cell",
"platform-info",
"predicates",
"rand 0.10.3",
"rust_decimal",
@@ -531,6 +539,16 @@ version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "platform-info"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba7c3f5af00d3596a148cc80178ac690152b71c283d15a8bcea0cddfea759a36"
dependencies = [
"libc",
"windows-sys 0.61.2",
]
[[package]]
name = "portable-atomic"
version = "1.15.0"
+2
View File
@@ -29,6 +29,8 @@ shell-words = "1.1"
thiserror = "2.0"
anyhow = "1.0"
uom = { version = "0.36.0", default-features = false, features = ["std", "si", "f64"] }
humantime = "2"
platform-info = "2.2"
[target.'cfg(not(windows))'.dependencies]
libc = "0.2"
+17 -5
View File
@@ -8,14 +8,19 @@ use crate::quantity::TimeUnit;
use anyhow::Result;
mod metadata;
use metadata::Metadata;
#[derive(Serialize, Debug)]
struct HyperfineSummary<'a> {
schema_version: u32,
metadata: &'a Metadata,
results: &'a [BenchmarkResult],
}
#[derive(Default)]
pub struct JsonExporter {}
pub struct JsonExporter {
metadata: Metadata,
}
impl Exporter for JsonExporter {
fn serialize(
@@ -26,7 +31,7 @@ impl Exporter for JsonExporter {
_reference_index: Option<usize>,
) -> Result<Vec<u8>> {
let mut output = to_vec_pretty(&HyperfineSummary {
schema_version: 2,
metadata: &self.metadata,
results,
});
if let Ok(ref mut content) = output {
@@ -62,12 +67,19 @@ fn test_json_optional_memory() {
}]),
..BenchmarkResult::default()
};
let output = JsonExporter::default()
let mut exporter = JsonExporter::default();
exporter.metadata.start_time = Some("2000-02-29T12:34:56Z".to_owned());
let output = exporter
.serialize(&[result], None, SortOrder::Command, None)
.unwrap();
let actual: serde_json::Value = serde_json::from_slice(&output).unwrap();
let mut expected = json!({
"schema_version": 2,
"metadata": {
"json_schema_version": metadata::JSON_SCHEMA_VERSION,
"hyperfine_version": env!("CARGO_PKG_VERSION"),
"start_time": "2000-02-29T12:34:56Z",
"platform": exporter.metadata.platform
},
"results": [{
"command": "example",
"measurements": [{
+63
View File
@@ -0,0 +1,63 @@
use serde::Serialize;
pub(super) const JSON_SCHEMA_VERSION: u32 = 2;
#[derive(Debug, Serialize)]
pub(super) struct Metadata {
json_schema_version: u32,
hyperfine_version: &'static str,
#[serde(skip_serializing_if = "Option::is_none")]
pub start_time: Option<String>,
pub platform: Platform,
}
impl Default for Metadata {
fn default() -> Self {
Self {
json_schema_version: JSON_SCHEMA_VERSION,
hyperfine_version: env!("CARGO_PKG_VERSION"),
start_time: utc_now(),
platform: Platform::detect(),
}
}
}
fn utc_now() -> Option<String> {
use std::{fmt::Write, time::SystemTime};
let now = SystemTime::now();
now.duration_since(SystemTime::UNIX_EPOCH).ok()?;
let mut timestamp = String::new();
write!(&mut timestamp, "{}", humantime::format_rfc3339_seconds(now)).ok()?;
Some(timestamp)
}
#[derive(Debug, Default, Serialize)]
pub(super) struct Platform {
#[serde(skip_serializing_if = "Option::is_none")]
os: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
architecture: Option<String>,
}
impl Platform {
fn detect() -> Self {
use platform_info::{PlatformInfo, PlatformInfoAPI, UNameAPI};
let Ok(info) = PlatformInfo::new() else {
return Self::default();
};
Self {
os: Some(info.sysname().to_string_lossy().into_owned()),
architecture: Some(info.machine().to_string_lossy().into_owned()),
}
}
}
#[test]
fn unavailable_platform_fields_are_omitted() {
assert_eq!(
serde_json::to_value(Platform::default()).unwrap(),
serde_json::json!({})
);
}