def print_profile_numbers(profile_numbers, profile_types, line_ending='\n'): """Helper function for printing the numbers of profile to output. :param dict profile_numbers: dictionary of number of profiles grouped by type :param str profile_types: type of the profiles (tracked, untracked, etc.) :param str line_ending: ending of the print (for different outputs of log and status) """ if profile_numbers['all']: print("{0[all]} {1} profiles (".format(profile_numbers, profile_types), end='') first_printed = False for profile_type in SUPPORTED_PROFILE_TYPES: if not profile_numbers[profile_type]: continue print(', ' if first_printed else '', end='') first_printed = True type_colour = PROFILE_TYPE_COLOURS[profile_type] cprint( "{0} {1}".format(profile_numbers[profile_type], profile_type), type_colour) print(')', end=line_ending) else: cprintln('(no {} profiles)'.format(profile_types), TEXT_WARN_COLOUR, attrs=TEXT_ATTRS)
def collect(**kwargs): """ Runs the created SystemTap script and the profiled command The output dictionary is updated with: - output: path to the collector output file :param dict kwargs: dictionary containing the configuration settings for the collector :returns: (int as a status code, nonzero values for errors, string as a status message, mainly for error states, dict of kwargs and new values) """ log.cprintln( 'Running the collector, progress output stored in collect_log_{0}.txt\n' 'This may take a while... '.format(kwargs['timestamp']), 'white') try: # Call the system tap code, kwargs['output_path'] = systemtap.systemtap_collect(**kwargs) result = _COLLECTOR_STATUS[code] if code != systemtap.Status.OK: log.failed() # Add specific return for STAP error which includes the precise log file path if code == systemtap.Status.STAP: return result[0], result[1].format( kwargs['log_path']), dict(kwargs) return result[0], result[1], dict(kwargs) except (OSError, CalledProcessError) as exception: log.failed() return CollectStatus.ERROR, str(exception), dict(kwargs)
def systemtap_collect(script_path, log_path, output_path, cmd, args, **kwargs): """Collects performance data using the system tap wrapper, assembled script and external command. This function serves as a interface to the system tap collector. :param str script_path: path to the assembled system tap script file :param str log_path: path to the collection log file :param str output_path: path to the collection output file :param str cmd: the external command that contains / invokes the profiled executable :param list args: the arguments supplied to the external command :param kwargs: additional collector configuration :return tuple: containing the collection status, path to the output file of the collector """ # Perform the cleanup if kwargs['cleanup']: _stap_cleanup() # Create the output and log file for collection with open(log_path, 'w') as logfile: # Start the SystemTap process log.cprint('Starting the SystemTap process... ', 'white') stap_pgid = set() try: stap_runner, code = start_systemtap_in_background( script_path, output_path, logfile, **kwargs) if code != Status.OK: return code, None stap_pgid.add(os.getpgid(stap_runner.pid)) log.done() # Run the command that is supposed to be profiled log.cprint( 'SystemTap up and running, execute the profiling target... ', 'white') run_profiled_command(cmd, args, **kwargs) log.done() # Terminate SystemTap process after the file was fully written log.cprint( 'Data collection complete, terminating the SystemTap process... ', 'white') # _wait_for_fully_written(output_path) _wait_for_fully_written(output_path) kill_systemtap_in_background(stap_pgid) log.done() return Status.OK, output_path # Timeout was reached, inform the user but continue normally except exceptions.HardTimeoutException as e: kill_systemtap_in_background(stap_pgid) log.cprintln('', 'white') log.warn(e.msg) log.warn('The profile creation might fail or be inaccurate.') return Status.OK, output_path # Critical error during profiling or collection interrupted # make sure we terminate the collector and remove module except (Exception, KeyboardInterrupt): if not stap_pgid: stap_pgid = None # Clean only our mess _stap_cleanup(stap_pgid, output_path) raise
def log(pcs, minor_version, short=False, **_): """Prints the log of the performance control system Either prints the short or longer version. In short version, only header and short list according to the formatting string from stored in the configuration. Prints the number of profiles associated with each of the minor version and some basic information about minor versions, like e.g. description, hash, etc. Arguments: pcs(PCS): object with performance control system wrapper minor_version(str): representation of the head version short(bool): true if the log should be in short format """ perun_log.msg_to_stdout("Running inner wrapper of the 'perun log '", 2) # Print header for --short-minors if short: print_short_minor_info_header() # Walk the minor versions and print them for minor in vcs.walk_minor_versions(pcs.vcs_type, pcs.vcs_path, minor_version): if short: print_short_minor_version_info(pcs, minor) else: cprintln("Minor Version {}".format(minor.checksum), TEXT_EMPH_COLOUR, attrs=TEXT_ATTRS) base_dir = pcs.get_object_directory() tracked_profiles = store.get_profile_number_for_minor( base_dir, minor.checksum) print_profile_numbers(tracked_profiles, 'tracked') print_minor_version_info(minor, indent=1)
def print_profile_info_list(profile_list, max_lengths, short, list_type='tracked'): """Prints list of profiles and counts per type of tracked/untracked profiles. Prints the list of profiles, trims the sizes of each information according to the computed maximal lengths If the output is short, the list itself is not printed, just the information about counts. Tracked and untracked differs in colours. :param list profile_list: list of profiles of ProfileInfo objects :param dict max_lengths: dictionary with maximal sizes for the output of profiles :param bool short: true if the output should be short :param str list_type: type of the profile list (either untracked or tracked) """ # Sort the profiles w.r.t time of creation profile.sort_profiles(profile_list) # Print with padding profile_output_colour = 'white' if list_type == 'tracked' else 'red' index_id_char = 'i' if list_type == 'tracked' else 'p' ending = ':\n\n' if not short else "\n" profile_numbers = calculate_profile_numbers_per_type(profile_list) print_profile_numbers(profile_numbers, list_type, ending) # Skip empty profile list profile_list_len = len(profile_list) profile_list_width = len(str(profile_list_len)) if not profile_list_len or short: return # Load formating string for profile profile_info_fmt = perun_config.lookup_key_recursively('format.status') fmt_tokens, _ = FMT_SCANNER.scan(profile_info_fmt) # Compute header length header_len = profile_list_width + 3 for (token_type, token) in fmt_tokens: if token_type == 'fmt_string': attr_type, limit, _ = FMT_REGEX.match(token).groups() limit = adjust_limit(limit, attr_type, max_lengths, (2 if attr_type == 'type' else 0)) header_len += limit else: header_len += len(token) cprintln("\u2550" * header_len + "\u25A3", profile_output_colour) # Print header (2 is padding for id) print(" ", end='') cprint("id".center(profile_list_width + 2, ' '), profile_output_colour) print(" ", end='') for (token_type, token) in fmt_tokens: if token_type == 'fmt_string': attr_type, limit, _ = FMT_REGEX.match(token).groups() limit = adjust_limit(limit, attr_type, max_lengths, (2 if attr_type == 'type' else 0)) token_string = attr_type.center(limit, ' ') cprint(token_string, profile_output_colour, []) else: # Print the rest (non token stuff) cprint(token, profile_output_colour) print("") cprintln("\u2550" * header_len + "\u25A3", profile_output_colour) # Print profiles for profile_no, profile_info in enumerate(profile_list): print(" ", end='') cprint( "{}@{}".format(profile_no, index_id_char).rjust(profile_list_width + 2, ' '), profile_output_colour) print(" ", end='') for (token_type, token) in fmt_tokens: if token_type == 'fmt_string': attr_type, limit, fill = FMT_REGEX.match(token).groups() limit = adjust_limit(limit, attr_type, max_lengths) print_formating_token(profile_info_fmt, profile_info, attr_type, limit, default_color=profile_output_colour, value_fill=fill or ' ') else: cprint(token, profile_output_colour) print("") if profile_no % 5 == 0 or profile_no == profile_list_len - 1: cprintln("\u2550" * header_len + "\u25A3", profile_output_colour)
def log(minor_version, short=False, **_): """Prints the log of the performance control system Either prints the short or longer version. In short version, only header and short list according to the formatting string from stored in the configuration. Prints the number of profiles associated with each of the minor version and some basic information about minor versions, like e.g. description, hash, etc. :param str minor_version: representation of the head version :param bool short: true if the log should be in short format """ perun_log.msg_to_stdout("Running inner wrapper of the 'perun log '", 2) # Print header for --short-minors if short: minor_versions = list(vcs.walk_minor_versions(minor_version)) # Reduce the descriptions of minor version to one liners for mv_no, minor_version in enumerate(minor_versions): minor_versions[mv_no] = minor_version._replace( desc=minor_version.desc.split("\n")[0]) minor_version_maxima = calculate_maximal_lengths_for_object_list( minor_versions, MinorVersion._fields) # Update manually the maxima for the printed supported profile types, each requires two # characters and 9 stands for " profiles" string def minor_stat_retriever(minor_v): """Helper function for picking stats of the given minor version :param MinorVersion minor_v: minor version for which we are retrieving the stats :return: dictionary with stats for minor version """ return store.get_profile_number_for_minor( pcs.get_object_directory(), minor_v.checksum) def deg_count_retriever(minor_v): """Helper function for picking stats of the degradation strings of form ++-- :param MinorVersion minor_v: minor version for which we are retrieving the stats :return: dictionary with stats for minor version """ counts = perun_log.count_degradations_per_group( store.load_degradation_list_for(pcs.get_object_directory(), minor_v.checksum)) return { 'changes': counts.get('Optimization', 0) * '+' + counts.get('Degradation', 0) * '-' } minor_version_maxima.update( calculate_maximal_lengths_for_stats(minor_versions, minor_stat_retriever)) minor_version_maxima.update( calculate_maximal_lengths_for_stats(minor_versions, deg_count_retriever, " changes ")) print_short_minor_version_info_list(minor_versions, minor_version_maxima) else: # Walk the minor versions and print them for minor in vcs.walk_minor_versions(minor_version): cprintln("Minor Version {}".format(minor.checksum), TEXT_EMPH_COLOUR, attrs=TEXT_ATTRS) base_dir = pcs.get_object_directory() tracked_profiles = store.get_profile_number_for_minor( base_dir, minor.checksum) print_profile_numbers(tracked_profiles, 'tracked') print_minor_version_info(minor, indent=1)