P28-01-COLLECTOR: 現HEADの性能基準:windowed x1/x3・Operations ON collector

This commit is contained in:
Agent Control Runner
2026-10-20 21:48:59 +09:00
parent 7bf44b072c
commit 3ac3243dfb
4 changed files with 222 additions and 34 deletions
@@ -8,6 +8,24 @@ from pathlib import Path
import re
PREFIX = "P28_01_PERF_REPORT="
RAW_SOURCE = "clock.performance_budget (product diagnostic, raw, not a verdict)"
CAP_SOURCE = "SPEC §5 phase cap"
DEADLINE_SOURCE = "H6 probe deadline (cap−60s)"
VERDICT_SCOPE = ("Budget verdicts come only from windowed phases, using kernel samples, for "
"simulation_x1, simulation_x3 and main_thread_stall. Save, load and path_query "
"verdicts are null in this collector, reason: not_measured_with_kernel_definition.")
RAW_FIELDS = {"budget_at_warmup_end", "budget_at_phase_end", "kernel_at_warmup_end", "kernel_at_phase_end"}
METRICS = {"simulation_x1", "simulation_x3", "main_thread_stall"}
R02_FIELDS = {
**{"x1_outer_advance_one_minute_wall_" + stat:
("advance_one_minute_wall_ms_x1." + stat, stat,
"headless outer advance_one_minute wall; not kernel-defined") for stat in ("mean", "p95", "max")},
"path_cold_p95": ("path_after_tick_ms.found_cold.p95", "p95", "headless probe path timing; not kernel-defined"),
"snapshot_mean": ("save_load_ms.snapshot.mean", "mean", "headless probe snapshot timing; not kernel-defined"),
**{kind + "_" + stat: ("save_load_ms." + kind + "." + stat, stat,
"headless probe " + kind + " timing; not kernel-defined")
for kind in ("manual", "autosave", "load") for stat in ("mean", "max")},
}
PHASES = {"R0": ("R", 1, 0, 10), "P1": ("R", 1, 30, 120),
"P2": ("R", 3, 30, 120), "O0": ("O", 1, 0, 10),
"P3": ("O", 1, 30, 120), "P4": ("O", 3, 30, 120)}
@@ -28,6 +46,64 @@ INTEGER_FIELDS = {
OPAQUE_FIELDS = {"rng_streams", "startup", "kernel_at_warmup_end", "kernel_at_phase_end",
"operations_at_end", "restore_result", "save_policy_after"}
def check_budget_scope(value, report_paths=(), path=(), raw=False):
"""Walk the entire receipt; privileges are tied to exact report locations."""
if isinstance(value, list):
for i, child in enumerate(value):
check_budget_scope(child, report_paths, path + (i,), raw)
return
if not isinstance(value, dict):
return
raw_root = any(path == root + (name,) for root in report_paths for name in RAW_FIELDS)
verdict = any(len(path) == len(root) + 2 and path[:len(root)] == root
and path[-2] == "budget_verdict" and path[-1] in METRICS for root in report_paths)
has_pass = bool({"passed", "within_budget"} & value.keys())
in_report = any(path[:len(root)] == root for root in report_paths)
if has_pass and in_report and not raw:
need(verdict, "pass flag copied outside kernel verdict")
if "budget_verdict" in value:
need(path in report_paths and isinstance(value["budget_verdict"], dict)
and set(value["budget_verdict"]) <= METRICS, "budget verdict location/metrics")
if has_pass and {"limit", "limit_us", "limit_ms"} & value.keys():
need(verdict, "budget verdict outside report.budget_verdict")
if raw_root:
need(value.get("source") == RAW_SOURCE, "raw diagnostic source missing")
keys(value, ("source", "data"))
if not raw:
need("budget_exceeded" not in value, "copied product budget_exceeded")
need("limit_us" not in value or verdict, "copied product limit_us")
if "limit_seconds" in value:
need(path and path[-1] in ("projection", "phase_stop", "probe_deadline"), "limit location")
deadline = path[-1] == "probe_deadline"
need(not has_pass and value["limit_seconds"] == (1740 if deadline else 1800)
and value.get("source") == (DEADLINE_SOURCE if deadline else CAP_SOURCE), "execution limit")
for key, child in value.items():
check_budget_scope(child, report_paths, path + (key,), raw or (raw_root and key == "data"))
def check_admission(admission):
check_budget_scope(admission)
need(admission.get("budget_verdict_scope") == VERDICT_SCOPE, "admission verdict scope")
reused = admission["reused_r02"]
def no_verdict_fields(value):
if isinstance(value, dict):
need(all(not k.startswith(("limit", "budget")) and k not in ("passed", "within_budget")
for k in value), "verdict fields in reused_r02")
for child in value.values():
no_verdict_fields(child)
elif isinstance(value, list):
for child in value:
no_verdict_fields(child)
no_verdict_fields(reused)
observations = reused["observations"]
need(set(observations) == set(R02_FIELDS), "r02 observations")
for name, (field, statistic, scope) in R02_FIELDS.items():
item = observations[name]
keys(item, ("value", "unit", "statistic", "scope", "r02_field"))
need(number(item["value"]) and item["value"] >= 0, "r02 value")
need((item["unit"], item["statistic"], item["scope"], item["r02_field"]) ==
("ms", statistic, scope, field), "r02 observation provenance")
def normalize_report(value, path=()):
"""Single ingestion boundary, also used when checking raw stdout binding."""
@@ -187,6 +263,12 @@ def interval(report, name):
def check_report(report):
check_budget_scope(report, ((),))
for name in RAW_FIELDS:
keys(report[name], ("source", "data"))
equal(report["probe_deadline"], {"limit_seconds": 1740, "source": DEADLINE_SOURCE}, "probe deadline")
equal(report["budget_verdict"], budget_verdict(report["budget_at_warmup_end"]["data"],
report["budget_at_phase_end"]["data"], report["speed"]), "kernel interval budget")
keys(report, ("schema", "pid", "phase", "input", "speed", "metadata", "input_sha256",
"frames", "stop_reason", "warmup", "measurement", "actual_minutes",
"elapsed_wall_seconds", "kernel_at_phase_end", "operations_at_end", "end_wall",
@@ -329,8 +411,8 @@ def check_report(report):
interval(report, name)
need(report["budget_scope"] == "kernel process_real_delta only; excludes observer_us", "budget scope")
need(report["render_submit_note"] == "CPU側の描画提出wall、GPU時間ではない", "render submit scope")
equal(report["budget_verdict"], budget_verdict(report["budget_at_warmup_end"],
report["budget_at_phase_end"], speed), "kernel interval budget")
equal(report["budget_verdict"], budget_verdict(report["budget_at_warmup_end"]["data"],
report["budget_at_phase_end"]["data"], speed), "kernel interval budget")
need(number(report["startup_wall_seconds"]) and report["startup_wall_seconds"] >= 0, "startup timing")
for save in report["save_events"]:
keys(save, ("frame", "game_minute", "ok", "operation", "path", "sample_counts"))
@@ -347,6 +429,10 @@ def check_report(report):
def check_receipt(receipt, directory):
from collect_p28_01 import OWNED_FILES, projection, extrapolation, guard_gate
check_budget_scope(receipt, tuple(("phases", i, "report")
for i, row in enumerate(receipt["phases"]) if "report" in row))
check_admission(receipt["admission"])
equal(receipt["admission"], json.loads((directory / "admission.json").read_text()), "admission binding")
need(receipt["source_before"] == receipt["source_after"], "source map changed")
need(receipt["default_player_before"] == receipt["default_player_after"], "player changed")
need(not receipt["cleanup_errors"] and all(r["engine_ended"] is True for r in receipt["cleanup"]), "cleanup")
@@ -359,6 +445,7 @@ def check_receipt(receipt, directory):
rows = receipt["phases"]
need([r["phase"] for r in rows] == ["import", *PHASES], "phase order")
for row in rows:
equal(row["phase_stop"], {"limit_seconds": 1800, "source": CAP_SOURCE}, "phase cap origin")
forecast = None
if row["phase"] in ("P1", "P2", "P3", "P4"):
source = PHASES[row["phase"]][0]
@@ -13,7 +13,7 @@ import uuid
from datetime import datetime, timezone
sys.dont_write_bytecode = True
from check_p28_01_report import check_report, check_receipt, check_probe, normalize_report
from check_p28_01_report import check_report, check_receipt, check_probe, normalize_report, check_admission, R02_FIELDS, VERDICT_SCOPE, CAP_SOURCE
HERE = Path(__file__).resolve().parent
ROOT = HERE.parents[5]
FIXTURE_SHA = "eb4f770071eb07cd7e136fe54c3f78768cc8de7a4264e62a9af1f06a8f90f9bd"
@@ -110,7 +110,7 @@ def projection(stage0):
if evidence["seconds_per_phase"] is not None else None)
return {"seconds_per_phase": evidence["seconds_per_phase"],
"startup_wall_seconds": stage0["report"]["startup_wall_seconds"],
"projected_total_seconds": seconds, "limit_seconds": LIMIT,
"projected_total_seconds": seconds, "limit_seconds": LIMIT, "source": CAP_SOURCE,
"stage0_phase": stage0["phase"],
"stop_reason": "projected_over_limit" if seconds is not None and seconds > LIMIT else None}
@@ -165,7 +165,8 @@ def phase_schedule(rows):
forecast = projection(stage0)
if forecast["stop_reason"]:
rows.append({"phase": label, "passed": False, "engines_started": 0,
"stop_reason": "projected_over_limit", "projection": forecast})
"stop_reason": "projected_over_limit", "projection": forecast,
"phase_stop": {"limit_seconds": LIMIT, "source": CAP_SOURCE}})
continue
yield index, phase, forecast
@@ -195,7 +196,8 @@ def run_engine(outer, command, folder, environment):
capture.get("passed") is True, capture.get("all_captured_ended") is True,
len(capture.get("actual_captured_pids", [])) == 1, all(raw.values()), not bad))
row = {"passed": clean, "process": result, "capture": capture, "raw": raw,
"diagnostics": bad, "timeout_seconds": LIMIT, "argv": command}
"diagnostics": bad, "timeout_seconds": LIMIT, "argv": command,
"phase_stop": {"limit_seconds": LIMIT, "source": CAP_SOURCE}}
row["owned_files"] = {name: {"sha256": sha(folder / name),
"bytes": (folder / name).stat().st_size}
for name in OWNED_FILES if (folder / name).is_file()}
@@ -218,6 +220,24 @@ def inputs(readonly):
return {"manifest_sha256": sha(manifest_path), "files": pins}
def reused_observations(readonly, pins):
relative = "run-r02/after/evidence/report.json"
path = readonly / relative
require(sha(path) == pins["files"][relative], "r02 report SHA changed")
raw = json.loads(path.read_text())
observations = {}
for name, (field, statistic, scope) in R02_FIELDS.items():
value = raw
for part in field.split("."):
value = value[part]
observations[name] = {"value": value, "unit": "ms", "statistic": statistic,
"scope": scope, "r02_field": field}
return {"execution_sha256": pins["files"]["run-r02/execution.json"],
"report_sha256": pins["files"][relative],
"scope": "x1 headless Operations OFF; reused without remeasurement",
"observations": observations}
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--readonly", type=Path, required=True)
@@ -262,19 +282,9 @@ def main():
admission = {"commit": commit, "source_map": before, "source_map_sha256": digest(before),
"readonly": pins, "engine_sha256": ENGINE_SHA, "engine_version": VERSION,
"fixture_sha256": FIXTURE_SHA, "planned_argv": planned, "phases": PHASES,
"reused_r02": {"execution_sha256": pins["files"]["run-r02/execution.json"],
"report_sha256": pins["files"]["run-r02/after/evidence/report.json"],
"scope": "x1 headless Operations OFF; reused without remeasurement",
"tick_ms": {"mean": 826.6, "p95": 1702.7, "max": 2918.1},
"path_cold_p95_ms": 0.61, "snapshot_mean_ms": 77.6,
"manual_save_ms": 297, "autosave_ms": 327,
"load_mean_ms": 3193, "load_max_ms": 3368,
"budget_comparisons_ms": {
"x1_tick_mean": {"value": 826.6, "limit": 4, "passed": False},
"path_cold_p95": {"value": 0.61, "limit": 2, "passed": True},
"manual_save": {"value": 297, "limit": 5000, "passed": True},
"autosave": {"value": 327, "limit": 5000, "passed": True},
"load_max": {"value": 3368, "limit": 8000, "passed": True}}}}
"budget_verdict_scope": VERDICT_SCOPE,
"reused_r02": reused_observations(args.readonly, pins)}
check_admission(admission)
if args.check:
print(json.dumps(dict(admission, stages_created=0, engines_started=0), ensure_ascii=False, indent=2))
return 0
@@ -374,7 +384,7 @@ def main():
cleanup, cleanup_errors = outer.finish_captured(outer.OWNED)
after = source_map(standard, ROOT)
player_after = standard.default_player(ROOT)
receipt = {"phases": rows, "error": error, "source_before": before, "source_after": after,
receipt = {"admission": admission, "phases": rows, "error": error, "source_before": before, "source_after": after,
"probe_check": probe_check,
"default_player_before": player, "default_player_after": player_after,
"cleanup": cleanup, "cleanup_errors": cleanup_errors,
@@ -266,7 +266,9 @@ func begin() -> void:
"render_submit_note": "CPU側の描画提出wall、GPU時間ではない",
"observer_note": "observer_us includes reads, serialization and frame emission; excludes its own timing line and final report; never subtracted",
"sample_definition": "is_minute_sample marks a changed game minute; other frames carry counts_from_minute",
"budget_at_warmup_end": {}, "save_events": save_events, "key_form": key_forms,
"budget_at_warmup_end": {"source": "clock.performance_budget (product diagnostic, raw, not a verdict)", "data": {}},
"kernel_at_warmup_end": {"source": "clock.performance_budget (product diagnostic, raw, not a verdict)", "data": null, "reason": "warmup_not_reached"},
"probe_deadline": {"limit_seconds": 1740, "source": "H6 probe deadline (cap−60s)"}, "save_events": save_events, "key_form": key_forms,
"count_definitions": {"registered": "service collections; walkable cells",
"active": "guests exclude SPAWNING/LEAVING/DESPAWNING; staff current attended unended shift; facilities OPERATING",
"drawn": "world_preview submitted model serial and viewport geometry"},
@@ -355,8 +357,8 @@ func begin() -> void:
last_stall_count = initial_budget.main_thread_stall.lifetime_count
if warmup == 0:
warmup_ended = true
report["kernel_at_warmup_end"] = clock.get_performance_reports()
report["budget_at_warmup_end"] = initial_budget
report["kernel_at_warmup_end"] = {"source": "clock.performance_budget (product diagnostic, raw, not a verdict)", "data": clock.get_performance_reports()}
report["budget_at_warmup_end"] = {"source": "clock.performance_budget (product diagnostic, raw, not a verdict)", "data": initial_budget}
report["startup_wall_seconds"] = float(start_us) / 1000000.0
report["start_minute"] = start_minute
report["start_wall"] = Time.get_unix_time_from_system()
@@ -430,8 +432,8 @@ func sample(observer_started_us: int) -> void:
previous_minute = minute
if not warmup_ended and minute - start_minute >= warmup:
warmup_ended = true
report["kernel_at_warmup_end"] = clock.get_performance_reports()
report["budget_at_warmup_end"] = budget_snapshot()
report["kernel_at_warmup_end"] = {"source": "clock.performance_budget (product diagnostic, raw, not a verdict)", "data": clock.get_performance_reports()}
report["budget_at_warmup_end"] = {"source": "clock.performance_budget (product diagnostic, raw, not a verdict)", "data": budget_snapshot()}
if warmup > 0:
measurement_start = minute
var operations: Dictionary = runtime.get_operations_report()
@@ -540,9 +542,9 @@ func finish(reason: String) -> void:
report["actual_minutes"] = int(clock.absolute_minute) - start_minute if start_us > 0 else 0
report["elapsed_wall_seconds"] = float(Time.get_ticks_usec() - start_us) / 1000000.0 if start_us > 0 else 0.0
report["end_wall"] = Time.get_unix_time_from_system()
report["kernel_at_phase_end"] = clock.get_performance_reports()
report["budget_at_phase_end"] = budget_snapshot()
report["budget_verdict"] = kernel_verdict(report.budget_at_warmup_end, report.budget_at_phase_end)
report["kernel_at_phase_end"] = {"source": "clock.performance_budget (product diagnostic, raw, not a verdict)", "data": clock.get_performance_reports()}
report["budget_at_phase_end"] = {"source": "clock.performance_budget (product diagnostic, raw, not a verdict)", "data": budget_snapshot()}
report["budget_verdict"] = kernel_verdict(report.budget_at_warmup_end.data, report.budget_at_phase_end.data)
report["operations_at_end"] = runtime.get_operations_report()
report["warmup"] = summary("warmup")
report["measurement"] = summary("measurement")
@@ -118,10 +118,97 @@ def synthetic_report():
"statistics": {"count": 10, "average_us": 3000, "p95_us": 3000, "maximum_us": 3000}}
for k, aggregate, limit in (("simulation_x1", "average", 4000),
("main_thread_stall", "maximum", 50000))}
for name in checker.RAW_FIELDS:
report[name] = {"source": checker.RAW_SOURCE, "data": report[name]}
report["probe_deadline"] = {"limit_seconds": 1740, "source": checker.DEADLINE_SOURCE}
return report
def synthetic_admission():
return {"budget_verdict_scope": checker.VERDICT_SCOPE, "reused_r02": {"observations": {
name: {"value": 1.25, "unit": "ms", "statistic": stat, "scope": scope, "r02_field": field}
for name, (field, stat, scope) in checker.R02_FIELDS.items()}}}
class ReportTests(unittest.TestCase):
def test_r1_recursive_verdict_scope(self):
report = synthetic_report()
receipt = {"admission": synthetic_admission(), "phases": [{"report": report, "passed": True}],
"process": {"passed": True}, "capture": {"passed": True},
"probe_check": {"passed": True}, "passed": True}
checker.check_budget_scope(receipt, (("phases", 0, "report"),))
for flag in ("passed", "within_budget"):
for limit in ("limit", "limit_us", "limit_ms"):
for location in ("admission", "extra"):
broken = copy.deepcopy(receipt)
broken.setdefault(location, {})["nested"] = [{flag: True, limit: 4}]
with self.subTest(flag=flag, limit=limit, location=location), self.assertRaises(ValueError):
checker.check_budget_scope(broken, (("phases", 0, "report"),))
self.reject(lambda r: r.update(extra={"passed": True, "limit": 4}))
self.reject(lambda r: r["budget_verdict"].update(path_query=copy.deepcopy(r["budget_verdict"]["simulation_x1"])))
def test_r2_raw_diagnostics_preserved(self):
r = synthetic_report()
original = [{"limit_us": 4000, "budget_exceeded": True, "measured_us": 5000}]
r["kernel_at_phase_end"]["data"] = copy.deepcopy(original)
checker.check_report(r)
self.assertEqual(r["kernel_at_phase_end"]["data"], original)
for name in checker.RAW_FIELDS:
self.reject(lambda r, name=name: r[name].pop("source"))
self.reject(lambda r, name=name: r[name].update(source="invented"))
for field, value in (("budget_exceeded", False), ("limit_us", 4000), ("passed", True)):
self.reject(lambda r, field=field, value=value: r.update(extra={field: value}))
self.reject(lambda r: r["kernel_at_phase_end"].update(data={"passed": True, "limit_us": 4000}))
def test_r3_execution_limit_origin(self):
projection = collector.projection({"phase": "R0", "report": {"startup_wall_seconds": 1},
"extrapolation": {"seconds_per_phase": 20}})
valid = {"projection": projection,
"phase_stop": {"limit_seconds": 1800, "source": checker.CAP_SOURCE},
"probe_deadline": {"limit_seconds": 1740, "source": checker.DEADLINE_SOURCE}}
checker.check_budget_scope(valid)
for location in valid:
for change in ({"passed": True}, {"limit_seconds": 1799}, {"source": "wrong"}):
broken = copy.deepcopy(valid)
broken[location].update(change)
with self.subTest(location=location, change=change), self.assertRaises(ValueError):
checker.check_budget_scope(broken)
with self.assertRaises(ValueError):
checker.check_budget_scope({"elsewhere": {"limit_seconds": 1800, "source": checker.CAP_SOURCE}})
self.reject(lambda r: r["probe_deadline"].update(limit_seconds=1800))
def test_r4_observations_and_pinned_input(self):
admission = synthetic_admission()
checker.check_admission(admission)
for key in ("limit", "limit_ms", "passed", "budget_comparisons_ms", "budget_exceeded"):
broken = copy.deepcopy(admission)
broken["reused_r02"]["nested"] = [{key: 4}]
with self.subTest(key=key), self.assertRaises(ValueError):
checker.check_admission(broken)
for key in ("unit", "statistic", "scope", "r02_field"):
broken = copy.deepcopy(admission)
broken["reused_r02"]["observations"]["manual_max"].pop(key)
with self.subTest(key=key), self.assertRaises(ValueError):
checker.check_admission(broken)
# Synthetic source with distinct exact values tests extraction, not rounded constants.
raw = {}
for i, (field, _, _) in enumerate(checker.R02_FIELDS.values()):
target = raw
parts = field.split(".")
for part in parts[:-1]:
target = target.setdefault(part, {})
target[parts[-1]] = i + .123456789
pins = {"files": {"run-r02/after/evidence/report.json": "a" * 64,
"run-r02/execution.json": "b" * 64}}
with patch.object(collector, "sha", return_value="a" * 64), \
patch.object(Path, "read_text", return_value=json.dumps(raw)):
reused = collector.reused_observations(Path("readonly"), pins)
for i, name in enumerate(checker.R02_FIELDS):
self.assertEqual(reused["observations"][name]["value"], i + .123456789)
checker.check_admission({"budget_verdict_scope": checker.VERDICT_SCOPE, "reused_r02": reused})
with patch.object(collector, "sha", return_value="c" * 64), self.assertRaises(ValueError):
collector.reused_observations(Path("readonly"), pins)
def reject(self, mutate):
report = synthetic_report()
mutate(report)
@@ -225,20 +312,22 @@ class ReportTests(unittest.TestCase):
"report": r, "capture": {"actual_captured_pids": [123]},
"extrapolation": collector.extrapolation(r),
"guard": synthetic_guard(), "runtime_profile": "private", "runtime_name": "runtime"}
receipt = {"source_before": {"x": "a"}, "source_after": {"x": "a"},
receipt = {"admission": synthetic_admission(), "source_before": {"x": "a"}, "source_after": {"x": "a"},
"default_player_before": {}, "default_player_after": {}, "cleanup_errors": [],
"cleanup": [{"engine_ended": True}], "error": None, "passed": True,
"phases": [{"phase": "import", "passed": True, "timeout_seconds": 1800, "raw": {}}, row]}
probe_text = (HERE / "measure_p28_01.gd").read_text()
receipt["probe_check"] = {"sha256": hashlib.sha256(probe_text.encode()).hexdigest(), "passed": True}
for phase in receipt["phases"]:
phase["phase_stop"] = {"limit_seconds": 1800, "source": checker.CAP_SOURCE}
phase["probe_check"] = receipt["probe_check"].copy()
phase["owned_files"] = {name: {"sha256": hashlib.sha256(raw).hexdigest(), "bytes": len(raw)}
for name in collector.OWNED_FILES}
with patch.object(checker, "PHASES", {"O0": ("O", 1, 0, 10)}), \
patch.object(Path, "read_bytes", return_value=raw), \
patch.object(Path, "read_text", autospec=True,
side_effect=lambda p: probe_text if p.name == "measure_p28_01.gd" else raw.decode()):
side_effect=lambda p: (probe_text if p.name == "measure_p28_01.gd" else
json.dumps(receipt["admission"]) if p.name == "admission.json" else raw.decode())):
checker.check_receipt(receipt, Path("synthetic"))
for label, mutate in {
"source": lambda x: x.update(source_after={"x": "b"}),
@@ -279,9 +368,9 @@ class ReportTests(unittest.TestCase):
for reason, count in (("interval_exceeds_window", 600),
("no_interval_samples", 30), ("counts_unavailable", None)):
r = synthetic_report()
r["budget_at_phase_end"]["simulation_x1"]["lifetime_count"] = count
r["budget_verdict"] = checker.budget_verdict(r["budget_at_warmup_end"],
r["budget_at_phase_end"], 1)
r["budget_at_phase_end"]["data"]["simulation_x1"]["lifetime_count"] = count
r["budget_verdict"] = checker.budget_verdict(r["budget_at_warmup_end"]["data"],
r["budget_at_phase_end"]["data"], 1)
self.assertEqual(r["budget_verdict"]["simulation_x1"]["reason"], reason)
self.assertIsNone(r["budget_verdict"]["simulation_x1"]["value"])
checker.check_report(r)
@@ -322,7 +411,7 @@ class ReportTests(unittest.TestCase):
r["metadata"]["machine"]["memory_bytes"] = 24000000000.0
r["pid"] = 123.0
r["frames"][0]["registered"]["guests"] = 3.0
r["budget_at_phase_end"]["simulation_x1"]["samples_us"] = [3000.0] * 40
r["budget_at_phase_end"]["data"]["simulation_x1"]["samples_us"] = [3000.0] * 40
normalized = collector.normalize_report(json.loads(json.dumps(r)))
checker.check_report(normalized)
self.assertIs(type(normalized["pid"]), int)