Skip to content

Commit

Permalink
fixup! fixup! fixup! fixup! fixup! add new Tracer engine based on the…
Browse files Browse the repository at this point in the history
… PIN framework
  • Loading branch information
PeterMocary committed Aug 27, 2024
1 parent edce749 commit a65e497
Show file tree
Hide file tree
Showing 7 changed files with 48 additions and 64 deletions.
25 changes: 13 additions & 12 deletions perun/collect/trace/pin/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from perun.utils.log import msg_to_stdout
from perun.utils.exceptions import (
InvalidBinaryException,
PinUnspecifiedPinRoot,
PinInvalidPinRoot,
PinBinaryInstrumentationFailed,
InvalidParameterException,
)
Expand Down Expand Up @@ -78,8 +78,8 @@ def check_dependencies(self) -> None:
"""Check that the tools for pintool creation are available and if pin's root folder is
specified.
:raises: PinUnspecifiedPinRoot: the PIN_ROOT environment variable is not set or contains
wrong path
:raises: PinInvalidPinRoot: the PIN_ROOT environment variable is not set or contains
wrong path
:raises: InvalidBinaryException: binary has no dwarf info
"""
msg_to_stdout("[Info]: Checking dependencies.", 2)
Expand All @@ -89,16 +89,13 @@ def check_dependencies(self) -> None:
# TODO: maybe add check if the specified directory contains some of the required contents
# TODO: check the version of pin (currently the version is capped on 3.27 because the
# pintool compilation changes in 3.28 with addition of dwarf reading capability)
if "PIN_ROOT" not in os.environ.keys():
raise PinUnspecifiedPinRoot()
if (
"PIN_ROOT" not in os.environ.keys()
or not os.path.isdir(os.environ["PIN_ROOT"])
or not os.path.isabs(os.environ["PIN_ROOT"])
):
raise PinInvalidPinRoot()
self.__pin_root = os.environ["PIN_ROOT"]
if not os.path.isdir(self.__pin_root) or not os.path.isabs(self.__pin_root):
msg_to_stdout(
"[Debug]: PIN_ROOT environmental variable exists, but is not valid "
"absolute path.",
3,
)
raise PinUnspecifiedPinRoot()

# The specified binary needs to include dwarf4 info
with open(self.binary, "rb") as binary:
Expand Down Expand Up @@ -134,6 +131,9 @@ def assemble_collect_program(self, **kwargs) -> None:
),
}
msg_to_stdout(f"[Debug]: Configuration: {configuration}.", 3)
if configuration["mode"] not in ["time", "instructions", "memory"]:
# TODO: exception
raise Exception("Unknown pin engine mode!")

if configuration["collect_arguments"]:
# Note: When collecting arguments of functions, the dwarf debug information
Expand Down Expand Up @@ -220,6 +220,7 @@ def transform(self, **kwargs) -> Generator[Dict[str, Union[str, int]], None, Non
)
else:
# TODO: exception
# Note: this should not be reachable - checked when pintool is being assembled
Exception("Unknown pin engine mode!")

