Verified Commit 30315c25 authored by Sebastian Endres's avatar Sebastian Endres
Browse files

Speed up plot_all by avoid dump and reload

parent cd4e0eae
......@@ -125,7 +125,7 @@ class PlotAllCli:
def plot_in_testcase_dir(
self,
measurement_result: ExtendedMeasurementResult,
mode: PlotMode,
modes: list[PlotMode],
) -> Optional[str]:
"""Generate plot for for this test case."""
assert self._current_log_dir
......@@ -169,56 +169,60 @@ class PlotAllCli:
return "no traces files found"
output_file = Path(test_case_dir / f"time_{mode.value}_plot.{self.format}")
if not self.force and output_file.is_file():
cprint(
(
f"[i] {output_file.relative_to(self._current_log_dir)} already exists. "
"Skipping. Use --force to overwrite."
),
file=sys.stderr,
color="cyan",
)
return "already exists"
cprint(
(
f"[i] Plotting in {create_relpath(test_case_dir)} "
f"({len(pcaps)} traces) -> {create_relpath(output_file)}"
),
attrs=["bold"],
)
cli = PlotCli(
pcap_files=pcaps,
output_file=output_file,
title=self.title.format(
combination=measurement_result.combination,
client=measurement_result.client.name,
server=measurement_result.server.name,
mode_default_title=DEFAULT_TITLES[mode],
),
annotate=self.annotate,
mode=mode,
cache=True,
debug=self.debug,
)
try:
cli.run()
except ParsingError as err:
for mode in modes:
output_file = Path(test_case_dir / f"time_{mode.value}_plot.{self.format}")
if not self.force and output_file.is_file():
cprint(
(
f"[i] {output_file.relative_to(self._current_log_dir)} already exists. "
"Skipping. Use --force to overwrite."
),
file=sys.stderr,
color="cyan",
)
return "already exists"
cprint(
(
f"[!] Could not parse {err.trace} in "
f"{test_case_dir.relative_to(self._current_log_dir)}. "
"Skipping..."
f"[i] Plotting in {create_relpath(test_case_dir)} "
f"({len(pcaps)} traces) -> {create_relpath(output_file)}"
),
file=sys.stderr,
color="red",
attrs=["bold"],
)
cprint(f"[!] {err}", file=sys.stderr, color="red")
return str(err)
cli.set_params(
title=self.title.format(
combination=measurement_result.combination,
client=measurement_result.client.name,
server=measurement_result.server.name,
mode_default_title=DEFAULT_TITLES[mode],
),
mode=mode,
output_file=output_file,
)
try:
cli.run()
except ParsingError as err:
cprint(
(
f"[!] Could not parse {err.trace} in "
f"{test_case_dir.relative_to(self._current_log_dir)}. "
"Skipping..."
),
file=sys.stderr,
color="red",
)
cprint(f"[!] {err}", file=sys.stderr, color="red")
return str(err)
return None
......@@ -243,11 +247,11 @@ class PlotAllCli:
measurement_results = list(tmp2.values())
for measurement_result in measurement_results:
for mode in self.modes:
plot_result = self.plot_in_testcase_dir(
measurement_result, mode=mode
)
plot_results[measurement_result] = plot_result
plot_result = self.plot_in_testcase_dir(
measurement_result,
modes=self.modes,
)
plot_results[measurement_result] = plot_result
self.evaluate_plot_results(plot_results)
......
......@@ -130,7 +130,7 @@ class PlotCli:
def __init__(
self,
pcap_files: list[Path],
title: Union[str, None],
title: Union[str, None] = None,
keylog_files: list[Optional[Path]] = [],
output_file: Optional[Path] = None,
annotate=True,
......@@ -138,16 +138,21 @@ class PlotCli:
cache=False,
debug=False,
):
self.title = title or DEFAULT_TITLES[mode]
self.output_file = output_file
self.output_file: Optional[Path] = output_file
self.title = title if title else DEFAULT_TITLES[mode] if mode else None
self.annotate = annotate
self.mode = mode
self.debug = debug
self.set_params(
title=title,
output_file=output_file,
annotate=annotate,
mode=mode,
)
if not keylog_files:
keylog_files = [None] * len(pcap_files)
cprint("Loading traces...", color="grey")
self.traces = [
Trace(
file=pcap_file,
......@@ -159,7 +164,25 @@ class PlotCli:
for pcap_file, keylog_file in zip(pcap_files, keylog_files)
]
def vline_annotate(
def set_params(
self,
title: Union[str, None] = None,
output_file: Optional[Path] = None,
annotate: Optional[bool] = None,
mode: Optional[PlotMode] = None,
):
self.output_file = output_file
if mode is not None:
self.title = title or DEFAULT_TITLES[mode]
if annotate is not None:
self.annotate = annotate
if mode is not None:
self.mode = mode
def _vline_annotate(
self,
ax,
x: Union[float, int],
......@@ -205,7 +228,7 @@ class PlotCli:
"right",
),
):
self.vline_annotate(
self._vline_annotate(
ax=ax,
x=value,
y=y,
......@@ -213,7 +236,7 @@ class PlotCli:
label_side=label_side,
)
def plot_offset_number(self):
def plot_offset_number(self, output_file: Optional[Path]):
"""Plot the offset number diagram."""
with Subplot(nrows=1, ncols=1) as (_fig, ax):
ax.grid(True)
......@@ -343,9 +366,9 @@ class PlotCli:
self._annotate_time_plot(ax, max_offset / 2)
spinner.ok("✔")
self._save()
self._save(output_file)
def plot_packet_number(self):
def plot_packet_number(self, output_file: Optional[Path]):
"""Plot the packet number diagram."""
with Subplot(nrows=1, ncols=1) as (_fig, ax):
ax.grid(True)
......@@ -430,9 +453,9 @@ class PlotCli:
self._annotate_time_plot(ax, max_packet_number / 2)
spinner.ok("✔")
self._save()
self._save(output_file)
def plot_file_size(self):
def plot_file_size(self, output_file: Optional[Path]):
"""Plot the file size diagram."""
with Subplot() as (_fig, ax):
ax.grid(True)
......@@ -505,14 +528,14 @@ class PlotCli:
self._annotate_time_plot(ax, max_file_size / 2)
spinner.ok("✔")
self._save()
self._save(output_file)
def _save(self):
def _save(self, output_file: Optional[Path]):
"""Save or show the plot."""
if self.output_file:
plt.savefig(self.output_file, dpi=300, transparent=True)
cprint(f"{create_relpath(self.output_file)} written.", color="green")
if output_file:
plt.savefig(output_file, dpi=300, transparent=True)
cprint(f"{create_relpath(output_file)} written.", color="green")
else:
plt.show()
......@@ -550,7 +573,7 @@ class PlotCli:
cprint(
f"Plotting {len(self.traces)} traces into a {desc} plot...", color="cyan"
)
callback()
callback(self.output_file)
def main():
......
......@@ -74,7 +74,7 @@ def follow_stream(stream_frames: list[Any]) -> bytes:
if not all(byte is not None for byte in buf):
cprint(
"Warning! Did not receive all bytes in follow_stream.",
"[!] Warning! Did not receive all bytes in follow_stream.",
color="yellow",
)
......@@ -231,7 +231,7 @@ class Trace:
if file.suffix not in (".pcap", ".pcapng"):
cprint(
f"Warning! Are you sure, that {file} is a pcap/pcapng file?",
f"[!] Warning! Are you sure, that {file} is a pcap/pcapng file?",
color="yellow",
)
......@@ -248,6 +248,7 @@ class Trace:
self._response_packets = list["Packet"]()
self._response_stream_frames_first_tx = list[QuicStreamLayer]()
self._response_stream_frames_retrans = list[QuicStreamLayer]()
self._parsed = False
def __str__(self):
trace_file_name = Path(self._cap.input_filename).name
......@@ -412,6 +413,8 @@ class Trace:
- client/server IP/port
- packet number of stream.fin == 1
"""
if self._parsed:
return
if len(self.packets) < 2:
raise ParsingError(
......@@ -436,6 +439,8 @@ class Trace:
client_tuple = (client_ip, client_port)
server_tuple = (server_ip, server_port)
request_packets = list["Packet"]()
response_packets = list["Packet"]()
for packet in self.packets:
try:
src_port = packet.udp.srcport
......@@ -455,15 +460,18 @@ class Trace:
dst_tuple = (dst_ip, dst_port)
if src_tuple == client_tuple and dst_tuple == server_tuple:
self._request_packets.append(packet)
request_packets.append(packet)
elif src_tuple == server_tuple and dst_tuple == client_tuple:
self._response_packets.append(packet)
response_packets.append(packet)
else:
raise ParsingError(
f"Packet #{packet.quic.packet_number} is neither a request nor a response.",
trace=self,
)
self._request_packets = request_packets
self._response_packets = response_packets
# check if first packet is an HTTP (0.9) request
request_quic_stream_frames = [
frame
......@@ -523,3 +531,5 @@ class Trace:
self._facts["response_stream_fin_pkns"] = get_stream_fin_packet_number(
self._response_packets, self
)
self._parsed = True
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment