///|
/// A benchmark workload scenario that can be documented and selected.
pub(all) struct WorkloadScenario {
id : String
name : String
category : String
description : String
tags : Array[String]
data_size : Int
warmup : Int
iterations : Int
expected_unit : String
complexity : String
priority : Int
} derive(Eq, Debug)
///|
/// Create a workload scenario descriptor.
pub fn WorkloadScenario::new(
id : String,
name : String,
category? : String = "general",
description? : String = "",
tags? : Array[String] = [],
data_size? : Int = 1,
warmup? : Int = 1,
iterations? : Int = 10,
expected_unit? : String = "us",
complexity? : String = "O(1)",
priority? : Int = 50,
) -> WorkloadScenario {
{
id,
name,
category,
description,
tags,
data_size: clamp_positive(data_size),
warmup: clamp_non_negative(warmup),
iterations: clamp_positive(iterations),
expected_unit,
complexity,
priority: clamp_priority(priority),
}
}
///|
fn clamp_priority(value : Int) -> Int {
if value < 0 {
0
} else if value > 100 {
100
} else {
value
}
}
///|
/// Build a benchmark runner using this scenario's sampling settings.
pub fn WorkloadScenario::runner(self : WorkloadScenario) -> BenchmarkRunner {
BenchmarkRunner::new(warmup=self.warmup, iterations=self.iterations)
}
///|
/// Whether the scenario has a tag.
pub fn WorkloadScenario::has_tag(self : WorkloadScenario, tag : String) -> Bool {
for item in self.tags {
if item == tag {
return true
}
}
false
}
///|
/// Whether the scenario belongs to a category.
pub fn WorkloadScenario::is_category(
self : WorkloadScenario,
category : String,
) -> Bool {
self.category == category
}
///|
/// Render tags as comma-separated text.
pub fn WorkloadScenario::tags_text(self : WorkloadScenario) -> String {
let mut body = ""
for i in 0.. 0 {
body = body + ", "
}
body = body + self.tags[i]
}
body
}
///|
/// Render a scenario as Markdown row.
pub fn WorkloadScenario::to_markdown_row(self : WorkloadScenario) -> String {
"| \{escape_markdown(self.id)} | \{escape_markdown(self.name)} | \{escape_markdown(self.category)} | \{self.data_size} | \{self.warmup} | \{self.iterations} | \{escape_markdown(self.complexity)} | \{self.priority} | \{escape_markdown(self.tags_text())} |\n"
}
///|
/// Render a scenario as JSON.
pub fn WorkloadScenario::to_json(self : WorkloadScenario) -> String {
"{" +
"\"id\":\"\{escape_json(self.id)}\"," +
"\"name\":\"\{escape_json(self.name)}\"," +
"\"category\":\"\{escape_json(self.category)}\"," +
"\"description\":\"\{escape_json(self.description)}\"," +
"\"tags\":\{string_array_to_json(self.tags)}," +
"\"data_size\":\{self.data_size}," +
"\"warmup\":\{self.warmup}," +
"\"iterations\":\{self.iterations}," +
"\"expected_unit\":\"\{escape_json(self.expected_unit)}\"," +
"\"complexity\":\"\{escape_json(self.complexity)}\"," +
"\"priority\":\{self.priority}" +
"}"
}
///|
fn string_array_to_json(values : Array[String]) -> String {
let mut body = "["
for i in 0.. 0 {
body = body + ","
}
body = body + "\"\{escape_json(values[i])}\""
}
body + "]"
}
///|
/// A catalog of recommended workload scenarios.
pub(all) struct ScenarioCatalog {
title : String
scenarios : Array[WorkloadScenario]
} derive(Eq, Debug)
///|
/// Create an empty scenario catalog.
pub fn ScenarioCatalog::new(
title? : String = "MoonBench Scenario Catalog",
) -> ScenarioCatalog {
{ title, scenarios: [] }
}
///|
/// Append one scenario.
pub fn ScenarioCatalog::add(
self : ScenarioCatalog,
scenario : WorkloadScenario,
) -> ScenarioCatalog {
let scenarios = self.scenarios.copy()
scenarios.push(scenario)
{ ..self, scenarios, }
}
///|
/// Number of scenarios.
pub fn ScenarioCatalog::count(self : ScenarioCatalog) -> Int {
self.scenarios.length()
}
///|
/// Total planned iterations in the catalog.
pub fn ScenarioCatalog::total_iterations(self : ScenarioCatalog) -> Int {
for scenario in self.scenarios; acc = 0 {
continue acc + scenario.iterations
} nobreak {
acc
}
}
///|
/// Average priority across scenarios.
pub fn ScenarioCatalog::average_priority(self : ScenarioCatalog) -> Double {
if self.scenarios.length() == 0 {
return 0.0
}
let total = for scenario in self.scenarios; acc = 0 {
continue acc + scenario.priority
} nobreak {
acc
}
total.to_double() / self.scenarios.length().to_double()
}
///|
/// Find a scenario by id.
pub fn ScenarioCatalog::find(
self : ScenarioCatalog,
id : String,
) -> WorkloadScenario {
for scenario in self.scenarios {
if scenario.id == id {
return scenario
}
}
WorkloadScenario::new("", "")
}
///|
/// Whether the catalog contains a scenario id.
pub fn ScenarioCatalog::contains_id(
self : ScenarioCatalog,
id : String,
) -> Bool {
self.find(id).id != ""
}
///|
/// Filter scenarios by category.
pub fn ScenarioCatalog::filter_category(
self : ScenarioCatalog,
category : String,
) -> ScenarioCatalog {
let mut catalog = ScenarioCatalog::new(title=self.title + " / " + category)
for scenario in self.scenarios {
if scenario.category == category {
catalog = catalog.add(scenario)
}
}
catalog
}
///|
/// Filter scenarios by tag.
pub fn ScenarioCatalog::filter_tag(
self : ScenarioCatalog,
tag : String,
) -> ScenarioCatalog {
let mut catalog = ScenarioCatalog::new(title=self.title + " / tag:" + tag)
for scenario in self.scenarios {
if scenario.has_tag(tag) {
catalog = catalog.add(scenario)
}
}
catalog
}
///|
/// Filter scenarios with priority greater than or equal to threshold.
pub fn ScenarioCatalog::filter_priority(
self : ScenarioCatalog,
minimum_priority : Int,
) -> ScenarioCatalog {
let mut catalog = ScenarioCatalog::new(title=self.title + " / priority")
for scenario in self.scenarios {
if scenario.priority >= minimum_priority {
catalog = catalog.add(scenario)
}
}
catalog
}
///|
/// Count scenarios by category.
pub fn ScenarioCatalog::count_category(
self : ScenarioCatalog,
category : String,
) -> Int {
for scenario in self.scenarios; acc = 0 {
if scenario.category == category {
continue acc + 1
} else {
continue acc
}
} nobreak {
acc
}
}
///|
/// Count scenarios by tag.
pub fn ScenarioCatalog::count_tag(self : ScenarioCatalog, tag : String) -> Int {
for scenario in self.scenarios; acc = 0 {
if scenario.has_tag(tag) {
continue acc + 1
} else {
continue acc
}
} nobreak {
acc
}
}
///|
/// Build a suite with manual estimated costs for all scenarios.
pub fn ScenarioCatalog::estimated_suite(
self : ScenarioCatalog,
) -> BenchmarkSuite {
let mut suite = BenchmarkSuite::new(
title=self.title + " Estimated Costs",
package_name="Han-Wentao/moonbench",
package_version="0.2.0",
)
for scenario in self.scenarios {
let runner = scenario.runner()
let result = runner.run(scenario.id, () => scenario.estimated_cost_us())
suite = suite.add(result)
}
suite
}
///|
/// Estimate cost in microseconds from scenario metadata.
pub fn WorkloadScenario::estimated_cost_us(self : WorkloadScenario) -> Double {
let size = self.data_size.to_double()
let complexity_factor = if self.complexity == "O(1)" {
1.0
} else if self.complexity == "O(log n)" {
2.0
} else if self.complexity == "O(n)" {
5.0
} else if self.complexity == "O(n log n)" {
8.0
} else if self.complexity == "O(n^2)" {
20.0
} else {
10.0
}
size.sqrt() * complexity_factor + self.priority.to_double() / 10.0
}
///|
/// Render catalog as Markdown.
pub fn ScenarioCatalog::to_markdown(self : ScenarioCatalog) -> String {
let mut body = "## \{escape_markdown(self.title)}\n\n"
body = body + "- Scenarios: \{self.count()}\n"
body = body + "- Total iterations: \{self.total_iterations()}\n"
body = body + "- Average priority: \{self.average_priority()}\n\n"
body = body +
"| id | name | category | data_size | warmup | iterations | complexity | priority | tags |\n"
body = body + "| --- | --- | --- | ---: | ---: | ---: | --- | ---: | --- |\n"
for scenario in self.scenarios {
body = body + scenario.to_markdown_row()
}
body
}
///|
/// Render catalog as JSON.
pub fn ScenarioCatalog::to_json(self : ScenarioCatalog) -> String {
let mut body = "{"
body = body + "\"title\":\"\{escape_json(self.title)}\","
body = body + "\"count\":\{self.count()},"
body = body + "\"total_iterations\":\{self.total_iterations()},"
body = body + "\"average_priority\":\{self.average_priority()},"
body = body + "\"scenarios\":["
for i in 0.. 0 {
body = body + ","
}
body = body + self.scenarios[i].to_json()
}
body + "]}"
}
///|
/// Build the standard MoonBench scenario catalog.
pub fn ScenarioCatalog::moonbench_standard() -> ScenarioCatalog {
ScenarioCatalog::new(title="MoonBench Standard Scenario Catalog")
.add(
WorkloadScenario::new(
"counter-small",
"Small counter update",
category="state",
description="Tiny mutable state update used as smoke benchmark",
tags=["state", "smoke", "micro"],
data_size=1,
warmup=2,
iterations=20,
complexity="O(1)",
priority=90,
),
)
.add(
WorkloadScenario::new(
"counter-batch",
"Batch counter update",
category="state",
description="Repeated mutable state updates in one benchmark body",
tags=["state", "loop", "micro"],
data_size=100,
warmup=2,
iterations=20,
complexity="O(n)",
priority=85,
),
)
.add(
WorkloadScenario::new(
"array-scan-small",
"Small array scan",
category="array",
description="Linear traversal over a small array",
tags=["array", "scan", "linear"],
data_size=64,
warmup=3,
iterations=30,
complexity="O(n)",
priority=80,
),
)
.add(
WorkloadScenario::new(
"array-scan-medium",
"Medium array scan",
category="array",
description="Linear traversal over a medium array",
tags=["array", "scan", "linear"],
data_size=1024,
warmup=3,
iterations=30,
complexity="O(n)",
priority=78,
),
)
.add(
WorkloadScenario::new(
"array-build",
"Array build",
category="array",
description="Append values to a fresh array",
tags=["array", "allocation", "append"],
data_size=512,
warmup=3,
iterations=25,
complexity="O(n)",
priority=76,
),
)
.add(
WorkloadScenario::new(
"array-copy",
"Array copy",
category="array",
description="Copy a prepared array before mutation",
tags=["array", "copy", "memory"],
data_size=512,
warmup=3,
iterations=25,
complexity="O(n)",
priority=75,
),
)
.add(
WorkloadScenario::new(
"sort-small",
"Small sort",
category="algorithm",
description="Sort a small numeric collection",
tags=["sort", "algorithm", "array"],
data_size=64,
warmup=3,
iterations=20,
complexity="O(n log n)",
priority=82,
),
)
.add(
WorkloadScenario::new(
"sort-medium",
"Medium sort",
category="algorithm",
description="Sort a medium numeric collection",
tags=["sort", "algorithm", "array"],
data_size=1024,
warmup=3,
iterations=15,
complexity="O(n log n)",
priority=80,
),
)
.add(
WorkloadScenario::new(
"string-concat-small",
"Small string concat",
category="string",
description="Build a short string with repeated concatenation",
tags=["string", "concat", "allocation"],
data_size=32,
warmup=2,
iterations=20,
complexity="O(n)",
priority=70,
),
)
.add(
WorkloadScenario::new(
"string-escape-json",
"JSON escaping",
category="string",
description="Escape text for JSON benchmark artifacts",
tags=["string", "json", "report"],
data_size=256,
warmup=2,
iterations=20,
complexity="O(n)",
priority=84,
),
)
.add(
WorkloadScenario::new(
"markdown-row",
"Markdown row rendering",
category="report",
description="Render one benchmark row for Markdown",
tags=["markdown", "report", "format"],
data_size=1,
warmup=2,
iterations=25,
complexity="O(1)",
priority=88,
),
)
.add(
WorkloadScenario::new(
"json-result",
"JSON result rendering",
category="report",
description="Render benchmark result JSON",
tags=["json", "report", "format"],
data_size=10,
warmup=2,
iterations=25,
complexity="O(n)",
priority=88,
),
)
.add(
WorkloadScenario::new(
"csv-suite",
"CSV suite rendering",
category="report",
description="Render a suite as CSV",
tags=["csv", "report", "format"],
data_size=20,
warmup=2,
iterations=25,
complexity="O(n)",
priority=86,
),
)
.add(
WorkloadScenario::new(
"baseline-lookup",
"Baseline lookup",
category="baseline",
description="Find a baseline by benchmark name",
tags=["baseline", "lookup", "comparison"],
data_size=50,
warmup=2,
iterations=25,
complexity="O(n)",
priority=90,
),
)
.add(
WorkloadScenario::new(
"baseline-compare",
"Baseline compare",
category="baseline",
description="Compare benchmark result with historical mean",
tags=["baseline", "comparison", "gate"],
data_size=50,
warmup=2,
iterations=25,
complexity="O(n)",
priority=90,
),
)
.add(
WorkloadScenario::new(
"trend-analysis",
"Trend analysis",
category="analysis",
description="Analyze a sample series for direction and noise",
tags=["trend", "analysis", "quality"],
data_size=30,
warmup=2,
iterations=20,
complexity="O(n)",
priority=87,
),
)
.add(
WorkloadScenario::new(
"moving-average",
"Moving average",
category="analysis",
description="Compute a moving average over a sample series",
tags=["trend", "analysis", "window"],
data_size=60,
warmup=2,
iterations=20,
complexity="O(n)",
priority=82,
),
)
.add(
WorkloadScenario::new(
"quality-gate",
"Quality gate evaluation",
category="quality",
description="Evaluate comparison rows against a threshold policy",
tags=["quality", "ci", "gate"],
data_size=20,
warmup=2,
iterations=25,
complexity="O(n)",
priority=92,
),
)
.add(
WorkloadScenario::new(
"artifact-bundle",
"Artifact bundle rendering",
category="submission",
description="Render contest artifact metadata",
tags=["artifact", "submission", "report"],
data_size=10,
warmup=2,
iterations=20,
complexity="O(n)",
priority=75,
),
)
.add(
WorkloadScenario::new(
"checklist-render",
"Checklist rendering",
category="submission",
description="Render a contest checklist",
tags=["checklist", "submission", "report"],
data_size=10,
warmup=2,
iterations=20,
complexity="O(n)",
priority=78,
),
)
.add(
WorkloadScenario::new(
"document-build",
"Markdown document build",
category="submission",
description="Compose a multi-section Markdown report",
tags=["markdown", "document", "submission"],
data_size=10,
warmup=2,
iterations=20,
complexity="O(n)",
priority=76,
),
)
.add(
WorkloadScenario::new(
"percentile-small",
"Small percentile summary",
category="statistics",
description="Compute p90 and p95 on a small sample set",
tags=["stats", "percentile", "summary"],
data_size=20,
warmup=2,
iterations=30,
complexity="O(n log n)",
priority=86,
),
)
.add(
WorkloadScenario::new(
"percentile-medium",
"Medium percentile summary",
category="statistics",
description="Compute p90 and p95 on a medium sample set",
tags=["stats", "percentile", "summary"],
data_size=200,
warmup=2,
iterations=20,
complexity="O(n log n)",
priority=84,
),
)
.add(
WorkloadScenario::new(
"stddev-small",
"Small standard deviation",
category="statistics",
description="Compute sample standard deviation on a small sample set",
tags=["stats", "stddev", "summary"],
data_size=20,
warmup=2,
iterations=30,
complexity="O(n)",
priority=80,
),
)
.add(
WorkloadScenario::new(
"validation-name",
"Benchmark name validation",
category="validation",
description="Validate benchmark names before reporting",
tags=["validation", "name", "safety"],
data_size=1,
warmup=2,
iterations=30,
complexity="O(n)",
priority=72,
),
)
.add(
WorkloadScenario::new(
"validation-normalize",
"Benchmark name normalization",
category="validation",
description="Normalize names for compact reports",
tags=["validation", "name", "format"],
data_size=1,
warmup=2,
iterations=30,
complexity="O(n)",
priority=72,
),
)
}