diff --git a/perun/collect/trace/pin/engine.py b/perun/collect/trace/pin/engine.py index 6d4aeb2b..3e1cea9f 100644 --- a/perun/collect/trace/pin/engine.py +++ b/perun/collect/trace/pin/engine.py @@ -23,7 +23,7 @@ from perun.utils.log import msg_to_stdout from perun.utils.exceptions import ( InvalidBinaryException, - PinUnspecifiedPinRoot, + PinInvalidPinRoot, PinBinaryInstrumentationFailed, InvalidParameterException, ) @@ -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) @@ -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: @@ -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 @@ -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() diff --git a/perun/collect/trace/pin/parse/instructions_parser.py b/perun/collect/trace/pin/parse/instructions_parser.py index 063425b3..aa185bad 100644 --- a/perun/collect/trace/pin/parse/instructions_parser.py +++ b/perun/collect/trace/pin/parse/instructions_parser.py @@ -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." ) @@ -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 @@ -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 @@ -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 """ @@ -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( @@ -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( diff --git a/perun/collect/trace/pin/parse/memory_parser.py b/perun/collect/trace/pin/parse/memory_parser.py index 5dfd71e5..6ba24117 100644 --- a/perun/collect/trace/pin/parse/memory_parser.py +++ b/perun/collect/trace/pin/parse/memory_parser.py @@ -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 @@ -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( @@ -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, ) diff --git a/perun/collect/trace/pin/parse/parser.py b/perun/collect/trace/pin/parse/parser.py index 96550e11..1cc1f907 100644 --- a/perun/collect/trace/pin/parse/parser.py +++ b/perun/collect/trace/pin/parse/parser.py @@ -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 @@ -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 @@ -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): @@ -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 diff --git a/perun/collect/trace/pin/parse/time_parser.py b/perun/collect/trace/pin/parse/time_parser.py index af0e77b6..c9d2e98d 100644 --- a/perun/collect/trace/pin/parse/time_parser.py +++ b/perun/collect/trace/pin/parse/time_parser.py @@ -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, @@ -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) :] @@ -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, ) @@ -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 {} diff --git a/perun/collect/trace/pin/scan_binary.py b/perun/collect/trace/pin/scan_binary.py index 30509166..dce92864 100644 --- a/perun/collect/trace/pin/scan_binary.py +++ b/perun/collect/trace/pin/scan_binary.py @@ -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 @@ -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] = [] diff --git a/perun/utils/exceptions.py b/perun/utils/exceptions.py index 807f250c..6f1fd722 100644 --- a/perun/utils/exceptions.py +++ b/perun/utils/exceptions.py @@ -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=" + 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 @@ -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):