]> sourceware.org Git - valgrind.git/commitdiff
Add diff and merge capability to `cg_annotate`.
authorNicholas Nethercote <n.nethercote@gmail.com>
Wed, 12 Apr 2023 00:02:13 +0000 (10:02 +1000)
committerNicholas Nethercote <n.nethercote@gmail.com>
Fri, 21 Apr 2023 12:18:09 +0000 (22:18 +1000)
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`.

34 files changed:
cachegrind/cg_annotate.in
cachegrind/cg_diff.in
cachegrind/cg_merge.in
cachegrind/tests/Makefile.am
cachegrind/tests/ann-diff1.post.exp
cachegrind/tests/ann-diff1.vgtest
cachegrind/tests/ann-diff2.post.exp
cachegrind/tests/ann-diff2.vgtest
cachegrind/tests/ann-diff2b.cgout
cachegrind/tests/ann-diff3.post.exp [new file with mode: 0644]
cachegrind/tests/ann-diff3.stderr.exp [new file with mode: 0644]
cachegrind/tests/ann-diff3.vgtest [new file with mode: 0644]
cachegrind/tests/ann-diff4.post.exp [new file with mode: 0644]
cachegrind/tests/ann-diff4.stderr.exp [new file with mode: 0644]
cachegrind/tests/ann-diff4.vgtest [new file with mode: 0644]
cachegrind/tests/ann-diff4a-aux/w.rs [new file with mode: 0644]
cachegrind/tests/ann-diff4a-aux/x.rs [new file with mode: 0644]
cachegrind/tests/ann-diff4a-aux/y.rs [new file with mode: 0644]
cachegrind/tests/ann-diff4a-aux/z.rs [new file with mode: 0644]
cachegrind/tests/ann-diff4a.cgout [new file with mode: 0644]
cachegrind/tests/ann-diff4b-aux/x.rs [new file with mode: 0644]
cachegrind/tests/ann-diff4b-aux/y.rs [new file with mode: 0644]
cachegrind/tests/ann-diff4b.cgout [new file with mode: 0644]
cachegrind/tests/ann-merge1.post.exp
cachegrind/tests/ann-merge1.vgtest
cachegrind/tests/ann-merge2.post.exp [new file with mode: 0644]
cachegrind/tests/ann-merge2.stderr.exp [new file with mode: 0644]
cachegrind/tests/ann-merge2.vgtest [new file with mode: 0644]
cachegrind/tests/ann1a.post.exp
cachegrind/tests/ann1a.vgtest
cachegrind/tests/ann1b.post.exp
cachegrind/tests/ann1b.vgtest
cachegrind/tests/ann2.post.exp
cachegrind/tests/ann2.vgtest

index 5e64a94485b95a53d04d5068eaa03631f455504d..c76a760be0c828d4e160929ae9c806777cf3bb23 100755 (executable)
 
 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
@@ -55,6 +66,42 @@ class Args(Namespace):
 
     @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(",")
 
@@ -97,9 +144,30 @@ class Args(Namespace):
                 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,
@@ -143,12 +211,19 @@ class Args(Namespace):
         )
         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.
@@ -178,7 +253,11 @@ class Events:
     # 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)
 
@@ -245,9 +324,15 @@ def add_cc_to_cc(a_cc: Cc, b_cc: Cc) -> None:
         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
@@ -255,6 +340,21 @@ def add_cc_to_ccs(
         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`.
@@ -266,59 +366,70 @@ def update_cc_extremes(self: Cc, min_cc: Cc, max_cc: Cc) -> None:
             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}")
 
