Skip to content

Commit 5047a33

Browse files
authored
[BOLT][heatmap] Produce zoomed-out heatmaps (#140153)
Add a capability to produce multiple heatmaps with given bucket sizes. The default heatmap block size (64B) could be too fine-grained for large binaries. Extend the option `block-size` to accept a list of bucket sizes for additional heatmaps with coarser granularity. The heatmap is simply rescaled so provided sizes should be multiples of each other. Human-readable suffixes can be used, e.g. 4K, 16kb, 1MiB. New defaults: 64B (base bucket size), 4KB (default page size), 256KB (for large binaries). Test Plan: updated heatmap-preagg.test
1 parent f1886b1 commit 5047a33

File tree

8 files changed

+166
-10
lines changed

8 files changed

+166
-10
lines changed

bolt/docs/Heatmaps.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,13 @@ For the generation, the default bucket size was used with a line size of 128.
8989
Some useful options are:
9090

9191
```
92-
-line-size=<uint> - number of entries per line (default 256)
92+
-line-size=<uint> - number of entries per line (default 256).
93+
Use a smaller value (e.g. 128) if the heatmap doesn't fit
94+
the screen horizontally.
95+
-block-size=<initial size>[,<zoom-out size>,...] - heatmap bucket size,
96+
optionally followed by zoom-out sizes to produce coarse-
97+
grained heatmaps. Size can be specified in human-readable
98+
format with [kKmMgG][i][B] suffix. Default 64B, 4K, 256K.
9399
-max-address=<uint> - maximum address considered valid for heatmap (default 4GB)
94100
-print-mappings - print mappings in the legend, between characters/blocks and text sections (default false)
95101
```

bolt/include/bolt/Profile/Heatmap.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ class Heatmap {
8585
void printSectionHotness(raw_ostream &OS) const;
8686

8787
size_t size() const { return Map.size(); }
88+
89+
/// Increase bucket size to \p NewSize, recomputing the heatmap.
90+
void resizeBucket(uint64_t NewSize);
8891
};
8992

9093
} // namespace bolt

bolt/include/bolt/Utils/CommandLineOpts.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,15 @@ enum HeatmapModeKind {
2323
HM_Optional // perf2bolt --heatmap
2424
};
2525

26+
using HeatmapBlockSizes = std::vector<unsigned>;
27+
struct HeatmapBlockSpecParser : public llvm::cl::parser<HeatmapBlockSizes> {
28+
explicit HeatmapBlockSpecParser(llvm::cl::Option &O)
29+
: llvm::cl::parser<HeatmapBlockSizes>(O) {}
30+
// Return true on error.
31+
bool parse(llvm::cl::Option &O, llvm::StringRef ArgName, llvm::StringRef Arg,
32+
HeatmapBlockSizes &Val);
33+
};
34+
2635
extern HeatmapModeKind HeatmapMode;
2736
extern bool BinaryAnalysisMode;
2837

@@ -47,7 +56,8 @@ extern llvm::cl::opt<bool> EqualizeBBCounts;
4756
extern llvm::cl::opt<bool> ForcePatch;
4857
extern llvm::cl::opt<bool> RemoveSymtab;
4958
extern llvm::cl::opt<unsigned> ExecutionCountThreshold;
50-
extern llvm::cl::opt<unsigned> HeatmapBlock;
59+
extern llvm::cl::opt<HeatmapBlockSizes, false, HeatmapBlockSpecParser>
60+
HeatmapBlock;
5161
extern llvm::cl::opt<unsigned long long> HeatmapMaxAddress;
5262
extern llvm::cl::opt<unsigned long long> HeatmapMinAddress;
5363
extern llvm::cl::opt<bool> HeatmapPrintMappings;

bolt/lib/Profile/DataAggregator.cpp

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1314,8 +1314,9 @@ std::error_code DataAggregator::printLBRHeatMap() {
13141314
opts::HeatmapMaxAddress = 0xffffffffffffffff;
13151315
opts::HeatmapMinAddress = KernelBaseAddr;
13161316
}
1317-
Heatmap HM(opts::HeatmapBlock, opts::HeatmapMinAddress,
1318-
opts::HeatmapMaxAddress, getTextSections(BC));
1317+
opts::HeatmapBlockSizes &HMBS = opts::HeatmapBlock;
1318+
Heatmap HM(HMBS[0], opts::HeatmapMinAddress, opts::HeatmapMaxAddress,
1319+
getTextSections(BC));
13191320
auto getSymbolValue = [&](const MCSymbol *Symbol) -> uint64_t {
13201321
if (Symbol)
13211322
if (ErrorOr<uint64_t> SymValue = BC->getSymbolValue(*Symbol))
@@ -1365,6 +1366,14 @@ std::error_code DataAggregator::printLBRHeatMap() {
13651366
HM.printCDF(opts::HeatmapOutput + ".csv");
13661367
HM.printSectionHotness(opts::HeatmapOutput + "-section-hotness.csv");
13671368
}
1369+
// Provide coarse-grained heatmaps if requested via zoom-out scales
1370+
for (const uint64_t NewBucketSize : ArrayRef(HMBS).drop_front()) {
1371+
HM.resizeBucket(NewBucketSize);
1372+
if (opts::HeatmapOutput == "-")
1373+
HM.print(opts::HeatmapOutput);
1374+
else
1375+
HM.print(formatv("{0}-{1}", opts::HeatmapOutput, NewBucketSize).str());
1376+
}
13681377

13691378
return std::error_code();
13701379
}