return dynamic_parser.parse_dynamic_data_file()
Expand Down
23 changes: 8 additions & 15 deletions perun/collect/trace/pin/parse/instructions_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,8 @@ def parse_dynamic_data_file(self) -> Generator[Dict[str, Union[str, int]], None,

while self._current_entry:
if self._current_entry.granularity != Granularity.BBL:
# TODO: Exceptions
Exception(
# TODO: Exception
raise Exception(
"The PinInstructionsParser expects only basic blocks in the dynamic "
"data file."
)
Expand Down Expand Up @@ -192,7 +192,7 @@ def parse_dynamic_data_file(self) -> Generator[Dict[str, Union[str, int]], None,
if function_index < 0:
# TODO: exception
raise Exception(
"Could not locate parent function of current basic block " "in backlog."
"Could not locate parent function of current basic block in backlog."
)
self.function_call_backlog[
function_index
Expand Down Expand Up @@ -255,9 +255,6 @@ def parse_dynamic_data_file(self) -> Generator[Dict[str, Union[str, int]], None,
# function will contain count of all executed instructions
if self.function_call_backlog:
function_index: int = self._find_backlog_index_of_caller_function()
if function_index < 0:
# TODO: exception
raise Exception("Could not locate caller of a function in backlog.")
self.function_call_backlog[
function_index
].instructions_count += function_start.instructions_count
Expand Down Expand Up @@ -302,13 +299,14 @@ def _find_backlog_index_of_caller_function(self) -> int:
for index in range(len(self.function_call_backlog) - 1, -1, -1):
if self.function_call_backlog[index].entry.is_in_same_scope(self._current_entry):
return index
return -1

# TODO: exception
raise Exception("Could not locate caller of a function in backlog.")

def _find_backlog_index_of_parent_function(self) -> int:
"""Based on current entry, finds the index of an entry with information about the parent
function of the basic block. In other words, finds the entry in function calls backlog that
represents the function that current basic block entry belongs to. Expects the current entry
to be basic block granularity.
function of the basic block in the current entry. Expects the current entry to be basic
block granularity when called.
:returns int: the index of the last complementary entry in the backlog or -1
"""
Expand All @@ -327,7 +325,6 @@ def _find_backlog_index_of_parent_function(self) -> int:
].function_name
if function_name == expected_function_name:
return index

return -1

def _form_profile_data(
Expand Down Expand Up @@ -411,10 +408,6 @@ def _form_caller_name(self) -> str:
# with the tid and pid combination? Needs some testing on application with different tid
# and pid.
caller_index: int = self._find_backlog_index_of_caller_function()
if caller_index < 0:
# TODO: exception
raise Exception("Could not locate caller of a function in backlog.")

caller_function_entry: InstructionDataEntry = self.function_call_backlog[caller_index].entry
caller_of_caller_function: str = self.function_call_backlog[caller_index].caller
caller_function_name: str = self.program_data.get_function_name(
Expand Down
4 changes: 2 additions & 2 deletions perun/collect/trace/pin/parse/memory_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,6 @@ def parse_dynamic_data_file(self) -> Generator[Dict[str, Union[str, int]], None,
self._advance()

while self._current_entry:

if self._current_entry.is_located_before():
if self._current_entry.name in ["free", "delete"]:
# the free and delete do not require after entry - yield the profile data
Expand All @@ -153,6 +152,7 @@ def parse_dynamic_data_file(self) -> Generator[Dict[str, Union[str, int]], None,
break
if not before_entry:
msg_to_stdout("[DEBUG]: Closing entry does not have a pair in the backlog.", 3)
self._advance()
continue

yield self._form_profile_data(
Expand All @@ -169,7 +169,7 @@ def parse_dynamic_data_file(self) -> Generator[Dict[str, Union[str, int]], None,
# specified as the new or delete, therefore they don't really introduce any confusion
# to the profile.
msg_to_stdout(
f"[Debug]: Unpaired memory entries in "
"[Debug]: Unpaired memory entries in "
f"backlog: {len(self.function_call_backlog)}.",
3,
)
Expand Down
17 changes: 7 additions & 10 deletions perun/collect/trace/pin/parse/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,8 @@ def parse_static_data(self) -> ProgramData:
table_parser: Callable = table_parsers[table_name]
table_parser() # reads next line before finishing
else:
msg_to_stdout("[Debug]: Skipping table with unknown separator: #{table_name}", 3)
msg_to_stdout(f"[Debug]: Skipping table with unknown separator: #{table_name}", 3)
self._advance()

self._file_descriptor.close()
return self._program_data
Expand All @@ -199,12 +200,10 @@ def _parse_source_files_table(self) -> None:
while self._current_line:
if ENTRY_VALUE_SEPARATOR not in self._current_line:
# The sequence of the table entries has ended
self._program_data.source_code_files = source_files
return
break

entry: List[str] = self._current_line.strip().split(ENTRY_VALUE_SEPARATOR, 1)
source_files.insert(0, entry[0])

self._advance()

self._program_data.source_code_files = source_files
Expand Down Expand Up @@ -232,8 +231,8 @@ def _parse_routines_table(self) -> None:

entry: List[Union[str, int]] = self._current_line.strip().split(ENTRY_VALUE_SEPARATOR)
if len(entry) != len(entry_format):
self._program_data.functions = functions
return
# format does no longer match therefore end the parsing of this table
break

# Convert numerical values to int
for idx, item in enumerate(entry_format):
Expand Down Expand Up @@ -273,14 +272,12 @@ def _parse_basic_blocks_table(self) -> None:

while self._current_line:
if ENTRY_VALUE_SEPARATOR not in self._current_line:
self._program_data.basic_blocks = basic_blocks
return
break

entry: List[Union[str, int]] = self._current_line.strip().split(ENTRY_VALUE_SEPARATOR)
if len(entry) < entry_format_size:
# Expected at least one source code line number at the end
self._program_data.basic_blocks = basic_blocks
return
break

# Convert numerical values to int
# Base format values - skips the source code lines which are handled after
Expand Down
26 changes: 9 additions & 17 deletions perun/collect/trace/pin/parse/time_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,14 @@ def __repr__(self) -> str:
def get_data_for_profile(self, other) -> Dict[str, Any]:
"""Extracts relevant information from the entry for the perun performance profile.
NOTE: Will not check if the other entry is compatible because when collecting only basic blocks
the basic block entries are used to create profile data for functions. These basic block entries
will have correct location, however, their id will be different since they are different basic blocks.
:param TimeDataEntry other: the complementary data entry
:return dict: partial perun performance profile entry
"""
if self != other:
# The entries are not compatible, therefore a profile entry won't be created
return {}

profile_data: Dict[str, Any] = {
"tid": self.tid,
Expand Down Expand Up @@ -165,9 +166,7 @@ def _parse_current_dynamic_entry(self) -> TimeDataEntry:
# Parse the optional arguments at the end of a function entry
if data_entry.is_function_granularity() and len(entry) > len(TimeDataEntry.FORMAT):
# There are additional function arguments present in the entry
function: FunctionData = self.program_data.functions[
data_entry.id
] # Information about the function
function: FunctionData = self.program_data.functions[data_entry.id]
# Values of function arguments
argument_values: List[str] = entry[len(TimeDataEntry.FORMAT) :]

Expand Down Expand Up @@ -280,17 +279,11 @@ def parse_dynamic_data_file(self) -> Generator[Dict[str, Union[str, int]], None,

self._advance()

if self.function_call_backlog:
if self.function_call_backlog or self.basic_block_backlog:
msg_to_stdout(
f"[DEBUG]: Routines backlog contains "
f"{len(self.function_call_backlog)} unpaired entries.",
3,
)

if self.basic_block_backlog:
msg_to_stdout(
"[DEBUG]: Basic blocks backlog contains "
f"{len(self.basic_block_backlog)} unpaired entries.",
"[DEBUG]: Unpaired entries in backlogs: "
f"Functions - {len(self.function_call_backlog)} and "
f"Basic blocks - {len(self.basic_block_backlog)}.",
3,
)

Expand Down Expand Up @@ -413,7 +406,6 @@ def _form_profile_data(
:returns dict: a perun profile data entry
"""
profile_data: Dict[str, Any] = start_entry.get_data_for_profile(end_entry)

if not profile_data:
return {}

Expand Down
6 changes: 2 additions & 4 deletions perun/collect/trace/pin/scan_binary.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ def get_function_info_from_binary(filename: str) -> List[FunctionInfo]:
"""Reads DWARF debug information form the specified binary file and extracts information
about functions contained in it. Namely, function name and argument names, types and indices.
Note: expects DWARF info to be present in the binary
:param str filename: path to binary with DWARF debug info one wants to analyze
:return list: list of FunctionInfo objects representing each function contained in
Expand All @@ -50,10 +52,6 @@ def get_function_info_from_binary(filename: str) -> List[FunctionInfo]:
with open(filename, "rb") as file_descriptor:
elf_file: ELFFile = ELFFile(file_descriptor)

if not elf_file.has_dwarf_info():
# File has no DWARF info
raise PinBinaryScanUnsuccessful

dwarf_info: DWARFInfo = elf_file.get_dwarf_info()

functions: List[FunctionInfo] = []
Expand Down
11 changes: 7 additions & 4 deletions perun/utils/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -517,12 +517,15 @@ def __exit__(self, exc_type: str, exc_val: Exception, exc_tb: traceback.StackSum
return isinstance(exc_val, self.exc)


class PinUnspecifiedPinRoot(Exception):
"""Raised when PIN_ROOT os variable isn't defined."""
class PinInvalidPinRoot(Exception):
"""Raised when PIN_ROOT os variable isn't defined or is not valid."""

def __init__(self):
super().__init__("")
self.msg = "Undefined PIN_ROOT! Please execute: export PIN_ROOT=<absolute-path-to-pin>"
self.msg = (
"Undefined or invalid pin root! Please export the PIN_ROOT variable "
"with the absolute path to the pin kit."
)

def __str__(self):
return self.msg
Expand All @@ -535,7 +538,7 @@ def __init__(self):
super().__init__("")
self.msg = (
"Couldn't read the DWARF debug info, please ensure that the binary is compiled "
"with -gdwarf-4 option (using gcc)."
"with -g option (when using gcc)."
)

def __str__(self):
Expand Down

0 comments on commit a65e497

Please sign in to comment.