[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,
#
# 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
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
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("-", "_")
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,
# 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]
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
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]
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")
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, "<unspecified>"))
+ fn = "<unspecified>"
+ 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.
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"
)
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:
# 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:
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])
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:
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()
line0_cc = dict_line_cc.pop(0, None)
if line0_cc:
suffix = "<unknown (line 0)>"
- 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
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])
if line_nums:
print()
for line_num in line_nums:
- printer.print_cc(dict_line_cc[line_num], f"<bogus line {line_num}>")
- annotated_ccs.line_nums_known_cc += dict_line_cc[line_num]
+ printer.print_cc(
+ dict_line_cc[line_num], None, f"<bogus line {line_num}>"
+ )
+ add_cc_to_cc(dict_line_cc[line_num], annotated_ccs.line_nums_known_cc)
print()
warn_bogus_lines(src_file.name)
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("???")
annotated_ccs,
summary_cc,
)
-
readable = True
break
except OSError:
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()
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
#
# 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`.
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
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
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.
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
# diffs anyway. It just means we get "This file was unreadable"
# for modified filenames rather than a single "<unknown (line
# 0)>" 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.
# 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"
# 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}")
# 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__":
#
# 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`.
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]
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
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.
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
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.
# 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
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:
--------------------------------------------------------------------------------
--- Cachegrind profile
+-- Metadata
--------------------------------------------------------------------------------
Files compared: ann1.cgout; ann1b.cgout
Command: ./a.out; ./a.out
--------------------------------------------------------------------------------
-- 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 <unknown (line 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
--------------------------------------------------------------------------------
--- Cachegrind profile
+-- Metadata
--------------------------------------------------------------------------------
Files compared: ann-diff2a.cgout; ann-diff2b.cgout
Command: cmd1; cmd2
--------------------------------------------------------------------------------
-- 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
--------------------------------------------------------------------------------
-- 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
--------------------------------------------------------------------------------
--- 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
--------------------------------------------------------------------------------
-- 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
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
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
--------------------------------------------------------------------------------
--- Cachegrind profile
+-- Metadata
--------------------------------------------------------------------------------
I1 cache: 32768 B, 64 B, 8-way associative
D1 cache: 32768 B, 64 B, 8-way associative
--------------------------------------------------------------------------------
-- 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
--------------------------------------------------------------------------------
-- Annotated source file: a.c
--------------------------------------------------------------------------------
-Ir I1mr ILmr
+Ir_______ I1mr ILmr
2 0 0 int main(void) {
1 1 1 int z = 0;
--------------------------------------------------------------------------------
-- 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
--------------------------------------------------------------------------------
--- Cachegrind profile
+-- Metadata
--------------------------------------------------------------------------------
I1 cache: 32768 B, 64 B, 8-way associative
D1 cache: 32768 B, 64 B, 8-way associative
--------------------------------------------------------------------------------
-- 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
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
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
--------------------------------------------------------------------------------
--- Cachegrind profile
+-- Metadata
--------------------------------------------------------------------------------
Command: ann2
Data file: ann2.cgout
--------------------------------------------------------------------------------
-- 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:<unspecified>
- 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:<unspecified>
+
+> 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) <unspecified>: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 <unknown (line 0)>
+ 7,100 (7.1%) 100 (0.1%) 0 <unknown (line 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
@ Annotations may not be correct.
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
-A SomeCount VeryLongEventName
+A___________ SomeCount VeryLongEventName
. . . one
1,000 (1.0%) 0 0 two
--------------------------------------------------------------------------------
-- Annotated source file: ann2-negatives.rs
--------------------------------------------------------------------------------
-A SomeCount VeryLongEventName
+A____________________ SomeCount_____ VeryLongEventName
- -2,000 (-2.0%) -1,000 (-1.0%) -990 (n/a) <unknown (line 0)>
+ -2,000 (-2.0%) -1,000 (-1.0%) -990 (n/a) <unknown (line 0)>
- 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 <bogus line 20>
-300 (0.3%) 100 (0.1%) 0 <bogus line 21>
-200 (0.2%) 0 -1,000 (n/a) <bogus line 22>
+300 (0.3%) 100 (0.1%) 0 <bogus line 20>
+300 (0.3%) 100 (0.1%) 0 <bogus line 21>
+200 (0.2%) 0 -1,000 (n/a) <bogus line 22>
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@ WARNING @@ WARNING @@ WARNING @@ WARNING @@ WARNING @@ WARNING @@ WARNING @@
--------------------------------------------------------------------------------
-- 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