bolt/lib/Profile/Heatmap.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ void Heatmap::print(StringRef FileName) const {
5555
errs() << "error opening output file: " << EC.message() << '\n';
5656
exit(1);
5757
}
58+
outs() << "HEATMAP: dumping heatmap with bucket size " << BucketSize << " to "
59+
<< FileName << '\n';
5860
print(OS);
5961
}
6062

@@ -364,5 +366,13 @@ void Heatmap::printSectionHotness(raw_ostream &OS) const {
364366
OS << formatv("[unmapped], 0x0, 0x0, {0:f4}, 0, 0\n",
365367
100.0 * UnmappedHotness / NumTotalCounts);
366368
}
369+
370+
void Heatmap::resizeBucket(uint64_t NewSize) {
371+
std::map<uint64_t, uint64_t> NewMap;
372+
for (const auto [Bucket, Count] : Map)
373+
NewMap[Bucket * BucketSize / NewSize] += Count;
374+
Map = NewMap;
375+
BucketSize = NewSize;
376+
}
367377
} // namespace bolt
368378
} // namespace llvm

bolt/lib/Utils/CommandLineOpts.cpp

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
#include "bolt/Utils/CommandLineOpts.h"
1414
#include "VCSVersion.inc"
15+
#include "llvm/Support/Regex.h"
1516

1617
using namespace llvm;
1718

@@ -103,10 +104,56 @@ ExecutionCountThreshold("execution-count-threshold",
103104
cl::Hidden,
104105
cl::cat(BoltOptCategory));
105106

106-
cl::opt<unsigned>
107-
HeatmapBlock("block-size",
108-
cl::desc("size of a heat map block in bytes (default 64)"),
109-
cl::init(64), cl::cat(HeatmapCategory));
107+
bool HeatmapBlockSpecParser::parse(cl::Option &O, StringRef ArgName,
108+
StringRef Arg, HeatmapBlockSizes &Val) {
109+
// Parses a human-readable suffix into a shift amount or nullopt on error.
110+
auto parseSuffix = [](StringRef Suffix) -> std::optional<unsigned> {
111+
if (Suffix.empty())
112+
return 0;
113+
if (!Regex{"^[kKmMgG]i?[bB]?$"}.match(Suffix))
114+
return std::nullopt;
115+
// clang-format off
116+
switch (Suffix.front()) {
117+
case 'k': case 'K': return 10;
118+
case 'm': case 'M': return 20;
119+
case 'g': case 'G': return 30;
120+
}
121+
// clang-format on
122+
llvm_unreachable("Unexpected suffix");
123+
};
124+
125+
SmallVector<StringRef> Sizes;
126+
Arg.split(Sizes, ',');
127+
unsigned PreviousSize = 0;
128+
for (StringRef Size : Sizes) {
129+
StringRef OrigSize = Size;
130+
unsigned &SizeVal = Val.emplace_back(0);
131+
if (Size.consumeInteger(10, SizeVal)) {
132+
O.error("'" + OrigSize + "' value can't be parsed as an integer");
133+
return true;
134+
}
135+
if (std::optional<unsigned> ShiftAmt = parseSuffix(Size)) {
136+
SizeVal <<= *ShiftAmt;
137+
} else {
138+
O.error("'" + Size + "' value can't be parsed as a suffix");
139+
return true;
140+
}
141+
if (SizeVal <= PreviousSize || (PreviousSize && SizeVal % PreviousSize)) {
142+
O.error("'" + OrigSize + "' must be a multiple of previous value");
143+
return true;
144+
}
145+
PreviousSize = SizeVal;
146+
}
147+
return false;
148+
}
149+
150+
cl::opt<opts::HeatmapBlockSizes, false, opts::HeatmapBlockSpecParser>
151+
HeatmapBlock(
152+
"block-size", cl::value_desc("initial_size{,zoom-out_size,...}"),
153+
cl::desc("heatmap bucket size, optionally followed by zoom-out sizes "
154+
"for coarse-grained heatmaps (default 64B, 4K, 256K)."),
155+
cl::init(HeatmapBlockSizes{/*Initial*/ 64, /*Zoom-out*/ 4096, 262144}),
156+
cl::cat(HeatmapCategory));
110157