@@ -340,40 +451,64 @@ def read_cgout_file() -> tuple[
                 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():
@@ -385,37 +520,54 @@ def read_cgout_file() -> tuple[
                 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")
 
@@ -427,31 +579,9 @@ def read_cgout_file() -> tuple[
                 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:
@@ -487,7 +617,7 @@ class CcPrinter:
     # 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
@@ -505,7 +635,7 @@ class CcPrinter:
 
         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()
@@ -516,10 +646,10 @@ class CcPrinter:
         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`.
@@ -604,24 +734,24 @@ class CcPrinter:
 
         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
@@ -645,15 +775,42 @@ def print_fancy(text: str) -> None:
     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()
 
@@ -668,8 +825,8 @@ def print_summary(events: Events, summary_cc: Cc) -> None:
     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]
@@ -677,66 +834,67 @@ def print_name_summary(
     # 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
@@ -744,6 +902,7 @@ class AnnotatedCcs:
     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",
@@ -752,6 +911,7 @@ class AnnotatedCcs:
     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()
@@ -760,6 +920,7 @@ class AnnotatedCcs:
         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,
@@ -776,10 +937,11 @@ def mk_warning(msg: str) -> str:
 """
 
 
-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))
 
@@ -798,10 +960,6 @@ def print_annotated_src_file(
     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.
@@ -884,52 +1042,101 @@ def print_annotated_src_file(
         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
@@ -965,26 +1172,46 @@ def print_annotation_summary(
 
 
 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)
index 38910f31b15c4be85ea00b7c042d3edca323840d..d3a63189ea7bbd10e35c25f8710773b4e43d8d36 100755 (executable)
@@ -66,7 +66,7 @@ class Args(Namespace):
             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)
@@ -74,7 +74,7 @@ class Args(Namespace):
                 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)
@@ -91,7 +91,11 @@ class Args(Namespace):
 
             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@")
 
@@ -304,8 +308,8 @@ def main() -> None:
     (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():
index 8304e8b2793c9ee0a04e69205dfe077a725f906b..7c385b4c8e5484a360025a2e112b6e30366ef6bb 100755 (executable)
@@ -51,7 +51,11 @@ class Args(Namespace):
 
     @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@")
 
@@ -272,8 +276,8 @@ def main() -> None:
             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.
index d38d300b90b489cbe40d91eb561446de5142eb1c..9b977d58106d3d5a575803c8ef2b3602dfc94e15 100644 (file)
@@ -16,9 +16,17 @@ EXTRA_DIST = \
        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 \
index 54962b513d3ba301ea3ca63ebdac797ceb9549f9..d8ccea091e32b161d1babc3c7cd8072a7bfd5365 100644 (file)
@@ -1,13 +1,13 @@
 --------------------------------------------------------------------------------
 -- 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
 
 --------------------------------------------------------------------------------
@@ -22,17 +22,17 @@ Ir________________ I1mr ILmr Dr_________________ D1mr DLmr Dw D1mw DLmw
 --------------------------------------------------------------------------------
   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 
 
@@ -45,6 +45,7 @@ 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
index e3794018769712d3d83d5736c0d4246aab125e48..ab119b3b3643b1db68707d53490a293126bbfaa5 100644 (file)
@@ -1,6 +1,8 @@
 # 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
index e1060dbd232c5d5ea8f51190ac333af15da8532f..b6567418f710f4612ed31d6ecf0882ae319c6826 100644 (file)
@@ -1,13 +1,13 @@
 --------------------------------------------------------------------------------
 -- 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
 
 --------------------------------------------------------------------------------
@@ -22,7 +22,7 @@ One___________ Two___________
 --------------------------------------------------------------------------------
   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
@@ -41,7 +41,8 @@ One___________ Two___________
 --------------------------------------------------------------------------------
 -- 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
@@ -50,6 +51,7 @@ 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
index 7b395e4e48e7c6a7a610b39c6c1e5336440b26d0..bae3ab98753f24bdc5c18890c2bd3c2391fe80ef 100644 (file)
@@ -1,6 +1,8 @@
 # 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
index 9fb733e70806f7598dbcbc2f6388f20ddf217e9e..e6864107bb69d0d5ce5e98679273d628030d1875 100644 (file)
@@ -1,4 +1,4 @@
-desc: Description for ann-diff2a.cgout
+desc: Description for ann-diff2b.cgout
 cmd: cmd2
 events: One Two
 
diff --git a/cachegrind/tests/ann-diff3.post.exp b/cachegrind/tests/ann-diff3.post.exp
new file mode 100644 (file)
index 0000000..fa7ea4a
--- /dev/null
@@ -0,0 +1,63 @@
+--------------------------------------------------------------------------------
+-- 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
+
diff --git a/cachegrind/tests/ann-diff3.stderr.exp b/cachegrind/tests/ann-diff3.stderr.exp
new file mode 100644 (file)
index 0000000..ec68407
--- /dev/null
@@ -0,0 +1,3 @@
+
+
+I   refs:
diff --git a/cachegrind/tests/ann-diff3.vgtest b/cachegrind/tests/ann-diff3.vgtest
new file mode 100644 (file)
index 0000000..5831e3d
--- /dev/null
@@ -0,0 +1,8 @@
+# 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
diff --git a/cachegrind/tests/ann-diff4.post.exp b/cachegrind/tests/ann-diff4.post.exp
new file mode 100644 (file)
index 0000000..0196948
--- /dev/null
@@ -0,0 +1,125 @@
+--------------------------------------------------------------------------------
+-- 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
+
diff --git a/cachegrind/tests/ann-diff4.stderr.exp b/cachegrind/tests/ann-diff4.stderr.exp
new file mode 100644 (file)
index 0000000..ec68407
--- /dev/null
@@ -0,0 +1,3 @@
+
+
+I   refs:
diff --git a/cachegrind/tests/ann-diff4.vgtest b/cachegrind/tests/ann-diff4.vgtest
new file mode 100644 (file)
index 0000000..da6e00a
--- /dev/null
@@ -0,0 +1,14 @@
+# 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
diff --git a/cachegrind/tests/ann-diff4a-aux/w.rs b/cachegrind/tests/ann-diff4a-aux/w.rs
new file mode 100644 (file)
index 0000000..4cb29ea
--- /dev/null
@@ -0,0 +1,3 @@
+one
+two
+three
diff --git a/cachegrind/tests/ann-diff4a-aux/x.rs b/cachegrind/tests/ann-diff4a-aux/x.rs
new file mode 100644 (file)
index 0000000..b2f931a
--- /dev/null
@@ -0,0 +1,5 @@
+one
+two
+three
+four
+five
diff --git a/cachegrind/tests/ann-diff4a-aux/y.rs b/cachegrind/tests/ann-diff4a-aux/y.rs
new file mode 100644 (file)
index 0000000..f196eb5
--- /dev/null
@@ -0,0 +1,5 @@
+ONE
+TWO
+THREE
+FOUR
+FIVE
diff --git a/cachegrind/tests/ann-diff4a-aux/z.rs b/cachegrind/tests/ann-diff4a-aux/z.rs
new file mode 100644 (file)
index 0000000..4cb29ea
--- /dev/null
@@ -0,0 +1,3 @@
+one
+two
+three
diff --git a/cachegrind/tests/ann-diff4a.cgout b/cachegrind/tests/ann-diff4a.cgout
new file mode 100644 (file)
index 0000000..a424c0a
--- /dev/null
@@ -0,0 +1,30 @@
+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
diff --git a/cachegrind/tests/ann-diff4b-aux/x.rs b/cachegrind/tests/ann-diff4b-aux/x.rs
new file mode 100644 (file)
index 0000000..b2f931a
--- /dev/null
@@ -0,0 +1,5 @@
+one
+two
+three
+four
+five
diff --git a/cachegrind/tests/ann-diff4b-aux/y.rs b/cachegrind/tests/ann-diff4b-aux/y.rs
new file mode 100644 (file)
index 0000000..cbb67cf
--- /dev/null
@@ -0,0 +1,6 @@
+ONE
+TWO
+THREE
+FOUR
+FIVE
+SIX
diff --git a/cachegrind/tests/ann-diff4b.cgout b/cachegrind/tests/ann-diff4b.cgout
new file mode 100644 (file)
index 0000000..6f0c92d
--- /dev/null
@@ -0,0 +1,31 @@
+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
index 1f47332b8a586321150ebb373b695d020d2ead46..ddb4931d808e292a1ae9ff42dfc86b64945b1a20 100644 (file)
@@ -1,14 +1,14 @@
 --------------------------------------------------------------------------------
 -- 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
 
 --------------------------------------------------------------------------------
@@ -23,12 +23,12 @@ A__________
 --------------------------------------------------------------------------------
   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
@@ -73,6 +73,7 @@ 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
index b0b0eedc96a96d9da8ad49ed718f7b7d736ce4f0..2779de1d128a3e0a38e692f83f8a9869251702b6 100644 (file)
@@ -1,7 +1,8 @@
 # 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
diff --git a/cachegrind/tests/ann-merge2.post.exp b/cachegrind/tests/ann-merge2.post.exp
new file mode 100644 (file)
index 0000000..bececb9
--- /dev/null
@@ -0,0 +1,85 @@
+--------------------------------------------------------------------------------
+-- 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
+
diff --git a/cachegrind/tests/ann-merge2.stderr.exp b/cachegrind/tests/ann-merge2.stderr.exp
new file mode 100644 (file)
index 0000000..ec68407
--- /dev/null
@@ -0,0 +1,3 @@
+
+
+I   refs:
diff --git a/cachegrind/tests/ann-merge2.vgtest b/cachegrind/tests/ann-merge2.vgtest
new file mode 100644 (file)
index 0000000..adbdcde
--- /dev/null
@@ -0,0 +1,8 @@
+# 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
index bde53e650116bdfdec78200288cf4fb358d78514..ed6e670481a37b7f4c69cdf8284f55137b26bf00 100644 (file)
@@ -1,15 +1,15 @@
 --------------------------------------------------------------------------------
 -- 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
 
 --------------------------------------------------------------------------------
@@ -24,26 +24,26 @@ Ir_______ I1mr ILmr
 --------------------------------------------------------------------------------
   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
 
 --------------------------------------------------------------------------------
@@ -74,37 +74,44 @@ Ir_______ I1mr ILmr
 --------------------------------------------------------------------------------
 -- 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
@@ -126,6 +133,7 @@ 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
+        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
index 740522f6ddd21721319b2862cce542519121baf8..16da66699e77ccb6af41ab5e8cb9083685e1608f 100644 (file)
@@ -1,6 +1,8 @@
 # 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
index 3ec4288cb493df000a97e20461b16495a28a11fb..3af40a7678392c1c18df4e4c5c2367609603bbcc 100644 (file)
@@ -1,15 +1,15 @@
 --------------------------------------------------------------------------------
 -- 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
 
 --------------------------------------------------------------------------------
@@ -24,19 +24,19 @@ Dw_____________ Dr________________ Ir________________
 --------------------------------------------------------------------------------
   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
index 320b7f3714ca40b27ef4e8ccfcc901a1f7dd9c85..0aa3a1e212f0ceee6eaa8730e0cc494238a07dc0 100644 (file)
@@ -1,6 +1,8 @@
 # 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
index 8db35f136fd98c9d30c8a0d5575afb7d32ad915b..0a8fd98db13132ab8649f07a1b2574fa69c94198 100644 (file)
@@ -1,12 +1,12 @@
 --------------------------------------------------------------------------------
 -- 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
 
 --------------------------------------------------------------------------------
@@ -21,24 +21,24 @@ A_______________ SomeCount_______ VeryLongEventName
 --------------------------------------------------------------------------------
   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
@@ -104,7 +104,8 @@ A_____________ SomeCount_____ VeryLongEventName
 --------------------------------------------------------------------------------
 -- 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
@@ -112,7 +113,8 @@ This file was unreadable
 @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 @@ 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.
 @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 
@@ -176,6 +178,7 @@ 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
+     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
index 4add2fe4ccc3601baa6df60c3c4707706976f73f..5598bdc4bd895056d2542de74e38f68911e32e04 100644 (file)
@@ -2,7 +2,7 @@
 # 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
 
This page took 0.101262 seconds and 5 git commands to generate.