]> git.ipfire.org Git - thirdparty/valgrind.git/commitdiff
Overhaul `cg_annotate` output.
authorNicholas Nethercote <n.nethercote@gmail.com>
Tue, 28 Mar 2023 22:55:51 +0000 (09:55 +1100)
committerNicholas Nethercote <n.nethercote@gmail.com>
Mon, 10 Apr 2023 23:58:43 +0000 (09:58 +1000)
Most notable, the "Function summary" section, which printed one CC for each
`file:function` combination, has been replaced by two sections, "File:function
summary" and "Function:file summary".

These new sections both feature "deep CCs", which have an "outer CC" for the
file (or function), and one or more "inner CCs" for the paired functions (or
files).

Here is a file:function example, which helps show which files have a lot of
events, even if those events are spread across a lot of functions.
```
> 12,427,830 (5.4%, 26.3%)  /home/njn/moz/gecko-dev/js/src/ds/LifoAlloc.h:
   6,107,862 (2.7%)           js::frontend::ParseNodeVerifier::visit(js::frontend::ParseNode*)
   3,685,203 (1.6%)           js::detail::BumpChunk::setBump(unsigned char*)
   1,640,591 (0.7%)           js::LifoAlloc::alloc(unsigned long)
     711,008 (0.3%)           js::detail::BumpChunk::assertInvariants()
```
And here is a function:file example, which shows how heavy inlining can result
in a machine code function being derived from source code from multiple files:
```
>  1,343,736 (0.6%, 35.6%)  js::gc::TenuredCell::isMarkedGray() const:
     651,108 (0.3%)           /home/njn/moz/gecko-dev/js/src/d64/dist/include/js/HeapAPI.h
     292,672 (0.1%)           /home/njn/moz/gecko-dev/js/src/gc/Cell.h
     254,854 (0.1%)           /home/njn/moz/gecko-dev/js/src/gc/Heap.h
```
Previously these patterns were very hard to find, and it was easy to overlook a
hot piece of code because its counts were spread across multiple non-adjacent
entries. I have already found these changes very useful for profiling Rust
code.

Also, cumulative percentages on the outer CCs (e.g. the 26.3% and 35.6% in the
example) tell you what fraction of all events are covered by the entries so
far, something I've wanted for a long time.

Some other, related changes:
- Column event headers are now padded with `_`, e.g. `Ir__________`. This makes
  the column/event mapping clearer.
- The "Cachegrind profile" section is now called "Metadata", which is
  shorter and clearer.
- A few minor test tweaks, beyond those required for the output changes.
- I converted some doc comments to normal comments. Not standard Python, but
  nicer to read, and there are no public APIs here.
- Roughly 2x speedups to `cg_annotate` and smaller improvements for `cg_diff`
  and `cg_merge`, due to the following.
  - Change the `Cc` class to a type alias for `list[int]`, to avoid the class
    overhead (sigh).
  - Process event count lines in a single split, instead of a regex
    match + split.
  - Add the `add_cc_to_ccs` function, which does multiple CC additions in a
    single function call.
  - Better handling of dicts while reading input, minimizing lookups.
  - Pre-computing the missing CC string for each CcPrinter, instead of
    regenerating it each time.

13 files changed:
auxprogs/pylintrc
cachegrind/cg_annotate.in
cachegrind/cg_diff.in
cachegrind/cg_merge.in
cachegrind/tests/ann-diff1.post.exp
cachegrind/tests/ann-diff2.post.exp
cachegrind/tests/ann-merge1.post.exp
cachegrind/tests/ann-merge1a.cgout
cachegrind/tests/ann-merge1b.cgout
cachegrind/tests/ann1a.post.exp
cachegrind/tests/ann1b.post.exp
cachegrind/tests/ann2.cgout
cachegrind/tests/ann2.post.exp

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