111158
cl::opt<unsigned long long> HeatmapMaxAddress(
112159
"max-address", cl::init(0xffffffff),

bolt/test/X86/heatmap-preagg.test

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,81 @@
33
RUN: yaml2obj %p/Inputs/blarge_new.yaml &> %t.exe
44
## Non-BOLTed input binary
55
RUN: llvm-bolt-heatmap %t.exe -o %t --pa -p %p/Inputs/blarge_new.preagg.txt \
6+
# Heatmaps for 64B, 128B, 1K buckets
7+
RUN: --block-size=64,128,1K --line-size 64 \
68
RUN: 2>&1 | FileCheck --check-prefix CHECK-HEATMAP %s
79
RUN: FileCheck %s --check-prefix CHECK-SEC-HOT --input-file %t-section-hotness.csv
10+
RUN: FileCheck %s --check-prefix CHECK-HM-64 --input-file %t
11+
RUN: FileCheck %s --check-prefix CHECK-HM-128 --input-file %t-128
12+
RUN: FileCheck %s --check-prefix CHECK-HM-1024 --input-file %t-1024
813

914
## BOLTed input binary
1015
RUN: llvm-bolt %t.exe -o %t.out --pa -p %p/Inputs/blarge_new.preagg.txt \
1116
RUN: --reorder-blocks=ext-tsp --split-functions --split-strategy=cdsplit \
1217
RUN: --reorder-functions=cdsort --enable-bat --dyno-stats --skip-funcs=main
18+
# Heatmaps for 64B, 4K, 16K, 1M buckets
1319
RUN: llvm-bolt-heatmap %t.out -o %t2 --pa -p %p/Inputs/blarge_new_bat.preagg.txt \
14-
RUN: 2>&1 | FileCheck --check-prefix CHECK-HEATMAP-BAT %s
20+
RUN: --block-size=64,4KB,16kb,1MiB 2>&1 | FileCheck --check-prefix CHECK-HEATMAP-BAT %s
1521
RUN: FileCheck %s --check-prefix CHECK-SEC-HOT-BAT --input-file %t2-section-hotness.csv
1622
RUN: llvm-nm -n %t.out | FileCheck %s --check-prefix=CHECK-HOT-SYMS
23+
RUN: FileCheck %s --check-prefix CHECK-BAT-HM-64 --input-file %t2
24+
# Identical hottest range for 4K, 16K, 1M heatmaps
25+
RUN: FileCheck %s --check-prefix CHECK-BAT-HM-4K --input-file %t2-4096
26+
RUN: FileCheck %s --check-prefix CHECK-BAT-HM-4K --input-file %t2-16384
27+
RUN: FileCheck %s --check-prefix CHECK-BAT-HM-4K --input-file %t2-1048576
28+
29+
# No zoomed-out heatmaps
30+
RUN: llvm-bolt-heatmap %t.out -o %t3 --pa -p %p/Inputs/blarge_new_bat.preagg.txt \
31+
RUN: --block-size=1024 | FileCheck --check-prefix CHECK-HEATMAP-BAT-1K %s
32+
CHECK-HEATMAP-BAT-1K: HEATMAP: dumping heatmap with bucket size 1024
33+
CHECK-HEATMAP-BAT-1K-NOT: HEATMAP: dumping heatmap with bucket size
1734

1835
CHECK-HEATMAP: PERF2BOLT: read 81 aggregated LBR entries
1936
CHECK-HEATMAP: HEATMAP: invalid traces: 1
37+
CHECK-HEATMAP: HEATMAP: dumping heatmap with bucket size 64
38+
CHECK-HEATMAP: HEATMAP: dumping heatmap with bucket size 128
39+
CHECK-HEATMAP: HEATMAP: dumping heatmap with bucket size 1024
40+
CHECK-HEATMAP-NOT: HEATMAP: dumping heatmap with bucket size
2041

2142
CHECK-SEC-HOT: Section Name, Begin Address, End Address, Percentage Hotness, Utilization Pct, Partition Score
2243
CHECK-SEC-HOT-NEXT: .init, 0x401000, 0x40101b, 16.8545, 100.0000, 0.1685
2344
CHECK-SEC-HOT-NEXT: .plt, 0x401020, 0x4010b0, 4.7583, 66.6667, 0.0317
2445
CHECK-SEC-HOT-NEXT: .text, 0x4010b0, 0x401c25, 78.3872, 85.1064, 0.6671
2546
CHECK-SEC-HOT-NEXT: .fini, 0x401c28, 0x401c35, 0.0000, 0.0000, 0.0000
2647

48+
# Only check x scales – can't check colors, and FileCheck doesn't strip color
49+
# codes by default.
50+
CHECK-HM-64: (299, 937]
51+
CHECK-HM-64-NEXT: 0
52+
CHECK-HM-64-NEXT: 0
53+
CHECK-HM-64-NEXT: 0 1 2 3 4 5 6 7 8 9 a b c d e f
54+
CHECK-HM-64-NEXT: 048c048c048c048c048c048c048c048c048c048c048c048c048c048c048c048c
55+
CHECK-HM-64-NEXT: 0
56+
57+
CHECK-HM-128: (299, 937]
58+
CHECK-HM-128-NEXT: 0
59+
CHECK-HM-128-NEXT: 0 1
60+
CHECK-HM-128-NEXT: 0 1 2 3 4 5 6 7 8 9 a b c d e f 0 1 2 3 4 5 6 7 8 9 a b c d e f
61+
CHECK-HM-128-NEXT: 0808080808080808080808080808080808080808080808080808080808080808
62+
CHECK-HM-128-NEXT: 0
63+
64+
CHECK-HM-1024: (483, 1663]
65+
CHECK-HM-1024-NEXT: 0
66+
CHECK-HM-1024-NEXT: 0 1 2 3 4 5 6 7 8 9 a b c d e f
67+
CHECK-HM-1024-NEXT: 048c048c048c048c048c048c048c048c048c048c048c048c048c048c048c048c
68+
CHECK-HM-1024-NEXT: 0
69+
CHECK-HM-1024-NEXT: 0
70+
71+
CHECK-BAT-HM-64: (349, 1126]
72+
CHECK-BAT-HM-4K: (605, 2182]
73+
2774
CHECK-HEATMAP-BAT: PERF2BOLT: read 79 aggregated LBR entries
2875
CHECK-HEATMAP-BAT: HEATMAP: invalid traces: 2
76+
CHECK-HEATMAP-BAT: HEATMAP: dumping heatmap with bucket size 64
77+
CHECK-HEATMAP-BAT: HEATMAP: dumping heatmap with bucket size 4096
78+
CHECK-HEATMAP-BAT: HEATMAP: dumping heatmap with bucket size 16384
79+
CHECK-HEATMAP-BAT: HEATMAP: dumping heatmap with bucket size 1048576
80+
CHECK-HEATMAP-BAT-NOT: HEATMAP: dumping heatmap with bucket size
2981

3082
CHECK-SEC-HOT-BAT: Section Name, Begin Address, End Address, Percentage Hotness, Utilization Pct, Partition Score
3183
CHECK-SEC-HOT-BAT-NEXT: .init, 0x401000, 0x40101b, 17.2888, 100.0000, 0.1729

bolt/tools/heatmap/heatmap.cpp

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,26 @@ static std::string GetExecutablePath(const char *Argv0) {
5959

6060
int main(int argc, char **argv) {
6161
cl::HideUnrelatedOptions(ArrayRef(opts::HeatmapCategories));
62-
cl::ParseCommandLineOptions(argc, argv, "");
62+
cl::ParseCommandLineOptions(
63+
argc, argv,
64+
" BOLT Code Heatmap tool\n\n"
65+
" Produces code heatmaps using sampled profile\n\n"
66+
67+
" Inputs:\n"
68+
" - Binary (supports BOLT-optimized binaries),\n"
69+
" - Sampled profile collected from the binary:\n"
70+
" - perf data or pre-aggregated profile data (instrumentation profile "
71+
"not supported)\n"
72+
" - perf data can have basic (IP) or branch-stack (LBR) samples\n\n"
73+
74+
" Outputs:\n"
75+
" - Heatmaps: colored ASCII (requires a color-capable terminal or a"
76+
" conversion tool like `aha`)\n"
77+
" Multiple heatmaps are produced by default with different "
78+
"granularities (set by `block-size` option)\n"
79+
" - Section hotness: per-section samples% and utilization%\n"
80+
" - Cumulative distribution: working set size corresponding to a "
81+
"given percentile of samples\n");
6382

6483
if (opts::PerfData.empty()) {
6584
errs() << ToolName << ": expected -perfdata=<filename> option.\n";

0 commit comments

Comments
 (0)