And deprecate the use of `cg_diff` and `cg_merge`.
Because `cg_annotate` can do a better job, even annotating source files
when doing diffs in some cases.
The user requests merging by passing multiple cgout files to
`cg_annotate`, and diffing by passing two cgout files to `cg_annotate`
along with `--diff`.
from __future__ import annotations
+import filecmp
import os
import re
import sys
from argparse import ArgumentParser, BooleanOptionalAction, Namespace
from collections import defaultdict
-from typing import DefaultDict, NoReturn, TextIO
+from typing import Callable, DefaultDict, NoReturn, TextIO
+def die(msg: str) -> NoReturn:
+ print("cg_annotate: error:", msg, file=sys.stderr)
+ sys.exit(1)
+
+
+SearchAndReplace = Callable[[str], str]
+
# A typed wrapper for parsed args.
class Args(Namespace):
# None of these fields are modified after arg parsing finishes.
+ diff: bool
+ mod_filename: SearchAndReplace
+ mod_funcname: SearchAndReplace
show: list[str]
sort: list[str]
threshold: float # a percentage
@staticmethod
def parse() -> Args:
+ # We support Perl-style `s/old/new/flags` search-and-replace
+ # expressions, because that's how this option was implemented in the
+ # old Perl version of `cg_diff`. This requires conversion from
+ # `s/old/new/` style to `re.sub`. The conversion isn't a perfect
+ # emulation of Perl regexps (e.g. Python uses `\1` rather than `$1` for
+ # using captures in the `new` part), but it should be close enough. The
+ # only supported flags are `g` (global) and `i` (ignore case).
+ def search_and_replace(regex: str | None) -> SearchAndReplace:
+ if regex is None:
+ return lambda s: s
+
+ # Extract the parts of an `s/old/new/tail` regex. `(?<!\\)/` is an
+ # example of negative lookbehind. It means "match a forward slash
+ # unless preceded by a backslash".
+ m = re.match(r"s/(.*)(?<!\\)/(.*)(?<!\\)/(g|i|gi|ig|)$", regex)
+ if m is None:
+ raise ValueError
+
+ # Forward slashes must be escaped in an `s/old/new/` expression,
+ # but we then must unescape them before using them with `re.sub`.
+ pat = m.group(1).replace(r"\/", r"/")
+ repl = m.group(2).replace(r"\/", r"/")
+ tail = m.group(3)
+
+ if "g" in tail:
+ count = 0 # unlimited
+ else:
+ count = 1
+
+ if "i" in tail:
+ flags = re.IGNORECASE
+ else:
+ flags = re.RegexFlag(0)
+
+ return lambda s: re.sub(re.compile(pat, flags=flags), repl, s, count=count)
+
def comma_separated_list(values: str) -> list[str]:
return values.split(",")
help=f"(deprecated) same as --no-{new_name}",
)
- p = ArgumentParser(description="Process a Cachegrind output file.")
+ p = ArgumentParser(description="Process one or more Cachegrind output files.")
p.add_argument("--version", action="version", version="%(prog)s-@VERSION@")
+ p.add_argument(
+ "--diff",
+ default=False,
+ action="store_true",
+ help="perform a diff between two Cachegrind output files",
+ )
+ p.add_argument(
+ "--mod-filename",
+ type=search_and_replace,
+ metavar="REGEX",
+ default=search_and_replace(None),
+ help="a search-and-replace regex applied to filenames, e.g. "
+ "`s/prog[0-9]/progN/`",
+ )
+ p.add_argument(
+ "--mod-funcname",
+ type=search_and_replace,
+ metavar="REGEX",
+ default=search_and_replace(None),
+ help="like --mod-filename, but for function names",
+ )
p.add_argument(
"--show",
type=comma_separated_list,
)
p.add_argument(
"cgout_filename",
- nargs=1,
+ nargs="+",
metavar="cachegrind-out-file",
help="file produced by Cachegrind",
)
- return p.parse_args(namespace=Args())
+ # `args0` name used to avoid shadowing the global `args`, which pylint
+ # doesn't like.
+ args0 = p.parse_args(namespace=Args())
+ if args0.diff and len(args0.cgout_filename) != 2:
+ p.print_usage(file=sys.stderr)
+ die("argument --diff: requires exactly two Cachegrind output files")
+
+ return args0
# Args are stored in a global for easy access.
# Like `sort_events`, but indices into `events`, rather than names.
sort_indices: list[int]
- def __init__(self, text: str) -> None:
+ def __init__(self) -> None:
+ # All fields are left uninitialized here, and set instead in `init`.
+ pass
+
+ def init(self, text: str) -> None:
self.events = text.split()
self.num_events = len(self.events)
b_cc[i] += a_count
+# 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
+
+
# 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
+ a_cc: Cc, b_cc1: Cc, b_cc2: Cc, b_cc3: Cc, b_cc4: Cc, b_cc5: Cc, total_cc: Cc
) -> None:
for i, a_count in enumerate(a_cc):
b_cc1[i] += a_count
b_cc3[i] += a_count
b_cc4[i] += a_count
b_cc5[i] += a_count
+ total_cc[i] += a_count
+
+
+# Unrolled version of `sub_cc_from_cc`, for speed. Note that the last one,
+# `total_cc`, is added.
+def sub_cc_from_ccs(
+ a_cc: Cc, b_cc1: Cc, b_cc2: Cc, b_cc3: Cc, b_cc4: Cc, b_cc5: Cc, total_cc: 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
+ total_cc[i] += a_count
# Update `min_cc` and `max_cc` with `self`.
min_cc[i] = count
-# A deep cost centre with a dict for the inner names and CCs.
+# Note: some abbrevations used below:
+# - Ofl/ofl: original filename, as mentioned in a cgout file.
+# - Ofn/ofn: original function name, as mentioned in a cgout file.
+# - Mfl/mfl: modified filename, the result of passing an Ofl through
+# `--mod-filename`.
+# - Mfn/mfn: modified function name, the result of passing an Ofn through
+# `--mod-funcname`.
+# - Mname/mname: modified name, used for what could be an Mfl or an Mfn.
+
+# A deep cost centre with a dict for the inner mnames and CCs.
class Dcc:
outer_cc: Cc
- inner_dict_name_cc: DictNameCc
+ inner_dict_mname_cc: DictMnameCc
- def __init__(self, outer_cc: Cc, inner_dict_name_cc: DictNameCc) -> None:
+ def __init__(self, outer_cc: Cc, inner_dict_mname_cc: DictMnameCc) -> None:
self.outer_cc = outer_cc
- self.inner_dict_name_cc = inner_dict_name_cc
+ self.inner_dict_mname_cc = inner_dict_mname_cc
-# A deep cost centre with a list for the inner names and CCs. Used during
+# A deep cost centre with a list for the inner mnames and CCs. Used during
# filtering and sorting.
class Lcc:
outer_cc: Cc
- inner_list_name_cc: ListNameCc
+ inner_list_mname_cc: ListMnameCc
- def __init__(self, outer_cc: Cc, inner_list_name_cc: ListNameCc) -> None:
+ def __init__(self, outer_cc: Cc, inner_list_mname_cc: ListMnameCc) -> None:
self.outer_cc = outer_cc
- self.inner_list_name_cc = inner_list_name_cc
+ self.inner_list_mname_cc = inner_list_mname_cc
-# Per-file/function CCs. The list version is used during filtering and sorting.
-DictNameCc = DefaultDict[str, Cc]
-ListNameCc = list[tuple[str, Cc]]
+# Per-Mfl/Mfn CCs. The list version is used during filtering and sorting.
+DictMnameCc = DefaultDict[str, Cc]
+ListMnameCc = list[tuple[str, Cc]]
-# Per-file/function DCCs. The outer names are filenames and the inner names are
-# function names, or vice versa. The list version is used during filtering and
-# sorting.
-DictNameDcc = DefaultDict[str, Dcc]
-ListNameLcc = list[tuple[str, Lcc]]
+# Per-Mfl/Mfn DCCs. The outer Mnames are Mfls and the inner Mnames are Mfns, or
+# vice versa. The list version is used during filtering and sorting.
+DictMnameDcc = DefaultDict[str, Dcc]
+ListMnameLcc = list[tuple[str, Lcc]]
-# Per-line CCs, organised by filename and line number.
+# Per-line CCs, organised by Mfl and line number.
DictLineCc = DefaultDict[int, Cc]
-DictFlDictLineCc = DefaultDict[str, DictLineCc]
+DictMflDictLineCc = DefaultDict[str, DictLineCc]
-
-def die(msg: str) -> NoReturn:
- print("cg_annotate: error:", msg, file=sys.stderr)
- sys.exit(1)
+# A dictionary tracking how Ofls get mapped to Mfls by `--mod-filename`. If
+# `--mod-filename` isn't used, each entry will be the identity mapping: ("foo"
+# -> set(["foo"])).
+DictMflOfls = DefaultDict[str, set[str]]
-def read_cgout_file() -> tuple[
- str,
- str,
- Events,
- DictNameDcc,
- DictNameDcc,
- DictFlDictLineCc,
- Cc,
-]:
+def read_cgout_file(
+ cgout_filename: str,
+ is_first_file: bool,
+ descs: list[str],
+ cmds: list[str],
+ events: Events,
+ dict_mfl_ofls: DictMflOfls,
+ dict_mfl_dcc: DictMnameDcc,
+ dict_mfn_dcc: DictMnameDcc,
+ dict_mfl_dict_line_cc: DictMflDictLineCc,
+ summary_cc: Cc,
+) -> None:
# The file format is described in Cachegrind's manual.
try:
- cgout_file = open(args.cgout_filename[0], "r", encoding="utf-8")
+ cgout_file = open(cgout_filename, "r", encoding="utf-8")
except OSError as err:
die(f"{err}")
desc += m.group(1) + "\n"
else:
break
+ descs.append(desc)
# Read "cmd:" line. (`line` is already set from the "desc:" loop.)
if m := re.match(r"cmd:\s+(.*)", line):
- cmd = m.group(1)
+ cmds.append(m.group(1))
else:
parse_die("missing a `command:` line")
# Read "events:" line.
line = readline()
if m := re.match(r"events:\s+(.*)", line):
- events = Events(m.group(1))
+ if is_first_file:
+ events.init(m.group(1))
+ else:
+ events2 = Events()
+ events2.init(m.group(1))
+ if events.events != events2.events:
+ die("events in data files don't match")
else:
parse_die("missing an `events:` line")
def mk_empty_dict_line_cc() -> DictLineCc:
return defaultdict(events.mk_empty_cc)
- # The current filename and function name.
- fl = ""
- fn = ""
-
- # Different places where we accumulate CC data.
- 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
+ # The current Mfl and Mfn.
+ mfl = ""
+ mfn = ""
+
+ # These values are passed in by reference and are modified by this
+ # function. But they can't be properly initialized until the `events:`
+ # line of the first file is read and the number of events is known. So
+ # we initialize them in an invalid state in `main`, and then
+ # reinitialize them properly here, before their first use.
+ if is_first_file:
+ dict_mfl_dcc.default_factory = events.mk_empty_dcc
+ dict_mfn_dcc.default_factory = events.mk_empty_dcc
+ dict_mfl_dict_line_cc.default_factory = mk_empty_dict_line_cc
+ summary_cc.extend(events.mk_empty_cc())
# 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()
+ mfl_dcc = events.mk_empty_dcc()
+ mfn_dcc = events.mk_empty_dcc()
+ mfl_dcc_inner_mfn_cc = events.mk_empty_cc()
+ mfn_dcc_inner_mfl_cc = events.mk_empty_cc()
dict_line_cc = mk_empty_dict_line_cc()
+ total_cc = events.mk_empty_cc()
+
+ # When diffing, we negate the first cgout file's counts to effectively
+ # achieve `cgout2 - cgout1`.
+ if args.diff and is_first_file:
+ combine_cc_with_cc = sub_cc_from_cc
+ combine_cc_with_ccs = sub_cc_from_ccs
+ else:
+ combine_cc_with_cc = add_cc_to_cc
+ combine_cc_with_ccs = add_cc_to_ccs
+
+ summary_cc_present = False
# Line matching is done in order of pattern frequency, for speed.
while line := readline():
except ValueError:
parse_die("malformed or too many event counts")
- # Record this CC at the file:function level, the function:file
- # level, and the file/line level.
- add_cc_to_ccs(
+ # Record this CC at various levels.
+ combine_cc_with_ccs(
cc,
- fl_dcc.outer_cc,
- fn_dcc.outer_cc,
- fl_dcc_inner_fn_cc,
- fn_dcc_inner_fl_cc,
+ mfl_dcc.outer_cc,
+ mfn_dcc.outer_cc,
+ mfl_dcc_inner_mfn_cc,
+ mfn_dcc_inner_mfl_cc,
dict_line_cc[line_num],
+ total_cc,
)
elif line.startswith("fn="):
- 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]
+ ofn = line[3:-1]
+ mfn = args.mod_funcname(ofn)
+ # `mfl_dcc` is unchanged.
+ mfn_dcc = dict_mfn_dcc[mfn]
+ mfl_dcc_inner_mfn_cc = mfl_dcc.inner_dict_mname_cc[mfn]
+ mfn_dcc_inner_mfl_cc = mfn_dcc.inner_dict_mname_cc[mfl]
elif line.startswith("fl="):
- fl = line[3:-1]
+ ofl = line[3:-1]
+ mfl = args.mod_filename(ofl)
+ dict_mfl_ofls[mfl].add(ofl)
# A `fn=` line should follow, overwriting the function name.
- 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]
+ mfn = "<unspecified>"
+ mfl_dcc = dict_mfl_dcc[mfl]
+ mfn_dcc = dict_mfn_dcc[mfn]
+ mfl_dcc_inner_mfn_cc = mfl_dcc.inner_dict_mname_cc[mfn]
+ mfn_dcc_inner_mfl_cc = mfn_dcc.inner_dict_mname_cc[mfl]
+ dict_line_cc = dict_mfl_dict_line_cc[mfl]
elif m := re.match(r"summary:\s+(.*)", line):
+ summary_cc_present = True
try:
- summary_cc = events.mk_cc(m.group(1).split())
+ this_summary_cc = events.mk_cc(m.group(1).split())
+ combine_cc_with_cc(this_summary_cc, summary_cc)
+
+ # Check summary is correct. Note that `total_cc` doesn't
+ # get negated for the first file in a diff, unlike the
+ # other CCs, because it's only used here as a sanity check.
+ if this_summary_cc != total_cc:
+ msg = (
+ "`summary:` line doesn't match computed total\n"
+ f"- summary: {this_summary_cc}\n"
+ f"- computed: {total_cc}"
+ )
+ parse_die(msg)
+
except ValueError:
parse_die("malformed or too many event counts")
parse_die(f"malformed line: {line[:-1]}")
# Check if summary line was present.
- if not summary_cc:
+ if not summary_cc_present:
parse_die("missing `summary:` line, aborting")
- # Check summary is correct. (Only using the outer CCs.)
- total_cc = events.mk_empty_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"
- f"- summary: {summary_cc}\n"
- f"- total: {total_cc}"
- )
- parse_die(msg)
-
- 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:
# Text of a missing CC, which can be computed in advance.
missing_cc_str: str
- # Must call `init_ccs` or `init_list_name_lcc` after this.
+ # Must call `init_ccs` or `init_list_mname_lcc` after this.
def __init__(self, events: Events, summary_cc: Cc) -> None:
self.events = events
self.summary_cc = summary_cc
self.init_widths(min_cc, max_cc, None, None)
- def init_list_name_lcc(self, list_name_lcc: ListNameLcc) -> None:
+ def init_list_mname_lcc(self, list_mname_lcc: ListMnameLcc) -> None:
self.events_prefix = " "
cumul_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:
+ for _, lcc in list_mname_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:
+ for _, inner_cc in lcc.inner_list_mname_cc:
update_cc_extremes(inner_cc, min_cc, max_cc)
# Consider only outer CCs for `perc2`.
print(suffix)
- def print_lcc(self, lcc: Lcc, outer_name: str, cumul_cc: Cc) -> None:
- print("> ", end="")
+ def print_lcc(self, indent: str, lcc: Lcc, outer_mname: str, cumul_cc: Cc) -> None:
+ print(indent, end="")
if (
- len(lcc.inner_list_name_cc) == 1
- and lcc.outer_cc == lcc.inner_list_name_cc[0][1]
+ len(lcc.inner_list_mname_cc) == 1
+ and lcc.outer_cc == lcc.inner_list_mname_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}")
+ inner_mname = lcc.inner_list_mname_cc[0][0]
+ self.print_cc(lcc.outer_cc, cumul_cc, f"{outer_mname}:{inner_mname}")
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:
+ self.print_cc(lcc.outer_cc, cumul_cc, f"{outer_mname}:")
+ for inner_mname, inner_cc in lcc.inner_list_mname_cc:
print(" ", end="")
- self.print_cc(inner_cc, None, f" {inner_name}")
+ self.print_cc(inner_cc, None, f" {inner_mname}")
print()
# If `cc2` is `None`, it's a vanilla CC or inner CC. Otherwise, it's an
print(fancy)
-def print_metadata(desc: str, cmd: str, events: Events) -> None:
+def print_metadata(descs: list[str], cmds: list[str], events: Events) -> None:
print_fancy("Metadata")
- print(desc, end="")
- print("Command: ", cmd)
- print("Data file: ", args.cgout_filename[0])
+
+ def all_the_same(strs: list[str]) -> bool:
+ for i in range(len(strs) - 1):
+ if strs[i] != strs[i + 1]:
+ return False
+
+ return True
+
+ print("Invocation: ", *sys.argv)
+
+ # When there are multiple descriptions, they are usually all the same. Only
+ # print the description once in that case.
+ if all_the_same(descs):
+ print(descs[0], end="")
+ else:
+ for i, desc in enumerate(descs):
+ print(f"Description {i+1}:")
+ print(desc, end="")
+
+ # Commands are sometimes the same, sometimes not. Always print them
+ # individually, but refer to the previous one when appropriate.
+ if len(cmds) == 1:
+ print("Command: ", cmds[0])
+ else:
+ for i, cmd in enumerate(cmds):
+ if i > 0 and cmds[i - 1] == cmd:
+ print(f"Command {i+1}: (same as Command {i})")
+ else:
+ print(f"Command {i+1}: ", cmd)
+
print("Events recorded: ", *events.events)
print("Events shown: ", *events.show_events)
print("Event sort order:", *events.sort_events)
- print("Threshold: ", args.threshold)
+ print("Threshold: ", args.threshold, "%", sep="")
print("Annotation: ", "on" if args.annotate else "off")
print()
print()
-def print_name_summary(
- kind: str, events: Events, dict_name_dcc: DictNameDcc, summary_cc: Cc
+def print_mname_summary(
+ kind: str, indent: str, events: Events, dict_mname_dcc: DictMnameDcc, summary_cc: Cc
) -> set[str]:
# 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[threshold_index]) / 100
- def meets_threshold(name_and_cc: tuple[str, Cc]) -> bool:
- cc = name_and_cc[1]
+ def meets_threshold(mname_and_cc: tuple[str, Cc]) -> bool:
+ cc = mname_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
+ def key_mname_and_lcc(mname_and_lcc: tuple[str, Lcc]) -> tuple[list[int], str]:
+ (outer_mname, lcc) = mname_and_lcc
return (
[abs(lcc.outer_cc[i]) for i in events.sort_indices],
- outer_name,
+ outer_mname,
)
- # 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)
+ # Similar to `key_mname_and_lcc`.
+ def key_mname_and_cc(mname_and_cc: tuple[str, Cc]) -> tuple[list[int], str]:
+ (mname, cc) = mname_and_cc
+ return ([abs(cc[i]) for i in events.sort_indices], mname)
# 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():
+ list_mname_lcc: ListMnameLcc = []
+ for outer_mname, dcc in dict_mname_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,
+ inner_list_mname_cc = sorted(
+ filter(meets_threshold, dcc.inner_dict_mname_cc.items()),
+ key=key_mname_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:
+ if len(inner_list_mname_cc) == 0:
continue
- list_name_lcc.append((outer_name, Lcc(dcc.outer_cc, inner_list_name_cc)))
+ list_mname_lcc.append((outer_mname, Lcc(dcc.outer_cc, inner_list_mname_cc)))
- list_name_lcc = sorted(list_name_lcc, key=key_name_and_lcc, reverse=True)
+ list_mname_lcc = sorted(list_mname_lcc, key=key_mname_and_lcc, reverse=True)
printer = CcPrinter(events, summary_cc)
- printer.init_list_name_lcc(list_name_lcc)
+ printer.init_list_mname_lcc(list_mname_lcc)
print_fancy(kind + " summary")
printer.print_events(" " + kind.lower())
print()
# Print LCCs.
- threshold_names = set([])
+ threshold_mnames = set([])
cumul_cc = events.mk_empty_cc()
- for name, lcc in list_name_lcc:
+ for mname, lcc in list_mname_lcc:
add_cc_to_cc(lcc.outer_cc, cumul_cc)
- printer.print_lcc(lcc, name, cumul_cc)
- threshold_names.add(name)
+ printer.print_lcc(indent, lcc, mname, cumul_cc)
+ threshold_mnames.add(mname)
- return threshold_names
+ return threshold_mnames
class AnnotatedCcs:
line_nums_known_cc: Cc
line_nums_unknown_cc: Cc
+ non_identical_cc: Cc
unreadable_cc: Cc
below_threshold_cc: Cc
files_unknown_cc: Cc
labels = [
" annotated: files known & above threshold & readable, line numbers known",
" annotated: files known & above threshold & readable, line numbers unknown",
+ "unannotated: files known & above threshold & two or more non-identical",
"unannotated: files known & above threshold & unreadable ",
"unannotated: files known & below threshold",
"unannotated: files unknown",
def __init__(self, events: Events) -> None:
self.line_nums_known_cc = events.mk_empty_cc()
self.line_nums_unknown_cc = events.mk_empty_cc()
+ self.non_identical_cc = events.mk_empty_cc()
self.unreadable_cc = events.mk_empty_cc()
self.below_threshold_cc = events.mk_empty_cc()
self.files_unknown_cc = events.mk_empty_cc()
return [
self.line_nums_known_cc,
self.line_nums_unknown_cc,
+ self.non_identical_cc,
self.unreadable_cc,
self.below_threshold_cc,
self.files_unknown_cc,
"""
-def warn_src_file_is_newer(src_filename: str, cgout_filename: str) -> None:
+def warn_ofls_are_all_newer(ofls: list[str], cgout_filename: str) -> None:
+ s = "".join([f"@ - {ofl}\n" for ofl in ofls])
msg = f"""\
-@ Source file '{src_filename}' is newer than data file '{cgout_filename}'.
-@ Annotations may not be correct.
+@ Original source files are all newer than data file '{cgout_filename}':
+{s}@ Annotations may not be correct.
"""
print(mk_warning(msg))
annotated_ccs: AnnotatedCcs,
summary_cc: Cc,
) -> None:
- # If the source file is more recent than the cgout file, issue warning.
- 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, summary_cc)
printer.init_ccs(list(dict_line_cc.values()))
# The starting fancy has already been printed by the caller.
print()
-# This (partially) consumes `dict_fl_dict_line_cc`.
+# This partially consumes `dict_mfl_dict_line_cc`, and fully consumes
+# `dict_mfl_olfs`.
def print_annotated_src_files(
+ ann_mfls: set[str],
events: Events,
- ann_src_filenames: set[str],
- dict_fl_dict_line_cc: DictFlDictLineCc,
+ dict_mfl_ofls: DictMflOfls,
+ dict_mfl_dict_line_cc: DictMflDictLineCc,
summary_cc: Cc,
) -> AnnotatedCcs:
annotated_ccs = AnnotatedCcs(events)
- 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():
- add_cc_to_cc(line_cc, accum_cc)
+ def add_dict_line_cc_to_cc(dict_line_cc: DictLineCc, accum_cc: Cc) -> None:
+ for line_cc in dict_line_cc.values():
+ add_cc_to_cc(line_cc, accum_cc)
# Exclude the unknown ("???") file, which is unannotatable.
- ann_src_filenames.discard("???")
- dict_line_cc = dict_fl_dict_line_cc.pop("???", None)
- add_dict_line_cc_to_cc(dict_line_cc, annotated_ccs.files_unknown_cc)
+ ann_mfls.discard("???")
+ if "???" in dict_mfl_dict_line_cc:
+ dict_line_cc = dict_mfl_dict_line_cc.pop("???")
+ add_dict_line_cc_to_cc(dict_line_cc, annotated_ccs.files_unknown_cc)
+
+ def print_ann_fancy(mfl: str) -> None:
+ print_fancy(f"Annotated source file: {mfl}")
+
+ # This can raise an `OSError`.
+ def all_ofl_contents_identical(ofls: list[str]) -> bool:
+ for i in range(len(ofls) - 1):
+ if not filecmp.cmp(ofls[i], ofls[i + 1], shallow=False):
+ return False
+
+ return True
- def print_ann_fancy(src_filename: str) -> None:
- print_fancy(f"Annotated source file: {src_filename}")
+ for mfl in sorted(ann_mfls):
+ ofls = sorted(dict_mfl_ofls.pop(mfl))
+ first_ofl = ofls[0]
- for src_filename in sorted(ann_src_filenames):
try:
- with open(src_filename, "r", encoding="utf-8") as src_file:
- dict_line_cc = dict_fl_dict_line_cc.pop(src_filename, None)
- assert dict_line_cc is not None
- print_ann_fancy(src_filename)
- print_annotated_src_file(
- events,
- dict_line_cc,
- src_file,
- annotated_ccs,
- summary_cc,
+ if all_ofl_contents_identical(ofls):
+ # All the Ofls that map to this Mfl are identical, which means we
+ # can annotate, and it doesn't matter which Ofl we use.
+ with open(first_ofl, "r", encoding="utf-8") as src_file:
+ dict_line_cc = dict_mfl_dict_line_cc.pop(mfl)
+ print_ann_fancy(mfl)
+
+ # Because all the Ofls are identical, we can treat their
+ # mtimes as if they are all as early as the earliest one.
+ # Therefore, we warn only if the earliest source file is
+ # more recent than the cgout file.
+ min_ofl_st_mtime_ns = min(
+ [os.stat(ofl).st_mtime_ns for ofl in ofls]
+ )
+
+ for cgout_filename in args.cgout_filename:
+ if min_ofl_st_mtime_ns > os.stat(cgout_filename).st_mtime_ns:
+ warn_ofls_are_all_newer(ofls, cgout_filename)
+
+ print_annotated_src_file(
+ events,
+ dict_line_cc,
+ src_file,
+ annotated_ccs,
+ summary_cc,
+ )
+ else:
+ dict_line_cc = dict_mfl_dict_line_cc.pop(mfl)
+ add_dict_line_cc_to_cc(dict_line_cc, annotated_ccs.non_identical_cc)
+
+ # We could potentially do better here.
+ # - Annotate until the first line where the src files diverge.
+ # - Also, heuristic resyncing, e.g. by looking for matching
+ # lines (of sufficient complexity) after a divergence.
+ print_ann_fancy(mfl)
+ print(
+ "Unannotated because two or more of these original files are not "
+ "identical:",
+ *ofls,
+ sep="\n- ",
)
+ print()
except OSError:
- dict_line_cc = dict_fl_dict_line_cc.pop(src_filename, None)
+ dict_line_cc = dict_mfl_dict_line_cc.pop(mfl)
add_dict_line_cc_to_cc(dict_line_cc, annotated_ccs.unreadable_cc)
- print_ann_fancy(src_filename)
- print("This file was unreadable")
+ print_ann_fancy(mfl)
+ print(
+ "Unannotated because one or more of these original files are "
+ "unreadable:",
+ *ofls,
+ sep="\n- ",
+ )
print()
- # Sum the CCs remaining in `dict_fl_dict_line_cc`, which are all in files
+ # Sum the CCs remaining in `dict_mfl_dict_line_cc`, which are all in files
# below the threshold.
- for dict_line_cc in dict_fl_dict_line_cc.values():
+ for dict_line_cc in dict_mfl_dict_line_cc.values():
add_dict_line_cc_to_cc(dict_line_cc, annotated_ccs.below_threshold_cc)
return annotated_ccs
def main() -> None:
- (
- desc,
- cmd,
- events,
- dict_fl_dcc,
- dict_fn_dcc,
- dict_fl_dict_line_cc,
- summary_cc,
- ) = read_cgout_file()
+ # Metadata, initialized to empty states.
+ descs: list[str] = []
+ cmds: list[str] = []
+ events = Events()
+
+ # For tracking original filenames to modified filenames.
+ dict_mfl_ofls: DictMflOfls = defaultdict(set)
+
+ # Different places where we accumulate CC data. Initialized to invalid
+ # states prior to the number of events being known.
+ dict_mfl_dcc: DictMnameDcc = defaultdict(None)
+ dict_mfn_dcc: DictMnameDcc = defaultdict(None)
+ dict_mfl_dict_line_cc: DictMflDictLineCc = defaultdict(None)
+ summary_cc: Cc = []
+
+ for n, filename in enumerate(args.cgout_filename):
+ is_first_file = n == 0
+ read_cgout_file(
+ filename,
+ is_first_file,
+ descs,
+ cmds,
+ events,
+ dict_mfl_ofls,
+ dict_mfl_dcc,
+ dict_mfn_dcc,
+ dict_mfl_dict_line_cc,
+ summary_cc,
+ )
# Each of the following calls prints a section of the output.
- print_metadata(desc, cmd, events)
+ print_metadata(descs, cmds, events)
print_summary(events, summary_cc)
- ann_src_filenames = print_name_summary(
- "File:function", events, dict_fl_dcc, summary_cc
+ ann_mfls = print_mname_summary(
+ "File:function", "< ", events, dict_mfl_dcc, summary_cc
)
- print_name_summary("Function:file", events, dict_fn_dcc, summary_cc)
+ print_mname_summary("Function:file", "> ", events, dict_mfn_dcc, summary_cc)
if args.annotate:
annotated_ccs = print_annotated_src_files(
- events, ann_src_filenames, dict_fl_dict_line_cc, summary_cc
+ ann_mfls, events, dict_mfl_ofls, dict_mfl_dict_line_cc, summary_cc
)
print_annotation_summary(events, annotated_ccs, summary_cc)
if regex is None:
return lambda s: s
- # Extract the parts of a `s/old/new/tail` regex. `(?<!\\)/` is an
+ # Extract the parts of an `s/old/new/tail` regex. `(?<!\\)/` is an
# example of negative lookbehind. It means "match a forward slash
# unless preceded by a backslash".
m = re.match(r"s/(.*)(?<!\\)/(.*)(?<!\\)/(g|i|gi|ig|)$", regex)
raise ValueError
# Forward slashes must be escaped in an `s/old/new/` expression,
- # but we then must unescape them before using them with `re.sub`
+ # but we then must unescape them before using them with `re.sub`.
pat = m.group(1).replace(r"\/", r"/")
repl = m.group(2).replace(r"\/", r"/")
tail = m.group(3)
return lambda s: re.sub(re.compile(pat, flags=flags), repl, s, count=count)
- p = ArgumentParser(description="Diff two Cachegrind output files.")
+ desc = (
+ "Diff two Cachegrind output files. Deprecated; use "
+ "`cg_annotate --diff` instead."
+ )
+ p = ArgumentParser(description=desc)
p.add_argument("--version", action="version", version="%(prog)s-@VERSION@")
(cmd1, events1, dict_flfn_cc1, summary_cc1) = read_cgout_file(filename1)
(cmd2, events2, dict_flfn_cc2, summary_cc2) = read_cgout_file(filename2)
- if events1.num_events != events2.num_events:
- die("events don't match")
+ if events1.events != events2.events:
+ die("events in data files don't match")
# Subtract file 1's CCs from file 2's CCs, at the Flfn level.
for flfn, flfn_cc1 in dict_flfn_cc1.items():
@staticmethod
def parse() -> Args:
- p = ArgumentParser(description="Merge multiple Cachegrind output files.")
+ desc = (
+ "Merge multiple Cachegrind output files. Deprecated; use "
+ "`cg_annotate` with multiple Cachegrind output files instead."
+ )
+ p = ArgumentParser(description=desc)
p.add_argument("--version", action="version", version="%(prog)s-@VERSION@")
events1 = events_n
else:
assert events1
- if events1.num_events != events_n.num_events:
- die("events don't match")
+ if events1.events != events_n.events:
+ die("events in data files don't match")
def write_output(f: TextIO) -> None:
# These assertions hold because the loop above executes at least twice.
ann-diff1.post.exp ann-diff1.stderr.exp ann-diff1.vgtest \
ann-diff2.post.exp ann-diff2.stderr.exp ann-diff2.vgtest \
ann-diff2a.cgout ann-diff2b.cgout \
+ ann-diff2-aux/ann-diff2-basic.rs \
+ ann-diff3.post.exp ann-diff3.stderr.exp ann-diff3.vgtest \
+ ann-diff4.post.exp ann-diff4.stderr.exp ann-diff4.vgtest \
+ ann-diff4a.cgout ann-diff4b.cgout \
+ ann-diff4a-aux/w.rs ann-diff4a-aux/x.rs ann-diff4a-aux/y.rs \
+ ann-diff4a-aux/z.rs \
+ ann-diff4b-aux/w.rs ann-diff4b-aux/x.rs ann-diff4b-aux/y.rs \
ann-merge1.post.exp ann-merge1.stderr.exp ann-merge1.vgtest \
ann-merge1a.cgout ann-merge1b.cgout \
ann-merge-x.rs ann-merge-y.rs \
+ ann-merge2.post.exp ann-merge2.stderr.exp ann-merge2.vgtest \
ann1a.post.exp ann1a.stderr.exp ann1a.vgtest ann1.cgout \
ann1b.post.exp ann1b.stderr.exp ann1b.vgtest ann1b.cgout \
ann2.post.exp ann2.stderr.exp ann2.vgtest ann2.cgout \
--------------------------------------------------------------------------------
-- Metadata
--------------------------------------------------------------------------------
+Invocation: ../cg_annotate --mod-filename=s/a.c/A.c/ --mod-funcname s/MAIN/Main/ ann-diff1.cgout
Files compared: ann1.cgout; ann1b.cgout
Command: ./a.out; ./a.out
-Data file: ann-diff1.cgout
Events recorded: Ir I1mr ILmr Dr D1mr DLmr Dw D1mw DLmw
Events shown: Ir I1mr ILmr Dr D1mr DLmr Dw D1mw DLmw
Event sort order: Ir I1mr ILmr Dr D1mr DLmr Dw D1mw DLmw
-Threshold: 0.1
+Threshold: 0.1%
Annotation: on
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
Ir________________________ I1mr________ ILmr________ Dr_________________________ D1mr________ DLmr________ Dw__________ D1mw________ DLmw________ file:function
-> 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
+< 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
+> 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
+-- Annotated source file: A.c
--------------------------------------------------------------------------------
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
+ 0 0 0 0 0 0 0 0 0 unannotated: files known & above threshold & two or more non-identical
0 0 0 0 0 0 0 0 0 unannotated: files known & above threshold & unreadable
0 0 0 0 0 0 0 0 0 unannotated: files known & below threshold
0 0 0 0 0 0 0 0 0 unannotated: files unknown
# The `prog` doesn't matter because we don't use its output. Instead we test
-# the post-processing of the `ann{1,1b}.cgout` test files.
+# the post-processing of the cgout files.
prog: ../../tests/true
vgopts: --cachegrind-out-file=cachegrind.out
-post: python3 ../cg_diff --mod-funcname="s/main/MAIN/" ann1.cgout ann1b.cgout > ann-diff1.cgout && python3 ../cg_annotate ann-diff1.cgout
-cleanup: rm ann-diff1.cgout
+
+post: python3 ../cg_diff --mod-funcname="s/main/MAIN/" ann1.cgout ann1b.cgout > ann-diff1.cgout && python3 ../cg_annotate --mod-filename="s/a.c/A.c/" --mod-funcname s/MAIN/Main/ ann-diff1.cgout
+
+cleanup: rm cachegrind.out ann-diff1.cgout
--------------------------------------------------------------------------------
-- Metadata
--------------------------------------------------------------------------------
+Invocation: ../cg_annotate ann-diff2c.cgout
Files compared: ann-diff2a.cgout; ann-diff2b.cgout
Command: cmd1; cmd2
-Data file: ann-diff2c.cgout
Events recorded: One Two
Events shown: One Two
Event sort order: One Two
-Threshold: 0.1
+Threshold: 0.1%
Annotation: on
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
One___________________ Two___________________ file:function
-> 2,100 (100.0%, 100.0%) 1,900 (100.0%, 100.0%) aux/ann-diff2-basic.rs:
+< 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
--------------------------------------------------------------------------------
-- Annotated source file: aux/ann-diff2-basic.rs
--------------------------------------------------------------------------------
-This file was unreadable
+Unannotated because one or more of these original files are unreadable:
+- aux/ann-diff2-basic.rs
--------------------------------------------------------------------------------
-- Annotation summary
0 0 annotated: files known & above threshold & readable, line numbers known
0 0 annotated: files known & above threshold & readable, line numbers unknown
+ 0 0 unannotated: files known & above threshold & two or more non-identical
2,100 (100.0%) 1,900 (100.0%) unannotated: files known & above threshold & unreadable
0 0 unannotated: files known & below threshold
0 0 unannotated: files unknown
# The `prog` doesn't matter because we don't use its output. Instead we test
-# the post-processing of the `ann-diff2{a,b}.cgout` test files.
+# the post-processing of the cgout files.
prog: ../../tests/true
vgopts: --cachegrind-out-file=cachegrind.out
+
post: python3 ../cg_diff --mod-filename="s/.*aux\//aux\//i" --mod-funcname="s/(f[a-z]*)[0-9]/\1N/g" ann-diff2a.cgout ann-diff2b.cgout > ann-diff2c.cgout && python3 ../cg_annotate ann-diff2c.cgout
-cleanup: rm ann-diff2c.cgout
+
+cleanup: rm cachegrind.out ann-diff2c.cgout
-desc: Description for ann-diff2a.cgout
+desc: Description for ann-diff2b.cgout
cmd: cmd2
events: One Two
--- /dev/null
+--------------------------------------------------------------------------------
+-- Metadata
+--------------------------------------------------------------------------------
+Invocation: ../cg_annotate --diff --mod-filename=s/.*aux\//aux\//i --mod-funcname=s/(f[a-z]*)[0-9]/\1N/g ann-diff2a.cgout ann-diff2b.cgout
+Description 1:
+Description for ann-diff2a.cgout
+Description 2:
+Description for ann-diff2b.cgout
+Command 1: cmd1
+Command 2: cmd2
+Events recorded: One Two
+Events shown: One Two
+Event sort order: One Two
+Threshold: 0.1%
+Annotation: on
+
+--------------------------------------------------------------------------------
+-- Summary
+--------------------------------------------------------------------------------
+One___________ Two___________
+
+2,100 (100.0%) 1,900 (100.0%) PROGRAM TOTALS
+
+--------------------------------------------------------------------------------
+-- File:function summary
+--------------------------------------------------------------------------------
+ One___________________ Two___________________ file:function
+
+< 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
+--------------------------------------------------------------------------------
+Unannotated because one or more of these original files are unreadable:
+- ann2-diff-AUX/ann-diff2-basic.rs
+- ann2-diff-Aux/ann-diff2-basic.rs
+
+--------------------------------------------------------------------------------
+-- Annotation summary
+--------------------------------------------------------------------------------
+One___________ Two___________
+
+ 0 0 annotated: files known & above threshold & readable, line numbers known
+ 0 0 annotated: files known & above threshold & readable, line numbers unknown
+ 0 0 unannotated: files known & above threshold & two or more non-identical
+2,100 (100.0%) 1,900 (100.0%) unannotated: files known & above threshold & unreadable
+ 0 0 unannotated: files known & below threshold
+ 0 0 unannotated: files unknown
+
--- /dev/null
+
+
+I refs:
--- /dev/null
+# The `prog` doesn't matter because we don't use its output. Instead we test
+# the post-processing of the cgout files.
+prog: ../../tests/true
+vgopts: --cachegrind-out-file=cachegrind.out
+
+post: python3 ../cg_annotate --diff --mod-filename="s/.*aux\//aux\//i" --mod-funcname="s/(f[a-z]*)[0-9]/\1N/g" ann-diff2a.cgout ann-diff2b.cgout
+
+cleanup: rm cachegrind.out
--- /dev/null
+--------------------------------------------------------------------------------
+-- Metadata
+--------------------------------------------------------------------------------
+Invocation: ../cg_annotate ann-diff4a.cgout ann-diff4b.cgout --mod-filename=s/ann-diff4[ab]/ann-diff4N/ --diff
+DescA
+DescB
+DescC
+Command 1: my-command
+Command 2: (same as Command 1)
+Events recorded: Ir
+Events shown: Ir
+Event sort order: Ir
+Threshold: 0.1%
+Annotation: on
+
+--------------------------------------------------------------------------------
+-- Summary
+--------------------------------------------------------------------------------
+Ir__________
+
+700 (100.0%) PROGRAM TOTALS
+
+--------------------------------------------------------------------------------
+-- File:function summary
+--------------------------------------------------------------------------------
+ Ir___________________ file:function
+
+< 600 (85.7%, 85.7%) ann-diff4N-aux/y.rs:b
+
+< 200 (28.6%, 114.3%) ann-diff4N-aux/x.rs:a
+
+< -200 (-28.6%, 85.7%) ann-diff4N-aux/w.rs:a
+
+< 200 (28.6%, 114.3%) ann-diff4N-aux/no-such-file.rs:f
+
+< -100 (-14.3%, 100.0%) ann-diff4N-aux/z.rs:c
+
+--------------------------------------------------------------------------------
+-- Function:file summary
+--------------------------------------------------------------------------------
+ Ir___________________ function:file
+
+> 600 (85.7%, 85.7%) b:ann-diff4N-aux/y.rs
+
+> 200 (28.6%, 114.3%) f:ann-diff4N-aux/no-such-file.rs
+
+> -100 (-14.3%, 100.0%) c:ann-diff4N-aux/z.rs
+
+> 0 (0.0%, 100.0%) a:
+ 200 (28.6%) ann-diff4N-aux/x.rs
+ -200 (-28.6%) ann-diff4N-aux/w.rs
+
+--------------------------------------------------------------------------------
+-- Annotated source file: ann-diff4N-aux/no-such-file.rs
+--------------------------------------------------------------------------------
+Unannotated because one or more of these original files are unreadable:
+- ann-diff4a-aux/no-such-file.rs
+- ann-diff4b-aux/no-such-file.rs
+
+--------------------------------------------------------------------------------
+-- Annotated source file: ann-diff4N-aux/w.rs
+--------------------------------------------------------------------------------
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+@@ WARNING @@ WARNING @@ WARNING @@ WARNING @@ WARNING @@ WARNING @@ WARNING @@
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+@ Original source files are all newer than data file 'ann-diff4a.cgout':
+@ - ann-diff4a-aux/w.rs
+@ - ann-diff4b-aux/w.rs
+@ Annotations may not be correct.
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+@@ WARNING @@ WARNING @@ WARNING @@ WARNING @@ WARNING @@ WARNING @@ WARNING @@
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+@ Original source files are all newer than data file 'ann-diff4b.cgout':
+@ - ann-diff4a-aux/w.rs
+@ - ann-diff4b-aux/w.rs
+@ Annotations may not be correct.
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+
+Ir___________
+
+-200 (-28.6%) one
+ . two
+ . three
+
+--------------------------------------------------------------------------------
+-- Annotated source file: ann-diff4N-aux/x.rs
+--------------------------------------------------------------------------------
+Ir___________
+
+ 100 (14.3%) <unknown (line 0)>
+
+-200 (-28.6%) one
+ 300 (42.9%) two
+ . three
+ . four
+ . five
+
+--------------------------------------------------------------------------------
+-- Annotated source file: ann-diff4N-aux/y.rs
+--------------------------------------------------------------------------------
+Unannotated because two or more of these original files are not identical:
+- ann-diff4a-aux/y.rs
+- ann-diff4b-aux/y.rs
+
+--------------------------------------------------------------------------------
+-- Annotated source file: ann-diff4N-aux/z.rs
+--------------------------------------------------------------------------------
+Unannotated because one or more of these original files are unreadable:
+- ann-diff4a-aux/z.rs
+- ann-diff4b-aux/z.rs
+
+--------------------------------------------------------------------------------
+-- Annotation summary
+--------------------------------------------------------------------------------
+Ir___________
+
+-100 (-14.3%) annotated: files known & above threshold & readable, line numbers known
+ 100 (14.3%) annotated: files known & above threshold & readable, line numbers unknown
+ 600 (85.7%) unannotated: files known & above threshold & two or more non-identical
+ 100 (14.3%) unannotated: files known & above threshold & unreadable
+ 0 unannotated: files known & below threshold
+ 0 unannotated: files unknown
+
--- /dev/null
+
+
+I refs:
--- /dev/null
+# The `prog` doesn't matter because we don't use its output. Instead we test
+# the post-processing of the cgout files.
+prog: ../../tests/true
+vgopts: --cachegrind-out-file=cachegrind.out
+
+# The `sleep` is to ensure the mtime of the second touched file is greater than
+# the mtime of the first touched file.
+#
+# Because only one of the two x.rs files is newer than the cgout files, we don't
+# get an mtime warning on x.rs. But both w.rs files are new than the cgout
+# files, so we do get an mtime warning on w.rs.
+post: touch ann-diff4a.cgout ann-diff4b.cgout && sleep 0.1 && touch ann-diff4a-aux/x.rs ann-diff4a-aux/w.rs ann-diff4b-aux/w.rs && python3 ../cg_annotate ann-diff4a.cgout ann-diff4b.cgout --mod-filename=s/ann-diff4[ab]/ann-diff4N/ --diff
+
+cleanup: rm cachegrind.out
--- /dev/null
+one
+two
+three
--- /dev/null
+one
+two
+three
+four
+five
--- /dev/null
+ONE
+TWO
+THREE
+FOUR
+FIVE
--- /dev/null
+one
+two
+three
--- /dev/null
+desc: DescA
+desc: DescB
+desc: DescC
+cmd: my-command
+events: Ir
+
+fl=ann-diff4a-aux/w.rs
+fn=a
+1 1000
+
+fl=ann-diff4a-aux/x.rs
+fn=a
+0 1000
+1 1000
+2 1000
+
+fl=ann-diff4a-aux/y.rs
+fn=b
+4 1000
+5 1000
+
+fl=ann-diff4a-aux/z.rs
+fn=c
+1 1000
+
+fl=ann-diff4a-aux/no-such-file.rs
+fn=f
+0 100
+
+summary: 7100
--- /dev/null
+one
+two
+three
+four
+five
--- /dev/null
+ONE
+TWO
+THREE
+FOUR
+FIVE
+SIX
--- /dev/null
+desc: DescA
+desc: DescB
+desc: DescC
+cmd: my-command
+events: Ir
+
+fl=ann-diff4b-aux/w.rs
+fn=a
+1 800
+
+fl=ann-diff4b-aux/x.rs
+fn=a
+0 1100
+1 800
+2 1300
+
+fl=ann-diff4b-aux/y.rs
+fn=b
+4 1200
+5 900
+6 500
+
+fl=ann-diff4b-aux/z.rs
+fn=c
+1 900
+
+fl=ann-diff4b-aux/no-such-file.rs
+fn=f
+0 300
+
+summary: 7800
--------------------------------------------------------------------------------
-- Metadata
--------------------------------------------------------------------------------
+Invocation: ../cg_annotate ann-merge1c.cgout
Description 1a
Description 1b
Command: Command 1
-Data file: ann-merge1c.cgout
Events recorded: A
Events shown: A
Event sort order: A
-Threshold: 0.1
+Threshold: 0.1%
Annotation: on
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
A_________________ file:function
-> 70 (81.4%, 81.4%) ann-merge-x.rs:
+< 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
+< 16 (18.6%, 100.0%) ann-merge-y.rs:y1
--------------------------------------------------------------------------------
-- Function:file summary
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 & two or more non-identical
0 unannotated: files known & above threshold & unreadable
0 unannotated: files known & below threshold
0 unannotated: files unknown
# The `prog` doesn't matter because we don't use its output. Instead we test
-# the post-processing of the `ann{1,1b}.cgout` test files.
+# the post-processing of the cgout files.
prog: ../../tests/true
vgopts: --cachegrind-out-file=cachegrind.out
+
post: python3 ../cg_merge ann-merge1a.cgout ann-merge1b.cgout > ann-merge1c.cgout && python3 ../cg_annotate ann-merge1c.cgout
-cleanup: rm ann-merge1c.cgout
+cleanup: rm cachegrind.out ann-merge1c.cgout
--- /dev/null
+--------------------------------------------------------------------------------
+-- Metadata
+--------------------------------------------------------------------------------
+Invocation: ../cg_annotate ann-merge1a.cgout ann-merge1b.cgout
+Description 1:
+Description 1a
+Description 1b
+Description 2:
+Description 2a
+Description 2b
+Command 1: Command 1
+Command 2: Command 2
+Events recorded: A
+Events shown: A
+Event sort order: A
+Threshold: 0.1%
+Annotation: on
+
+--------------------------------------------------------------------------------
+-- Summary
+--------------------------------------------------------------------------------
+A__________
+
+86 (100.0%) PROGRAM TOTALS
+
+--------------------------------------------------------------------------------
+-- File:function summary
+--------------------------------------------------------------------------------
+ A_________________ file:function
+
+< 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_________
+
+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_______
+
+8 (9.3%) one
+8 (9.3%) two
+. three
+. four
+. five
+. six
+
+--------------------------------------------------------------------------------
+-- Annotation summary
+--------------------------------------------------------------------------------
+A__________
+
+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 & two or more non-identical
+ 0 unannotated: files known & above threshold & unreadable
+ 0 unannotated: files known & below threshold
+ 0 unannotated: files unknown
+
--- /dev/null
+
+
+I refs:
--- /dev/null
+# The `prog` doesn't matter because we don't use its output. Instead we test
+# the post-processing of the cgout files.
+prog: ../../tests/true
+vgopts: --cachegrind-out-file=cachegrind.out
+
+post: python3 ../cg_annotate ann-merge1a.cgout ann-merge1b.cgout
+
+cleanup: rm cachegrind.out
--------------------------------------------------------------------------------
-- Metadata
--------------------------------------------------------------------------------
+Invocation: ../cg_annotate --show=Ir,I1mr,ILmr --show-percs=no ann1.cgout
I1 cache: 32768 B, 64 B, 8-way associative
D1 cache: 32768 B, 64 B, 8-way associative
LL cache: 19922944 B, 64 B, 19-way associative
Command: ./a.out
-Data file: ann1.cgout
Events recorded: Ir I1mr ILmr Dr D1mr DLmr Dw D1mw DLmw
Events shown: Ir I1mr ILmr
Event sort order: Ir I1mr ILmr Dr D1mr DLmr Dw D1mw DLmw
-Threshold: 0.1
+Threshold: 0.1%
Annotation: on
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
Ir_______ I1mr ILmr file:function
-> 5,000,015 1 1 a.c:main
+< 5,000,015 1 1 a.c:main
-> 76,688 32 32 /build/glibc-OTsEL5/glibc-2.27/elf/dl-lookup.c:
+< 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,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
+< 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:
+< 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,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
+< 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,939 5 5 /build/glibc-OTsEL5/glibc-2.27/elf/dl-misc.c:
6,898 2 2 _dl_name_match_p
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- Annotated source file: /build/glibc-OTsEL5/glibc-2.27/elf/../sysdeps/x86_64/dl-machine.h
--------------------------------------------------------------------------------
-This file was unreadable
+Unannotated because one or more of these original files are unreadable:
+- /build/glibc-OTsEL5/glibc-2.27/elf/../sysdeps/x86_64/dl-machine.h
--------------------------------------------------------------------------------
-- Annotated source file: /build/glibc-OTsEL5/glibc-2.27/elf/dl-lookup.c
--------------------------------------------------------------------------------
-This file was unreadable
+Unannotated because one or more of these original files are unreadable:
+- /build/glibc-OTsEL5/glibc-2.27/elf/dl-lookup.c
--------------------------------------------------------------------------------
-- Annotated source file: /build/glibc-OTsEL5/glibc-2.27/elf/dl-misc.c
--------------------------------------------------------------------------------
-This file was unreadable
+Unannotated because one or more of these original files are unreadable:
+- /build/glibc-OTsEL5/glibc-2.27/elf/dl-misc.c
--------------------------------------------------------------------------------
-- Annotated source file: /build/glibc-OTsEL5/glibc-2.27/elf/dl-tunables.c
--------------------------------------------------------------------------------
-This file was unreadable
+Unannotated because one or more of these original files are unreadable:
+- /build/glibc-OTsEL5/glibc-2.27/elf/dl-tunables.c
--------------------------------------------------------------------------------
-- Annotated source file: /build/glibc-OTsEL5/glibc-2.27/elf/dl-tunables.h
--------------------------------------------------------------------------------
-This file was unreadable
+Unannotated because one or more of these original files are unreadable:
+- /build/glibc-OTsEL5/glibc-2.27/elf/dl-tunables.h
--------------------------------------------------------------------------------
-- Annotated source file: /build/glibc-OTsEL5/glibc-2.27/elf/do-rel.h
--------------------------------------------------------------------------------
-This file was unreadable
+Unannotated because one or more of these original files are unreadable:
+- /build/glibc-OTsEL5/glibc-2.27/elf/do-rel.h
--------------------------------------------------------------------------------
-- Annotated source file: /build/glibc-OTsEL5/glibc-2.27/string/../sysdeps/x86_64/strcmp.S
--------------------------------------------------------------------------------
-This file was unreadable
+Unannotated because one or more of these original files are unreadable:
+- /build/glibc-OTsEL5/glibc-2.27/string/../sysdeps/x86_64/strcmp.S
--------------------------------------------------------------------------------
-- Annotated source file: a.c
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
+ 0 0 0 unannotated: files known & above threshold & two or more non-identical
179,512 136 134 unannotated: files known & above threshold & unreadable
49,754 770 758 unannotated: files known & below threshold
472 45 38 unannotated: files unknown
# The `prog` doesn't matter because we don't use its output. Instead we test
-# the post-processing of the `ann1.cgout` file.
+# the post-processing of the cgout file.
prog: ../../tests/true
vgopts: --cachegrind-out-file=cachegrind.out
+
post: touch ann1.cgout && python3 ../cg_annotate --show=Ir,I1mr,ILmr --show-percs=no ann1.cgout
+
cleanup: rm cachegrind.out
--------------------------------------------------------------------------------
-- Metadata
--------------------------------------------------------------------------------
+Invocation: ../cg_annotate --sort=Dr --show=Dw,Dr,Ir --auto=no ann1.cgout
I1 cache: 32768 B, 64 B, 8-way associative
D1 cache: 32768 B, 64 B, 8-way associative
LL cache: 19922944 B, 64 B, 19-way associative
Command: ./a.out
-Data file: ann1.cgout
Events recorded: Ir I1mr ILmr Dr D1mr DLmr Dw D1mw DLmw
Events shown: Dw Dr Ir
Event sort order: Dr
-Threshold: 0.1
+Threshold: 0.1%
Annotation: off
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
Dw__________________ Dr______________________ Ir______________________ file:function
-> 3 (0.0%, 0.0%) 4,000,004 (98.6%, 98.6%) 5,000,015 (95.6%, 95.6%) a.c:main
+< 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:
+< 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:
+< 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,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
+< 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
# The `prog` doesn't matter because we don't use its output. Instead we test
-# the post-processing of the `ann1.cgout` file.
+# the post-processing of the cgout file.
prog: ../../tests/true
vgopts: --cachegrind-out-file=cachegrind.out
+
post: touch ann1.cgout && python3 ../cg_annotate --sort=Dr --show=Dw,Dr,Ir --auto=no ann1.cgout
+
cleanup: rm cachegrind.out
--------------------------------------------------------------------------------
-- Metadata
--------------------------------------------------------------------------------
+Invocation: ../cg_annotate --context 2 --annotate --show-percs=yes --threshold=0.5 ann2.cgout
Command: ann2
-Data file: ann2.cgout
Events recorded: A SomeCount VeryLongEventName
Events shown: A SomeCount VeryLongEventName
Event sort order: A SomeCount VeryLongEventName
-Threshold: 0.5
+Threshold: 0.5%
Annotation: on
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
A___________________________ SomeCount____________ VeryLongEventName__ file:function
-> 86,590 (86.6%, 86.6%) 93,000 (93.0%, 93.0%) 0 (n/a, n/a) ann2-basic.rs:
+< 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
+< 9,000 (9.0%, 95.6%) 6,000 (6.0%, 99.0%) 0 (n/a, n/a) ann2-could-not-be-found.rs:f1
-> 2,000 (2.0%, 97.6%) 800 (0.8%, 99.8%) -1,000 (n/a, n/a) ann2-past-the-end.rs:
+< 2,000 (2.0%, 97.6%) 800 (0.8%, 99.8%) -1,000 (n/a, n/a) ann2-past-the-end.rs:
1,000 (1.0%) 500 (0.5%) 0 <unspecified>
1,000 (1.0%) 300 (0.3%) -1,000 (n/a) 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%, 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
+< 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:
+< 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
--------------------------------------------------------------------------------
-- Annotated source file: ann2-could-not-be-found.rs
--------------------------------------------------------------------------------
-This file was unreadable
+Unannotated because one or more of these original files are unreadable:
+- ann2-could-not-be-found.rs
--------------------------------------------------------------------------------
-- Annotated source file: ann2-more-recent-than-cgout.rs
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@ WARNING @@ WARNING @@ WARNING @@ WARNING @@ WARNING @@ WARNING @@ WARNING @@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
-@ Source file 'ann2-more-recent-than-cgout.rs' is newer than data file 'ann2.cgout'.
+@ Original source files are all newer than data file 'ann2.cgout':
+@ - ann2-more-recent-than-cgout.rs
@ Annotations may not be correct.
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
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
+ 0 0 0 unannotated: files known & above threshold & two or more non-identical
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
# more details.
#
# The `prog` doesn't matter because we don't use its output. Instead we test
-# the post-processing of the `ann2.cgout` file.
+# the post-processing of the cgout file.
prog: ../../tests/true
vgopts: --cachegrind-out-file=cachegrind.out