diff --git a/auxprogs/pylintrc b/auxprogs/pylintrc index 8f51d2d68..2d26cf00d 100644 --- a/auxprogs/pylintrc +++ b/auxprogs/pylintrc @@ -12,10 +12,12 @@ [MESSAGES CONTROL] disable= - # We don't care about having docstrings for all functions/classes. + # We don't care about having docstrings everywhere. missing-class-docstring, missing-function-docstring, - # We don't care about large functions, sometimes it's necessary. - too-many-branches, too-many-locals, too-many-statements, + missing-module-docstring, + # We don't care about these, sometimes they are necessary. + too-many-arguments, too-many-branches, too-many-lines, too-many-locals, + too-many-statements, # Zero or one public methods in a class is fine. too-few-public-methods, diff --git a/cachegrind/cg_annotate.in b/cachegrind/cg_annotate.in index 5dad969f6..0b68e094c 100755 --- a/cachegrind/cg_annotate.in +++ b/cachegrind/cg_annotate.in @@ -26,15 +26,12 @@ # # The GNU General Public License is contained in the file COPYING. -""" -This script reads Cachegrind output files and produces human-readable reports. -""" - +# This script reads Cachegrind output files and produces human-readable output. +# # Use `make pyann` to "build" this script with `auxprogs/pybuild.rs` every time # it is changed. This runs the formatters, type-checkers, and linters on # `cg_annotate.in` and then generates `cg_annotate`. - from __future__ import annotations import os @@ -42,16 +39,12 @@ import re import sys from argparse import ArgumentParser, BooleanOptionalAction, Namespace from collections import defaultdict -from typing import DefaultDict, NewType, NoReturn, TextIO +from typing import DefaultDict, NoReturn, TextIO +# A typed wrapper for parsed args. class Args(Namespace): - """ - A typed wrapper for parsed args. - - None of these fields are modified after arg parsing finishes. - """ - + # None of these fields are modified after arg parsing finishes. show: list[str] sort: list[str] threshold: float # a percentage @@ -72,16 +65,14 @@ class Args(Namespace): return f raise ValueError + # Add a bool argument that defaults to true. + # + # Supports these forms: `--foo`, `--no-foo`, `--foo=yes`, `--foo=no`. + # The latter two were the forms supported by the old Perl version of + # `cg_annotate`, and are now deprecated. def add_bool_argument( p: ArgumentParser, new_name: str, old_name: str, help_: str ) -> None: - """ - Add a bool argument that defaults to true. - - Supports these forms: `--foo`, `--no-foo`, `--foo=yes`, `--foo=no`. - The latter two were the forms supported by the old Perl version of - `cg_annotate`, and are now deprecated. - """ new_flag = "--" + new_name old_flag = "--" + old_name dest = new_name.replace("-", "_") @@ -110,28 +101,25 @@ class Args(Namespace): p = ArgumentParser(description="Process a Cachegrind output file.") p.add_argument("--version", action="version", version="%(prog)s-@VERSION@") - p.add_argument( "--show", type=comma_separated_list, metavar="A,B,C", help="only show figures for events A,B,C (default: all events)", ) - p.add_argument( "--sort", type=comma_separated_list, metavar="A,B,C", help="sort functions by events A,B,C (default: event column order)", ) - p.add_argument( "--threshold", type=threshold, default=0.1, metavar="N:[0,20]", - help="only show functions with more than N%% of primary sort event " - "counts (default: %(default)s)", + help="only show file:function/function:file pairs with more than " + "N%% of primary sort event counts (default: %(default)s)", ) add_bool_argument( p, @@ -182,6 +170,9 @@ class Events: # The event names. events: list[str] + # Equal to `len(self.events)`. + num_events: int + # The order in which we must traverse events for --show. Can be shorter # than `events`. show_events: list[str] @@ -226,10 +217,10 @@ class Events: self.sort_indices = [event_indices[event] for event in self.sort_events] - def mk_cc(self, text: str) -> Cc: - """Raises a `ValueError` exception on syntax error.""" + # Raises a `ValueError` exception on syntax error. + def mk_cc(self, str_counts: list[str]) -> Cc: # This is slightly faster than a list comprehension. - counts = list(map(int, text.split())) + counts = list(map(int, str_counts)) if len(counts) == self.num_events: pass @@ -239,48 +230,81 @@ class Events: else: raise ValueError - return Cc(counts) + return counts def mk_empty_cc(self) -> Cc: # This is much faster than a list comprehension. - return Cc([0] * self.num_events) + return [0] * self.num_events + + def mk_empty_dcc(self) -> Dcc: + return Dcc(self.mk_empty_cc(), defaultdict(self.mk_empty_cc)) -class Cc: - """ - This is a dumb container for counts. +# A "cost centre", which is a dumb container for counts. Always the same length +# as `Events.events`, but it doesn't even know event names. `Events.mk_cc` and +# `Events.mk_empty_cc` are used for construction. +# +# This used to be a class with a single field `counts: list[int]`, but this +# type is very hot and just using a type alias is much faster. +Cc = list[int] - It doesn't know anything about events, i.e. what each count means. It can - do basic operations like `__iadd__` and `__eq__`, and anything more must be - done elsewhere. `Events.mk_cc` and `Events.mk_empty_cc` are used for - construction. - """ - - # Always the same length as `Events.events`. - counts: list[int] - - def __init__(self, counts: list[int]) -> None: - self.counts = counts - - def __repr__(self) -> str: - return str(self.counts) - - def __eq__(self, other: object) -> bool: - if not isinstance(other, Cc): - return NotImplemented - return self.counts == other.counts - - def __iadd__(self, other: Cc) -> Cc: - for i, other_count in enumerate(other.counts): - self.counts[i] += other_count - return self +# Add the counts in `a_cc` to `b_cc`. +def add_cc_to_cc(a_cc: Cc, b_cc: Cc) -> None: + for i, a_count in enumerate(a_cc): + b_cc[i] += a_count -# A paired filename and function name. -Flfn = NewType("Flfn", tuple[str, str]) +# Unrolled version of `add_cc_to_cc`, for speed. +def add_cc_to_ccs( + a_cc: Cc, b_cc1: Cc, b_cc2: Cc, b_cc3: Cc, b_cc4: Cc, b_cc5: Cc +) -> None: + for i, a_count in enumerate(a_cc): + b_cc1[i] += a_count + b_cc2[i] += a_count + b_cc3[i] += a_count + b_cc4[i] += a_count + b_cc5[i] += a_count -# Per-function CCs. -DictFlfnCc = DefaultDict[Flfn, Cc] + +# Update `min_cc` and `max_cc` with `self`. +def update_cc_extremes(self: Cc, min_cc: Cc, max_cc: Cc) -> None: + for i, count in enumerate(self): + if count > max_cc[i]: + max_cc[i] = count + elif count < min_cc[i]: + min_cc[i] = count + + +# A deep cost centre with a dict for the inner names and CCs. +class Dcc: + outer_cc: Cc + inner_dict_name_cc: DictNameCc + + def __init__(self, outer_cc: Cc, inner_dict_name_cc: DictNameCc) -> None: + self.outer_cc = outer_cc + self.inner_dict_name_cc = inner_dict_name_cc + + +# A deep cost centre with a list for the inner names and CCs. Used during +# filtering and sorting. +class Lcc: + outer_cc: Cc + inner_list_name_cc: ListNameCc + + def __init__(self, outer_cc: Cc, inner_list_name_cc: ListNameCc) -> None: + self.outer_cc = outer_cc + self.inner_list_name_cc = inner_list_name_cc + + +# Per-file/function CCs. The list version is used during filtering and sorting. +DictNameCc = DefaultDict[str, Cc] +ListNameCc = list[tuple[str, Cc]] + +# Per-file/function DCCs. The outer names are filenames and the inner names are +# function names, or vice versa. The list version is used during filtering and +# sorting. +DictNameDcc = DefaultDict[str, Dcc] +ListNameLcc = list[tuple[str, Lcc]] # Per-line CCs, organised by filename and line number. DictLineCc = DefaultDict[int, Cc] @@ -292,7 +316,15 @@ def die(msg: str) -> NoReturn: sys.exit(1) -def read_cgout_file() -> tuple[str, str, Events, DictFlfnCc, DictFlDictLineCc, Cc]: +def read_cgout_file() -> tuple[ + str, + str, + Events, + DictNameDcc, + DictNameDcc, + DictFlDictLineCc, + Cc, +]: # The file format is described in Cachegrind's manual. try: cgout_file = open(args.cgout_filename[0], "r", encoding="utf-8") @@ -334,52 +366,67 @@ def read_cgout_file() -> tuple[str, str, Events, DictFlfnCc, DictFlDictLineCc, C def mk_empty_dict_line_cc() -> DictLineCc: return defaultdict(events.mk_empty_cc) - curr_fl = "" - curr_flfn = Flfn(("", "")) + # The current filename and function name. + fl = "" + fn = "" # Different places where we accumulate CC data. - dict_flfn_cc: DictFlfnCc = defaultdict(events.mk_empty_cc) + dict_fl_dcc: DictNameDcc = defaultdict(events.mk_empty_dcc) + dict_fn_dcc: DictNameDcc = defaultdict(events.mk_empty_dcc) dict_fl_dict_line_cc: DictFlDictLineCc = defaultdict(mk_empty_dict_line_cc) summary_cc = None - # Compile the one hot regex. - count_pat = re.compile(r"(\d+)\s+(.*)") + # These are refs into the dicts above, used to avoid repeated lookups. + # They are all overwritten before first use. + fl_dcc = events.mk_empty_dcc() + fn_dcc = events.mk_empty_dcc() + fl_dcc_inner_fn_cc = events.mk_empty_cc() + fn_dcc_inner_fl_cc = events.mk_empty_cc() + dict_line_cc = mk_empty_dict_line_cc() # Line matching is done in order of pattern frequency, for speed. - while True: - line = readline() - - if m := count_pat.match(line): - line_num = int(m.group(1)) + while line := readline(): + if line[0].isdigit(): + split_line = line.split() try: - cc = events.mk_cc(m.group(2)) + line_num = int(split_line[0]) + cc = events.mk_cc(split_line[1:]) except ValueError: parse_die("malformed or too many event counts") - # Record this CC at the function level. - flfn_cc = dict_flfn_cc[curr_flfn] - flfn_cc += cc - - # Record this CC at the file/line level. - line_cc = dict_fl_dict_line_cc[curr_fl][line_num] - line_cc += cc + # Record this CC at the file:function level, the function:file + # level, and the file/line level. + add_cc_to_ccs( + cc, + fl_dcc.outer_cc, + fn_dcc.outer_cc, + fl_dcc_inner_fn_cc, + fn_dcc_inner_fl_cc, + dict_line_cc[line_num], + ) elif line.startswith("fn="): - curr_flfn = Flfn((curr_fl, line[3:-1])) + fn = line[3:-1] + # `fl_dcc` is unchanged. + fn_dcc = dict_fn_dcc[fn] + fl_dcc_inner_fn_cc = fl_dcc.inner_dict_name_cc[fn] + fn_dcc_inner_fl_cc = fn_dcc.inner_dict_name_cc[fl] elif line.startswith("fl="): - curr_fl = line[3:-1] + fl = line[3:-1] # A `fn=` line should follow, overwriting the function name. - curr_flfn = Flfn((curr_fl, "")) + fn = "" + fl_dcc = dict_fl_dcc[fl] + fn_dcc = dict_fn_dcc[fn] + fl_dcc_inner_fn_cc = fl_dcc.inner_dict_name_cc[fn] + fn_dcc_inner_fl_cc = fn_dcc.inner_dict_name_cc[fl] + dict_line_cc = dict_fl_dict_line_cc[fl] elif m := re.match(r"summary:\s+(.*)", line): try: - summary_cc = events.mk_cc(m.group(1)) + summary_cc = events.mk_cc(m.group(1).split()) except ValueError: - parse_die("too many event counts") - - elif line == "": - break # EOF + parse_die("malformed or too many event counts") elif line == "\n" or line.startswith("#"): # Skip empty lines and comment lines. @@ -392,10 +439,10 @@ def read_cgout_file() -> tuple[str, str, Events, DictFlfnCc, DictFlDictLineCc, C if not summary_cc: parse_die("missing `summary:` line, aborting") - # Check summary is correct. + # Check summary is correct. (Only using the outer CCs.) total_cc = events.mk_empty_cc() - for flfn_cc in dict_flfn_cc.values(): - total_cc += flfn_cc + for dcc in dict_fl_dcc.values(): + add_cc_to_cc(dcc.outer_cc, total_cc) if summary_cc != total_cc: msg = ( "`summary:` line doesn't match computed total\n" @@ -404,7 +451,32 @@ def read_cgout_file() -> tuple[str, str, Events, DictFlfnCc, DictFlDictLineCc, C ) parse_die(msg) - return (desc, cmd, events, dict_flfn_cc, dict_fl_dict_line_cc, summary_cc) + return ( + desc, + cmd, + events, + dict_fl_dcc, + dict_fn_dcc, + dict_fl_dict_line_cc, + summary_cc, + ) + + +# The width of a column, in three parts. +class Width: + # Width of the widest commified event count. + count: int + + # Width of the widest first percentage, of the form ` (n.n%)` or ` (n.n%,`. + perc1: int + + # Width of the widest second percentage, of the form ` n.n%)`. + perc2: int + + def __init__(self, count: int, perc1: int, perc2: int) -> None: + self.count = count + self.perc1 = perc1 + self.perc2 = perc2 class CcPrinter: @@ -414,90 +486,164 @@ class CcPrinter: # Note: every `CcPrinter` gets the same summary CC. summary_cc: Cc - # The width of each event count column. (This column is also used for event - # names.) For simplicity, its length matches `events.events`, even though - # not all events are necessarily shown. - count_widths: list[int] + # String to print before the event names. + events_prefix: str - # The width of each percentage column. Zero if --show-percs is disabled. - # Its length matches `count_widths`. - perc_widths: list[int] + # The widths of each event column. For simplicity, its length matches + # `events.events`, even though not all events are necessarily shown. + widths: list[Width] - def __init__(self, events: Events, ccs: list[Cc], summary_cc: Cc) -> None: + # Text of a missing CC, which can be computed in advance. + missing_cc_str: str + + # Must call `init_ccs` or `init_list_name_lcc` after this. + def __init__(self, events: Events, summary_cc: Cc) -> None: self.events = events self.summary_cc = summary_cc + # Other fields initialized in `init_*`. - # Find min and max value for each event. One of them will be the - # widest value. - min_cc = events.mk_empty_cc() - max_cc = events.mk_empty_cc() + def init_ccs(self, ccs: list[Cc]) -> None: + self.events_prefix = "" + + # Find min and max count for each event. One of them will be the widest + # value. + min_cc = self.events.mk_empty_cc() + max_cc = self.events.mk_empty_cc() for cc in ccs: - for i, _ in enumerate(events.events): - count = cc.counts[i] - if count > max_cc.counts[i]: - max_cc.counts[i] = count - elif count < min_cc.counts[i]: - min_cc.counts[i] = count + update_cc_extremes(cc, min_cc, max_cc) - # Find maximum width for each column. - self.count_widths = [0] * events.num_events - self.perc_widths = [0] * events.num_events - for i, event in enumerate(events.events): - # Get count and perc widths of the min and max CCs. - (min_count, min_perc) = self.count_and_perc(min_cc, i) - (max_count, max_perc) = self.count_and_perc(max_cc, i) + self.init_widths(min_cc, max_cc, None, None) - # The event name goes in the count column. - self.count_widths[i] = max(len(min_count), len(max_count), len(event)) - self.perc_widths[i] = max(len(min_perc), len(max_perc)) + def init_list_name_lcc(self, list_name_lcc: ListNameLcc) -> None: + self.events_prefix = " " + + cumul_cc = self.events.mk_empty_cc() + + # Find min and max value for each event. One of them will be the widest + # value. Likewise for the cumulative counts. + min_cc = self.events.mk_empty_cc() + max_cc = self.events.mk_empty_cc() + min_cumul_cc = self.events.mk_empty_cc() + max_cumul_cc = self.events.mk_empty_cc() + for _, lcc in list_name_lcc: + # Consider both outer and inner CCs for `count` and `perc1`. + update_cc_extremes(lcc.outer_cc, min_cc, max_cc) + for _, inner_cc in lcc.inner_list_name_cc: + update_cc_extremes(inner_cc, min_cc, max_cc) + + # Consider only outer CCs for `perc2`. + add_cc_to_cc(lcc.outer_cc, cumul_cc) + update_cc_extremes(cumul_cc, min_cumul_cc, max_cumul_cc) + + self.init_widths(min_cc, max_cc, min_cumul_cc, max_cumul_cc) + + def init_widths( + self, min_cc1: Cc, max_cc1: Cc, min_cc2: Cc | None, max_cc2: Cc | None + ) -> None: + self.widths = [Width(0, 0, 0)] * self.events.num_events + for i in range(len(self.events.events)): + # Get count and percs widths of the min and max CCs. + (min_count, min_perc1, min_perc2) = self.count_and_percs_strs( + min_cc1, min_cc2, i + ) + (max_count, max_perc1, max_perc2) = self.count_and_percs_strs( + max_cc1, max_cc2, i + ) + self.widths[i] = Width( + max(len(min_count), len(max_count)), + max(len(min_perc1), len(max_perc1)), + max(len(min_perc2), len(max_perc2)), + ) + + self.missing_cc_str = "" + for i in self.events.show_indices: + self.missing_cc_str += self.count_and_percs_str(i, ".", "", "") + + # Get the count and perc string for `cc1[i]` and the perc string for + # `cc2[i]`. (Unless `cc2` is `None`, in which case `perc2` will be "".) + def count_and_percs_strs( + self, cc1: Cc, cc2: Cc | None, i: int + ) -> tuple[str, str, str]: + count = f"{cc1[i]:,d}" # commify + if args.show_percs: + summary_count = self.summary_cc[i] + if cc2 is None: + # A plain or inner CC, with a single percentage. + if cc1[i] == 0: + # Don't show percentages for "0" entries, it's just clutter. + perc1 = "" + elif summary_count == 0: + # Avoid dividing by zero. + perc1 = " (n/a)" + else: + perc1 = f" ({cc1[i] * 100 / summary_count:.1f}%)" + perc2 = "" + else: + # An outer CC, with two percentages. + if summary_count == 0: + # Avoid dividing by zero. + perc1 = " (n/a," + perc2 = " n/a)" + else: + perc1 = f" ({cc1[i] * 100 / summary_count:.1f}%," + perc2 = f" {cc2[i] * 100 / summary_count:.1f}%)" + else: + perc1 = "" + perc2 = "" + + return (count, perc1, perc2) + + def count_and_percs_str(self, i: int, count: str, perc1: str, perc2: str) -> str: + event_w = len(self.events.events[i]) + count_w = self.widths[i].count + perc1_w = self.widths[i].perc1 + perc2_w = self.widths[i].perc2 + pre_w = max(0, event_w - count_w - perc1_w - perc2_w) + return f"{'':>{pre_w}}{count:>{count_w}}{perc1:>{perc1_w}}{perc2:>{perc2_w}} " def print_events(self, suffix: str) -> None: + print(self.events_prefix, end="") for i in self.events.show_indices: - # The event name goes in the count column. event = self.events.events[i] - nwidth = self.count_widths[i] - pwidth = self.perc_widths[i] - empty_perc = "" - print(f"{event:<{nwidth}}{empty_perc:>{pwidth}} ", end="") + event_w = len(event) + count_w = self.widths[i].count + perc1_w = self.widths[i].perc1 + perc2_w = self.widths[i].perc2 + print(f"{event:_<{max(event_w, count_w + perc1_w + perc2_w)}} ", end="") print(suffix) - def print_count_and_perc(self, i: int, count: str, perc: str) -> None: - nwidth = self.count_widths[i] - pwidth = self.perc_widths[i] - print(f"{count:>{nwidth}}{perc:>{pwidth}} ", end="") - - def count_and_perc(self, cc: Cc, i: int) -> tuple[str, str]: - count = f"{cc.counts[i]:,d}" # commify - if args.show_percs: - if cc.counts[i] == 0: - # Don't show percentages for "0" entries, it's just clutter. - perc = "" - else: - summary_count = self.summary_cc.counts[i] - if summary_count == 0: - perc = " (n/a)" - else: - p = cc.counts[i] * 100 / summary_count - perc = f" ({p:.1f}%)" + def print_lcc(self, lcc: Lcc, outer_name: str, cumul_cc: Cc) -> None: + print("> ", end="") + if ( + len(lcc.inner_list_name_cc) == 1 + and lcc.outer_cc == lcc.inner_list_name_cc[0][1] + ): + # There is only one inner CC, it met the threshold, and it is equal + # to the outer CC. Print the inner CC and outer CC in a single + # line, because they are the same. + inner_name = lcc.inner_list_name_cc[0][0] + self.print_cc(lcc.outer_cc, cumul_cc, f"{outer_name}:{inner_name}") else: - perc = "" + # There are multiple inner CCs, and at least one met the threshold. + # Print the outer CC and then the inner CCs, indented. + self.print_cc(lcc.outer_cc, cumul_cc, f"{outer_name}:") + for inner_name, inner_cc in lcc.inner_list_name_cc: + print(" ", end="") + self.print_cc(inner_cc, None, f" {inner_name}") + print() - return (count, perc) - - def print_cc(self, cc: Cc, suffix: str) -> None: + # If `cc2` is `None`, it's a vanilla CC or inner CC. Otherwise, it's an + # outer CC. + def print_cc(self, cc: Cc, cc2: Cc | None, suffix: str) -> None: for i in self.events.show_indices: - (count, perc) = self.count_and_perc(cc, i) - self.print_count_and_perc(i, count, perc) + (count, perc1, perc2) = self.count_and_percs_strs(cc, cc2, i) + print(self.count_and_percs_str(i, count, perc1, perc2), end="") print("", suffix) def print_missing_cc(self, suffix: str) -> None: - # Don't show percentages for "." entries, it's just clutter. - for i in self.events.show_indices: - self.print_count_and_perc(i, ".", "") - - print("", suffix) + print(self.missing_cc_str, suffix) # Used in various places in the output. @@ -508,8 +654,8 @@ def print_fancy(text: str) -> None: print(fancy) -def print_cachegrind_profile(desc: str, cmd: str, events: Events) -> None: - print_fancy("Cachegrind profile") +def print_metadata(desc: str, cmd: str, events: Events) -> None: + print_fancy("Metadata") print(desc, end="") print("Command: ", cmd) print("Data file: ", args.cgout_filename[0]) @@ -530,53 +676,79 @@ def print_cachegrind_profile(desc: str, cmd: str, events: Events) -> None: def print_summary(events: Events, summary_cc: Cc) -> None: - printer = CcPrinter(events, [summary_cc], summary_cc) + printer = CcPrinter(events, summary_cc) + printer.init_ccs([summary_cc]) print_fancy("Summary") printer.print_events("") print() - printer.print_cc(summary_cc, "PROGRAM TOTALS") + printer.print_cc(summary_cc, None, "PROGRAM TOTALS") print() -def print_function_summary( - events: Events, dict_flfn_cc: DictFlfnCc, summary_cc: Cc +def print_name_summary( + kind: str, events: Events, dict_name_dcc: DictNameDcc, summary_cc: Cc ) -> set[str]: - # Only the first threshold percentage is actually used. + # The primary sort event is used for the threshold. threshold_index = events.sort_indices[0] # Convert the threshold from a percentage to an event count. - threshold = args.threshold * abs(summary_cc.counts[threshold_index]) / 100 + threshold = args.threshold * abs(summary_cc[threshold_index]) / 100 - def meets_threshold(flfn_and_cc: tuple[Flfn, Cc]) -> bool: - cc = flfn_and_cc[1] - return abs(cc.counts[threshold_index]) >= threshold + def meets_threshold(name_and_cc: tuple[str, Cc]) -> bool: + cc = name_and_cc[1] + return abs(cc[threshold_index]) >= threshold - # Create a list with the counts in sort order, so that left-to-right list - # comparison does the right thing. Plus the `Flfn` at the end for - # deterministic output when all the event counts are identical in two CCs. - def key(flfn_and_cc: tuple[Flfn, Cc]) -> tuple[list[int], Flfn]: - cc = flfn_and_cc[1] - return ([abs(cc.counts[i]) for i in events.sort_indices], flfn_and_cc[0]) + # Create a list with the outer CC counts in sort order, so that + # left-to-right list comparison does the right thing. Plus the outer name + # at the end for deterministic output when all the event counts are + # identical in two CCs. + def key_name_and_lcc(name_and_lcc: tuple[str, Lcc]) -> tuple[list[int], str]: + (outer_name, lcc) = name_and_lcc + return ( + [abs(lcc.outer_cc[i]) for i in events.sort_indices], + outer_name, + ) - # Filter out functions for which the primary sort event count is below the - # threshold, and sort the remainder. - filtered_flfns_and_ccs = filter(meets_threshold, dict_flfn_cc.items()) - sorted_flfns_and_ccs = sorted(filtered_flfns_and_ccs, key=key, reverse=True) - sorted_ccs = list(map(lambda flfn_and_cc: flfn_and_cc[1], sorted_flfns_and_ccs)) + # Similar to `key_name_and_lcc`. + def key_name_and_cc(name_and_cc: tuple[str, Cc]) -> tuple[list[int], str]: + (name, cc) = name_and_cc + return ([abs(cc[i]) for i in events.sort_indices], name) - printer = CcPrinter(events, sorted_ccs, summary_cc) - print_fancy("Function summary") - printer.print_events(" file:function") + # This is a `filter_map` operation, which Python doesn't directly support. + list_name_lcc: ListNameLcc = [] + for outer_name, dcc in dict_name_dcc.items(): + # Filter out inner CCs for which the primary sort event count is below the + # threshold, and sort the remainder. + inner_list_name_cc = sorted( + filter(meets_threshold, dcc.inner_dict_name_cc.items()), + key=key_name_and_cc, + reverse=True, + ) + + # If no inner CCs meet the threshold, ignore the entire DCC, even if + # the outer CC meets the threshold. + if len(inner_list_name_cc) == 0: + continue + + list_name_lcc.append((outer_name, Lcc(dcc.outer_cc, inner_list_name_cc))) + + list_name_lcc = sorted(list_name_lcc, key=key_name_and_lcc, reverse=True) + + printer = CcPrinter(events, summary_cc) + printer.init_list_name_lcc(list_name_lcc) + print_fancy(kind + " summary") + printer.print_events(" " + kind.lower()) print() - # Print per-function counts. - for flfn, flfn_cc in sorted_flfns_and_ccs: - printer.print_cc(flfn_cc, f"{flfn[0]}:{flfn[1]}") + # Print LCCs. + threshold_names = set([]) + cumul_cc = events.mk_empty_cc() + for name, lcc in list_name_lcc: + add_cc_to_cc(lcc.outer_cc, cumul_cc) + printer.print_lcc(lcc, name, cumul_cc) + threshold_names.add(name) - print() - - # Files containing a function that met the threshold. - return set(flfn_and_cc[0][0] for flfn_and_cc in sorted_flfns_and_ccs) + return threshold_names class AnnotatedCcs: @@ -647,7 +819,8 @@ def print_annotated_src_file( if os.stat(src_file.name).st_mtime_ns > os.stat(args.cgout_filename[0]).st_mtime_ns: warn_src_file_is_newer(src_file.name, args.cgout_filename[0]) - printer = CcPrinter(events, list(dict_line_cc.values()), summary_cc) + printer = CcPrinter(events, summary_cc) + printer.init_ccs(list(dict_line_cc.values())) # The starting fancy has already been printed by the caller. printer.print_events("") print() @@ -658,8 +831,8 @@ def print_annotated_src_file( line0_cc = dict_line_cc.pop(0, None) if line0_cc: suffix = "" - printer.print_cc(line0_cc, suffix) - annotated_ccs.line_nums_unknown_cc += line0_cc + printer.print_cc(line0_cc, None, suffix) + add_cc_to_cc(line0_cc, annotated_ccs.line_nums_unknown_cc) print() # Find interesting line ranges: all lines with a CC, and all lines within @@ -698,8 +871,10 @@ def print_annotated_src_file( if not src_line: return # EOF if line_nums and line_num == line_nums[0]: - printer.print_cc(dict_line_cc[line_num], src_line[:-1]) - annotated_ccs.line_nums_known_cc += dict_line_cc[line_num] + printer.print_cc(dict_line_cc[line_num], None, src_line[:-1]) + add_cc_to_cc( + dict_line_cc[line_num], annotated_ccs.line_nums_known_cc + ) del line_nums[0] else: printer.print_missing_cc(src_line[:-1]) @@ -715,8 +890,10 @@ def print_annotated_src_file( if line_nums: print() for line_num in line_nums: - printer.print_cc(dict_line_cc[line_num], f"") - annotated_ccs.line_nums_known_cc += dict_line_cc[line_num] + printer.print_cc( + dict_line_cc[line_num], None, f"" + ) + add_cc_to_cc(dict_line_cc[line_num], annotated_ccs.line_nums_known_cc) print() warn_bogus_lines(src_file.name) @@ -736,7 +913,7 @@ def print_annotated_src_files( def add_dict_line_cc_to_cc(dict_line_cc: DictLineCc | None, accum_cc: Cc) -> None: if dict_line_cc: for line_cc in dict_line_cc.values(): - accum_cc += line_cc + add_cc_to_cc(line_cc, accum_cc) # Exclude the unknown ("???") file, which is unannotatable. ann_src_filenames.discard("???") @@ -771,7 +948,6 @@ def print_annotated_src_files( annotated_ccs, summary_cc, ) - readable = True break except OSError: @@ -799,15 +975,16 @@ def print_annotation_summary( summary_cc: Cc, ) -> None: # Show how many events were covered by annotated lines above. - printer = CcPrinter(events, annotated_ccs.ccs(), summary_cc) + printer = CcPrinter(events, summary_cc) + printer.init_ccs(annotated_ccs.ccs()) print_fancy("Annotation summary") printer.print_events("") print() total_cc = events.mk_empty_cc() for (cc, label) in zip(annotated_ccs.ccs(), AnnotatedCcs.labels): - printer.print_cc(cc, label) - total_cc += cc + printer.print_cc(cc, None, label) + add_cc_to_cc(cc, total_cc) print() @@ -826,19 +1003,19 @@ def main() -> None: desc, cmd, events, - dict_flfn_cc, + dict_fl_dcc, + dict_fn_dcc, dict_fl_dict_line_cc, summary_cc, ) = read_cgout_file() # Each of the following calls prints a section of the output. - - print_cachegrind_profile(desc, cmd, events) - + print_metadata(desc, cmd, events) print_summary(events, summary_cc) - - ann_src_filenames = print_function_summary(events, dict_flfn_cc, summary_cc) - + ann_src_filenames = print_name_summary( + "File:function", events, dict_fl_dcc, summary_cc + ) + print_name_summary("Function:file", events, dict_fn_dcc, summary_cc) if args.annotate: annotated_ccs = print_annotated_src_files( events, ann_src_filenames, dict_fl_dict_line_cc, summary_cc diff --git a/cachegrind/cg_diff.in b/cachegrind/cg_diff.in index bae0c7abe..38910f31b 100755 --- a/cachegrind/cg_diff.in +++ b/cachegrind/cg_diff.in @@ -26,10 +26,8 @@ # # The GNU General Public License is contained in the file COPYING. -""" -This script diffs Cachegrind output files. -""" - +# This script diffs Cachegrind output files. +# # Use `make pydiff` to "build" this script every time it is changed. This runs # the formatters, type-checkers, and linters on `cg_diff.in` and then generates # `cg_diff`. @@ -47,13 +45,9 @@ from typing import Callable, DefaultDict, NewType, NoReturn SearchAndReplace = Callable[[str], str] +# A typed wrapper for parsed args. class Args(Namespace): - """ - A typed wrapper for parsed args. - - None of these fields are modified after arg parsing finishes. - """ - + # None of these fields are modified after arg parsing finishes. mod_filename: SearchAndReplace mod_funcname: SearchAndReplace cgout_filename1: str @@ -146,10 +140,10 @@ class Events: self.events = text.split() self.num_events = len(self.events) - def mk_cc(self, text: str) -> Cc: - """Raises a `ValueError` exception on syntax error.""" + # Raises a `ValueError` exception on syntax error. + def mk_cc(self, str_counts: list[str]) -> Cc: # This is slightly faster than a list comprehension. - counts = list(map(int, text.split())) + counts = list(map(int, str_counts)) if len(counts) == self.num_events: pass @@ -159,46 +153,31 @@ class Events: else: raise ValueError - return Cc(counts) + return counts def mk_empty_cc(self) -> Cc: # This is much faster than a list comprehension. - return Cc([0] * self.num_events) + return [0] * self.num_events -class Cc: - """ - This is a dumb container for counts. +# A "cost centre", which is a dumb container for counts. Always the same length +# as `Events.events`, but it doesn't even know event names. `Events.mk_cc` and +# `Events.mk_empty_cc` are used for construction. +# +# This used to be a class with a single field `counts: list[int]`, but this +# type is very hot and just using a type alias is much faster. +Cc = list[int] - It doesn't know anything about events, i.e. what each count means. It can - do basic operations like `__iadd__` and `__eq__`, and anything more must be - done elsewhere. `Events.mk_cc` and `Events.mk_empty_cc` are used for - construction. - """ +# Add the counts in `a_cc` to `b_cc`. +def add_cc_to_cc(a_cc: Cc, b_cc: Cc) -> None: + for i, a_count in enumerate(a_cc): + b_cc[i] += a_count - # Always the same length as `Events.events`. - counts: list[int] - def __init__(self, counts: list[int]) -> None: - self.counts = counts - - def __repr__(self) -> str: - return str(self.counts) - - def __eq__(self, other: object) -> bool: - if not isinstance(other, Cc): - return NotImplemented - return self.counts == other.counts - - def __iadd__(self, other: Cc) -> Cc: - for i, other_count in enumerate(other.counts): - self.counts[i] += other_count - return self - - def __isub__(self, other: Cc) -> Cc: - for i, other_count in enumerate(other.counts): - self.counts[i] -= other_count - return self +# Subtract the counts in `a_cc` from `b_cc`. +def sub_cc_from_cc(a_cc: Cc, b_cc: Cc) -> None: + for i, a_count in enumerate(a_cc): + b_cc[i] -= a_count # A paired filename and function name. @@ -252,33 +231,28 @@ def read_cgout_file(cgout_filename: str) -> tuple[str, Events, DictFlfnCc, Cc]: else: parse_die("missing an `events:` line") - curr_fl = "" - curr_flfn = Flfn(("", "")) + fl = "" + flfn = Flfn(("", "")) # Different places where we accumulate CC data. dict_flfn_cc: DictFlfnCc = defaultdict(events.mk_empty_cc) summary_cc = None - # Compile the one hot regex. - count_pat = re.compile(r"(\d+)\s+(.*)") - # Line matching is done in order of pattern frequency, for speed. - while True: - line = readline() - - if m := count_pat.match(line): - # The line_num isn't used. + while line := readline(): + if line[0].isdigit(): + split_line = line.split() try: - cc = events.mk_cc(m.group(2)) + # The line_num isn't used. + cc = events.mk_cc(split_line[1:]) except ValueError: parse_die("malformed or too many event counts") # Record this CC at the function level. - flfn_cc = dict_flfn_cc[curr_flfn] - flfn_cc += cc + add_cc_to_cc(cc, dict_flfn_cc[flfn]) elif line.startswith("fn="): - curr_flfn = Flfn((curr_fl, args.mod_funcname(line[3:-1]))) + flfn = Flfn((fl, args.mod_funcname(line[3:-1]))) elif line.startswith("fl="): # A longstanding bug: the use of `--mod-filename` makes it @@ -287,18 +261,15 @@ def read_cgout_file(cgout_filename: str) -> tuple[str, Events, DictFlfnCc, Cc]: # diffs anyway. It just means we get "This file was unreadable" # for modified filenames rather than a single "" CC. - curr_fl = args.mod_filename(line[3:-1]) + fl = args.mod_filename(line[3:-1]) # A `fn=` line should follow, overwriting the "???". - curr_flfn = Flfn((curr_fl, "???")) + flfn = Flfn((fl, "???")) elif m := re.match(r"summary:\s+(.*)", line): try: - summary_cc = events.mk_cc(m.group(1)) + summary_cc = events.mk_cc(m.group(1).split()) except ValueError: - parse_die("too many event counts") - - elif line == "": - break # EOF + parse_die("malformed or too many event counts") elif line == "\n" or line.startswith("#"): # Skip empty lines and comment lines. @@ -314,7 +285,7 @@ def read_cgout_file(cgout_filename: str) -> tuple[str, Events, DictFlfnCc, Cc]: # Check summary is correct. total_cc = events.mk_empty_cc() for flfn_cc in dict_flfn_cc.values(): - total_cc += flfn_cc + add_cc_to_cc(flfn_cc, total_cc) if summary_cc != total_cc: msg = ( "`summary:` line doesn't match computed total\n" @@ -339,8 +310,8 @@ def main() -> None: # Subtract file 1's CCs from file 2's CCs, at the Flfn level. for flfn, flfn_cc1 in dict_flfn_cc1.items(): flfn_cc2 = dict_flfn_cc2[flfn] - flfn_cc2 -= flfn_cc1 - summary_cc2 -= summary_cc1 + sub_cc_from_cc(flfn_cc1, flfn_cc2) + sub_cc_from_cc(summary_cc1, summary_cc2) print(f"desc: Files compared: {filename1}; {filename2}") print(f"cmd: {cmd1}; {cmd2}") @@ -356,9 +327,9 @@ def main() -> None: # move around. print(f"fl={flfn[0]}") print(f"fn={flfn[1]}") - print("0", *flfn_cc2.counts, sep=" ") + print("0", *flfn_cc2, sep=" ") - print("summary:", *summary_cc2.counts, sep=" ") + print("summary:", *summary_cc2, sep=" ") if __name__ == "__main__": diff --git a/cachegrind/cg_merge.in b/cachegrind/cg_merge.in index fca73439e..8304e8b27 100755 --- a/cachegrind/cg_merge.in +++ b/cachegrind/cg_merge.in @@ -26,10 +26,8 @@ # # The GNU General Public License is contained in the file COPYING. -""" -This script diffs Cachegrind output files. -""" - +# This script merges Cachegrind output files. +# # Use `make pymerge` to "build" this script every time it is changed. This runs # the formatters, type-checkers, and linters on `cg_merge.in` and then # generates `cg_merge`. @@ -45,13 +43,9 @@ from collections import defaultdict from typing import DefaultDict, NoReturn, TextIO +# A typed wrapper for parsed args. class Args(Namespace): - """ - A typed wrapper for parsed args. - - None of these fields are modified after arg parsing finishes. - """ - + # None of these fields are modified after arg parsing finishes. output: str cgout_filename: list[str] @@ -92,10 +86,10 @@ class Events: self.events = text.split() self.num_events = len(self.events) - def mk_cc(self, text: str) -> Cc: - """Raises a `ValueError` exception on syntax error.""" + # Raises a `ValueError` exception on syntax error. + def mk_cc(self, str_counts: list[str]) -> Cc: # This is slightly faster than a list comprehension. - counts = list(map(int, text.split())) + counts = list(map(int, str_counts)) if len(counts) == self.num_events: pass @@ -105,41 +99,26 @@ class Events: else: raise ValueError - return Cc(counts) + return counts def mk_empty_cc(self) -> Cc: # This is much faster than a list comprehension. - return Cc([0] * self.num_events) + return [0] * self.num_events -class Cc: - """ - This is a dumb container for counts. +# A "cost centre", which is a dumb container for counts. Always the same length +# as `Events.events`, but it doesn't even know event names. `Events.mk_cc` and +# `Events.mk_empty_cc` are used for construction. +# +# This used to be a class with a single field `counts: list[int]`, but this +# type is very hot and just using a type alias is much faster. +Cc = list[int] - It doesn't know anything about events, i.e. what each count means. It can - do basic operations like `__iadd__` and `__eq__`, and anything more must be - done elsewhere. `Events.mk_cc` and `Events.mk_empty_cc` are used for - construction. - """ - # Always the same length as `Events.events`. - counts: list[int] - - def __init__(self, counts: list[int]) -> None: - self.counts = counts - - def __repr__(self) -> str: - return str(self.counts) - - def __eq__(self, other: object) -> bool: - if not isinstance(other, Cc): - return NotImplemented - return self.counts == other.counts - - def __iadd__(self, other: Cc) -> Cc: - for i, other_count in enumerate(other.counts): - self.counts[i] += other_count - return self +# Add the counts in `a_cc` to `b_cc`. +def add_cc_to_cc(a_cc: Cc, b_cc: Cc) -> None: + for i, a_count in enumerate(a_cc): + b_cc[i] += a_count # Per-line CCs, organised by filename, function name, and line number. @@ -205,8 +184,8 @@ def read_cgout_file( summary_cc_present = False - curr_fl = "" - curr_fn = "" + fl = "" + fn = "" # The `cumul_*` values are passed in by reference and are modified by # this function. But they can't be properly initialized until the @@ -217,43 +196,35 @@ def read_cgout_file( cumul_dict_fl_dict_fn_dict_line_cc.default_factory = ( mk_empty_dict_fn_dict_line_cc ) - cumul_summary_cc.counts = events.mk_empty_cc().counts - - # Compile the one hot regex. - count_pat = re.compile(r"(\d+)\s+(.*)") + cumul_summary_cc.extend(events.mk_empty_cc()) # Line matching is done in order of pattern frequency, for speed. - while True: - line = readline() - - if m := count_pat.match(line): - line_num = int(m.group(1)) + while line := readline(): + if line[0].isdigit(): + split_line = line.split() try: - cc = events.mk_cc(m.group(2)) + line_num = int(split_line[0]) + cc = events.mk_cc(split_line[1:]) except ValueError: parse_die("malformed or too many event counts") # Record this CC at the file/func/line level. - line_cc = cumul_dict_fl_dict_fn_dict_line_cc[curr_fl][curr_fn][line_num] - line_cc += cc + add_cc_to_cc(cc, cumul_dict_fl_dict_fn_dict_line_cc[fl][fn][line_num]) elif line.startswith("fn="): - curr_fn = line[3:-1] + fn = line[3:-1] elif line.startswith("fl="): - curr_fl = line[3:-1] + fl = line[3:-1] # A `fn=` line should follow, overwriting the "???". - curr_fn = "???" + fn = "???" elif m := re.match(r"summary:\s+(.*)", line): summary_cc_present = True try: - cumul_summary_cc += events.mk_cc(m.group(1)) + add_cc_to_cc(events.mk_cc(m.group(1).split()), cumul_summary_cc) except ValueError: - parse_die("too many event counts") - - elif line == "": - break # EOF + parse_die("malformed or too many event counts") elif line == "\n" or line.startswith("#"): # Skip empty lines and comment lines. @@ -283,7 +254,7 @@ def main() -> None: # Different places where we accumulate CC data. Initialized to invalid # states prior to the number of events being known. cumul_dict_fl_dict_fn_dict_line_cc: DictFlDictFnDictLineCc = defaultdict(None) - cumul_summary_cc: Cc = Cc([]) + cumul_summary_cc: Cc = [] for n, filename in enumerate(args.cgout_filename): is_first_file = n == 0 @@ -321,9 +292,9 @@ def main() -> None: for fn, dict_line_cc in dict_fn_dict_line_cc.items(): print(f"fn={fn}", file=f) for line, cc in dict_line_cc.items(): - print(line, *cc.counts, file=f) + print(line, *cc, file=f) - print("summary:", *cumul_summary_cc.counts, sep=" ", file=f) + print("summary:", *cumul_summary_cc, sep=" ", file=f) if args.output: try: diff --git a/cachegrind/tests/ann-diff1.post.exp b/cachegrind/tests/ann-diff1.post.exp index cbb7f8448..4e13f0c08 100644 --- a/cachegrind/tests/ann-diff1.post.exp +++ b/cachegrind/tests/ann-diff1.post.exp @@ -1,5 +1,5 @@ -------------------------------------------------------------------------------- --- Cachegrind profile +-- Metadata -------------------------------------------------------------------------------- Files compared: ann1.cgout; ann1b.cgout Command: ./a.out; ./a.out @@ -14,28 +14,35 @@ Annotation: on -------------------------------------------------------------------------------- -- Summary -------------------------------------------------------------------------------- -Ir I1mr ILmr Dr D1mr DLmr Dw D1mw DLmw +Ir________________ I1mr ILmr Dr_________________ D1mr DLmr Dw D1mw DLmw 5,000,000 (100.0%) 0 0 -2,000,000 (100.0%) 0 0 0 0 0 PROGRAM TOTALS -------------------------------------------------------------------------------- --- Function summary +-- File:function summary -------------------------------------------------------------------------------- -Ir I1mr ILmr Dr D1mr DLmr Dw D1mw DLmw file:function + Ir________________________ I1mr________ ILmr________ Dr_________________________ D1mr________ DLmr________ Dw__________ D1mw________ DLmw________ file:function -5,000,000 (100.0%) 0 0 -2,000,000 (100.0%) 0 0 0 0 0 a.c:MAIN +> 5,000,000 (100.0%, 100.0%) 0 (n/a, n/a) 0 (n/a, n/a) -2,000,000 (100.0%, 100.0%) 0 (n/a, n/a) 0 (n/a, n/a) 0 (n/a, n/a) 0 (n/a, n/a) 0 (n/a, n/a) a.c:MAIN + +-------------------------------------------------------------------------------- +-- Function:file summary +-------------------------------------------------------------------------------- + Ir________________________ I1mr________ ILmr________ Dr_________________________ D1mr________ DLmr________ Dw__________ D1mw________ DLmw________ function:file + +> 5,000,000 (100.0%, 100.0%) 0 (n/a, n/a) 0 (n/a, n/a) -2,000,000 (100.0%, 100.0%) 0 (n/a, n/a) 0 (n/a, n/a) 0 (n/a, n/a) 0 (n/a, n/a) 0 (n/a, n/a) MAIN:a.c -------------------------------------------------------------------------------- -- Annotated source file: a.c -------------------------------------------------------------------------------- -Ir I1mr ILmr Dr D1mr DLmr Dw D1mw DLmw +Ir________________ I1mr ILmr Dr_________________ D1mr DLmr Dw D1mw DLmw 5,000,000 (100.0%) 0 0 -2,000,000 (100.0%) 0 0 0 0 0 -------------------------------------------------------------------------------- -- Annotation summary -------------------------------------------------------------------------------- -Ir I1mr ILmr Dr D1mr DLmr Dw D1mw DLmw +Ir________________ I1mr ILmr Dr_________________ D1mr DLmr Dw D1mw DLmw 0 0 0 0 0 0 0 0 0 annotated: files known & above threshold & readable, line numbers known 5,000,000 (100.0%) 0 0 -2,000,000 (100.0%) 0 0 0 0 0 annotated: files known & above threshold & readable, line numbers unknown diff --git a/cachegrind/tests/ann-diff2.post.exp b/cachegrind/tests/ann-diff2.post.exp index 4c1c46d7b..bcf09ea9c 100644 --- a/cachegrind/tests/ann-diff2.post.exp +++ b/cachegrind/tests/ann-diff2.post.exp @@ -1,5 +1,5 @@ -------------------------------------------------------------------------------- --- Cachegrind profile +-- Metadata -------------------------------------------------------------------------------- Files compared: ann-diff2a.cgout; ann-diff2b.cgout Command: cmd1; cmd2 @@ -14,18 +14,30 @@ Annotation: on -------------------------------------------------------------------------------- -- Summary -------------------------------------------------------------------------------- -One Two +One___________ Two___________ 2,100 (100.0%) 1,900 (100.0%) PROGRAM TOTALS -------------------------------------------------------------------------------- --- Function summary +-- File:function summary -------------------------------------------------------------------------------- -One Two file:function + One___________________ Two___________________ file:function -1,000 (47.6%) 1,000 (52.6%) aux/ann-diff2-basic.rs:groffN -1,000 (47.6%) 1,000 (52.6%) aux/ann-diff2-basic.rs:fN_ffN_fooN_F4_g5 - 100 (4.8%) -100 (-5.3%) aux/ann-diff2-basic.rs:basic1 +> 2,100 (100.0%, 100.0%) 1,900 (100.0%, 100.0%) aux/ann-diff2-basic.rs: + 1,000 (47.6%) 1,000 (52.6%) groffN + 1,000 (47.6%) 1,000 (52.6%) fN_ffN_fooN_F4_g5 + 100 (4.8%) -100 (-5.3%) basic1 + +-------------------------------------------------------------------------------- +-- Function:file summary +-------------------------------------------------------------------------------- + One__________________ Two__________________ function:file + +> 1,000 (47.6%, 47.6%) 1,000 (52.6%, 52.6%) groffN:aux/ann-diff2-basic.rs + +> 1,000 (47.6%, 95.2%) 1,000 (52.6%, 105.3%) fN_ffN_fooN_F4_g5:aux/ann-diff2-basic.rs + +> 100 (4.8%, 100.0%) -100 (-5.3%, 100.0%) basic1:aux/ann-diff2-basic.rs -------------------------------------------------------------------------------- -- Annotated source file: aux/ann-diff2-basic.rs @@ -35,7 +47,7 @@ This file was unreadable -------------------------------------------------------------------------------- -- Annotation summary -------------------------------------------------------------------------------- -One Two +One___________ Two___________ 0 0 annotated: files known & above threshold & readable, line numbers known 0 0 annotated: files known & above threshold & readable, line numbers unknown diff --git a/cachegrind/tests/ann-merge1.post.exp b/cachegrind/tests/ann-merge1.post.exp index 1d133d8d7..f12f1c235 100644 --- a/cachegrind/tests/ann-merge1.post.exp +++ b/cachegrind/tests/ann-merge1.post.exp @@ -1,13 +1,13 @@ -------------------------------------------------------------------------------- --- Cachegrind profile +-- Metadata -------------------------------------------------------------------------------- Description 1a Description 1b Command: Command 1 Data file: ann-merge1c.cgout -Events recorded: A B C -Events shown: A B C -Event sort order: A B C +Events recorded: A +Events shown: A +Event sort order: A Threshold: 0.1 Include dirs: Annotation: on @@ -15,51 +15,66 @@ Annotation: on -------------------------------------------------------------------------------- -- Summary -------------------------------------------------------------------------------- -A B C +A__________ -86 (100.0%) 113 (100.0%) 145 (100.0%) PROGRAM TOTALS +86 (100.0%) PROGRAM TOTALS -------------------------------------------------------------------------------- --- Function summary +-- File:function summary -------------------------------------------------------------------------------- -A B C file:function + A_________________ file:function -40 (46.5%) 80 (70.8%) 120 (82.8%) ann-merge-x.rs:x1 -20 (23.3%) 10 (8.8%) 5 (3.4%) ann-merge-x.rs:x3 -16 (18.6%) 18 (15.9%) 20 (13.8%) ann-merge-y.rs:y1 -10 (11.6%) 5 (4.4%) 0 ann-merge-x.rs:x2 +> 70 (81.4%, 81.4%) ann-merge-x.rs: + 40 (46.5%) x1 + 20 (23.3%) x3 + 10 (11.6%) x2 + +> 16 (18.6%, 100.0%) ann-merge-y.rs:y1 + +-------------------------------------------------------------------------------- +-- Function:file summary +-------------------------------------------------------------------------------- + A_________________ function:file + +> 40 (46.5%, 46.5%) x1:ann-merge-x.rs + +> 20 (23.3%, 69.8%) x3:ann-merge-x.rs + +> 16 (18.6%, 88.4%) y1:ann-merge-y.rs + +> 10 (11.6%, 100.0%) x2:ann-merge-x.rs -------------------------------------------------------------------------------- -- Annotated source file: ann-merge-x.rs -------------------------------------------------------------------------------- -A B C +A_________ -20 (23.3%) 40 (35.4%) 60 (41.4%) one -10 (11.6%) 20 (17.7%) 30 (20.7%) two -10 (11.6%) 20 (17.7%) 30 (20.7%) three -10 (11.6%) 5 (4.4%) 0 four -20 (23.3%) 10 (8.8%) 5 (3.4%) five +20 (23.3%) one +10 (11.6%) two +10 (11.6%) three +10 (11.6%) four +20 (23.3%) five -------------------------------------------------------------------------------- -- Annotated source file: ann-merge-y.rs -------------------------------------------------------------------------------- -A B C +A_______ -8 (9.3%) 9 (8.0%) 10 (6.9%) one -8 (9.3%) 9 (8.0%) 10 (6.9%) two -. . . three -. . . four -. . . five -. . . six +8 (9.3%) one +8 (9.3%) two +. three +. four +. five +. six -------------------------------------------------------------------------------- -- Annotation summary -------------------------------------------------------------------------------- -A B C +A__________ -86 (100.0%) 113 (100.0%) 145 (100.0%) annotated: files known & above threshold & readable, line numbers known - 0 0 0 annotated: files known & above threshold & readable, line numbers unknown - 0 0 0 unannotated: files known & above threshold & unreadable - 0 0 0 unannotated: files known & below threshold - 0 0 0 unannotated: files unknown +86 (100.0%) annotated: files known & above threshold & readable, line numbers known + 0 annotated: files known & above threshold & readable, line numbers unknown + 0 unannotated: files known & above threshold & unreadable + 0 unannotated: files known & below threshold + 0 unannotated: files unknown diff --git a/cachegrind/tests/ann-merge1a.cgout b/cachegrind/tests/ann-merge1a.cgout index d3d1aec29..fb6836cbb 100644 --- a/cachegrind/tests/ann-merge1a.cgout +++ b/cachegrind/tests/ann-merge1a.cgout @@ -1,19 +1,19 @@ desc: Description 1a desc: Description 1b cmd: Command 1 -events: A B C +events: A fl=ann-merge-x.rs fn=x1 -1 10 20 30 -2 10 20 30 +1 10 +2 10 fn=x2 -4 10 5 0 +4 10 fl=ann-merge-y.rs fn=y1 -1 8 9 10 -2 8 9 10 +1 8 +2 8 -summary: 46 63 80 +summary: 46 diff --git a/cachegrind/tests/ann-merge1b.cgout b/cachegrind/tests/ann-merge1b.cgout index 8552ce275..330beebcb 100644 --- a/cachegrind/tests/ann-merge1b.cgout +++ b/cachegrind/tests/ann-merge1b.cgout @@ -1,14 +1,14 @@ desc: Description 2a desc: Description 2b cmd: Command 2 -events: A B C +events: A fl=ann-merge-x.rs fn=x1 -1 10 20 30 -3 10 20 30 +1 10 +3 10 fn=x3 -5 20 10 5 +5 20 -summary: 40 50 65 +summary: 40 diff --git a/cachegrind/tests/ann1a.post.exp b/cachegrind/tests/ann1a.post.exp index e5a054e23..a83767cb0 100644 --- a/cachegrind/tests/ann1a.post.exp +++ b/cachegrind/tests/ann1a.post.exp @@ -1,5 +1,5 @@ -------------------------------------------------------------------------------- --- Cachegrind profile +-- Metadata -------------------------------------------------------------------------------- I1 cache: 32768 B, 64 B, 8-way associative D1 cache: 32768 B, 64 B, 8-way associative @@ -16,24 +16,61 @@ Annotation: on -------------------------------------------------------------------------------- -- Summary -------------------------------------------------------------------------------- -Ir I1mr ILmr +Ir_______ I1mr ILmr 5,229,753 952 931 PROGRAM TOTALS -------------------------------------------------------------------------------- --- Function summary +-- File:function summary -------------------------------------------------------------------------------- -Ir I1mr ILmr file:function + Ir_______ I1mr ILmr file:function -5,000,015 1 1 a.c:main - 47,993 19 19 /build/glibc-OTsEL5/glibc-2.27/elf/dl-lookup.c:do_lookup_x - 28,534 11 11 /build/glibc-OTsEL5/glibc-2.27/elf/dl-lookup.c:_dl_lookup_symbol_x - 28,136 7 7 /build/glibc-OTsEL5/glibc-2.27/elf/dl-tunables.c:__GI___tunables_init - 25,408 47 47 /build/glibc-OTsEL5/glibc-2.27/string/../sysdeps/x86_64/strcmp.S:strcmp - 21,821 23 23 /build/glibc-OTsEL5/glibc-2.27/elf/../sysdeps/x86_64/dl-machine.h:_dl_relocate_object - 11,521 15 15 /build/glibc-OTsEL5/glibc-2.27/elf/do-rel.h:_dl_relocate_object - 8,055 0 0 /build/glibc-OTsEL5/glibc-2.27/elf/dl-tunables.h:__GI___tunables_init - 6,898 2 2 /build/glibc-OTsEL5/glibc-2.27/elf/dl-misc.c:_dl_name_match_p +> 5,000,015 1 1 a.c:main + +> 76,688 32 32 /build/glibc-OTsEL5/glibc-2.27/elf/dl-lookup.c: + 47,993 19 19 do_lookup_x + 28,534 11 11 _dl_lookup_symbol_x + +> 28,391 11 9 /build/glibc-OTsEL5/glibc-2.27/elf/dl-tunables.c: + 28,136 7 7 __GI___tunables_init + +> 25,408 47 47 /build/glibc-OTsEL5/glibc-2.27/string/../sysdeps/x86_64/strcmp.S:strcmp + +> 22,214 25 25 /build/glibc-OTsEL5/glibc-2.27/elf/../sysdeps/x86_64/dl-machine.h: + 21,821 23 23 _dl_relocate_object + +> 11,817 16 16 /build/glibc-OTsEL5/glibc-2.27/elf/do-rel.h: + 11,521 15 15 _dl_relocate_object + +> 8,055 0 0 /build/glibc-OTsEL5/glibc-2.27/elf/dl-tunables.h:__GI___tunables_init + +> 6,939 5 5 /build/glibc-OTsEL5/glibc-2.27/elf/dl-misc.c: + 6,898 2 2 _dl_name_match_p + +-------------------------------------------------------------------------------- +-- Function:file summary +-------------------------------------------------------------------------------- + Ir_______ I1mr ILmr function:file + +> 5,000,015 1 1 main:a.c + +> 48,347 20 20 do_lookup_x: + 47,993 19 19 /build/glibc-OTsEL5/glibc-2.27/elf/dl-lookup.c + +> 36,191 7 7 __GI___tunables_init: + 28,136 7 7 /build/glibc-OTsEL5/glibc-2.27/elf/dl-tunables.c + 8,055 0 0 /build/glibc-OTsEL5/glibc-2.27/elf/dl-tunables.h + +> 34,576 51 51 _dl_relocate_object: + 21,821 23 23 /build/glibc-OTsEL5/glibc-2.27/elf/../sysdeps/x86_64/dl-machine.h + 11,521 15 15 /build/glibc-OTsEL5/glibc-2.27/elf/do-rel.h + +> 28,534 11 11 _dl_lookup_symbol_x:/build/glibc-OTsEL5/glibc-2.27/elf/dl-lookup.c + +> 25,426 48 48 strcmp: + 25,408 47 47 /build/glibc-OTsEL5/glibc-2.27/string/../sysdeps/x86_64/strcmp.S + +> 6,898 2 2 _dl_name_match_p:/build/glibc-OTsEL5/glibc-2.27/elf/dl-misc.c -------------------------------------------------------------------------------- -- Annotated source file: /build/glibc-OTsEL5/glibc-2.27/elf/../sysdeps/x86_64/dl-machine.h @@ -73,7 +110,7 @@ This file was unreadable -------------------------------------------------------------------------------- -- Annotated source file: a.c -------------------------------------------------------------------------------- -Ir I1mr ILmr +Ir_______ I1mr ILmr 2 0 0 int main(void) { 1 1 1 int z = 0; @@ -86,7 +123,7 @@ Ir I1mr ILmr -------------------------------------------------------------------------------- -- Annotation summary -------------------------------------------------------------------------------- -Ir I1mr ILmr +Ir_______ I1mr ILmr 5,000,015 1 1 annotated: files known & above threshold & readable, line numbers known 0 0 0 annotated: files known & above threshold & readable, line numbers unknown diff --git a/cachegrind/tests/ann1b.post.exp b/cachegrind/tests/ann1b.post.exp index 8c8af6c51..b76b4236f 100644 --- a/cachegrind/tests/ann1b.post.exp +++ b/cachegrind/tests/ann1b.post.exp @@ -1,5 +1,5 @@ -------------------------------------------------------------------------------- --- Cachegrind profile +-- Metadata -------------------------------------------------------------------------------- I1 cache: 32768 B, 64 B, 8-way associative D1 cache: 32768 B, 64 B, 8-way associative @@ -16,19 +16,47 @@ Annotation: off -------------------------------------------------------------------------------- -- Summary -------------------------------------------------------------------------------- -Dw Dr Ir +Dw_____________ Dr________________ Ir________________ 18,005 (100.0%) 4,057,955 (100.0%) 5,229,753 (100.0%) PROGRAM TOTALS -------------------------------------------------------------------------------- --- Function summary +-- File:function summary -------------------------------------------------------------------------------- -Dw Dr Ir file:function + Dw__________________ Dr______________________ Ir______________________ file:function - 3 (0.0%) 4,000,004 (98.6%) 5,000,015 (95.6%) a.c:main -4,543 (25.2%) 17,566 (0.4%) 47,993 (0.9%) /build/glibc-OTsEL5/glibc-2.27/elf/dl-lookup.c:do_lookup_x -3,083 (17.1%) 5,750 (0.1%) 28,534 (0.5%) /build/glibc-OTsEL5/glibc-2.27/elf/dl-lookup.c:_dl_lookup_symbol_x - 8 (0.0%) 5,521 (0.1%) 28,136 (0.5%) /build/glibc-OTsEL5/glibc-2.27/elf/dl-tunables.c:__GI___tunables_init -2,490 (13.8%) 5,219 (0.1%) 21,821 (0.4%) /build/glibc-OTsEL5/glibc-2.27/elf/../sysdeps/x86_64/dl-machine.h:_dl_relocate_object - 0 5,158 (0.1%) 25,408 (0.5%) /build/glibc-OTsEL5/glibc-2.27/string/../sysdeps/x86_64/strcmp.S:strcmp +> 3 (0.0%, 0.0%) 4,000,004 (98.6%, 98.6%) 5,000,015 (95.6%, 95.6%) a.c:main + +> 7,668 (42.6%, 42.6%) 23,365 (0.6%, 99.1%) 76,688 (1.5%, 97.1%) /build/glibc-OTsEL5/glibc-2.27/elf/dl-lookup.c: + 4,543 (25.2%) 17,566 (0.4%) 47,993 (0.9%) do_lookup_x + 3,083 (17.1%) 5,750 (0.1%) 28,534 (0.5%) _dl_lookup_symbol_x + +> 22 (0.1%, 42.7%) 5,577 (0.1%, 99.3%) 28,391 (0.5%, 97.6%) /build/glibc-OTsEL5/glibc-2.27/elf/dl-tunables.c: + 8 (0.0%) 5,521 (0.1%) 28,136 (0.5%) __GI___tunables_init + +> 2,542 (14.1%, 56.8%) 5,343 (0.1%, 99.4%) 22,214 (0.4%, 98.0%) /build/glibc-OTsEL5/glibc-2.27/elf/../sysdeps/x86_64/dl-machine.h: + 2,490 (13.8%) 5,219 (0.1%) 21,821 (0.4%) _dl_relocate_object + +> 0 (0.0%, 56.8%) 5,158 (0.1%, 99.5%) 25,408 (0.5%, 98.5%) /build/glibc-OTsEL5/glibc-2.27/string/../sysdeps/x86_64/strcmp.S:strcmp + +-------------------------------------------------------------------------------- +-- Function:file summary +-------------------------------------------------------------------------------- + Dw__________________ Dr______________________ Ir______________________ function:file + +> 3 (0.0%, 0.0%) 4,000,004 (98.6%, 98.6%) 5,000,015 (95.6%, 95.6%) main:a.c + +> 4,543 (25.2%, 25.2%) 17,684 (0.4%, 99.0%) 48,347 (0.9%, 96.5%) do_lookup_x: + 4,543 (25.2%) 17,566 (0.4%) 47,993 (0.9%) /build/glibc-OTsEL5/glibc-2.27/elf/dl-lookup.c + +> 3,010 (16.7%, 42.0%) 8,480 (0.2%, 99.2%) 34,576 (0.7%, 97.2%) _dl_relocate_object: + 2,490 (13.8%) 5,219 (0.1%) 21,821 (0.4%) /build/glibc-OTsEL5/glibc-2.27/elf/../sysdeps/x86_64/dl-machine.h + +> 8 (0.0%, 42.0%) 7,430 (0.2%, 99.4%) 36,191 (0.7%, 97.9%) __GI___tunables_init: + 8 (0.0%) 5,521 (0.1%) 28,136 (0.5%) /build/glibc-OTsEL5/glibc-2.27/elf/dl-tunables.c + +> 3,083 (17.1%, 59.1%) 5,750 (0.1%, 99.5%) 28,534 (0.5%, 98.4%) _dl_lookup_symbol_x:/build/glibc-OTsEL5/glibc-2.27/elf/dl-lookup.c + +> 0 (0.0%, 59.1%) 5,166 (0.1%, 99.7%) 25,426 (0.5%, 98.9%) strcmp: + 0 5,158 (0.1%) 25,408 (0.5%) /build/glibc-OTsEL5/glibc-2.27/string/../sysdeps/x86_64/strcmp.S diff --git a/cachegrind/tests/ann2.cgout b/cachegrind/tests/ann2.cgout index 2bb2bf8eb..b5d62b180 100644 --- a/cachegrind/tests/ann2.cgout +++ b/cachegrind/tests/ann2.cgout @@ -5,7 +5,7 @@ events: A SomeCount VeryLongEventName fl=ann2-basic.rs # This one has the counts to get the totals to 100,000/100,000/0. fn=f0 -7 70091 90291 0 +7 68081 90291 0 fn=f1 # Different whitespace. Mix of line 0 and other lines. 0 5000 0 0 @@ -45,24 +45,29 @@ fl=ann2-more-recent-than-cgout.rs fn=new 2 1000 0 0 -# File with negative and positive values. +# File with negative and positive values. Some values are very large (much +# bigger than in the summary). The file total is below the threshold, but it +# still is printed because the function CCs are above the threshold. Also, +# because the summary value for `ThisIsAVeryLongEventName` is zero, that events +# percentages here show up as "n/a". fl=ann2-negatives.rs # Various, and the sum is zero. fn=neg1 0 -1000 -1000 -1000 1 2000 2000 2000 2 -1000 -1000 0 -fn=neg2 -# Enormous numbers, but the sum is zero or almost zero. -# Also, because the summary value for `ThisIsAVeryLongEventName` is zero, the -# percentages here show up as zero. -5 999000 0 -150000 -6 -1000000 0 150000 +fn=neg2a +5 500000 0 -150000 +fn=neg2b +6 499999 0 0 fn=neg3 # Ditto. 0 -1000 0 10 -10 -10000 0 10 -11 10000 0 -20 +8 -1000000 0 150000 +10 -8000 0 10 +11 9000 0 -20 +fn=neg4 +13 11 0 0 # File with source newer than the cgout file. fl=ann2-past-the-end.rs diff --git a/cachegrind/tests/ann2.post.exp b/cachegrind/tests/ann2.post.exp index cc95f8281..5cfdbfd1d 100644 --- a/cachegrind/tests/ann2.post.exp +++ b/cachegrind/tests/ann2.post.exp @@ -1,5 +1,5 @@ -------------------------------------------------------------------------------- --- Cachegrind profile +-- Metadata -------------------------------------------------------------------------------- Command: ann2 Data file: ann2.cgout @@ -15,55 +15,94 @@ Annotation: on -------------------------------------------------------------------------------- -- Summary -------------------------------------------------------------------------------- -A SomeCount VeryLongEventName +A_______________ SomeCount_______ VeryLongEventName -100,000 (100.0%) 100,000 (100.0%) 0 PROGRAM TOTALS +100,000 (100.0%) 100,000 (100.0%) 0 PROGRAM TOTALS -------------------------------------------------------------------------------- --- Function summary +-- File:function summary -------------------------------------------------------------------------------- -A SomeCount VeryLongEventName file:function + A___________________________ SomeCount____________ VeryLongEventName__ file:function -70,091 (70.1%) 90,291 (90.3%) 0 ann2-basic.rs:f0 -15,000 (15.0%) 600 (0.6%) 0 ann2-basic.rs:f1 - 9,000 (9.0%) 6,000 (6.0%) 0 ann2-could-not-be-found.rs:f1 - 2,000 (2.0%) 100 (0.1%) 0 ann2-basic.rs:f2 - 1,000 (1.0%) 500 (0.5%) 0 ann2-via-I.rs: - 1,000 (1.0%) 300 (0.3%) -1,000 (n/a) ann2-past-the-end.rs:f1 --1,000 (-1.0%) 0 0 ann2-negatives.rs:neg3 --1,000 (-1.0%) 0 0 ann2-negatives.rs:neg2 - 1,000 (1.0%) 0 0 ann2-more-recent-than-cgout.rs:new - 1,000 (1.0%) 0 0 ???:unknown - 500 (0.5%) 0 0 ann2-basic.rs:f6 - 500 (0.5%) 0 0 ann2-basic.rs:f4 +> 86,590 (86.6%, 86.6%) 93,000 (93.0%, 93.0%) 0 (n/a, n/a) ann2-basic.rs: + 68,081 (68.1%) 90,291 (90.3%) 0 f0 + 15,000 (15.0%) 600 (0.6%) 0 f1 + 2,000 (2.0%) 100 (0.1%) 0 f2 + 500 (0.5%) 0 0 f6 + 500 (0.5%) 0 0 f4 + +> 9,000 (9.0%, 95.6%) 6,000 (6.0%, 99.0%) 0 (n/a, n/a) ann2-could-not-be-found.rs:f1 + +> 1,000 (1.0%, 96.6%) 500 (0.5%, 99.5%) 0 (n/a, n/a) ann2-via-I.rs: + +> 1,000 (1.0%, 97.6%) 300 (0.3%, 99.8%) -1,000 (n/a, n/a) ann2-past-the-end.rs:f1 + +> 1,000 (1.0%, 98.6%) 0 (0.0%, 99.8%) 0 (n/a, n/a) ann2-more-recent-than-cgout.rs:new + +> 1,000 (1.0%, 99.6%) 0 (0.0%, 99.8%) 0 (n/a, n/a) ???:unknown + +> 10 (0.0%, 99.6%) 0 (0.0%, 99.8%) 1,000 (n/a, n/a) ann2-negatives.rs: + -1,000,000 (-1000.0%) 0 150,000 (n/a) neg3 + 500,000 (500.0%) 0 -150,000 (n/a) neg2a + 499,999 (500.0%) 0 0 neg2b + +-------------------------------------------------------------------------------- +-- Function:file summary +-------------------------------------------------------------------------------- + A______________________________ SomeCount____________ VeryLongEventName__ function:file + +> -1,000,000 (-1000.0%, -1000.0%) 0 (0.0%, 0.0%) 150,000 (n/a, n/a) neg3:ann2-negatives.rs + +> 500,000 (500.0%, -500.0%) 0 (0.0%, 0.0%) -150,000 (n/a, n/a) neg2a:ann2-negatives.rs + +> 499,999 (500.0%, -0.0%) 0 (0.0%, 0.0%) 0 (n/a, n/a) neg2b:ann2-negatives.rs + +> 68,081 (68.1%, 68.1%) 90,291 (90.3%, 90.3%) 0 (n/a, n/a) f0:ann2-basic.rs + +> 25,000 (25.0%, 93.1%) 6,900 (6.9%, 97.2%) -1,000 (n/a, n/a) f1: + 15,000 (15.0%) 600 (0.6%) 0 ann2-basic.rs + 9,000 (9.0%) 6,000 (6.0%) 0 ann2-could-not-be-found.rs + 1,000 (1.0%) 300 (0.3%) -1,000 (n/a) ann2-past-the-end.rs + +> 2,000 (2.0%, 95.1%) 100 (0.1%, 97.3%) 0 (n/a, n/a) f2:ann2-basic.rs + +> 1,000 (1.0%, 96.1%) 500 (0.5%, 97.8%) 0 (n/a, n/a) :ann2-via-I.rs + +> 1,000 (1.0%, 97.1%) 0 (0.0%, 97.8%) 0 (n/a, n/a) unknown:??? + +> 1,000 (1.0%, 98.1%) 0 (0.0%, 97.8%) 0 (n/a, n/a) new:ann2-more-recent-than-cgout.rs + +> 500 (0.5%, 98.6%) 0 (0.0%, 97.8%) 0 (n/a, n/a) f6:ann2-basic.rs + +> 500 (0.5%, 99.1%) 0 (0.0%, 97.8%) 0 (n/a, n/a) f4:ann2-basic.rs -------------------------------------------------------------------------------- -- Annotated source file: ann2-basic.rs -------------------------------------------------------------------------------- -A SomeCount VeryLongEventName +A_____________ SomeCount_____ VeryLongEventName - 7,100 (7.1%) 100 (0.1%) 0 + 7,100 (7.1%) 100 (0.1%) 0 -- line 2 ---------------------------------------- - . . . two - . . . three - 5,000 (5.0%) 500 (0.5%) 0 four - 5,000 (5.0%) 100 (0.1%) 0 five - . . . six -70,091 (70.1%) 90,291 (90.3%) 0 seven - . . . eight - 110 (0.1%) 9 (0.0%) 0 nine - . . . ten - . . . eleven - 200 (0.2%) 0 0 twelve - 200 (0.2%) 0 0 thirteen - 100 (0.1%) 0 0 fourteen - 0 0 0 fifteen - 0 0 0 sixteen - 0 0 0 seventeen - 0 0 0 eighteen - 499 (0.5%) 2,000 (2.0%) 0 nineteen - 300 (0.3%) 0 0 twenty + . . . two + . . . three + 5,000 (5.0%) 500 (0.5%) 0 four + 5,000 (5.0%) 100 (0.1%) 0 five + . . . six +68,081 (68.1%) 90,291 (90.3%) 0 seven + . . . eight + 110 (0.1%) 9 (0.0%) 0 nine + . . . ten + . . . eleven + 200 (0.2%) 0 0 twelve + 200 (0.2%) 0 0 thirteen + 100 (0.1%) 0 0 fourteen + 0 0 0 fifteen + 0 0 0 sixteen + 0 0 0 seventeen + 0 0 0 eighteen + 499 (0.5%) 2,000 (2.0%) 0 nineteen + 300 (0.3%) 0 0 twenty -------------------------------------------------------------------------------- -- Annotated source file: ann2-could-not-be-found.rs @@ -80,7 +119,7 @@ This file was unreadable @ Annotations may not be correct. @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -A SomeCount VeryLongEventName +A___________ SomeCount VeryLongEventName . . . one 1,000 (1.0%) 0 0 two @@ -91,38 +130,40 @@ A SomeCount VeryLongEventName -------------------------------------------------------------------------------- -- Annotated source file: ann2-negatives.rs -------------------------------------------------------------------------------- -A SomeCount VeryLongEventName +A____________________ SomeCount_____ VeryLongEventName - -2,000 (-2.0%) -1,000 (-1.0%) -990 (n/a) + -2,000 (-2.0%) -1,000 (-1.0%) -990 (n/a) - 2,000 (2.0%) 2,000 (2.0%) 2,000 (n/a) one - -1,000 (-1.0%) -1,000 (-1.0%) 0 two - . . . three - . . . four - 999,000 (999.0%) 0 -150,000 (n/a) five --1,000,000 (-1000.0%) 0 150,000 (n/a) six - . . . seven - . . . eight - . . . nine - -10,000 (-10.0%) 0 10 (n/a) ten - 10,000 (10.0%) 0 -20 (n/a) eleven - . . . twelve - . . . thirteen --- line 13 ---------------------------------------- + 2,000 (2.0%) 2,000 (2.0%) 2,000 (n/a) one + -1,000 (-1.0%) -1,000 (-1.0%) 0 two + . . . three + . . . four + 500,000 (500.0%) 0 -150,000 (n/a) five + 499,999 (500.0%) 0 0 six + . . . seven +-1,000,000 (-1000.0%) 0 150,000 (n/a) eight + . . . nine + -8,000 (-8.0%) 0 10 (n/a) ten + 9,000 (9.0%) 0 -20 (n/a) eleven + . . . twelve + 11 (0.0%) 0 0 thirteen + . . . fourteen + . . . fifteen +-- line 15 ---------------------------------------- -------------------------------------------------------------------------------- -- Annotated source file: ann2-past-the-end.rs -------------------------------------------------------------------------------- -A SomeCount VeryLongEventName +A_________ SomeCount_ VeryLongEventName -200 (0.2%) 100 (0.1%) 0 one - . . . two - . . . three +200 (0.2%) 100 (0.1%) 0 one + . . . two + . . . three -- line 3 ---------------------------------------- -300 (0.3%) 100 (0.1%) 0 -300 (0.3%) 100 (0.1%) 0 -200 (0.2%) 0 -1,000 (n/a) +300 (0.3%) 100 (0.1%) 0 +300 (0.3%) 100 (0.1%) 0 +200 (0.2%) 0 -1,000 (n/a) @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@ WARNING @@ WARNING @@ WARNING @@ WARNING @@ WARNING @@ WARNING @@ WARNING @@ @@ -133,18 +174,18 @@ A SomeCount VeryLongEventName -------------------------------------------------------------------------------- -- Annotated source file: ann2-aux/ann2-via-I.rs -------------------------------------------------------------------------------- -A SomeCount VeryLongEventName +A___________ SomeCount_ VeryLongEventName -1,000 (1.0%) 500 (0.5%) 0 one +1,000 (1.0%) 500 (0.5%) 0 one -------------------------------------------------------------------------------- -- Annotation summary -------------------------------------------------------------------------------- -A SomeCount VeryLongEventName +A_____________ SomeCount_____ VeryLongEventName -84,500 (84.5%) 94,700 (94.7%) 990 (n/a) annotated: files known & above threshold & readable, line numbers known - 5,100 (5.1%) -900 (-0.9%) -990 (n/a) annotated: files known & above threshold & readable, line numbers unknown - 9,000 (9.0%) 6,000 (6.0%) 0 unannotated: files known & above threshold & unreadable - 400 (0.4%) 200 (0.2%) 0 unannotated: files known & below threshold - 1,000 (1.0%) 0 0 unannotated: files unknown +84,500 (84.5%) 94,700 (94.7%) 990 (n/a) annotated: files known & above threshold & readable, line numbers known + 5,100 (5.1%) -900 (-0.9%) -990 (n/a) annotated: files known & above threshold & readable, line numbers unknown + 9,000 (9.0%) 6,000 (6.0%) 0 unannotated: files known & above threshold & unreadable + 400 (0.4%) 200 (0.2%) 0 unannotated: files known & below threshold + 1,000 (1.0%) 0 0 unannotated: files unknown