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:
|
def _prepared_response_size(value: object) -> int:
|
||||||
prepared = cast(PreparedRender, value)
|
prepared = cast(PreparedRender, value)
|
||||||
return _compact_size(prepared.projection_receipt)
|
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]:
|
def _benchmark(root: Path, node_count: int, samples: int) -> dict[str, object]:
|
||||||
project = Project.open(root)
|
project = Project.open(root)
|
||||||
snapshot = project.load()
|
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]
|
manual_view = render_config.views[0]
|
||||||
template_bytes = manual_view.template_path.read_bytes()
|
template_bytes = manual_view.template_path.read_bytes()
|
||||||
production_renderer = GenericHtmlRenderer()
|
production_renderer = GenericHtmlRenderer()
|
||||||
operations["manual_incremental_cold"], cold_prepared = _measure(
|
operations["manual_incremental_cold"], cold_prepared = _prepared_measurement(
|
||||||
lambda: production_renderer.prepare(
|
lambda: production_renderer.prepare(
|
||||||
snapshot,
|
snapshot,
|
||||||
manual_view,
|
manual_view,
|
||||||
|
|
@ -609,11 +649,8 @@ def _benchmark(root: Path, node_count: int, samples: int) -> dict[str, object]:
|
||||||
),
|
),
|
||||||
samples=1,
|
samples=1,
|
||||||
p95_limit_ms=20_000,
|
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(
|
lambda: production_renderer.prepare(
|
||||||
snapshot,
|
snapshot,
|
||||||
manual_view,
|
manual_view,
|
||||||
|
|
@ -622,11 +659,8 @@ def _benchmark(root: Path, node_count: int, samples: int) -> dict[str, object]:
|
||||||
),
|
),
|
||||||
samples=samples,
|
samples=samples,
|
||||||
p95_limit_ms=20_000,
|
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(
|
lambda: GenericHtmlRenderer(incremental=False).prepare(
|
||||||
snapshot,
|
snapshot,
|
||||||
manual_view,
|
manual_view,
|
||||||
|
|
@ -635,13 +669,8 @@ def _benchmark(root: Path, node_count: int, samples: int) -> dict[str, object]:
|
||||||
),
|
),
|
||||||
samples=samples,
|
samples=samples,
|
||||||
p95_limit_ms=20_000,
|
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:
|
if len({value.output for value in production_values}) != 1:
|
||||||
raise RuntimeError("Production cold, warm, and forced-full manual output differs")
|
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_equivalence: dict[str, bool] = {}
|
||||||
|
variant_child_peaks: list[int] = []
|
||||||
for name, variant in variants.items():
|
for name, variant in variants.items():
|
||||||
incremental = GenericHtmlRenderer().prepare(
|
incremental = GenericHtmlRenderer().prepare(
|
||||||
variant,
|
variant,
|
||||||
|
|
@ -701,6 +731,12 @@ def _benchmark(root: Path, node_count: int, samples: int) -> dict[str, object]:
|
||||||
template_bytes,
|
template_bytes,
|
||||||
changeset_hash=None,
|
changeset_hash=None,
|
||||||
)
|
)
|
||||||
|
variant_child_peaks.extend(
|
||||||
|
(
|
||||||
|
_prepared_child_peak(incremental),
|
||||||
|
_prepared_child_peak(full),
|
||||||
|
)
|
||||||
|
)
|
||||||
variant_equivalence[name] = incremental.output == full.output
|
variant_equivalence[name] = incremental.output == full.output
|
||||||
if not all(variant_equivalence.values()):
|
if not all(variant_equivalence.values()):
|
||||||
raise RuntimeError("Production incremental mutation output differs from forced full")
|
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": {
|
"memory": {
|
||||||
"process_peak_rss_kib": int(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss),
|
"process_peak_rss_kib": int(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss),
|
||||||
"manual_worker_peak_bytes": manual_worker_peak,
|
"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,
|
"portable_graph_worker_peak_bytes": graph_worker_peak,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue