blob: 48a3ecc455a88d2a504a523b9674e490f4234f27 [file]
#!/usr/bin/env python3
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
"""Reduce raw offline Host receipts; preserve failed runs and measurement coverage."""
import argparse
import hashlib
import json
import statistics
from itertools import pairwise
from pathlib import Path
def percentile(values, fraction):
if not values:
return None
values = sorted(values)
point = (len(values) - 1) * fraction
lower = int(point)
return values[lower] + (values[min(lower + 1, len(values) - 1)] - values[lower]) * (
point - lower
)
def distribution(values):
return {
"count": len(values),
"min": min(values) if values else None,
"p50": percentile(values, 0.5),
"p95": percentile(values, 0.95),
"p99": percentile(values, 0.99),
"max": max(values) if values else None,
}
def process_metrics(samples, key, ticks):
rows = [
(sample["time"], sample["processes"][key])
for sample in samples
if key in sample["processes"] and not sample["processes"][key].get("gone")
]
if not rows:
return None
elapsed = rows[-1][0] - rows[0][0]
cpu_seconds, interval_cpu = 0, []
for (before_time, before), (after_time, after) in pairwise(rows):
if (before["pid"], before["startTicks"]) != (after["pid"], after["startTicks"]):
continue
delta = (
after["utime"] + after["stime"] - before["utime"] - before["stime"]
) / ticks
cpu_seconds += delta
if after_time > before_time:
interval_cpu.append(100 * delta / (after_time - before_time))
io_rows = [(t, row) for t, row in rows if "ioBytesAndCalls" in row]
io_delta = {}
for (_, before), (_, after) in pairwise(io_rows):
if (before["pid"], before["startTicks"]) != (after["pid"], after["startTicks"]):
continue
for field in before["ioBytesAndCalls"].keys() & after["ioBytesAndCalls"].keys():
io_delta[field] = io_delta.get(field, 0) + max(
0, after["ioBytesAndCalls"][field] - before["ioBytesAndCalls"][field]
)
return {
"sampleCount": len(rows),
"sampledSeconds": elapsed,
"cpuSeconds": cpu_seconds,
"meanCpuPercentOneCore": 100 * cpu_seconds / elapsed if elapsed else None,
"intervalCpuPercentOneCore": distribution(interval_cpu),
"rssMiB": distribution([row["rssKiB"] / 1024 for _, row in rows]),
"pssMiB": distribution(
[row["pssKiB"] / 1024 for _, row in rows if "pssKiB" in row]
),
"peakSwapMiB": max(row["swapKiB"] for _, row in rows) / 1024,
"peakThreads": max(row["threads"] for _, row in rows),
"peakFds": max(row["fds"] for _, row in rows),
"ioCounterDeltas": io_delta,
"ioSampledSeconds": io_rows[-1][0] - io_rows[0][0]
if len(io_rows) > 1
else None,
}
def reduce_run(directory):
value = json.loads((directory / "result.json").read_text())
manifest = (
json.loads((directory / "manifest.json").read_text())
if (directory / "manifest.json").exists()
else {}
)
samples = value["samples"]
ticks = value.get("clockTicksPerSecond", manifest.get("clockTicksPerSecond"))
info = value.get("scale", value.get("baseline", {}))
cohort = {
"source": manifest.get("gitCommit"),
"files": {
Path(path).name: digest
for path, digest in manifest.get("fileSha256", {}).items()
},
"options": {
key: option
for key, option in manifest.get("options", {}).items()
if key not in {"output", "rust_checkout", "ts_checkout"}
},
"environment": {
key: manifest.get(key)
for key in ("python", "node", "platform", "cpuCount", "clockTicksPerSecond")
},
}
summary = {
"run": directory.name,
"kind": value["kind"],
"mode": manifest.get("options", {}).get(
"mode",
"background"
if "scale" in value
else "baseline"
if "baseline" in value
else "smoke",
),
"passed": value.get("passed", False),
"error": value.get("error"),
"cleanupErrors": value.get("cleanupErrors", []),
"telemetryErrors": value.get("telemetryErrors", []),
"source": manifest.get("gitCommit"),
"cohort": cohort,
"cohortSha256": hashlib.sha256(
json.dumps(cohort, sort_keys=True).encode()
).hexdigest(),
"manifest": str(directory / "manifest.json") if manifest else None,
"started": value["started"],
"finished": value["finished"],
"wallSeconds": value["finished"] - value["started"],
"providerRequests": len(value["providerRequests"]),
"providerErrors": len(value["providerErrors"]),
"providerPeak": value["providerPeak"],
"resources": {
key: process_metrics(samples, key, ticks)
for key in ("host", "adapter", "providerController")
},
"phases": [],
"providerWakeLatenessMs": distribution(
[
r["maxWakeLatenessMs"]
for r in value["providerRequests"]
if "maxWakeLatenessMs" in r
]
),
"rampPrefixChars": distribution(
[r.get("rampPrefixChars", 0) for r in value["providerRequests"]]
),
"providerOutputBytes": sum(
r.get("outputBytes", 0) for r in value["providerRequests"]
),
"samplingMs": distribution(
[r["samplingMs"] for r in samples if "samplingMs" in r]
),
}
phases = info.get("phases", [])
for index, phase in enumerate(phases):
end = (
phases[index + 1]["time"]
if index + 1 < len(phases)
else samples[-1]["time"]
if samples
else phase["time"]
)
selected = [
sample for sample in samples if phase["time"] <= sample["time"] < end
]
summary["phases"].append(
{
"name": phase["phase"],
"startedMonotonic": phase["time"],
"durationSeconds": max(0, end - phase["time"]),
"resources": {
key: process_metrics(selected, key, ticks)
for key in ("host", "adapter", "providerController")
},
}
)
if "scale" in value:
keys = (
"requested",
"created",
"admitted",
"admittedAtBarrier",
"activeAtBarrier",
"completed",
"verified",
"unknownAdmissionCanonicalVerified",
"unconfirmedTerminalCanonicalVerified",
"notSubmittedOrAdmitted",
"barrierReachedAll",
"rampLimitSeconds",
"resourceStop",
"scopes",
"admissionConcurrency",
"observer",
)
summary["scale"] = {key: info[key] for key in keys if key in info}
summary["scale"]["errors"] = {
key: {"count": len(rows), "examples": rows[:3]}
for key, rows in info.items()
if key.endswith("Errors")
}
summary["scale"]["providerRequestsWithoutAdmissionReply"] = (
len(info.get("providerRequestsWithoutAdmissionReply", []))
if "providerRequestsWithoutAdmissionReply" in info
else None
)
admissions = [a for a in info.get("admissions", []) if a is not None]
summary["scale"]["admissionReplyMs"] = distribution(
[1000 * (a["admitted"] - a["started"]) for a in admissions]
)
times = {row["phase"]: row["time"] for row in phases}
for name, first, last in [
("createSeconds", "create_start", "resident_idle_start"),
("rampSeconds", "active_ramp_start", "active_hold_start"),
(
"releaseToTerminalChecksFinishedSeconds",
"stream_release",
"terminal_observed",
),
("releaseToProviderDrainedSeconds", "stream_release", "provider_drained"),
("observerAttachSeconds", "provider_drained", "observer_ready"),
("terminalChecksSeconds", "observer_ready", "terminal_observed"),
("canonicalVerificationSeconds", "terminal_observed", "canonical_verified"),
]:
if first in times and last in times:
summary["scale"][name] = times[last] - times[first]
# The phase is emitted when checks finish, including when every query
# fails. Only label it an observation of all admitted terminals if true.
if (
info.get("admitted", 0) > 0
and info.get("completed") == info["admitted"]
and not info.get("terminalErrors")
):
for observed, checked in (
(
"releaseToTerminalObservedSeconds",
"releaseToTerminalChecksFinishedSeconds",
),
("terminalObservationSeconds", "terminalChecksSeconds"),
):
if checked in summary["scale"]:
summary["scale"][observed] = summary["scale"][checked]
if "baseline" in value:
summary["baseline"] = {key: row for key, row in info.items() if key != "phases"}
stream = summary["baseline"].get("largeStream", {})
if (
"startedMs" in stream
and stream.get("firstTextMs") is not None
and stream.get("terminalMs") is not None
):
stream["firstTextObservedMs"] = stream["firstTextMs"] - stream["startedMs"]
stream["completionObservedMs"] = stream["terminalMs"] - stream["startedMs"]
elif "firstVisibleMs" in stream and "completedMs" in stream:
stream["firstTextObservedMs"] = stream["firstVisibleMs"]
stream["completionObservedMs"] = stream["completedMs"]
if (directory / "supplemental-metrics.jsonl").exists():
extra = [
json.loads(line)
for line in (directory / "supplemental-metrics.jsonl")
.read_text()
.splitlines()
]
summary["lateSupplementalCoverage"] = {
"metadata": extra[0],
"resources": {
key: process_metrics(extra[1:], key, ticks)
for key in (
"host",
"adapter",
"providerController",
"supplementalMonitor",
)
},
}
system = [sample for sample in samples if "system" in sample]
if len(system) > 1:
first, last = system[0], system[-1]
elapsed = last["time"] - first["time"]
summary["system"] = {
"sampledSeconds": elapsed,
"minimumAvailableMiB": min(row["availableKiB"] for row in samples) / 1024,
"pressureStallPercent": {
resource: {
level: 100
* (
float(last["system"]["pressure"][resource][level]["total"])
- float(first["system"]["pressure"][resource][level]["total"])
)
/ 1e6
/ elapsed
for level in first["system"]["pressure"][resource]
}
for resource in first["system"]["pressure"]
},
"swapPagesInDelta": last["system"]["vmstat"]["pswpin"]
- first["system"]["vmstat"]["pswpin"],
"swapPagesOutDelta": last["system"]["vmstat"]["pswpout"]
- first["system"]["vmstat"]["pswpout"],
}
return summary
def grouped_formal(runs):
groups = {}
for run in runs:
if "-formal-" not in run["run"] or not run["campaignSelected"]:
continue
groups.setdefault((run["kind"], run["mode"], run["cohortSha256"]), []).append(
run
)
output = []
for (kind, mode, cohort_hash), rows in sorted(groups.items()):
def metric(name, getter, rows=rows):
values = [getter(row) for row in rows if row["passed"]]
values = [value for value in values if value is not None]
return name, {
"median": statistics.median(values),
"min": min(values),
"max": max(values),
"runs": len(values),
} if values else None
metrics = [
metric("hostPeakRssMiB", lambda r: r["resources"]["host"]["rssMiB"]["max"]),
metric("hostCpuSeconds", lambda r: r["resources"]["host"]["cpuSeconds"]),
metric("hostPeakPssMiB", lambda r: r["resources"]["host"]["pssMiB"]["max"]),
]
if mode == "background":
for key in (
"createSeconds",
"rampSeconds",
"releaseToTerminalObservedSeconds",
"canonicalVerificationSeconds",
):
metrics.append(metric(key, lambda r, k=key: r["scale"][k]))
else:
for key, path in [
("largeStreamMs", ("largeStream", "completionObservedMs")),
("firstTextMs", ("largeStream", "firstTextObservedMs")),
("historyReadMs", ("historyRead", "controllerReadMs")),
("reopenAndReadMs", ("reopenAndReadMs",)),
]:
def get(row, path=path):
value = row["baseline"]
for part in path:
value = value[part]
return value
metrics.append(metric(key, get))
for name, field, subfield in [
("idleHostRssMiB", "rssMiB", "p50"),
("idleHostPssMiB", "pssMiB", "p50"),
("idleHostCpuPercentOneCore", "meanCpuPercentOneCore", None),
]:
def get(row, field=field, subfield=subfield):
value = next(p for p in row["phases"] if p["name"] == "idle_start")[
"resources"
]["host"][field]
return value[subfield] if subfield else value
metrics.append(metric(name, get))
for name, field, subfield in [
("streamHostPeakRssMiB", "rssMiB", "max"),
("streamHostPeakPssMiB", "pssMiB", "max"),
("streamHostSampledCpuSeconds", "cpuSeconds", None),
("streamHostSampledSeconds", "sampledSeconds", None),
("streamHostSampleCount", "sampleCount", None),
]:
def get(row, field=field, subfield=subfield):
host = next(
p for p in row["phases"] if p["name"] == "large_stream_start"
)["resources"]["host"]
if host is None:
return None
value = host[field]
return value[subfield] if subfield else value
metrics.append(metric(name, get))
metrics.append(
metric(
"streamPhaseSeconds",
lambda row: next(
p for p in row["phases"] if p["name"] == "large_stream_start"
)["durationSeconds"],
)
)
output.append(
{
"kind": kind,
"mode": mode,
"cohortSha256": cohort_hash,
"runIds": [row["run"] for row in rows],
"totalRuns": len(rows),
"passedRuns": sum(r["passed"] for r in rows),
"metrics": dict(metrics),
}
)
return output
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("directory", type=Path)
parser.add_argument("--output", type=Path, required=True)
parser.add_argument("--campaign", type=Path)
options = parser.parse_args()
campaign_path = options.campaign or options.directory / "campaign-manifest.json"
campaign = json.loads(campaign_path.read_text())
planned = set(campaign["plannedRunIds"])
if len(planned) != len(campaign["plannedRunIds"]):
raise ValueError("Campaign has duplicate run IDs")
frozen = campaign["harnessSha256"]
if not frozen:
raise ValueError("Campaign must identify frozen harness hashes")
runs = []
for path in sorted(options.directory.glob("*/result.json")):
if not any(
marker in path.parent.name
for marker in ("-formal-", "background1000-", "background10000-")
):
continue
run = reduce_run(path.parent)
exclusion = None
if run["run"] not in planned:
exclusion = "Run ID is not in the selected campaign"
elif any(run["cohort"]["files"].get(k) != v for k, v in frozen.items()):
exclusion = "Harness hashes do not match the selected campaign"
run["campaignSelected"] = exclusion is None
run["campaignExclusion"] = exclusion
runs.append(run)
options.output.write_text(
json.dumps(
{
"notes": [
"CPU 100% means one logical core. Resource counter deltas cover sampled intervals, excluding unobserved startup and cleanup.",
"System PSI/disk/network counters include the whole machine. mainThreadContextSwitches are not process totals.",
"Capacity completion is publicly confirmed terminal state; provider completion means bytes written, not accepted. Admission timeouts may still execute.",
"Formal summaries keep failure counts; medians use successful rounds only and are incomplete unless all five pass.",
"Held SSE ramps use variable prefix chunk counts; release/drain timings are scenario observations, not pure Host throughput.",
],
"campaign": {
"manifest": str(campaign_path),
"plannedRunIds": campaign["plannedRunIds"],
"missingRunIds": sorted(planned - {run["run"] for run in runs}),
"excludedRuns": [
{"run": run["run"], "reason": run["campaignExclusion"]}
for run in runs
if not run["campaignSelected"]
],
},
"groups": grouped_formal(runs),
"runs": runs,
},
ensure_ascii=False,
indent=2,
)
+ "\n"
)
print(json.dumps({"runs": len(runs), "output": str(options.output)}))