Skip to content

Commit c541cb9

Browse files
AnHeuermannclaude
andauthored
Generate machine readable summary.json (#8)
* Log meta information and results in a machine readable summary.json --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 188e620 commit c541cb9

File tree

7 files changed

+229
-19
lines changed

7 files changed

+229
-19
lines changed

.gitignore

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,5 @@
1313
.CondaPkg/
1414

1515
# Generated files and example results
16-
*.log
17-
*.html
18-
!asserts/*.html
19-
*.csv
20-
*.bmo
16+
results/
2117
MAP-LIB_ReferenceResults/
22-
main/

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,15 @@ main(
4949
version = "4.1.0",
5050
filter = "Modelica.Electrical.Analog.Examples.ChuaCircuit",
5151
omc_exe = "omc",
52-
results_root = "main/Modelica/4.1.0/",
52+
results_root = "results/main/Modelica/4.1.0/",
5353
ref_root = "MAP-LIB_ReferenceResults"
5454
)
5555
```
5656

5757
Preview the generated HTML report at `main/Modelica/4.1.0/report.html`.
5858

5959
```bash
60-
python -m http.server -d main/Modelica/4.1.0/
60+
python -m http.server -d results/main/Modelica/4.1.0/
6161
```
6262

6363
## License

src/BaseModelicaLibraryTesting.jl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,13 @@ include("export.jl")
1414
include("parse_bm.jl")
1515
include("simulate.jl")
1616
include("report.jl")
17+
include("summary.jl")
1718
include("pipeline.jl")
1819

1920
# ── Public API ─────────────────────────────────────────────────────────────────
2021

2122
# Shared types and constants
22-
export ModelResult, CompareSettings
23+
export ModelResult, CompareSettings, RunInfo
2324
export LIBRARY, LIBRARY_VERSION, CMP_REL_TOL, CMP_ABS_TOL
2425

2526
# Comparison configuration
@@ -36,6 +37,9 @@ export compare_with_reference, write_diff_html
3637
# HTML report
3738
export generate_report
3839

40+
# Summary JSON
41+
export RunSummary, write_summary, load_summary
42+
3943
# Top-level orchestration
4044
export test_model, main
4145

src/pipeline.jl

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ function main(;
6666
results_root :: String = "",
6767
ref_root :: String = get(ENV, "MAPLIB_REF", ""),
6868
)
69+
t0 = time()
70+
6971
if isempty(results_root)
7072
results_root = joinpath(library, version)
7173
end
@@ -76,6 +78,7 @@ function main(;
7678
@info "Starting OMC session ($(omc_exe))..."
7779
omc = OMJulia.OMCSession(omc_exe)
7880

81+
omc_version = "unknown"
7982
results = ModelResult[]
8083
try
8184
omc_version = sendExpression(omc, "getVersion()")
@@ -136,6 +139,23 @@ function main(;
136139
OMJulia.quit(omc)
137140
end
138141

139-
generate_report(results, results_root, library, version)
142+
cpu_info = Sys.cpu_info()
143+
info = RunInfo(
144+
library,
145+
version,
146+
something(filter, ""),
147+
omc_exe,
148+
results_root,
149+
ref_root,
150+
omc_version,
151+
string(pkgversion(BaseModelica)),
152+
isempty(cpu_info) ? "unknown" : strip(cpu_info[1].model),
153+
length(cpu_info),
154+
Sys.total_memory() / 1024^3,
155+
time() - t0,
156+
)
157+
158+
generate_report(results, results_root, info)
159+
write_summary(results, results_root, info)
140160
return results
141161
end

src/report.jl

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import Dates: now
44
import Printf: @sprintf
5-
import BaseModelica
65

76
function _status_cell(ok::Bool, t::Float64, logFile::Union{String,Nothing})
87
link = isnothing(logFile) ? "" : """ <a href="$(logFile)">(log)</a>"""
@@ -34,13 +33,22 @@ function rel_log_file_or_nothing(results_root::String, model::String,
3433
isfile(path) ? joinpath("files", model, "$(model)_$(phase).log") : nothing
3534
end
3635

36+
function _format_duration(t::Float64)::String
37+
t < 60 && return @sprintf("%.1f s", t)
38+
m = div(floor(Int, t), 60)
39+
s = floor(Int, t) % 60
40+
m < 60 && return @sprintf("%d min %d s", m, s)
41+
h = div(m, 60)
42+
return @sprintf("%d h %d min %d s", h, m % 60, s)
43+
end
44+
3745
"""
38-
generate_report(results, results_root, library, version) → report_path
46+
generate_report(results, results_root, info) → report_path
3947
4048
Write an `index.html` overview report to `results_root` and return its path.
4149
"""
4250
function generate_report(results::Vector{ModelResult}, results_root::String,
43-
library::String, version::String)
51+
info::RunInfo)
4452
n = length(results)
4553
n_exp = count(r -> r.export_success, results)
4654
n_par = count(r -> r.parse_success, results)
@@ -64,14 +72,16 @@ function generate_report(results::Vector{ModelResult}, results_root::String,
6472
$(_cmp_cell(r, results_root))
6573
</tr>""" for r in results], "\n")
6674

67-
omc_ver = try sendExpression(OMJulia.OMCSession("omc"), "getVersion()") catch; "unknown" end
68-
bm_ver = string(pkgversion(BaseModelica))
75+
filter_row = isempty(info.filter) ? "" : "<br>Filter: $(info.filter)"
76+
ref_row = isempty(info.ref_root) ? "" : "<br>Reference results: $(info.ref_root)"
77+
ram_str = @sprintf("%.1f", info.ram_gb)
78+
time_str = _format_duration(info.total_time_s)
6979

7080
html = """<!DOCTYPE html>
7181
<html lang="en">
7282
<head>
7383
<meta charset="UTF-8"/>
74-
<title>$library $version — Base Modelica / MTK Results</title>
84+
<title>$(info.library) $(info.lib_version) — Base Modelica / MTK Results</title>
7585
<style>
7686
body { font-family: sans-serif; margin: 2em; font-size: 14px; }
7787
h1 { font-size: 1.4em; }
@@ -86,10 +96,13 @@ function generate_report(results::Vector{ModelResult}, results_root::String,
8696
</style>
8797
</head>
8898
<body>
89-
<h1>$library $version — Base Modelica / MTK Pipeline Test Results</h1>
99+
<h1>$(info.library) $(info.lib_version) — Base Modelica / MTK Pipeline Test Results</h1>
90100
<p>Generated: $(now())<br>
91-
OpenModelica: $omc_ver<br>
92-
BaseModelica.jl: $bm_ver</p>
101+
OpenModelica: $(info.omc_version)<br>
102+
BaseModelica.jl: $(info.bm_version)$(filter_row)$(ref_row)</p>
103+
<p>CPU: $(info.cpu_model) ($(info.cpu_threads) threads)<br>
104+
RAM: $(ram_str) GiB<br>
105+
Total run time: $(time_str)</p>
93106
94107
<table style="width:auto; margin-bottom:1.5em;">
95108
<tr><th>Stage</th><th>Passed</th><th>Total</th><th>Rate</th></tr>

src/summary.jl

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# ── Summary JSON serialization ─────────────────────────────────────────────────
2+
3+
function _esc_json(s::String)::String
4+
replace(s, "\\" => "\\\\", "\"" => "\\\"", "\n" => "\\n")
5+
end
6+
7+
"""
8+
write_summary(results, results_root, info)
9+
10+
Write a `summary.json` to `results_root` encoding run settings, tool versions,
11+
machine info, and per-model pipeline pass/fail data.
12+
Called automatically by `main()` at the end of each run.
13+
"""
14+
function write_summary(
15+
results :: Vector{ModelResult},
16+
results_root :: String,
17+
info :: RunInfo,
18+
)
19+
path = joinpath(results_root, "summary.json")
20+
open(path, "w") do io
21+
print(io, "{\n")
22+
print(io, " \"library\": \"$(_esc_json(info.library))\",\n")
23+
print(io, " \"lib_version\": \"$(_esc_json(info.lib_version))\",\n")
24+
print(io, " \"filter\": \"$(_esc_json(info.filter))\",\n")
25+
print(io, " \"omc_exe\": \"$(_esc_json(info.omc_exe))\",\n")
26+
print(io, " \"results_root\": \"$(_esc_json(info.results_root))\",\n")
27+
print(io, " \"ref_root\": \"$(_esc_json(info.ref_root))\",\n")
28+
print(io, " \"omc_version\": \"$(_esc_json(info.omc_version))\",\n")
29+
print(io, " \"bm_version\": \"$(_esc_json(info.bm_version))\",\n")
30+
print(io, " \"cpu_model\": \"$(_esc_json(info.cpu_model))\",\n")
31+
print(io, " \"cpu_threads\": $(info.cpu_threads),\n")
32+
print(io, " \"ram_gb\": $(@sprintf "%.2f" info.ram_gb),\n")
33+
print(io, " \"total_time_s\": $(@sprintf "%.2f" info.total_time_s),\n")
34+
print(io, " \"models\": [\n")
35+
for (i, r) in enumerate(results)
36+
sep = i < length(results) ? "," : ""
37+
print(io,
38+
" {\"name\":\"$(_esc_json(r.name))\"," *
39+
"\"export\":$(r.export_success)," *
40+
"\"parse\":$(r.parse_success)," *
41+
"\"sim\":$(r.sim_success)," *
42+
"\"cmp_total\":$(r.cmp_total)," *
43+
"\"cmp_pass\":$(r.cmp_pass)}$sep\n")
44+
end
45+
print(io, " ]\n}\n")
46+
end
47+
@info "summary.json written to $results_root"
48+
end
49+
50+
# ── Summary type and JSON loading ──────────────────────────────────────────────
51+
52+
"""
53+
RunSummary
54+
55+
Parsed contents of a single `summary.json` file.
56+
57+
# Fields
58+
- `library` — Modelica library name (e.g. `"Modelica"`)
59+
- `lib_version` — library version (e.g. `"4.1.0"`)
60+
- `filter` — model name filter regex, or `""` when none was given
61+
- `omc_exe` — path / command used to launch OMC
62+
- `results_root` — absolute path where results were written
63+
- `ref_root` — absolute path to reference results, or `""` when unused
64+
- `omc_version` — OMC version string
65+
- `bm_version` — BaseModelica.jl version string (e.g. `"1.6.0"`)
66+
- `cpu_model` — CPU model name
67+
- `cpu_threads` — number of logical CPU threads
68+
- `ram_gb` — total system RAM in GiB
69+
- `total_time_s` — wall-clock duration of the full test run in seconds
70+
- `models` — vector of per-model dicts; each has keys
71+
`"name"`, `"export"`, `"parse"`, `"sim"`, `"cmp_total"`, `"cmp_pass"`
72+
"""
73+
struct RunSummary
74+
library :: String
75+
lib_version :: String
76+
filter :: String
77+
omc_exe :: String
78+
results_root :: String
79+
ref_root :: String
80+
omc_version :: String
81+
bm_version :: String
82+
cpu_model :: String
83+
cpu_threads :: Int
84+
ram_gb :: Float64
85+
total_time_s :: Float64
86+
models :: Vector{Dict{String,Any}}
87+
end
88+
89+
"""
90+
load_summary(results_root) → RunSummary or nothing
91+
92+
Read and parse the `summary.json` written by `write_summary` from `results_root`.
93+
Returns `nothing` if the file does not exist or cannot be parsed.
94+
"""
95+
function load_summary(results_root::String)::Union{RunSummary,Nothing}
96+
path = joinpath(results_root, "summary.json")
97+
isfile(path) || return nothing
98+
txt = read(path, String)
99+
100+
_str(key) = begin
101+
m = match(Regex("\"$(key)\"\\s*:\\s*\"([^\"]*)\""), txt)
102+
m === nothing ? "" : string(m.captures[1])
103+
end
104+
_int(key) = begin
105+
m = match(Regex("\"$(key)\"\\s*:\\s*(\\d+)"), txt)
106+
m === nothing ? 0 : parse(Int, m.captures[1])
107+
end
108+
_float(key) = begin
109+
m = match(Regex("\"$(key)\"\\s*:\\s*([\\d.]+)"), txt)
110+
m === nothing ? 0.0 : parse(Float64, m.captures[1])
111+
end
112+
113+
models = Dict{String,Any}[]
114+
for m in eachmatch(
115+
r"\{\"name\":\"([^\"]*)\",\"export\":(true|false),\"parse\":(true|false),\"sim\":(true|false),\"cmp_total\":(\d+),\"cmp_pass\":(\d+)\}",
116+
txt)
117+
push!(models, Dict{String,Any}(
118+
"name" => string(m.captures[1]),
119+
"export" => m.captures[2] == "true",
120+
"parse" => m.captures[3] == "true",
121+
"sim" => m.captures[4] == "true",
122+
"cmp_total" => parse(Int, m.captures[5]),
123+
"cmp_pass" => parse(Int, m.captures[6]),
124+
))
125+
end
126+
return RunSummary(
127+
_str("library"),
128+
_str("lib_version"),
129+
_str("filter"),
130+
_str("omc_exe"),
131+
_str("results_root"),
132+
_str("ref_root"),
133+
_str("omc_version"),
134+
_str("bm_version"),
135+
_str("cpu_model"),
136+
_int("cpu_threads"),
137+
_float("ram_gb"),
138+
_float("total_time_s"),
139+
models,
140+
)
141+
end

src/types.jl

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,43 @@ Base.@kwdef mutable struct CompareSettings
3333
error_fn :: Symbol = :mixed
3434
end
3535

36+
# ── Run metadata ───────────────────────────────────────────────────────────────
37+
38+
"""
39+
RunInfo
40+
41+
Metadata about a single test run, collected by `main()` and written into both
42+
`index.html` and `summary.json`.
43+
44+
# Fields
45+
- `library` — Modelica library name (e.g. `"Modelica"`)
46+
- `lib_version` — library version (e.g. `"4.1.0"`)
47+
- `filter` — model name filter regex, or `""` when none was given
48+
- `omc_exe` — path / command used to launch OMC
49+
- `results_root` — absolute path where results are written
50+
- `ref_root` — absolute path to reference results, or `""` when unused
51+
- `omc_version` — version string returned by `getVersion()`, e.g. `"v1.23.0"`
52+
- `bm_version` — BaseModelica.jl version string, e.g. `"1.6.0"`
53+
- `cpu_model` — CPU model name from `Sys.cpu_info()`
54+
- `cpu_threads` — number of logical CPU threads
55+
- `ram_gb` — total system RAM in GiB
56+
- `total_time_s` — wall-clock duration of the full test run in seconds
57+
"""
58+
struct RunInfo
59+
library :: String
60+
lib_version :: String
61+
filter :: String # "" when no filter was given
62+
omc_exe :: String
63+
results_root :: String
64+
ref_root :: String # "" when no reference root was given
65+
omc_version :: String
66+
bm_version :: String
67+
cpu_model :: String
68+
cpu_threads :: Int
69+
ram_gb :: Float64
70+
total_time_s :: Float64
71+
end
72+
3673
# ── Result type ────────────────────────────────────────────────────────────────
3774

3875
struct ModelResult

0 commit comments

Comments
 (0)