From: Nicholas Nethercote Date: Tue, 28 Mar 2023 22:55:51 +0000 (+1100) Subject: Overhaul `cg_annotate` output. X-Git-Tag: VALGRIND_3_21_0~67 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8765b3358faae875684c6f3b48e3af103162fe36;p=thirdparty%2Fvalgrind.git Overhaul `cg_annotate` output. Most notable, the "Function summary" section, which printed one CC for each `file:function` combination, has been replaced by two sections, "File:function summary" and "Function:file summary". These new sections both feature "deep CCs", which have an "outer CC" for the file (or function), and one or more "inner CCs" for the paired functions (or files). Here is a file:function example, which helps show which files have a lot of events, even if those events are spread across a lot of functions. ``` > 12,427,830 (5.4%, 26.3%) /home/njn/moz/gecko-dev/js/src/ds/LifoAlloc.h: 6,107,862 (2.7%) js::frontend::ParseNodeVerifier::visit(js::frontend::ParseNode*) 3,685,203 (1.6%) js::detail::BumpChunk::setBump(unsigned char*) 1,640,591 (0.7%) js::LifoAlloc::alloc(unsigned long) 711,008 (0.3%) js::detail::BumpChunk::assertInvariants() ``` And here is a function:file example, which shows how heavy inlining can result in a machine code function being derived from source code from multiple files: ``` > 1,343,736 (0.6%, 35.6%) js::gc::TenuredCell::isMarkedGray() const: 651,108 (0.3%) /home/njn/moz/gecko-dev/js/src/d64/dist/include/js/HeapAPI.h 292,672 (0.1%) /home/njn/moz/gecko-dev/js/src/gc/Cell.h 254,854 (0.1%) /home/njn/moz/gecko-dev/js/src/gc/Heap.h ``` Previously these patterns were very hard to find, and it was easy to overlook a hot piece of code because its counts were spread across multiple non-adjacent entries. I have already found these changes very useful for profiling Rust code. Also, cumulative percentages on the outer CCs (e.g. the 26.3% and 35.6% in the example) tell you what fraction of all events are covered by the entries so far, something I've wanted for a long time. Some other, related changes: - Column event headers are now padded with `_`, e.g. `Ir__________`. This makes the column/event mapping clearer. - The "Cachegrind profile" section is now called "Metadata", which is shorter and clearer. - A few minor test tweaks, beyond those required for the output changes. - I converted some doc comments to normal comments. Not standard Python, but nicer to read, and there are no public APIs here. - Roughly 2x speedups to `cg_annotate` and smaller improvements for `cg_diff` and `cg_merge`, due to the following. - Change the `Cc` class to a type alias for `list[int]`, to avoid the class overhead (sigh). - Process event count lines in a single split, instead of a regex match + split. - Add the `add_cc_to_ccs` function, which does multiple CC additions in a single function call. - Better handling of dicts while reading input, minimizing lookups. - Pre-computing the missing CC string for each CcPrinter, instead of regenerating it each time. --- diff --git a/auxprogs/pylintrc b/auxprogs/pylintrc index 8f51d2d686..2d26cf00d2 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 5dad969f6a..0b68e094c3 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)) + + +# 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] + +# 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 + + +# 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 -class Cc: - """ - This is a dumb container for counts. +# 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 - 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] +# 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, counts: list[int]) -> None: - self.counts = counts + 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 - 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 +# 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 __iadd__(self, other: Cc) -> Cc: - for i, other_count in enumerate(other.counts): - self.counts[i] += other_count - return self + 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 -# A paired filename and function name. -Flfn = NewType("Flfn", tuple[str, str]) +# Per-file/function CCs. The list version is used during filtering and sorting. +DictNameCc = DefaultDict[str, Cc] +ListNameCc = list[tuple[str, Cc]] -# Per-function CCs. -DictFlfnCc = DefaultDict[Flfn, 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,91 +486,165 @@ 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 widths of each event column. For simplicity, its length matches + # `events.events`, even though not all events are necessarily shown. + widths: list[Width] - # The width of each percentage column. Zero if --show-percs is disabled. - # Its length matches `count_widths`. - perc_widths: list[int] + # Text of a missing CC, which can be computed in advance. + missing_cc_str: str - def __init__(self, events: Events, ccs: list[Cc], summary_cc: Cc) -> None: + # 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 - - # 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) - - # 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)) + update_cc_extremes(cc, min_cc, max_cc) + + self.init_widths(min_cc, max_cc, None, None) + + 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)), + ) - def print_events(self, suffix: str) -> None: + self.missing_cc_str = "" 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="") - - 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 + 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: - if cc.counts[i] == 0: - # Don't show percentages for "0" entries, it's just clutter. - perc = "" + 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: - summary_count = self.summary_cc.counts[i] + # An outer CC, with two percentages. if summary_count == 0: - perc = " (n/a)" + # Avoid dividing by zero. + perc1 = " (n/a," + perc2 = " n/a)" else: - p = cc.counts[i] * 100 / summary_count - perc = f" ({p:.1f}%)" + perc1 = f" ({cc1[i] * 100 / summary_count:.1f}%," + perc2 = f" {cc2[i] * 100 / summary_count:.1f}%)" else: - perc = "" + perc1 = "" + perc2 = "" + + return (count, perc1, perc2) - return (count, perc) + 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_cc(self, cc: Cc, suffix: str) -> None: + def print_events(self, suffix: str) -> None: + print(self.events_prefix, end="") for i in self.events.show_indices: - (count, perc) = self.count_and_perc(cc, i) - self.print_count_and_perc(i, count, perc) + event = self.events.events[i] + 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) + print(suffix) - def print_missing_cc(self, suffix: str) -> None: - # Don't show percentages for "." entries, it's just clutter. + 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: + # 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() + + # 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: - self.print_count_and_perc(i, ".", "") + (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: + print(self.missing_cc_str, suffix) + # Used in various places in the output. def print_fancy(text: str) -> None: @@ -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 - - def meets_threshold(flfn_and_cc: tuple[Flfn, Cc]) -> bool: - cc = flfn_and_cc[1] - return abs(cc.counts[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]) - - # 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)) - - printer = CcPrinter(events, sorted_ccs, summary_cc) - print_fancy("Function summary") - printer.print_events(" file:function") - print() + threshold = args.threshold * abs(summary_cc[threshold_index]) / 100 + + 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 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, + ) + + # 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) + + # 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 - # Print per-function counts. - for flfn, flfn_cc in sorted_flfns_and_ccs: - printer.print_cc(flfn_cc, f"{flfn[0]}:{flfn[1]}") + 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() - # Files containing a function that met the threshold. - return set(flfn_and_cc[0][0] for flfn_and_cc in sorted_flfns_and_ccs) + # 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) + + 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 bae0c7abe4..38910f31b1 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) - - -class Cc: - """ - This is a dumb container for counts. - - 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. - """ + return [0] * self.num_events - # 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) +# 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] - def __eq__(self, other: object) -> bool: - if not isinstance(other, Cc): - return NotImplemented - return self.counts == other.counts +# 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 - 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 fca73439eb..8304e8b279 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) - - -class Cc: - """ - This is a dumb container for counts. - - 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. - """ + return [0] * self.num_events - # 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) +# 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] - 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 cbb7f8448c..4e13f0c089 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 4c1c46d7bb..bcf09ea9ca 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 1d133d8d7d..f12f1c235d 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 d3d1aec29b..fb6836cbb3 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 8552ce275c..330beebcb7 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 e5a054e233..a83767cb0a 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 8c8af6c51a..b76b4236f2 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 2bb2bf8eb8..b5d62b1806 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 cc95f82814..5cfdbfd1df 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