From dac3aeaafb817e2ec987e510542e07fc9db7f74f Mon Sep 17 00:00:00 2001 From: Maxim Vershinin Date: Mon, 28 Sep 2026 18:52:32 +0400 Subject: [PATCH] feat(project-setup): add CLI entry point and __main__.py --- scripts/cli.py | 97 ++++++++++++++++++++++++++++++++++++ src/disk_checker/__main__.py | 6 +++ 2 files changed, 103 insertions(+) create mode 100644 scripts/cli.py create mode 100644 src/disk_checker/__main__.py diff --git a/scripts/cli.py b/scripts/cli.py new file mode 100644 index 0000000..0b713f7 --- /dev/null +++ b/scripts/cli.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +""" +CLI entry point for disk-checker. + +Usage: + python -m disk_checker scan_latency.csv + disk-checker scan_latency.csv --metric read_clat --unit ms +""" + +import argparse +import sys +from pathlib import Path + +from disk_checker.core.extractor import load_latency_csv +from disk_checker.core.helpers import percentile_table +from disk_checker.plotting.plots import generate_plot + + +def main(): + parser = argparse.ArgumentParser( + description="Plot fio json+ wide-format latency CSV." + ) + parser.add_argument("csv", help="CSV from fio_jsonplus_clat2csv") + parser.add_argument("--metric", default=None, + help="Metric to plot (e.g. read_clat). " + "If omitted, the metric with the most I/Os is used.") + parser.add_argument("--all-metrics", action="store_true", + help="Plot every metric that has data") + parser.add_argument("--unit", choices=["ns", "us", "ms", "s"], default="us", + help="X-axis latency unit (default: us)") + parser.add_argument("--log-y", action="store_true") + parser.add_argument("--log-x", action="store_true") + parser.add_argument("--cdf", action="store_true", + help="Add a CDF subplot below the histogram") + parser.add_argument("--cdf-only", action="store_true") + parser.add_argument("--normalize", action="store_true", + help="Normalize histogram to a probability density") + parser.add_argument("--percentiles", default="50,90,99,99.9,99.99") + parser.add_argument("--no-percentiles", action="store_true") + parser.add_argument("--output", "-o", default=None) + parser.add_argument("--dpi", type=int, default=150) + parser.add_argument("--title", default="") + parser.add_argument("--show", action="store_true") + args = parser.parse_args() + + if not Path(args.csv).is_file(): + sys.exit(f"error: file not found: {args.csv}") + + df = load_latency_csv(args.csv) + if df.empty: + sys.exit("error: no latency data in CSV") + + available = sorted(df["metric"].unique()) + print("Metrics with data:", ", ".join(available)) + + if args.all_metrics: + metrics = available + elif args.metric: + if args.metric not in available: + sys.exit(f"error: metric {args.metric!r} not in {available}") + metrics = [args.metric] + else: + totals = df.groupby("metric")["count"].sum().sort_values(ascending=False) + metrics = [totals.index[0]] + print(f"Auto-selected metric: {metrics[0]}") + + output = args.output or f"{Path(args.csv).stem}_latency.png" + generate_plot( + df=df, + metrics=metrics, + output_path=Path(output), + unit=args.unit, + log_y=args.log_y, + log_x=args.log_x, + normalize=args.normalize, + show_cdf=args.cdf, + cdf_only=args.cdf_only, + title=args.title, + dpi=args.dpi, + ) + print(f"Saved plot to {output}") + + if not args.no_percentiles: + try: + pcts = [float(x) for x in args.percentiles.split(",") if x.strip()] + except ValueError: + sys.exit("error: --percentiles must be comma-separated numbers") + for m in metrics: + print(percentile_table(df, m, args.unit, pcts)) + + if args.show: + import matplotlib.pyplot as plt + plt.show() + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/disk_checker/__main__.py b/src/disk_checker/__main__.py new file mode 100644 index 0000000..6e1b3a5 --- /dev/null +++ b/src/disk_checker/__main__.py @@ -0,0 +1,6 @@ +"""Allow running disk-checker as a module: python -m disk_checker""" + +from disk_checker.scripts.cli import main + +if __name__ == "__main__": + main() \ No newline at end of file