Overhaul cg_annotate output.

Most notable, the "Function summary" section, which printed one CC for each
`file:function` combination, has been replaced by two sections, "File:function
summary" and "Function:file summary".

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

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

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

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

View File

@@ -12,10 +12,12 @@
[MESSAGES CONTROL]
disable=
# We don't care about having docstrings for all functions/classes.
# We don't care about having docstrings everywhere.
missing-class-docstring, missing-function-docstring,
# We don't care about large functions, sometimes it's necessary.
too-many-branches, too-many-locals, too-many-statements,
missing-module-docstring,
# We don't care about these, sometimes they are necessary.
too-many-arguments, too-many-branches, too-many-lines, too-many-locals,
too-many-statements,
# Zero or one public methods in a class is fine.
too-few-public-methods,

View File

@@ -26,15 +26,12 @@
#
# The GNU General Public License is contained in the file COPYING.
"""
This script reads Cachegrind output files and produces human-readable reports.
"""
# This script reads Cachegrind output files and produces human-readable output.
#
# Use `make pyann` to "build" this script with `auxprogs/pybuild.rs` every time
# it is changed. This runs the formatters, type-checkers, and linters on
# `cg_annotate.in` and then generates `cg_annotate`.
from __future__ import annotations
import os
@@ -42,16 +39,12 @@ import re
import sys
from argparse import ArgumentParser, BooleanOptionalAction, Namespace
from collections import defaultdict
from typing import DefaultDict, NewType, NoReturn, TextIO
from typing import DefaultDict, NoReturn, TextIO
# A typed wrapper for parsed args.
class Args(Namespace):
"""
A typed wrapper for parsed args.
None of these fields are modified after arg parsing finishes.
"""
# None of these fields are modified after arg parsing finishes.
show: list[str]
sort: list[str]
threshold: float # a percentage
@@ -72,16 +65,14 @@ class Args(Namespace):
return f
raise ValueError
# Add a bool argument that defaults to true.
#
# Supports these forms: `--foo`, `--no-foo`, `--foo=yes`, `--foo=no`.
# The latter two were the forms supported by the old Perl version of
# `cg_annotate`, and are now deprecated.
def add_bool_argument(
p: ArgumentParser, new_name: str, old_name: str, help_: str
) -> None:
"""
Add a bool argument that defaults to true.
Supports these forms: `--foo`, `--no-foo`, `--foo=yes`, `--foo=no`.
The latter two were the forms supported by the old Perl version of
`cg_annotate`, and are now deprecated.
"""
new_flag = "--" + new_name
old_flag = "--" + old_name
dest = new_name.replace("-", "_")
@@ -110,28 +101,25 @@ class Args(Namespace):
p = ArgumentParser(description="Process a Cachegrind output file.")
p.add_argument("--version", action="version", version="%(prog)s-@VERSION@")
p.add_argument(
"--show",
type=comma_separated_list,
metavar="A,B,C",
help="only show figures for events A,B,C (default: all events)",
)
p.add_argument(
"--sort",
type=comma_separated_list,
metavar="A,B,C",
help="sort functions by events A,B,C (default: event column order)",
)
p.add_argument(
"--threshold",
type=threshold,
default=0.1,
metavar="N:[0,20]",
help="only show functions with more than N%% of primary sort event "
"counts (default: %(default)s)",
help="only show file:function/function:file pairs with more than "
"N%% of primary sort event counts (default: %(default)s)",
)
add_bool_argument(
p,
@@ -182,6 +170,9 @@ class Events:
# The event names.
events: list[str]
# Equal to `len(self.events)`.
num_events: int
# The order in which we must traverse events for --show. Can be shorter
# than `events`.
show_events: list[str]
@@ -226,10 +217,10 @@ class Events:
self.sort_indices = [event_indices[event] for event in self.sort_events]
def mk_cc(self, text: str) -> Cc:
"""Raises a `ValueError` exception on syntax error."""
# Raises a `ValueError` exception on syntax error.
def mk_cc(self, str_counts: list[str]) -> Cc:
# This is slightly faster than a list comprehension.
counts = list(map(int, text.split()))
counts = list(map(int, str_counts))
if len(counts) == self.num_events:
pass
@@ -239,48 +230,81 @@ class Events:
else:
raise ValueError
return Cc(counts)
return counts
def mk_empty_cc(self) -> Cc:
# This is much faster than a list comprehension.
return Cc([0] * self.num_events)
return [0] * self.num_events
def mk_empty_dcc(self) -> Dcc:
return Dcc(self.mk_empty_cc(), defaultdict(self.mk_empty_cc))
class Cc:
"""
This is a dumb container for 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]
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]
def __init__(self, counts: list[int]) -> None:
self.counts = counts
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
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
# A paired filename and function name.
Flfn = NewType("Flfn", tuple[str, str])
# 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
# Per-function CCs.
DictFlfnCc = DefaultDict[Flfn, Cc]
# 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
# 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, outer_cc: Cc, inner_dict_name_cc: DictNameCc) -> None:
self.outer_cc = outer_cc
self.inner_dict_name_cc = inner_dict_name_cc
# 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 __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
# Per-file/function CCs. The list version is used during filtering and sorting.
DictNameCc = DefaultDict[str, Cc]
ListNameCc = 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-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,90 +486,164 @@ 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 width of each percentage column. Zero if --show-percs is disabled.
# Its length matches `count_widths`.
perc_widths: list[int]
# The widths of each event column. For simplicity, its length matches
# `events.events`, even though not all events are necessarily shown.
widths: list[Width]
def __init__(self, events: Events, ccs: list[Cc], summary_cc: Cc) -> None:
# 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.
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
update_cc_extremes(cc, min_cc, max_cc)
# 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)
self.init_widths(min_cc, max_cc, None, None)
# 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))
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)),
)
self.missing_cc_str = ""
for i in self.events.show_indices:
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:
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:
# An outer CC, with two percentages.
if summary_count == 0:
# Avoid dividing by zero.
perc1 = " (n/a,"
perc2 = " n/a)"
else:
perc1 = f" ({cc1[i] * 100 / summary_count:.1f}%,"
perc2 = f" {cc2[i] * 100 / summary_count:.1f}%)"
else:
perc1 = ""
perc2 = ""
return (count, perc1, perc2)
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_events(self, suffix: str) -> None:
print(self.events_prefix, end="")
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="")
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)
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
if args.show_percs:
if cc.counts[i] == 0:
# Don't show percentages for "0" entries, it's just clutter.
perc = ""
else:
summary_count = self.summary_cc.counts[i]
if summary_count == 0:
perc = " (n/a)"
else:
p = cc.counts[i] * 100 / summary_count
perc = f" ({p:.1f}%)"
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:
perc = ""
# 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()
return (count, perc)
def print_cc(self, cc: Cc, suffix: str) -> None:
# 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:
(count, perc) = self.count_and_perc(cc, i)
self.print_count_and_perc(i, count, perc)
(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:
# Don't show percentages for "." entries, it's just clutter.
for i in self.events.show_indices:
self.print_count_and_perc(i, ".", "")
print("", suffix)
print(self.missing_cc_str, suffix)
# Used in various places in the output.
@@ -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
threshold = args.threshold * abs(summary_cc[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
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 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])
# 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,
)
# 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))
# 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)
printer = CcPrinter(events, sorted_ccs, summary_cc)
print_fancy("Function summary")
printer.print_events(" file:function")
# 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
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()
# Print per-function counts.
for flfn, flfn_cc in sorted_flfns_and_ccs:
printer.print_cc(flfn_cc, f"{flfn[0]}:{flfn[1]}")
# 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)
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)
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

View File

@@ -26,10 +26,8 @@
#
# The GNU General Public License is contained in the file COPYING.
"""
This script diffs Cachegrind output files.
"""
# This script diffs Cachegrind output files.
#
# Use `make pydiff` to "build" this script every time it is changed. This runs
# the formatters, type-checkers, and linters on `cg_diff.in` and then generates
# `cg_diff`.
@@ -47,13 +45,9 @@ from typing import Callable, DefaultDict, NewType, NoReturn
SearchAndReplace = Callable[[str], str]
# A typed wrapper for parsed args.
class Args(Namespace):
"""
A typed wrapper for parsed args.
None of these fields are modified after arg parsing finishes.
"""
# None of these fields are modified after arg parsing finishes.
mod_filename: SearchAndReplace
mod_funcname: SearchAndReplace
cgout_filename1: str
@@ -146,10 +140,10 @@ class Events:
self.events = text.split()
self.num_events = len(self.events)
def mk_cc(self, text: str) -> Cc:
"""Raises a `ValueError` exception on syntax error."""
# Raises a `ValueError` exception on syntax error.
def mk_cc(self, str_counts: list[str]) -> Cc:
# This is slightly faster than a list comprehension.
counts = list(map(int, text.split()))
counts = list(map(int, str_counts))
if len(counts) == self.num_events:
pass
@@ -159,46 +153,31 @@ class Events:
else:
raise ValueError
return Cc(counts)
return counts
def mk_empty_cc(self) -> Cc:
# This is much faster than a list comprehension.
return Cc([0] * self.num_events)
return [0] * self.num_events
class Cc:
"""
This is a dumb container for 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]
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.
"""
# 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
# 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)
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
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__":

View File

@@ -26,10 +26,8 @@
#
# The GNU General Public License is contained in the file COPYING.
"""
This script diffs Cachegrind output files.
"""
# This script merges Cachegrind output files.
#
# Use `make pymerge` to "build" this script every time it is changed. This runs
# the formatters, type-checkers, and linters on `cg_merge.in` and then
# generates `cg_merge`.
@@ -45,13 +43,9 @@ from collections import defaultdict
from typing import DefaultDict, NoReturn, TextIO
# A typed wrapper for parsed args.
class Args(Namespace):
"""
A typed wrapper for parsed args.
None of these fields are modified after arg parsing finishes.
"""
# None of these fields are modified after arg parsing finishes.
output: str
cgout_filename: list[str]
@@ -92,10 +86,10 @@ class Events:
self.events = text.split()
self.num_events = len(self.events)
def mk_cc(self, text: str) -> Cc:
"""Raises a `ValueError` exception on syntax error."""
# Raises a `ValueError` exception on syntax error.
def mk_cc(self, str_counts: list[str]) -> Cc:
# This is slightly faster than a list comprehension.
counts = list(map(int, text.split()))
counts = list(map(int, str_counts))
if len(counts) == self.num_events:
pass
@@ -105,41 +99,26 @@ class Events:
else:
raise ValueError
return Cc(counts)
return counts
def mk_empty_cc(self) -> Cc:
# This is much faster than a list comprehension.
return Cc([0] * self.num_events)
return [0] * self.num_events
class Cc:
"""
This is a dumb container for 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]
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]
def __init__(self, counts: list[int]) -> None:
self.counts = counts
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
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:

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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