Skip to content

Commit 39ec703

Browse files
committed
Fix/timing plots (#124)
* fix plotting to work with khronos style names * fix comparison * clean up command line interface
1 parent 51acc84 commit 39ec703

File tree

2 files changed

+89
-63
lines changed

2 files changed

+89
-63
lines changed
Lines changed: 58 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
"""Command to gather timing information."""
22

3-
import json
43
import pathlib
54
import sys
65

@@ -16,9 +15,16 @@ def cli():
1615

1716
@cli.command(name="show")
1817
@click.argument("result_path", type=click.Path(exists=True))
19-
@click.option("-k", "--key", "keys", multiple=True, default=None)
18+
@click.option(
19+
"-k",
20+
"--key",
21+
"keys",
22+
multiple=True,
23+
default=None,
24+
help="optional regex filters to select relevant timers",
25+
)
2026
def show(result_path, keys):
21-
"""Print timing information for a single set of Hydra results."""
27+
"""Print timing distribution from Hydra's output at RESULT_PATH."""
2228
result_path = pathlib.Path(result_path).expanduser().absolute()
2329
durations = timing.collect_timing_info(result_path)
2430
if len(durations) == 0:
@@ -34,33 +40,67 @@ def show(result_path, keys):
3440

3541
@cli.command(name="plot")
3642
@click.argument("result_path", type=click.Path(exists=True))
37-
@click.option("-t", "--show-trends", is_flag=True, default=False)
38-
@click.option("-k", "--key", "keys", multiple=True, default=("frontend", "backend"))
39-
def plot(result_path, show_trends, keys):
40-
"""Plot timing information for a single set of Hydra results."""
43+
@click.option(
44+
"-k",
45+
"--key",
46+
"keys",
47+
multiple=True,
48+
default=("frontend.*", "backend.*"),
49+
help="optional regex filters to select timers for each subplot",
50+
)
51+
def plot(result_path, keys):
52+
"""Plot timing distribution from Hydra's output at RESULT_PATH."""
4153
result_path = pathlib.Path(result_path).expanduser().absolute()
4254
durations = timing.collect_timing_info(result_path)
4355
if len(durations) == 0:
4456
click.secho(f"Result path {result_path} does not contain timing info", fg="red")
4557
sys.exit(1)
4658

47-
if show_trends:
48-
timing.plot_trends(durations, keys=keys)
49-
else:
50-
timing.plot_durations(durations, keys=keys)
59+
timing.plot_durations(durations, keys=keys)
60+
61+
62+
@cli.command(name="timeline")
63+
@click.argument("result_path", type=click.Path(exists=True))
64+
@click.option(
65+
"-k",
66+
"--key",
67+
"keys",
68+
multiple=True,
69+
default=("frontend.*", "backend.*"),
70+
help="optional regex filters to select timers to plot",
71+
)
72+
def timeline(result_path, keys):
73+
"""Plot timing information against time from Hydra's output at RESULT_PATH."""
74+
result_path = pathlib.Path(result_path).expanduser().absolute()
75+
durations = timing.collect_timing_info(result_path)
76+
if len(durations) == 0:
77+
click.secho(f"Result path {result_path} does not contain timing info", fg="red")
78+
sys.exit(1)
79+
80+
timing.plot_trends(durations, keys=keys)
5181

5282

5383
@cli.command(name="compare")
5484
@click.argument("results", nargs=-1, type=click.Path(exists=True))
55-
@click.option("-k", "--key", "keys", multiple=True, default=timing.DEFAULT_COMPARE_KEYS)
56-
@click.option("-c", "--config", default=None)
57-
def compare(results, keys, config):
58-
"""Plot timing comparison between different runs of Hydra."""
85+
@click.option(
86+
"-k",
87+
"--key",
88+
"keys",
89+
multiple=True,
90+
default=("frontend/spin", "backend/spin"),
91+
help="timer names to use for comparison",
92+
)
93+
@click.option(
94+
"--use-bar",
95+
is_flag=True,
96+
default=False,
97+
help="use a bar plot instead of a boxen plot",
98+
)
99+
def compare(results, keys, use_bar):
100+
"""Plot timing comparison between different runs of Hydra from RESULTS."""
59101
if len(results) == 0:
60102
return
61103

62104
paths = [pathlib.Path(x).expanduser().absolute() for x in results]
63105
results = {path.stem: timing.collect_timing_info(path) for path in paths}
64-
65-
plot_config = {} if config is None else json.loads(config)
66-
timing.plot_comparison(results, plot_config, keys)
106+
timing.plot_comparison(results, keys, use_bars=use_bar)

eval/python/hydra_eval/timing.py

Lines changed: 31 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Some small helpers for getting timing information."""
22

33
import logging
4+
import pathlib
45
import re
56

67
import matplotlib.pyplot as plt
@@ -10,12 +11,6 @@
1011
import rich.console
1112
import seaborn as sns
1213

13-
DEFAULT_FOLDERS = ["frontend", "backend", "lcd", "timing"]
14-
FRONTEND_TIMERS = ["frontend/spin"]
15-
BACKEND_TIMERS = ["backend/spin"]
16-
OBJECT_TIMERS = ["frontend/objects_detection", "frontend/objects_graph_update"]
17-
PLACE_TIMERS = ["frontend/detect_gvd", "frontend/update_gvd_places"]
18-
ROOM_TIMERS = ["backend/room_detection"]
1914
DEFAULT_COMPARE_KEYS = [
2015
"frontend/object_detection",
2116
"gvd/extract_graph",
@@ -31,11 +26,12 @@ def _get_time_array_from_log(filename):
3126
return arr
3227

3328

34-
def _get_filenames(result_path, subdir):
35-
if not (result_path / subdir).exists():
29+
def _get_filenames(result_path):
30+
result_path = pathlib.Path(result_path)
31+
if not result_path.exists():
3632
return []
3733

38-
return [f for f in (result_path / subdir).iterdir() if f.match("*timing_raw.csv")]
34+
return [f for f in result_path.rglob("*timing_raw.csv")]
3935

4036

4137
def _get_timer_name(path):
@@ -64,22 +60,20 @@ def _get_longform_df(durations, filter_func=None):
6460
continue
6561

6662
times = np.squeeze(info[:, 1])
67-
part_name = name.split("/")[1]
6863
data = np.concatenate((data, times), axis=None)
69-
names += [part_name] * len(times)
64+
names += [name] * len(times)
7065

7166
if len(names) == 0:
7267
return None
7368

7469
return pd.DataFrame({"Name": names, "Elapsed Time [s]": data})
7570

7671

77-
def collect_timing_info(path, default_folders=None):
72+
def collect_timing_info(path, folders_to_use=None):
7873
"""Collect timing info for a specfic path."""
79-
timing_files = []
80-
folders_to_use = default_folders or DEFAULT_FOLDERS
81-
for folder in folders_to_use:
82-
timing_files += _get_filenames(path, folder)
74+
timing_files = _get_filenames(path)
75+
if folders_to_use:
76+
timing_files = [x for x in timing_files if x.parent in folders_to_use]
8377

8478
return {_get_timer_name(f): _get_time_array_from_log(f) for f in timing_files}
8579

@@ -139,30 +133,30 @@ def _get_stat_str(stat):
139133
console.print(table)
140134

141135

142-
def plot_durations(durations, keys=["frontend", "backend"], rt_threshold=None):
136+
def plot_durations(durations, keys):
143137
"""Make a plot of durations."""
144138
sns.set()
145139
sns.set_style("whitegrid")
146140
sns.set_context("notebook")
147141
fig, ax = plt.subplots(len(keys), 1, squeeze=False)
148142

149143
for idx, key in enumerate(keys):
150-
ax[idx][0].set_title(f"{key.capitalize()} Timing Distributions")
151-
df = _get_longform_df(durations, lambda x: key not in x)
144+
matcher = re.compile(key)
145+
ax[idx][0].set_title(f"{key} Timing Distributions")
146+
df = _get_longform_df(durations, lambda x: matcher.match(x))
152147
if df is None:
153148
continue
154149

155150
sns.boxenplot(data=df, x="Name", y="Elapsed Time [s]", ax=ax[idx][0])
156151
lax = ax[idx][0]
157152
lax.set_xticks(lax.get_xticks(), lax.get_xticklabels(), rotation=30, ha="right")
158-
_draw_realtime_threshold(lax, rt_threshold)
159153

160154
fig.set_size_inches([14, 12 * len(keys)])
161155
fig.tight_layout()
162156
plt.show()
163157

164158

165-
def plot_trends(durations, keys=["frontend", "backend"], rt_threshold=None):
159+
def plot_trends(durations, keys):
166160
"""Make a plot of durations."""
167161
sns.set()
168162
sns.set_style("whitegrid")
@@ -171,8 +165,9 @@ def plot_trends(durations, keys=["frontend", "backend"], rt_threshold=None):
171165

172166
for idx, key in enumerate(keys):
173167
added_plot = False
168+
matcher = re.compile(key)
174169
for name, time_array in durations.items():
175-
if key not in name:
170+
if not matcher.match(name):
176171
continue
177172

178173
assert len(time_array.shape) == 2 and time_array.shape[1] == 2
@@ -183,60 +178,51 @@ def plot_trends(durations, keys=["frontend", "backend"], rt_threshold=None):
183178
ax[idx][0].plot(to_plot[:, 0], to_plot[:, 1], label=name)
184179
added_plot = True
185180

186-
ax[idx][0].set_title(f"{key.capitalize()} Timing Trends")
181+
ax[idx][0].set_title(f"{key} Timing Trends")
187182
ax[idx][0].set_xlabel("Timestamp [s]")
188183
ax[idx][0].set_ylabel("Elapsed Time [s]")
189184
if added_plot:
190-
_draw_realtime_threshold(ax[idx][0], rt_threshold)
191185
ax[idx][0].legend()
192186

193187
fig.set_size_inches([16, 8 * len(keys)])
194188
plt.show()
195189

196190

197-
def plot_comparison(results, plot_config, keys, use_bars=False, rt_threshold=None):
191+
def plot_comparison(results, keys, use_bars=False):
198192
"""Plot timing comparison for different results directories."""
193+
sns.set()
194+
sns.set_style("whitegrid")
195+
sns.set_context("notebook")
196+
199197
data = np.array([])
200198
labels = []
201199
result_set = []
202200

203201
keys = [x.replace("/", "_") for x in keys]
204202
for stem, result in results.items():
205-
result_label = plot_config.get("result_map", {}).get(stem, stem.upper())
206203
for key in keys:
207204
if key not in result:
208205
continue
209206

210-
# TODO(nathan) remap key to actual name
211-
# TODO(nathan) add result key
212-
key_label = plot_config.get("key_map", {}).get(key, key)
213-
214207
num_values = len(result[key])
215-
labels += num_values * [key_label]
216-
result_set += num_values * [result_label]
208+
data = np.hstack((data, np.squeeze(result[key][:, 1])))
209+
labels += num_values * [key]
210+
result_set += num_values * [stem.upper()]
217211

218-
sns.set()
219-
sns.set_style("white")
220-
sns.set_context("poster", font_scale=1.7, rc={"lines.linewidth": 2.0})
221-
222-
fig, ax = plt.subplots()
223212
value_key = "Elapsed Time [s]"
224-
timer_key = plot_config.get("xlabel", "Timer Name")
213+
timer_key = "Timer Name"
225214
result_key = "Result Name"
226215

216+
fig, ax = plt.subplots()
227217
df = pd.DataFrame({value_key: data, timer_key: labels, result_key: result_set})
228218
if use_bars:
229-
sns.barplot(x=timer_key, y=value_key, hue=result_key, data=df)
219+
sns.barplot(x=timer_key, y=value_key, hue=result_key, data=df, ax=ax)
230220
else:
231-
sns.boxenplot(x=timer_key, y=value_key, hue=result_key, data=df)
232-
233-
if "title" in plot_config:
234-
ax.set_title(plot_config["title"])
221+
sns.boxenplot(x=timer_key, y=value_key, hue=result_key, data=df, ax=ax)
235222

236-
_draw_realtime_threshold(ax, rt_threshold)
237223
ax.legend()
224+
ax.set_xticks(ax.get_xticks(), ax.get_xticklabels(), rotation=30, ha="right")
238225

239-
sns.despine()
240226
fig.set_size_inches([14, 12])
241227
fig.tight_layout()
242228
plt.show()

0 commit comments

Comments
 (0)