blob: 44f257c6a215e134275c72c2d44e37e232da0208 [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.
import argparse
import subprocess
import sys
try:
from tabulate import tabulate
except ImportError:
print("Error: tabulate library not found.")
print("Please install it with: pip3 install tabulate")
sys.exit(1)
PARSERS = ["pooled", "prost", "rust-protobuf", "quick-protobuf"]
BINARY_PATH = "../../target/release/parser_mem"
def build_binary(use_unsafe: bool):
print(f"Building binary{' with unsafe-split' if use_unsafe else ''}...")
cmd = ["cargo", "build", "--release", "--bin", "parser_mem"]
if use_unsafe:
cmd.extend(["--features", "unsafe-split"])
result = subprocess.run(cmd, cwd=".")
return result.returncode == 0
def parse_total_alloc(stdout: str):
for line in stdout.splitlines():
if line.startswith("Total allocated bytes (cumulative):"):
try:
raw = line.split(":", 1)[1].strip().split(" ")[0]
return int(raw)
except Exception:
return None
return None
def run_parser(parser: str, mode: str, scale: int):
cmd = [BINARY_PATH, mode, str(scale), parser]
try:
res = subprocess.run(
cmd, cwd=".", capture_output=True, text=True, timeout=300)
except Exception as e:
print(f"[error] run {parser}: {e}", file=sys.stderr)
return None
if res.returncode != 0:
print(f"[error] run {parser} failed: {res.stderr}", file=sys.stderr)
return None
total = parse_total_alloc(res.stdout)
if total is None:
print(
f"[error] parse total bytes failed for {parser}. Output:\n{res.stdout}",
file=sys.stderr,
)
return total
def print_table(results: dict, mode: str, scale: int):
print(f"\n{'='*60}")
print("MEMORY BENCHMARK RESULTS")
print(f"{'='*60}")
print(f"Mode: {mode}, Scale: {scale}")
headers = ["Parser", "TotalAllocatedBytes"]
rows = [[p, f"{results[p]:,}"] for p in PARSERS if p in results]
print(
tabulate(
rows,
headers=headers,
tablefmt="grid",
stralign="right",
numalign="right",
)
)
def main():
ap = argparse.ArgumentParser(
description="Memory benchmark for protobuf parsers")
ap.add_argument("--unsafe", action="store_true",
help="Enable unsafe-split feature")
ap.add_argument(
"--mode", choices=["sequential", "concurrent"], default="sequential", help="Run mode")
ap.add_argument("--scale", type=int, default=10, help="Benchmark scale")
args = ap.parse_args()
if args.scale <= 0:
print("scale must be positive", file=sys.stderr)
return 1
if not build_binary(args.unsafe):
print("build failed", file=sys.stderr)
return 1
results = {}
failed = False
for parser in PARSERS:
total = run_parser(parser, args.mode, args.scale)
if total is None:
failed = True
else:
results[parser] = total
if results:
print_table(results, args.mode, args.scale)
return 1 if failed else 0
if __name__ == "__main__":
sys.exit(main())