Skip to content

Commit b701902

Browse files
committed
add a download_timer script for simple benchmarking
1 parent 05bbc41 commit b701902

1 file changed

Lines changed: 168 additions & 0 deletions

File tree

download_timer.py

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
# /// script
2+
# requires-python = ">=3.13"
3+
# dependencies = [
4+
# "herbie-data>=2026.3.0",
5+
# ]
6+
# ///
7+
8+
"""
9+
Herbie Download Timer Benchmark Tool.
10+
11+
This script benchmarks data retrieval performance across available Herbie
12+
data sources (e.g., AWS, Azure, NOMADS, etc.) for a given forecast model
13+
and search pattern.
14+
15+
Example usage:
16+
uv run download_timer.py
17+
uv run download_timer.py --model hrrr --search ":TMP:2 m:"
18+
uv run download_timer.py --verbose
19+
"""
20+
21+
import argparse
22+
import time
23+
from datetime import date
24+
25+
from herbie import Herbie
26+
27+
28+
# ---------------------------
29+
# Bar rendering
30+
# ---------------------------
31+
def make_bar(t, t_min, t_max, max_width=30, min_width=1):
32+
if t_max == t_min:
33+
return "█" * max_width
34+
35+
norm = (t - t_min) / (t_max - t_min)
36+
units = min_width + norm * (max_width - min_width)
37+
38+
full_blocks = int(units)
39+
remainder = units - full_blocks
40+
half_block = 1 if remainder >= 0.5 else 0
41+
42+
return "█" * full_blocks + ("▌" if half_block else "")
43+
44+
45+
# ---------------------------
46+
# Core run logic
47+
# ---------------------------
48+
def run(model, date, search, verbose=False):
49+
50+
results = {}
51+
52+
sources = Herbie(date, model=model, verbose=False).SOURCES.keys()
53+
54+
# ---------------------------
55+
# Download + timing
56+
# ---------------------------
57+
for source in sources:
58+
print(f"Downloading from {source:20s}", end="\r")
59+
try:
60+
H = Herbie(
61+
date,
62+
model=model,
63+
priority=source,
64+
overwrite=True,
65+
verbose=verbose,
66+
)
67+
68+
start = time.time()
69+
f = H.download(search, overwrite=True)
70+
elapsed = time.time() - start
71+
72+
# Not found case
73+
if H.grib is None:
74+
results[source] = ("not_found", None, None, None)
75+
continue
76+
77+
# Field count
78+
nfields = len(H.inventory(search))
79+
80+
# File size
81+
if isinstance(f, (list, tuple)):
82+
total_size = sum(fp.stat().st_size for fp in f)
83+
else:
84+
total_size = f.stat().st_size
85+
86+
size_mb = total_size / (1024 * 1024)
87+
88+
results[source] = ("ok", elapsed, nfields, size_mb)
89+
90+
except Exception:
91+
results[source] = ("error", None, None, None)
92+
93+
print(f"{' ':40s}", end="\r")
94+
95+
# ---------------------------
96+
# Prepare valid items
97+
# ---------------------------
98+
valid_items = [
99+
(s, t, n, sz) for s, (status, t, n, sz) in results.items() if status == "ok"
100+
]
101+
102+
print("\nDownload Summary:")
103+
print(f"{model=}, {date=}, {search=}\n")
104+
105+
# ---------------------------
106+
# Print successful downloads
107+
# ---------------------------
108+
if valid_items:
109+
t_min = min(t for _, t, _, _ in valid_items)
110+
t_max = max(t for _, t, _, _ in valid_items)
111+
112+
valid_items.sort(key=lambda x: x[1])
113+
114+
for source, t, nfields, size_mb in valid_items:
115+
bar = make_bar(t, t_min, t_max, max_width=30)
116+
print(
117+
f"{source:<10} {bar:<31} "
118+
f"{t:.3f} s ({nfields} fields, {size_mb:.2f} MB)"
119+
)
120+
121+
# ---------------------------
122+
# Print non-success cases
123+
# ---------------------------
124+
for source, (status, _, _, _) in results.items():
125+
if status == "not_found":
126+
print(f"{source:<10} NOT FOUND")
127+
elif status == "error":
128+
print(f"{source:<10} ERROR")
129+
130+
131+
# ---------------------------
132+
# CLI
133+
# ---------------------------
134+
def parse_args():
135+
parser = argparse.ArgumentParser(
136+
description=(
137+
"Benchmark GRIB download performance across Herbie data sources "
138+
"(AWS, Azure, NOMADS, etc.), including timing, file size, and field count."
139+
)
140+
)
141+
142+
today = date.today()
143+
144+
parser.add_argument("--model", default="hrrr", help="Model name (default: hrrr)")
145+
parser.add_argument("--date", default=today, help="Forecast date")
146+
parser.add_argument(
147+
"--search",
148+
default=":TMP:",
149+
help="GRIB search string (e.g. ':TMP:', ':t:' for ifs model, ':GRD:')",
150+
)
151+
parser.add_argument("--verbose", action="store_true", help="Verbose output")
152+
153+
return parser.parse_args()
154+
155+
156+
def main():
157+
args = parse_args()
158+
159+
run(
160+
model=args.model,
161+
date=args.date,
162+
search=args.search,
163+
verbose=args.verbose,
164+
)
165+
166+
167+
if __name__ == "__main__":
168+
main()

0 commit comments

Comments
 (0)