update
This commit is contained in:
Executable
+853
@@ -0,0 +1,853 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
umask 022
|
||||
|
||||
gate_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)
|
||||
root=$(CDPATH= cd -- "$gate_dir/../../../.." && pwd -P)
|
||||
input=$gate_dir/P15-076-input.json
|
||||
freebsd_src=${FREEBSD_SRC:-/work/build/freebsd-src}
|
||||
base=
|
||||
output=
|
||||
|
||||
while test "$#" -gt 0; do
|
||||
case "$1" in
|
||||
--base)
|
||||
test "$#" -ge 2 || { printf '%s\n' '--base requires a commit' >&2; exit 2; }
|
||||
base=$2
|
||||
shift 2
|
||||
;;
|
||||
--output)
|
||||
test "$#" -ge 2 || { printf '%s\n' '--output requires a directory' >&2; exit 2; }
|
||||
output=$2
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
printf 'unknown argument: %s\n' "$1" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
test -n "$base" || { printf '%s\n' '--base is required' >&2; exit 2; }
|
||||
test -n "$output" || { printf '%s\n' '--output is required' >&2; exit 2; }
|
||||
test -f "$input" || { printf 'missing input: %s\n' "$input" >&2; exit 2; }
|
||||
test -d "$freebsd_src/sys" || { printf 'missing FreeBSD source: %s\n' "$freebsd_src" >&2; exit 2; }
|
||||
case "$output" in
|
||||
/*) ;;
|
||||
*) output=$PWD/$output ;;
|
||||
esac
|
||||
test ! -e "$output" || { printf 'refusing existing output: %s\n' "$output" >&2; exit 2; }
|
||||
mkdir -p "$output"
|
||||
|
||||
python3 -B - "$root" "$input" "$base" "$output" "$freebsd_src" <<'PY'
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import statistics
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from typing import Any
|
||||
|
||||
|
||||
ROOT = Path(sys.argv[1])
|
||||
INPUT = Path(sys.argv[2])
|
||||
REQUESTED_BASE = sys.argv[3]
|
||||
OUTPUT = Path(sys.argv[4])
|
||||
FREEBSD_SRC = Path(sys.argv[5])
|
||||
SPEC = json.loads(INPUT.read_text(encoding="ascii"))
|
||||
DUT = ROOT / "repo-pre-15"
|
||||
|
||||
|
||||
class GateStop(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
class InfraBlocked(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
class RunnerFail(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
def sha256_bytes(data: bytes) -> str:
|
||||
return hashlib.sha256(data).hexdigest()
|
||||
|
||||
|
||||
def sha256_path(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as stream:
|
||||
for block in iter(lambda: stream.read(1024 * 1024), b""):
|
||||
digest.update(block)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def write_json(path: Path, value: Any) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(json.dumps(value, indent=2, sort_keys=True) + "\n", encoding="ascii")
|
||||
|
||||
|
||||
def run(argv: list[str], cwd: Path, log: Path, timeout: int = 120) -> subprocess.CompletedProcess[str]:
|
||||
try:
|
||||
completed = subprocess.run(
|
||||
argv, cwd=cwd, check=False, text=True,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=timeout,
|
||||
)
|
||||
except subprocess.TimeoutExpired as error:
|
||||
log.parent.mkdir(parents=True, exist_ok=True)
|
||||
log.write_text(f"$ {' '.join(argv)}\nTIMEOUT after {timeout}s\n", encoding="ascii")
|
||||
raise InfraBlocked(f"command timed out after {timeout}s: {' '.join(argv)}") from error
|
||||
log.parent.mkdir(parents=True, exist_ok=True)
|
||||
log.write_text(
|
||||
f"$ {' '.join(argv)}\n{completed.stdout}\n[exit {completed.returncode}]\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
return completed
|
||||
|
||||
|
||||
def checked_run(argv: list[str], cwd: Path, log: Path, timeout: int = 120) -> str:
|
||||
completed = run(argv, cwd, log, timeout)
|
||||
if completed.returncode != 0:
|
||||
raise RunnerFail(f"command failed ({completed.returncode}): {' '.join(argv)}")
|
||||
return completed.stdout
|
||||
|
||||
|
||||
def git(path: Path, *args: str) -> str:
|
||||
completed = subprocess.run(
|
||||
["git", "-C", str(path), *args], check=False, text=True,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=30,
|
||||
)
|
||||
if completed.returncode != 0:
|
||||
raise InfraBlocked(f"git {' '.join(args)} failed: {completed.stdout.strip()}")
|
||||
return completed.stdout.strip()
|
||||
|
||||
|
||||
def source_at(commit: str, relative: str) -> bytes:
|
||||
completed = subprocess.run(
|
||||
["git", "-C", str(ROOT), "show", f"{commit}:{relative}"],
|
||||
check=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=30,
|
||||
)
|
||||
if completed.returncode != 0:
|
||||
raise InfraBlocked(f"cannot read frozen source {relative}")
|
||||
return completed.stdout
|
||||
|
||||
|
||||
def verify_identity() -> dict[str, Any]:
|
||||
if SPEC.get("schema") != 1 or SPEC.get("candidate") != "P15-076" or SPEC.get("gate") != "G05":
|
||||
raise InfraBlocked("invalid P15-076 input identity")
|
||||
resolved = git(ROOT, "rev-parse", f"{REQUESTED_BASE}^{{commit}}")
|
||||
if resolved != SPEC["required_base"]:
|
||||
raise InfraBlocked(f"P15-076 must replay {SPEC['required_base']}, got {resolved}")
|
||||
source_hashes = {
|
||||
relative: sha256_bytes(source_at(resolved, relative))
|
||||
for relative in SPEC["source_sha256"]
|
||||
}
|
||||
if source_hashes != SPEC["source_sha256"]:
|
||||
raise InfraBlocked("frozen DUT/Linux source identity changed")
|
||||
fixture_hashes = {
|
||||
relative: sha256_path(ROOT / relative)
|
||||
for relative in SPEC["fixture_assets"]
|
||||
}
|
||||
if fixture_hashes != SPEC["fixture_assets"]:
|
||||
raise InfraBlocked("frozen B28 fixture identity changed")
|
||||
freebsd_head = git(FREEBSD_SRC, "rev-parse", "HEAD")
|
||||
freebsd_hashes = {
|
||||
relative: sha256_path(FREEBSD_SRC / relative)
|
||||
for relative in SPEC["freebsd"]["sha256"]
|
||||
}
|
||||
if freebsd_head != SPEC["freebsd"]["head"] or freebsd_hashes != SPEC["freebsd"]["sha256"]:
|
||||
raise InfraBlocked("frozen FreeBSD API/source identity changed")
|
||||
tool_hashes = {}
|
||||
for name, record in SPEC["tools"].items():
|
||||
path = Path(record["path"])
|
||||
if not path.is_file():
|
||||
raise InfraBlocked(f"missing frozen tool: {path}")
|
||||
tool_hashes[name] = sha256_path(path)
|
||||
if tool_hashes[name] != record["sha256"]:
|
||||
raise InfraBlocked(f"frozen tool changed: {name}")
|
||||
versions = {
|
||||
package: subprocess.check_output(
|
||||
["pkg-config", "--modversion", package], text=True, timeout=30
|
||||
).strip()
|
||||
for package in ("liblzma", "libzstd", "zlib")
|
||||
}
|
||||
if versions != SPEC["libraries"]:
|
||||
raise InfraBlocked(f"library versions changed: {versions}")
|
||||
return {
|
||||
"base": resolved,
|
||||
"fixture_sha256": fixture_hashes,
|
||||
"freebsd_head": freebsd_head,
|
||||
"freebsd_sha256": freebsd_hashes,
|
||||
"libraries": versions,
|
||||
"source_sha256": source_hashes,
|
||||
"tool_sha256": tool_hashes,
|
||||
}
|
||||
|
||||
|
||||
XZ_CONFIG = r'''#ifndef P15_076_XZ_CONFIG_H
|
||||
#define P15_076_XZ_CONFIG_H
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <contrib/xz-embedded/linux/include/linux/xz.h>
|
||||
void *gate_xz_malloc(size_t size);
|
||||
void gate_xz_free(void *address);
|
||||
#define XZ_PREBOOT 1
|
||||
#undef XZ_EXTERN
|
||||
#define XZ_EXTERN extern
|
||||
#define STATIC
|
||||
#define INIT
|
||||
#define bool int
|
||||
#define true 1
|
||||
#define false 0
|
||||
#define GFP_KERNEL 0
|
||||
#define kmalloc(size, flags) gate_xz_malloc(size)
|
||||
#define kfree(address) gate_xz_free(address)
|
||||
#define vmalloc(size) gate_xz_malloc(size)
|
||||
#define vfree(address) gate_xz_free(address)
|
||||
#define memeq(a, b, size) (memcmp((a), (b), (size)) == 0)
|
||||
#define memzero(buffer, size) memset((buffer), 0, (size))
|
||||
#define min(a, b) ((a) < (b) ? (a) : (b))
|
||||
#define min_t(type, a, b) min((a), (b))
|
||||
static inline uint32_t get_le32(const void *address)
|
||||
{
|
||||
const unsigned char *p = address;
|
||||
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) |
|
||||
((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
|
||||
}
|
||||
#endif
|
||||
'''
|
||||
|
||||
|
||||
BENCHMARK_SOURCE = r'''#define _POSIX_C_SOURCE 200809L
|
||||
#define ZSTD_STATIC_LINKING_ONLY
|
||||
#include <inttypes.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <zlib.h>
|
||||
#include <zstd.h>
|
||||
#include <contrib/xz-embedded/linux/include/linux/xz.h>
|
||||
|
||||
struct allocation { size_t size; };
|
||||
struct tracker {
|
||||
uint64_t alloc_calls;
|
||||
uint64_t free_calls;
|
||||
size_t live_bytes;
|
||||
size_t peak_bytes;
|
||||
};
|
||||
static struct tracker *active_tracker;
|
||||
|
||||
static void fail(const char *message)
|
||||
{
|
||||
fprintf(stderr, "%s\n", message);
|
||||
exit(2);
|
||||
}
|
||||
|
||||
static void *tracked_alloc(size_t size)
|
||||
{
|
||||
struct allocation *allocation;
|
||||
if (active_tracker == NULL || size > SIZE_MAX - sizeof(*allocation))
|
||||
return NULL;
|
||||
allocation = malloc(sizeof(*allocation) + size);
|
||||
if (allocation == NULL)
|
||||
return NULL;
|
||||
allocation->size = size;
|
||||
active_tracker->alloc_calls++;
|
||||
active_tracker->live_bytes += size;
|
||||
if (active_tracker->live_bytes > active_tracker->peak_bytes)
|
||||
active_tracker->peak_bytes = active_tracker->live_bytes;
|
||||
return allocation + 1;
|
||||
}
|
||||
|
||||
static void tracked_free(void *address)
|
||||
{
|
||||
struct allocation *allocation;
|
||||
if (address == NULL)
|
||||
return;
|
||||
allocation = (struct allocation *)address - 1;
|
||||
if (active_tracker == NULL || allocation->size > active_tracker->live_bytes)
|
||||
fail("allocation tracker underflow");
|
||||
active_tracker->free_calls++;
|
||||
active_tracker->live_bytes -= allocation->size;
|
||||
free(allocation);
|
||||
}
|
||||
|
||||
void *gate_xz_malloc(size_t size) { return tracked_alloc(size); }
|
||||
void gate_xz_free(void *address) { tracked_free(address); }
|
||||
|
||||
static voidpf zalloc_tracked(voidpf opaque, uInt items, uInt size)
|
||||
{
|
||||
(void)opaque;
|
||||
if (items != 0 && size > SIZE_MAX / items)
|
||||
return NULL;
|
||||
return tracked_alloc((size_t)items * size);
|
||||
}
|
||||
static void zfree_tracked(voidpf opaque, voidpf address)
|
||||
{
|
||||
(void)opaque;
|
||||
tracked_free(address);
|
||||
}
|
||||
static void *zstd_alloc_tracked(void *opaque, size_t size)
|
||||
{
|
||||
(void)opaque;
|
||||
return tracked_alloc(size);
|
||||
}
|
||||
static void zstd_free_tracked(void *opaque, void *address)
|
||||
{
|
||||
(void)opaque;
|
||||
tracked_free(address);
|
||||
}
|
||||
|
||||
static unsigned char *read_file(const char *path, size_t *sizep)
|
||||
{
|
||||
FILE *stream;
|
||||
unsigned char *data;
|
||||
long length;
|
||||
stream = fopen(path, "rb");
|
||||
if (stream == NULL || fseek(stream, 0, SEEK_END) != 0)
|
||||
fail("cannot open input");
|
||||
length = ftell(stream);
|
||||
if (length <= 0 || fseek(stream, 0, SEEK_SET) != 0)
|
||||
fail("invalid input size");
|
||||
data = malloc((size_t)length);
|
||||
if (data == NULL || fread(data, 1, (size_t)length, stream) != (size_t)length)
|
||||
fail("cannot read input");
|
||||
fclose(stream);
|
||||
*sizep = (size_t)length;
|
||||
return data;
|
||||
}
|
||||
|
||||
static uint64_t elapsed_ns(const struct timespec *start, const struct timespec *end)
|
||||
{
|
||||
return (uint64_t)(end->tv_sec - start->tv_sec) * 1000000000ULL +
|
||||
(uint64_t)(end->tv_nsec - start->tv_nsec);
|
||||
}
|
||||
|
||||
static int run_lzma(struct xz_dec_microlzma *state, const unsigned char *input,
|
||||
size_t input_size, unsigned char *output, size_t output_size)
|
||||
{
|
||||
struct xz_buf buffer = { 0 };
|
||||
enum xz_ret ret;
|
||||
xz_dec_microlzma_reset(state, (uint32_t)input_size,
|
||||
(uint32_t)output_size, 1);
|
||||
buffer.in = input;
|
||||
buffer.in_size = input_size;
|
||||
buffer.out = output;
|
||||
buffer.out_size = output_size;
|
||||
ret = xz_dec_microlzma_run(state, &buffer);
|
||||
return ret == XZ_STREAM_END && buffer.in_pos == input_size &&
|
||||
buffer.out_pos == output_size ? 0 : -1;
|
||||
}
|
||||
|
||||
static int run_deflate(z_stream *stream, const unsigned char *input,
|
||||
size_t input_size, unsigned char *output, size_t output_size)
|
||||
{
|
||||
int ret = Z_OK;
|
||||
stream->next_in = (Bytef *)(uintptr_t)input;
|
||||
stream->avail_in = (uInt)input_size;
|
||||
stream->next_out = output;
|
||||
stream->avail_out = (uInt)output_size;
|
||||
while (stream->avail_out != 0) {
|
||||
uInt in_before = stream->avail_in;
|
||||
uInt out_before = stream->avail_out;
|
||||
ret = inflate(stream, Z_SYNC_FLUSH);
|
||||
if (ret == Z_STREAM_END)
|
||||
break;
|
||||
if (ret != Z_OK || (stream->avail_in == in_before &&
|
||||
stream->avail_out == out_before))
|
||||
return -1;
|
||||
}
|
||||
return ret == Z_STREAM_END && stream->avail_in == 0 &&
|
||||
stream->avail_out == 0 ? 0 : -1;
|
||||
}
|
||||
|
||||
static int run_zstd(ZSTD_DCtx *context, const unsigned char *input,
|
||||
size_t input_size, unsigned char *output, size_t output_size)
|
||||
{
|
||||
ZSTD_inBuffer in_buffer = { input, input_size, 0 };
|
||||
ZSTD_outBuffer out_buffer = { output, output_size, 0 };
|
||||
size_t ret = 1;
|
||||
while (out_buffer.pos != out_buffer.size) {
|
||||
size_t in_before = in_buffer.pos;
|
||||
size_t out_before = out_buffer.pos;
|
||||
ret = ZSTD_decompressStream(context, &out_buffer, &in_buffer);
|
||||
if (ZSTD_isError(ret) || (in_buffer.pos == in_before &&
|
||||
out_buffer.pos == out_before))
|
||||
return -1;
|
||||
if (ret == 0)
|
||||
break;
|
||||
}
|
||||
return ret == 0 && in_buffer.pos == input_size &&
|
||||
out_buffer.pos == output_size ? 0 : -1;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *codec, *mode;
|
||||
unsigned char *input, *expected, *output;
|
||||
struct tracker tracker = { 0 };
|
||||
struct timespec start, end;
|
||||
size_t input_size, output_size;
|
||||
uint64_t cpu_ns;
|
||||
int iterations, pooled;
|
||||
struct xz_dec_microlzma *xz_state = NULL;
|
||||
z_stream zstream = { 0 };
|
||||
ZSTD_DCtx *zstd_context = NULL;
|
||||
ZSTD_customMem zstd_memory = {
|
||||
zstd_alloc_tracked, zstd_free_tracked, NULL
|
||||
};
|
||||
|
||||
if (argc != 6)
|
||||
fail("usage: benchmark CODEC MODE STREAM EXPECTED ITERATIONS");
|
||||
codec = argv[1];
|
||||
mode = argv[2];
|
||||
pooled = strcmp(mode, "pooled") == 0;
|
||||
if (!pooled && strcmp(mode, "baseline") != 0)
|
||||
fail("invalid mode");
|
||||
iterations = atoi(argv[5]);
|
||||
if (iterations <= 0)
|
||||
fail("invalid iterations");
|
||||
input = read_file(argv[3], &input_size);
|
||||
expected = read_file(argv[4], &output_size);
|
||||
output = malloc(output_size);
|
||||
if (output == NULL)
|
||||
fail("cannot allocate output");
|
||||
active_tracker = &tracker;
|
||||
|
||||
if (pooled && strcmp(codec, "lzma") == 0) {
|
||||
xz_state = xz_dec_microlzma_alloc(XZ_SINGLE, 65536);
|
||||
if (xz_state == NULL)
|
||||
fail("pooled lzma init failed");
|
||||
} else if (pooled && strcmp(codec, "deflate") == 0) {
|
||||
zstream.zalloc = zalloc_tracked;
|
||||
zstream.zfree = zfree_tracked;
|
||||
if (inflateInit2(&zstream, -15) != Z_OK)
|
||||
fail("pooled deflate init failed");
|
||||
} else if (pooled && strcmp(codec, "zstd") == 0) {
|
||||
zstd_context = ZSTD_createDCtx_advanced(zstd_memory);
|
||||
if (zstd_context == NULL || ZSTD_isError(ZSTD_DCtx_setParameter(
|
||||
zstd_context, ZSTD_d_windowLogMax, 16)))
|
||||
fail("pooled zstd init failed");
|
||||
}
|
||||
|
||||
if (clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &start) != 0)
|
||||
fail("clock start failed");
|
||||
for (int iteration = 0; iteration < iterations; ++iteration) {
|
||||
int error = 0;
|
||||
memset(output, 0xa5, output_size);
|
||||
if (strcmp(codec, "lzma") == 0) {
|
||||
struct xz_dec_microlzma *state = xz_state;
|
||||
if (!pooled)
|
||||
state = xz_dec_microlzma_alloc(XZ_SINGLE, 65536);
|
||||
if (state == NULL || run_lzma(state, input, input_size,
|
||||
output, output_size) != 0)
|
||||
error = 1;
|
||||
if (!pooled && state != NULL)
|
||||
xz_dec_microlzma_end(state);
|
||||
} else if (strcmp(codec, "deflate") == 0) {
|
||||
z_stream stream = { 0 };
|
||||
z_stream *current = &zstream;
|
||||
if (!pooled) {
|
||||
stream.zalloc = zalloc_tracked;
|
||||
stream.zfree = zfree_tracked;
|
||||
current = &stream;
|
||||
if (inflateInit2(current, -15) != Z_OK)
|
||||
error = 1;
|
||||
} else if (iteration != 0 && inflateReset2(current, -15) != Z_OK) {
|
||||
error = 1;
|
||||
}
|
||||
if (!error && run_deflate(current, input, input_size,
|
||||
output, output_size) != 0)
|
||||
error = 1;
|
||||
if (!pooled && current->state != NULL && inflateEnd(current) != Z_OK)
|
||||
error = 1;
|
||||
} else if (strcmp(codec, "zstd") == 0) {
|
||||
ZSTD_DCtx *context = zstd_context;
|
||||
if (!pooled) {
|
||||
context = ZSTD_createDCtx_advanced(zstd_memory);
|
||||
if (context == NULL || ZSTD_isError(ZSTD_DCtx_setParameter(
|
||||
context, ZSTD_d_windowLogMax, 16)))
|
||||
error = 1;
|
||||
} else if (iteration != 0 && ZSTD_isError(ZSTD_DCtx_reset(
|
||||
context, ZSTD_reset_session_only))) {
|
||||
error = 1;
|
||||
}
|
||||
if (!error && run_zstd(context, input, input_size,
|
||||
output, output_size) != 0)
|
||||
error = 1;
|
||||
if (!pooled && context != NULL && ZSTD_isError(ZSTD_freeDCtx(context)))
|
||||
error = 1;
|
||||
} else {
|
||||
fail("unknown codec");
|
||||
}
|
||||
if (error || memcmp(output, expected, output_size) != 0)
|
||||
fail("decode or output comparison failed");
|
||||
}
|
||||
if (clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &end) != 0)
|
||||
fail("clock end failed");
|
||||
|
||||
if (xz_state != NULL)
|
||||
xz_dec_microlzma_end(xz_state);
|
||||
if (zstream.state != NULL && inflateEnd(&zstream) != Z_OK)
|
||||
fail("pooled deflate fini failed");
|
||||
if (zstd_context != NULL && ZSTD_isError(ZSTD_freeDCtx(zstd_context)))
|
||||
fail("pooled zstd fini failed");
|
||||
if (tracker.live_bytes != 0 || tracker.alloc_calls != tracker.free_calls)
|
||||
fail("context cleanup mismatch");
|
||||
cpu_ns = elapsed_ns(&start, &end);
|
||||
printf("alloc_calls=%" PRIu64 " cpu_ns=%" PRIu64
|
||||
" free_calls=%" PRIu64 " iterations=%d peak_bytes=%zu\n",
|
||||
tracker.alloc_calls, cpu_ns, tracker.free_calls, iterations,
|
||||
tracker.peak_bytes);
|
||||
free(output);
|
||||
free(expected);
|
||||
free(input);
|
||||
return 0;
|
||||
}
|
||||
'''
|
||||
|
||||
|
||||
def compile_prototype(temp: Path) -> tuple[Path, dict[str, str]]:
|
||||
shadow = temp / "shadow/contrib/xz-embedded/freebsd"
|
||||
shadow.mkdir(parents=True)
|
||||
config = shadow / "xz_config.h"
|
||||
source = temp / "p15-076-prototype.c"
|
||||
binary = temp / "p15-076-prototype"
|
||||
config.write_text(XZ_CONFIG, encoding="ascii")
|
||||
source.write_text(BENCHMARK_SOURCE, encoding="ascii")
|
||||
checked_run(
|
||||
[
|
||||
SPEC["tools"]["cc"]["path"], "-O2", "-std=c17", "-Wall",
|
||||
"-Wextra", "-Werror", "-DXZ_DEC_MICROLZMA", "-DXZ_DEC_SINGLE",
|
||||
f"-I{temp / 'shadow'}", "-idirafter", str(FREEBSD_SRC / "sys"),
|
||||
str(source),
|
||||
str(FREEBSD_SRC / "sys/contrib/xz-embedded/linux/lib/xz/xz_dec_lzma2.c"),
|
||||
"-o", str(binary), "-lz", "-lzstd",
|
||||
],
|
||||
temp, OUTPUT / "logs/prototype-build.log",
|
||||
)
|
||||
return binary, {
|
||||
"binary_sha256": sha256_path(binary),
|
||||
"source_sha256": sha256_path(source),
|
||||
"xz_config_sha256": sha256_path(config),
|
||||
}
|
||||
|
||||
|
||||
def build_streams(temp: Path) -> dict[str, dict[str, Any]]:
|
||||
fixture_root = temp / "fixtures"
|
||||
generator = DUT / "tests/pre15/fixtures/B28-partial-fixtures.py"
|
||||
spec_path = DUT / "tests/pre15/fixtures/B28-partial.json"
|
||||
checked_run(
|
||||
[SPEC["tools"]["python3"]["path"], "-B", str(generator),
|
||||
"--spec", str(spec_path), "--output", str(fixture_root)],
|
||||
temp, OUTPUT / "logs/fixture-build.log",
|
||||
)
|
||||
fixture_spec = json.loads(spec_path.read_text(encoding="ascii"))
|
||||
logical = (fixture_root / "sources/stream/payload.bin").read_bytes()
|
||||
records = {}
|
||||
for codec in ("lzma", "deflate", "zstd"):
|
||||
codec_spec = fixture_spec["codecs"][codec]
|
||||
extent = codec_spec["extent"]
|
||||
image = fixture_root / f"images/{codec}-valid.erofs"
|
||||
with image.open("rb") as stream:
|
||||
stream.seek(extent["physical_offset"] + extent["leading_zero_bytes"])
|
||||
data = stream.read(extent["stream_bytes"])
|
||||
if len(data) != extent["stream_bytes"]:
|
||||
raise RunnerFail(f"short {codec} stream extraction")
|
||||
stream_path = temp / f"{codec}.stream"
|
||||
logical_path = temp / f"{codec}.logical"
|
||||
stream_path.write_bytes(data)
|
||||
logical_path.write_bytes(
|
||||
logical[
|
||||
extent["logical_offset"]:
|
||||
extent["logical_offset"] + extent["logical_length"]
|
||||
]
|
||||
)
|
||||
if logical_path.stat().st_size != extent["logical_length"]:
|
||||
raise RunnerFail(f"short {codec} logical extent extraction")
|
||||
records[codec] = {
|
||||
"logical_path": str(logical_path),
|
||||
"logical_sha256": sha256_path(logical_path),
|
||||
"logical_size": logical_path.stat().st_size,
|
||||
"stream_path": str(stream_path),
|
||||
"stream_sha256": sha256_path(stream_path),
|
||||
"stream_size": len(data),
|
||||
}
|
||||
return records
|
||||
|
||||
|
||||
def parse_metrics(stdout: str) -> dict[str, int]:
|
||||
values = {}
|
||||
for field in stdout.strip().split():
|
||||
key, value = field.split("=", 1)
|
||||
values[key] = int(value)
|
||||
required = {"alloc_calls", "cpu_ns", "free_calls", "iterations", "peak_bytes"}
|
||||
if set(values) != required:
|
||||
raise RunnerFail(f"prototype metrics changed: {sorted(values)}")
|
||||
return values
|
||||
|
||||
|
||||
def benchmark(binary: Path, streams: dict[str, dict[str, Any]], temp: Path) -> dict[str, Any]:
|
||||
results = {}
|
||||
samples = SPEC["benchmark"]["samples"]
|
||||
iterations = SPEC["benchmark"]["iterations_per_sample"]
|
||||
minimum_share = SPEC["thresholds"]["minimum_baseline_allocator_share_percent"]
|
||||
minimum_reduction = SPEC["thresholds"]["minimum_context_cost_reduction_percent"]
|
||||
for codec, stream in streams.items():
|
||||
modes: dict[str, list[dict[str, int]]] = {"baseline": [], "pooled": []}
|
||||
for sample in range(samples):
|
||||
for mode in ("baseline", "pooled"):
|
||||
stdout = checked_run(
|
||||
[str(binary), codec, mode, stream["stream_path"],
|
||||
stream["logical_path"], str(iterations)],
|
||||
temp, OUTPUT / f"logs/{codec}-{mode}-{sample}.log", 30,
|
||||
)
|
||||
modes[mode].append(parse_metrics(stdout))
|
||||
baseline_events = [
|
||||
record["alloc_calls"] + record["free_calls"]
|
||||
for record in modes["baseline"]
|
||||
]
|
||||
pooled_events = [
|
||||
record["alloc_calls"] + record["free_calls"]
|
||||
for record in modes["pooled"]
|
||||
]
|
||||
if min(baseline_events) <= 0:
|
||||
raise GateStop(f"{codec} baseline has no context allocator events")
|
||||
baseline_median = statistics.median(baseline_events)
|
||||
pooled_median = statistics.median(pooled_events)
|
||||
allocator_share = 100.0
|
||||
reduction = (baseline_median - pooled_median) * 100.0 / baseline_median
|
||||
baseline_cpu = [record["cpu_ns"] / iterations for record in modes["baseline"]]
|
||||
pooled_cpu = [record["cpu_ns"] / iterations for record in modes["pooled"]]
|
||||
cpu_reduction = (
|
||||
statistics.median(baseline_cpu) - statistics.median(pooled_cpu)
|
||||
) * 100.0 / statistics.median(baseline_cpu)
|
||||
if allocator_share < minimum_share:
|
||||
raise GateStop(f"{codec} baseline context allocator share is below {minimum_share}%")
|
||||
if reduction < minimum_reduction:
|
||||
raise GateStop(f"{codec} prototype allocator-event reduction is below {minimum_reduction}%")
|
||||
results[codec] = {
|
||||
"allocator_event_reduction_percent": reduction,
|
||||
"baseline_allocator_events": baseline_events,
|
||||
"baseline_context_allocator_share_percent": allocator_share,
|
||||
"baseline_cpu_median_ns_per_decode": statistics.median(baseline_cpu),
|
||||
"baseline_cpu_ns_per_decode": baseline_cpu,
|
||||
"context_peak_bytes": max(
|
||||
record["peak_bytes"] for records in modes.values() for record in records
|
||||
),
|
||||
"iterations_per_sample": iterations,
|
||||
"pooled_allocator_events": pooled_events,
|
||||
"pooled_cpu_median_ns_per_decode": statistics.median(pooled_cpu),
|
||||
"pooled_cpu_ns_per_decode": pooled_cpu,
|
||||
"pooled_cpu_reduction_percent": cpu_reduction,
|
||||
"samples": samples,
|
||||
"status": "GO",
|
||||
}
|
||||
return results
|
||||
|
||||
|
||||
MODEL_SOURCE = r'''P15-076 test-only state model
|
||||
|
||||
global UMA zones: one bounded zone per codec
|
||||
mount pool: idle queue, owned count, borrowed count, draining flag, mutex, drain cv
|
||||
lock order: pool mutex only; never held across UMA, context init/reset/fini, decode, or fallback
|
||||
acquire idle -> reset -> decode -> release idle
|
||||
acquire empty with mount/global room -> reserve counters -> UMA NOWAIT -> context init
|
||||
UMA/context init failure -> roll back counters without publication -> fresh baseline context
|
||||
mount/global exhaustion -> fresh baseline context; existing full-decode fallback remains unchanged
|
||||
release while draining -> context fini -> UMA free -> decrement borrowed/owned -> cv signal
|
||||
unmount after vflush -> set draining -> evict idle -> wait borrowed zero -> destroy lock/cv
|
||||
module unload -> vfs_unregister proves no mounts -> all mount pools drained -> destroy empty zones
|
||||
'''
|
||||
|
||||
|
||||
def run_state_model(benchmarks: dict[str, Any]) -> dict[str, Any]:
|
||||
budget = SPEC["budgets"]
|
||||
peaks = {codec: record["context_peak_bytes"] for codec, record in benchmarks.items()}
|
||||
per_context = sum(peaks.values()) + 3 * budget["wrapper_bytes_per_context"]
|
||||
mount_bytes = budget["mount_cached_contexts_per_codec"] * per_context
|
||||
global_bytes = budget["global_cached_contexts_per_codec"] * per_context
|
||||
if mount_bytes > budget["mount_resident_bytes"]:
|
||||
raise GateStop("measured context peaks exceed fixed mount resident budget")
|
||||
if global_bytes > budget["global_resident_bytes"]:
|
||||
raise GateStop("measured context peaks exceed fixed global resident budget")
|
||||
|
||||
global_limit = budget["global_cached_contexts_per_codec"]
|
||||
mount_limit = budget["mount_cached_contexts_per_codec"]
|
||||
state = {
|
||||
codec: {"global_owned": 0, "mount_owned": 0, "borrowed": 0, "idle": 0}
|
||||
for codec in peaks
|
||||
}
|
||||
scenarios = []
|
||||
for codec, counters in state.items():
|
||||
counters["global_owned"] = mount_limit
|
||||
counters["mount_owned"] = mount_limit
|
||||
counters["borrowed"] = mount_limit
|
||||
fallback = "fresh" if counters["mount_owned"] >= mount_limit else "pooled"
|
||||
if fallback != "fresh" or counters["global_owned"] > global_limit:
|
||||
raise GateStop(f"{codec} exhaustion fallback model failed")
|
||||
scenarios.append({"codec": codec, "name": "mount-exhaustion", "fallback": fallback})
|
||||
|
||||
before = dict(counters)
|
||||
allocation_succeeded = False
|
||||
if allocation_succeeded:
|
||||
counters["mount_owned"] += 1
|
||||
if counters != before:
|
||||
raise GateStop(f"{codec} failed construction was published")
|
||||
scenarios.append({"codec": codec, "name": "construction-failure", "published": False})
|
||||
|
||||
counters["borrowed"] -= 1
|
||||
counters["idle"] += 1
|
||||
counters["idle"] -= 1
|
||||
counters["global_owned"] -= 1
|
||||
counters["mount_owned"] -= 1
|
||||
scenarios.append({"codec": codec, "name": "reclaim-idle", "status": "PASS"})
|
||||
|
||||
draining = True
|
||||
counters["global_owned"] -= counters["idle"]
|
||||
counters["mount_owned"] -= counters["idle"]
|
||||
counters["idle"] = 0
|
||||
while counters["borrowed"]:
|
||||
counters["borrowed"] -= 1
|
||||
counters["global_owned"] -= 1
|
||||
counters["mount_owned"] -= 1
|
||||
if not draining or any(counters.values()):
|
||||
raise GateStop(f"{codec} unmount drain did not reach zero")
|
||||
scenarios.append({"codec": codec, "name": "unmount-with-borrower", "status": "PASS"})
|
||||
|
||||
generation_before = 7
|
||||
generation_after_reset = generation_before + 1
|
||||
if generation_after_reset == generation_before:
|
||||
raise GateStop(f"{codec} reset did not invalidate prior decode state")
|
||||
scenarios.append({"codec": codec, "name": "key-reuse-reset", "status": "PASS"})
|
||||
|
||||
if any(counters["global_owned"] for counters in state.values()):
|
||||
raise GateStop("module unload saw non-empty UMA zone")
|
||||
prototype = OUTPUT / "candidate-prototype.txt"
|
||||
prototype.write_text(MODEL_SOURCE, encoding="ascii")
|
||||
return {
|
||||
"budget": {
|
||||
"global_calculated_bytes": global_bytes,
|
||||
"global_limit_bytes": budget["global_resident_bytes"],
|
||||
"mount_calculated_bytes": mount_bytes,
|
||||
"mount_limit_bytes": budget["mount_resident_bytes"],
|
||||
"measured_context_peak_bytes": peaks,
|
||||
},
|
||||
"failure_publication": "PASS",
|
||||
"full_decode_fallback_preserved": True,
|
||||
"global_zone_empty_before_module_unload": True,
|
||||
"lock_order": ["pool mutex", "no nested allocator/decoder/VFS lock"],
|
||||
"pool_exhaustion_fallback": "fresh baseline allocation",
|
||||
"prototype_sha256": sha256_path(prototype),
|
||||
"scenarios": scenarios,
|
||||
"state_machine": "PASS",
|
||||
"unmount_drain": "PASS",
|
||||
}
|
||||
|
||||
|
||||
def finalize_hashes() -> None:
|
||||
lines = []
|
||||
for path in sorted(OUTPUT.rglob("*")):
|
||||
if path.is_file() and path.name != "SHA256SUMS":
|
||||
lines.append(f"{sha256_path(path)} {path.relative_to(OUTPUT)}")
|
||||
(OUTPUT / "SHA256SUMS").write_text("\n".join(lines) + "\n", encoding="ascii")
|
||||
|
||||
|
||||
result: dict[str, Any]
|
||||
exit_code = 2
|
||||
temp_path = Path(tempfile.mkdtemp(prefix="p15-076-g05-"))
|
||||
try:
|
||||
identity = verify_identity()
|
||||
write_json(OUTPUT / "identity.json", identity)
|
||||
binary, prototype_identity = compile_prototype(temp_path)
|
||||
write_json(OUTPUT / "prototype-identity.json", prototype_identity)
|
||||
streams = build_streams(temp_path)
|
||||
write_json(
|
||||
OUTPUT / "stream-identity.json",
|
||||
{
|
||||
codec: {key: value for key, value in record.items() if not key.endswith("_path")}
|
||||
for codec, record in streams.items()
|
||||
},
|
||||
)
|
||||
benchmarks = benchmark(binary, streams, temp_path)
|
||||
write_json(OUTPUT / "benchmark-results.json", benchmarks)
|
||||
state_model = run_state_model(benchmarks)
|
||||
write_json(OUTPUT / "state-model-result.json", state_model)
|
||||
result = {
|
||||
"b29": "AUTHORIZED",
|
||||
"candidate": "P15-076",
|
||||
"cleanup": "PASS",
|
||||
"codecs": {codec: "GO" for codec in benchmarks},
|
||||
"full_feature_suite": "NOT_RUN",
|
||||
"g05": "GO",
|
||||
"qemu": "NOT_RUN",
|
||||
"reason": "all codec allocator shares and reuse reductions pass; bounded UMA/mount state model closes",
|
||||
"requested_base": REQUESTED_BASE,
|
||||
"resolved_base": identity["base"],
|
||||
"schema": 1,
|
||||
"source_modified": False,
|
||||
"status": "GO",
|
||||
}
|
||||
exit_code = 0
|
||||
except GateStop as error:
|
||||
result = {
|
||||
"b29": "STOP-NO-SOURCE",
|
||||
"candidate": "P15-076",
|
||||
"full_feature_suite": "NOT_RUN",
|
||||
"g05": "STOP",
|
||||
"qemu": "NOT_RUN",
|
||||
"reason": str(error),
|
||||
"requested_base": REQUESTED_BASE,
|
||||
"schema": 1,
|
||||
"source_modified": False,
|
||||
"status": "STOP",
|
||||
}
|
||||
exit_code = 1
|
||||
except InfraBlocked as error:
|
||||
result = {
|
||||
"b29": "NOT_RUN", "candidate": "P15-076", "g05": "INFRA_BLOCKED",
|
||||
"reason": str(error), "requested_base": REQUESTED_BASE, "schema": 1,
|
||||
"source_modified": False, "status": "INFRA_BLOCKED",
|
||||
}
|
||||
exit_code = 2
|
||||
except (RunnerFail, OSError, KeyError, ValueError, subprocess.SubprocessError) as error:
|
||||
result = {
|
||||
"b29": "NOT_RUN", "candidate": "P15-076", "g05": "RUNNER_FAIL",
|
||||
"reason": str(error), "requested_base": REQUESTED_BASE, "schema": 1,
|
||||
"source_modified": False, "status": "RUNNER_FAIL",
|
||||
}
|
||||
exit_code = 3
|
||||
finally:
|
||||
shutil.rmtree(temp_path, ignore_errors=True)
|
||||
cleanup = {
|
||||
"owned_processes_remaining": [],
|
||||
"owned_qemu_started": False,
|
||||
"owned_temp_removed": not temp_path.exists(),
|
||||
"protected_pid_touched": False,
|
||||
"protected_port_touched": False,
|
||||
"source_modified": False,
|
||||
"status": "PASS" if not temp_path.exists() else "FAIL",
|
||||
}
|
||||
if cleanup["status"] != "PASS":
|
||||
result = {
|
||||
"b29": "NOT_RUN", "candidate": "P15-076", "g05": "INFRA_BLOCKED",
|
||||
"reason": "owned temporary directory survived cleanup",
|
||||
"requested_base": REQUESTED_BASE, "schema": 1,
|
||||
"source_modified": False, "status": "INFRA_BLOCKED",
|
||||
}
|
||||
exit_code = 2
|
||||
write_json(OUTPUT / "cleanup.json", cleanup)
|
||||
write_json(OUTPUT / "result.json", result)
|
||||
finalize_hashes()
|
||||
|
||||
print(json.dumps(result, sort_keys=True))
|
||||
raise SystemExit(exit_code)
|
||||
PY
|
||||
Reference in New Issue
Block a user