Skip to content

Commit

Permalink
Adds linux perf report
Browse files Browse the repository at this point in the history
  • Loading branch information
vulder committed Oct 20, 2023
1 parent d11b128 commit 3e97822
Show file tree
Hide file tree
Showing 2 changed files with 226 additions and 0 deletions.
103 changes: 103 additions & 0 deletions tests/report/test_linux_perf_report.py
Original file line number Diff line number Diff line change
@@ -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
<not counted> L1-dcache-loads:u (0.00%)
<not counted> L1-dcache-load-misses:u (0.00%)
<not supported> LLC-loads:u
<not supported> 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%)
<not counted> L1-dcache-load-misses:u (0.00%)
<not supported> LLC-loads:u
<not supported> 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)
123 changes: 123 additions & 0 deletions varats-core/varats/report/linux_perf_report.py
Original file line number Diff line number Diff line change
@@ -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("<not counted>"):
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()]

0 comments on commit 3e97822

Please sign in to comment.