unrip/scripts/ops/live_near_intents.py
philipp aa8a868c14
All checks were successful
deploy / deploy (push) Successful in 41s
fix: OOM heuristic bug in quote outcomes
Proof: Successful trades only dashboard is empty
Assumptions: The downtime resulted in a massive gap we can attribute
Still fake: Some outcomes may be heuristic
2026-06-16 16:30:09 +02:00

212 lines
6 KiB
Python
Executable file

#!/usr/bin/env python3
from __future__ import annotations
import argparse
import json
import signal
import subprocess
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from common import DEFAULT_NAMESPACE, build_env, kafka_brokers, raw_near_topic
DEFAULT_OUTPUT_FORMAT = (
"%d{go[2006-01-02T15:04:05Z07:00]} topic=%t partition=%p offset=%o\n"
"%v\n"
)
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Live-inspect raw NEAR intents arriving in Redpanda."
)
parser.add_argument(
"--namespace",
default=DEFAULT_NAMESPACE,
help=f"Kubernetes namespace to inspect (default: {DEFAULT_NAMESPACE})",
)
parser.add_argument(
"--brokers",
default="",
help="Override Kafka brokers instead of reading them from unrip-config.",
)
parser.add_argument(
"--topic",
default="",
help="Topic to consume. Defaults to KAFKA_TOPIC_RAW_NEAR_INTENTS_QUOTE from unrip-config.",
)
offset_group = parser.add_mutually_exclusive_group()
offset_group.add_argument(
"--offset",
default="end",
help=(
"Kafka offset selector passed to rpk. "
"The default 'end' means live-tail from now, not replay history."
),
)
offset_group.add_argument(
"--last",
type=int,
default=0,
help=(
"Read the most recent N retained records and exit. "
"Equivalent to '--offset -N --num N'."
),
)
parser.add_argument(
"--num",
type=int,
default=0,
help=(
"Number of records to consume before exiting, starting from the chosen "
"offset. 0 means unbounded. With '--offset end' this waits for new records."
),
)
parser.add_argument(
"--timeout",
type=float,
default=0,
help="Stop after this many seconds even if the stream is still open.",
)
parser.add_argument(
"--value-only",
action="store_true",
help="Print only the record value without topic/partition/offset metadata.",
)
parser.add_argument(
"--rpk-json",
action="store_true",
help="Use rpk's built-in JSON record formatter instead of the default metadata+value output.",
)
parser.add_argument(
"--output",
choices=("json", "jsonl", "text"),
default="json",
help="Output format (default: json).",
)
return parser.parse_args()
def build_command(args: argparse.Namespace, brokers: str, topic: str) -> list[str]:
offset = args.offset
num = args.num
if args.last:
offset = f"-{args.last}"
num = args.last
command = [
"kubectl",
"exec",
"-i",
"-n",
args.namespace,
"deploy/redpanda",
"--",
"rpk",
"topic",
"consume",
topic,
"--brokers",
brokers,
"--offset",
offset,
"--num",
str(num),
]
if args.output in {"json", "jsonl"} or args.rpk_json:
command.extend(["--format", "json", "--pretty-print=false"])
elif args.rpk_json:
command.extend(["--format", "json", "--pretty-print=false"])
elif args.value_only:
command.extend(["--format", "%v\n"])
else:
command.extend(["--format", DEFAULT_OUTPUT_FORMAT])
return command
def main() -> int:
args = parse_args()
brokers = args.brokers or kafka_brokers(namespace=args.namespace)
topic = args.topic or raw_near_topic(namespace=args.namespace)
command = build_command(args, brokers, topic)
effective_offset = args.offset if not args.last else f"-{args.last}"
effective_num = args.num if not args.last else args.last
if args.output == "text":
process = subprocess.Popen(command, env=build_env())
try:
process.wait(timeout=args.timeout if args.timeout and args.timeout > 0 else None)
except subprocess.TimeoutExpired:
process.send_signal(signal.SIGINT)
try:
process.wait(timeout=5)
except subprocess.TimeoutExpired:
process.kill()
process.wait(timeout=5)
return 124
except KeyboardInterrupt:
process.send_signal(signal.SIGINT)
return process.wait()
return process.returncode
try:
result = subprocess.run(
command,
env=build_env(),
text=True,
capture_output=True,
timeout=args.timeout if args.timeout and args.timeout > 0 else None,
check=False,
)
stdout = result.stdout
return_code = result.returncode
except subprocess.TimeoutExpired as exc:
stdout = exc.stdout or ""
return_code = 124
records = parse_rpk_json_lines(stdout, value_only=args.value_only)
if args.output == "json":
print(json.dumps(records, indent=2))
else:
for record in records:
print(json.dumps(record, separators=(",", ":")))
return return_code
def parse_rpk_json_lines(stdout: str, *, value_only: bool) -> list[object]:
records: list[object] = []
for raw_line in stdout.splitlines():
line = raw_line.strip()
if not line:
continue
record = json.loads(line)
if value_only:
record = decode_jsonish(record.get("value"))
else:
if "value" in record:
record["value"] = decode_jsonish(record.get("value"))
records.append(record)
return records
def decode_jsonish(value: object) -> object:
if not isinstance(value, str):
return value
stripped = value.strip()
if not stripped:
return value
if stripped[0] not in "{[":
return value
try:
return json.loads(stripped)
except json.JSONDecodeError:
return value
if __name__ == "__main__":
raise SystemExit(main())