log-search: Add --stats flag.

This commit is contained in:
Alex Vandiver
2025-07-15 15:07:27 +00:00
committed by Tim Abbott
parent 2616b7d030
commit e9a2ee56c3

View File

@@ -7,6 +7,7 @@ import logging
import os
import re
import signal
import statistics
import sys
from datetime import date, datetime, timedelta, timezone
from enum import Enum, auto
@@ -114,6 +115,7 @@ def parser() -> argparse.ArgumentParser:
output = parser.add_argument_group("Output")
output.add_argument("--full-line", "-F", help="Show full matching line", action="store_true")
output.add_argument("--timeline", "-T", help="Show start, end, and gaps", action="store_true")
output.add_argument("--stats", "-S", help="Compute and show statistics", action="store_true")
return parser
@@ -207,6 +209,10 @@ def main() -> None:
use_color = sys.stdout.isatty()
lowered_terms = [term.lower() for term in substr_terms]
if args.stats:
durations: list[int] | None = []
else:
durations = None
try:
for logfile_name in reversed(logfile_names):
with maybe_gzip(logfile_name) as logfile:
@@ -232,6 +238,7 @@ def main() -> None:
args,
filter_types=filter_types,
use_color=use_color,
durations=durations,
)
except BrokenPipeError:
# Python flushes standard streams on exit; redirect remaining output
@@ -242,6 +249,19 @@ def main() -> None:
except KeyboardInterrupt:
sys.exit(signal.SIGINT + 128)
if durations is not None:
# Prepend [0] to make the percentiles 1-indexed, instead of 0-indexed
percentiles = [0, *statistics.quantiles(durations, n=100)]
print()
print(f"Total requests: {len(durations)}")
print(f"Min duration: {min(durations):>5}ms")
print(f"p50 duration: {int(percentiles[50]):>5}ms")
print(f"p75 duration: {int(percentiles[75]):>5}ms")
print(f"p90 duration: {int(percentiles[90]):>5}ms")
print(f"p95 duration: {int(percentiles[95]):>5}ms")
print(f"p99 duration: {int(percentiles[99]):>5}ms")
print(f"Max duration: {max(durations):>5}ms")
def parse_logfile_names(args: argparse.Namespace) -> list[str]:
if args.nginx:
@@ -437,9 +457,17 @@ def print_line(
args: argparse.Namespace,
filter_types: set[FilterType],
use_color: bool,
durations: list[int] | None = None,
) -> None:
global last_match_end
if match["duration"].endswith("ms"):
duration_ms = int(match["duration"].removesuffix("ms"))
else:
duration_ms = int(float(match["duration"].removesuffix("s")) * 1000)
if durations is not None:
durations.append(duration_ms)
if args.full_line:
print(match.group(0))
return