Gate production projection worker memory
This commit is contained in:
parent
d65b83a140
commit
f5dccb5e1c
1 changed files with 66 additions and 15 deletions
|
|
@ -416,11 +416,51 @@ def _prepared_summary(value: object) -> Mapping[str, object]:
|
|||
}
|
||||
|
||||
|
||||
def _prepared_child_peak(prepared: PreparedRender) -> int:
|
||||
receipt = prepared.projection_receipt
|
||||
if not isinstance(receipt, Mapping):
|
||||
raise RuntimeError("Production detached render did not return a projection receipt")
|
||||
peak = receipt.get("peak_memory_bytes")
|
||||
if type(peak) is not int or peak < 1:
|
||||
raise RuntimeError("Production detached render did not report valid child peak memory")
|
||||
if peak > MAX_CHILD_PEAK_BYTES:
|
||||
raise RuntimeError(
|
||||
f"Production detached worker peak memory {peak} exceeds {MAX_CHILD_PEAK_BYTES} bytes"
|
||||
)
|
||||
return peak
|
||||
|
||||
|
||||
def _prepared_response_size(value: object) -> int:
|
||||
prepared = cast(PreparedRender, value)
|
||||
return _compact_size(prepared.projection_receipt)
|
||||
|
||||
|
||||
def _prepared_measurement(
|
||||
operation: Callable[[], PreparedRender],
|
||||
*,
|
||||
samples: int,
|
||||
p95_limit_ms: float,
|
||||
) -> tuple[dict[str, object], PreparedRender]:
|
||||
child_peaks: list[int] = []
|
||||
|
||||
def observed_operation() -> PreparedRender:
|
||||
prepared = operation()
|
||||
child_peaks.append(_prepared_child_peak(prepared))
|
||||
return prepared
|
||||
|
||||
measurement, value = _measure(
|
||||
observed_operation,
|
||||
samples=samples,
|
||||
p95_limit_ms=p95_limit_ms,
|
||||
response_limit_bytes=MAX_RECEIPT_BYTES,
|
||||
summary=_prepared_summary,
|
||||
response_size=_prepared_response_size,
|
||||
)
|
||||
measurement["maximum_child_peak_bytes"] = max(child_peaks)
|
||||
measurement["child_peak_limit_bytes"] = MAX_CHILD_PEAK_BYTES
|
||||
return measurement, cast(PreparedRender, value)
|
||||
|
||||
|
||||
def _benchmark(root: Path, node_count: int, samples: int) -> dict[str, object]:
|
||||
project = Project.open(root)
|
||||
snapshot = project.load()
|
||||
|
|
@ -600,7 +640,7 @@ def _benchmark(root: Path, node_count: int, samples: int) -> dict[str, object]:
|
|||
manual_view = render_config.views[0]
|
||||
template_bytes = manual_view.template_path.read_bytes()
|
||||
production_renderer = GenericHtmlRenderer()
|
||||
operations["manual_incremental_cold"], cold_prepared = _measure(
|
||||
operations["manual_incremental_cold"], cold_prepared = _prepared_measurement(
|
||||
lambda: production_renderer.prepare(
|
||||
snapshot,
|
||||
manual_view,
|
||||
|
|
@ -609,11 +649,8 @@ def _benchmark(root: Path, node_count: int, samples: int) -> dict[str, object]:
|
|||
),
|
||||
samples=1,
|
||||
p95_limit_ms=20_000,
|
||||
response_limit_bytes=MAX_RECEIPT_BYTES,
|
||||
summary=_prepared_summary,
|
||||
response_size=_prepared_response_size,
|
||||
)
|
||||
operations["manual_incremental_warm"], warm_prepared = _measure(
|
||||
operations["manual_incremental_warm"], warm_prepared = _prepared_measurement(
|
||||
lambda: production_renderer.prepare(
|
||||
snapshot,
|
||||
manual_view,
|
||||
|
|
@ -622,11 +659,8 @@ def _benchmark(root: Path, node_count: int, samples: int) -> dict[str, object]:
|
|||
),
|
||||
samples=samples,
|
||||
p95_limit_ms=20_000,
|
||||
response_limit_bytes=MAX_RECEIPT_BYTES,
|
||||
summary=_prepared_summary,
|
||||
response_size=_prepared_response_size,
|
||||
)
|
||||
operations["manual_forced_full"], forced_prepared = _measure(
|
||||
operations["manual_forced_full"], forced_prepared = _prepared_measurement(
|
||||
lambda: GenericHtmlRenderer(incremental=False).prepare(
|
||||
snapshot,
|
||||
manual_view,
|
||||
|
|
@ -635,13 +669,8 @@ def _benchmark(root: Path, node_count: int, samples: int) -> dict[str, object]:
|
|||
),
|
||||
samples=samples,
|
||||
p95_limit_ms=20_000,
|
||||
response_limit_bytes=MAX_RECEIPT_BYTES,
|
||||
summary=_prepared_summary,
|
||||
response_size=_prepared_response_size,
|
||||
)
|
||||
production_values = tuple(
|
||||
cast(PreparedRender, value) for value in (cold_prepared, warm_prepared, forced_prepared)
|
||||
)
|
||||
production_values = (cold_prepared, warm_prepared, forced_prepared)
|
||||
if len({value.output for value in production_values}) != 1:
|
||||
raise RuntimeError("Production cold, warm, and forced-full manual output differs")
|
||||
|
||||
|
|
@ -688,6 +717,7 @@ def _benchmark(root: Path, node_count: int, samples: int) -> dict[str, object]:
|
|||
),
|
||||
}
|
||||
variant_equivalence: dict[str, bool] = {}
|
||||
variant_child_peaks: list[int] = []
|
||||
for name, variant in variants.items():
|
||||
incremental = GenericHtmlRenderer().prepare(
|
||||
variant,
|
||||
|
|
@ -701,6 +731,12 @@ def _benchmark(root: Path, node_count: int, samples: int) -> dict[str, object]:
|
|||
template_bytes,
|
||||
changeset_hash=None,
|
||||
)
|
||||
variant_child_peaks.extend(
|
||||
(
|
||||
_prepared_child_peak(incremental),
|
||||
_prepared_child_peak(full),
|
||||
)
|
||||
)
|
||||
variant_equivalence[name] = incremental.output == full.output
|
||||
if not all(variant_equivalence.values()):
|
||||
raise RuntimeError("Production incremental mutation output differs from forced full")
|
||||
|
|
@ -776,6 +812,21 @@ def _benchmark(root: Path, node_count: int, samples: int) -> dict[str, object]:
|
|||
"memory": {
|
||||
"process_peak_rss_kib": int(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss),
|
||||
"manual_worker_peak_bytes": manual_worker_peak,
|
||||
"manual_production_worker_peak_bytes": max(
|
||||
cast(
|
||||
int,
|
||||
operations["manual_incremental_cold"]["maximum_child_peak_bytes"],
|
||||
),
|
||||
cast(
|
||||
int,
|
||||
operations["manual_incremental_warm"]["maximum_child_peak_bytes"],
|
||||
),
|
||||
cast(
|
||||
int,
|
||||
operations["manual_forced_full"]["maximum_child_peak_bytes"],
|
||||
),
|
||||
*variant_child_peaks,
|
||||
),
|
||||
"portable_graph_worker_peak_bytes": graph_worker_peak,
|
||||
},
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue