Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds linux perf report #857

Merged
merged 3 commits into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions tests/report/test_linux_perf_report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
"""Test LinuxPerfReport."""

import unittest
from pathlib import Path
from unittest import mock

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 'foobar':

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)
117 changes: 117 additions & 0 deletions varats-core/varats/report/linux_perf_report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
"""
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 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

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) -> tp.Optional[int]:
if line.startswith("<not counted>"):
return None
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()]
Loading