From 3e97822d344a1b6275dcf670ef995a704f284e35 Mon Sep 17 00:00:00 2001 From: Florian Sattler Date: Fri, 20 Oct 2023 14:14:02 +0200 Subject: [PATCH] Adds linux perf report --- tests/report/test_linux_perf_report.py | 103 +++++++++++++++ .../varats/report/linux_perf_report.py | 123 ++++++++++++++++++ 2 files changed, 226 insertions(+) create mode 100644 tests/report/test_linux_perf_report.py create mode 100644 varats-core/varats/report/linux_perf_report.py diff --git a/tests/report/test_linux_perf_report.py b/tests/report/test_linux_perf_report.py new file mode 100644 index 000000000..2480d531a --- /dev/null +++ b/tests/report/test_linux_perf_report.py @@ -0,0 +1,103 @@ +"""Test LinuxPerfReport.""" + +import unittest +import unittest.mock as mock +from datetime import timedelta +from pathlib import Path + +from varats.report.linux_perf_report import LinuxPerfReport + +PERF_REPORT_1 = """# started on Sun Jul 23 22:51:54 2023 + + + Performance counter stats for 'echo foo:bar': + + 0.30 msec task-clock:u # 0.406 CPUs utilized + 0 context-switches:u # 0.000 /sec + 0 cpu-migrations:u # 0.000 /sec + 64 page-faults:u # 212.723 K/sec + 360,721 cycles:u # 1.199 GHz + 26,199 stalled-cycles-frontend:u # 7.26% frontend cycles idle + 111,008 stalled-cycles-backend:u # 30.77% backend cycles idle + 200,655 instructions:u # 0.56 insn per cycle + # 0.55 stalled cycles per insn + 48,631 branches:u # 161.639 M/sec + 3,012 branch-misses:u # 6.19% of all branches + L1-dcache-loads:u (0.00%) + L1-dcache-load-misses:u (0.00%) + LLC-loads:u + LLC-load-misses:u + + 0.000741511 seconds time elapsed + + 0.000000000 seconds user + 0.000822000 seconds sys + + + +""" + +PERF_REPORT_2 = """# started on Sun Jul 23 22:44:31 2023 + + + Performance counter stats for '/home/vulder/vara-root/benchbuild/results/GenBBBaselineO/SynthSAContextSensitivity-perf_tests@a8c3a8722f,0/SynthSAContextSensitivity/build/bin/ContextSense --compress --mem 10 8': + + 1.23 msec task-clock:u # 0.000 CPUs utilized + 0 context-switches:u # 0.000 /sec + 0 cpu-migrations:u # 0.000 /sec + 132 page-faults:u # 107.572 K/sec + 850,975 cycles:u # 0.693 GHz (12.81%) + 140,154 stalled-cycles-frontend:u # 16.47% frontend cycles idle + 1,012,322 stalled-cycles-backend:u # 118.96% backend cycles idle + 1,785,912 instructions:u # 2.10 insn per cycle + # 0.57 stalled cycles per insn + 325,708 branches:u # 265.433 M/sec + 11,160 branch-misses:u # 3.43% of all branches + 840,918 L1-dcache-loads:u # 685.298 M/sec (87.19%) + L1-dcache-load-misses:u (0.00%) + LLC-loads:u + LLC-load-misses:u + + 5.945920439 seconds time elapsed + + 0.000376000 seconds user + 0.001390000 seconds sys + + + +""" + + +class TestLinuxPerfReport(unittest.TestCase): + """Tests if the Linux perf report can be loaded correctly.""" + + report_1: LinuxPerfReport + report_2: LinuxPerfReport + + @classmethod + def setUpClass(cls) -> None: + """Load Linux perf report.""" + with mock.patch( + "builtins.open", new=mock.mock_open(read_data=PERF_REPORT_1) + ): + cls.report_1 = LinuxPerfReport(Path("fake_file_path")) + + with mock.patch( + "builtins.open", new=mock.mock_open(read_data=PERF_REPORT_2) + ): + cls.report_2 = LinuxPerfReport(Path("fake_file_path")) + + def test_task_clock_parsing(self) -> None: + """Checks if we correctly parsed the value for task clock.""" + self.assertEqual(self.report_1.elapsed_time, 0.000741511) + self.assertEqual(self.report_2.elapsed_time, 5.945920439) + + def test_context_switches_parsing(self) -> None: + """Checks if we correctly parsed the value for context switches.""" + self.assertEqual(self.report_1.ctx_switches, 0) + self.assertEqual(self.report_2.ctx_switches, 0) + + def test_branch_misses_parsing(self) -> None: + """Checks if we correctly parsed the value for branch misses.""" + self.assertEqual(self.report_1.branch_misses, 3012) + self.assertEqual(self.report_2.branch_misses, 11160) diff --git a/varats-core/varats/report/linux_perf_report.py b/varats-core/varats/report/linux_perf_report.py new file mode 100644 index 000000000..3af794b73 --- /dev/null +++ b/varats-core/varats/report/linux_perf_report.py @@ -0,0 +1,123 @@ +""" +Simple report module to create and handle the standard timing output of perf +stat. + +Examples to produce a ``LinuxPerfReport``: + + Commandline usage: + .. code-block:: bash + + export REPORT_FILE="Path/To/MyFile" + perf stat -x ";" -o $REPORT_FILE -- sleep 2 + + Experiment code: + .. code-block:: python + + from benchbuild.utils.cmd import time, sleep + report_file = "Path/To/MyFile" + command = sleep["2"] + perf("stat", "-x", "';'", "-o", f"{report_file}", "--", command) +""" +import csv +import math +import typing as tp +from pathlib import Path + +import numpy as np + +from varats.report.report import BaseReport, ReportAggregate + + +class LinuxPerfReport(BaseReport, shorthand="LPR", file_type="txt"): + """Report class to access perf stat output.""" + + def __init__(self, path: Path) -> None: + super().__init__(path) + self.__elapsed_time = math.nan + self.__ctx_switches: int = -1 + self.__branch_misses: int = -1 + + with open(self.path, 'r', newline="") as stream: + for line in stream: + line = line.strip("\n ") + # print(f"{line=}") + + if line == "" or line.startswith("#"): + continue + + # TODO: impl cmd + # if line.startswith("Performance counter"): + # print(f"CMD: {line}") + + if "time elapsed" in line: + self.__elapsed_time = self.__parse_elapsed_time(line) + + if "context-switches:u" in line: + self.__ctx_switches = self.__parse_ctx_switches(line) + + if "branch-misses:u" in line: + self.__branch_misses = self.__parse_branch_misses(line) + + if self.__branch_misses == math.nan: + raise AssertionError() + + @staticmethod + def __parse_elapsed_time(line: str) -> float: + return float(line.split(" ")[0].replace(",", "")) + + @staticmethod + def __parse_ctx_switches(line: str) -> int: + return int(line.split(" ")[0].replace(",", "")) + + @staticmethod + def __parse_branch_misses(line: str) -> int: + # TODO: fix return type + if line.startswith(""): + return np.NaN + return int(line.split(" ")[0].replace(",", "")) + + @property + def elapsed_time(self) -> float: + return self.__elapsed_time + + @property + def ctx_switches(self) -> int: + return self.__ctx_switches + + @property + def branch_misses(self) -> int: + return self.__branch_misses + + def __repr__(self) -> str: + return str(self) + + def __str__(self) -> str: + return f"""LPR ({self.path}) + ├─ ElapsedTime: {self.elapsed_time} + ├─ CtxSwitches: {self.ctx_switches} + └─ BranchMisses: {self.branch_misses} +""" + + +class LinuxPerfReportAggregate( + ReportAggregate[LinuxPerfReport], + shorthand=LinuxPerfReport.SHORTHAND + ReportAggregate.SHORTHAND, + file_type=ReportAggregate.FILE_TYPE +): + """Meta report for parsing multiple Linux perf reports stored inside a zip + file.""" + + def __init__(self, path: Path) -> None: + super().__init__(path, LinuxPerfReport) + + @property + def elapsed_time(self) -> tp.List[float]: + return [report.elapsed_time for report in self.reports()] + + @property + def ctx_switches(self) -> tp.List[int]: + return [report.ctx_switches for report in self.reports()] + + @property + def branch_misses(self) -> tp.List[int]: + return [report.branch_misses for report in self.reports()]