xref: /llvm-project/bolt/lib/Profile/Heatmap.cpp (revision 34433fdceb63cb14b69f847a39f6ce98459f3129)
12f09f445SMaksim Panchenko //===- bolt/Profile/Heatmap.cpp -------------------------------------------===//
2a34c753fSRafael Auler //
3a34c753fSRafael Auler // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4a34c753fSRafael Auler // See https://llvm.org/LICENSE.txt for license information.
5a34c753fSRafael Auler // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6a34c753fSRafael Auler //
7a34c753fSRafael Auler //===----------------------------------------------------------------------===//
8a34c753fSRafael Auler 
9a34c753fSRafael Auler #include "bolt/Profile/Heatmap.h"
10a34c753fSRafael Auler #include "bolt/Utils/CommandLineOpts.h"
11733dc3e5SRahman Lavaee #include "llvm/ADT/StringMap.h"
12a34c753fSRafael Auler #include "llvm/ADT/Twine.h"
13a34c753fSRafael Auler #include "llvm/Support/Debug.h"
14a34c753fSRafael Auler #include "llvm/Support/FileSystem.h"
15a34c753fSRafael Auler #include "llvm/Support/Format.h"
16*34433fdcSPaschalis Mpeis #include "llvm/Support/FormatVariadic.h"
17a34c753fSRafael Auler #include "llvm/Support/MathExtras.h"
18a34c753fSRafael Auler #include "llvm/Support/raw_ostream.h"
19a34c753fSRafael Auler #include <algorithm>
2005aa2a16SFabian Parzefall #include <cctype>
21a34c753fSRafael Auler #include <cmath>
22a34c753fSRafael Auler #include <vector>
23a34c753fSRafael Auler 
24a34c753fSRafael Auler #define DEBUG_TYPE "bolt-heatmap"
25a34c753fSRafael Auler 
26a34c753fSRafael Auler using namespace llvm;
27a34c753fSRafael Auler 
28a34c753fSRafael Auler namespace llvm {
29a34c753fSRafael Auler namespace bolt {
30a34c753fSRafael Auler 
31a34c753fSRafael Auler void Heatmap::registerAddressRange(uint64_t StartAddress, uint64_t EndAddress,
32a34c753fSRafael Auler                                    uint64_t Count) {
33a34c753fSRafael Auler   if (ignoreAddress(StartAddress)) {
34a34c753fSRafael Auler     ++NumSkippedRanges;
35a34c753fSRafael Auler     return;
36a34c753fSRafael Auler   }
37a34c753fSRafael Auler 
3840c2e0faSMaksim Panchenko   if (StartAddress > EndAddress || EndAddress - StartAddress > 64 * 1024) {
39a34c753fSRafael Auler     LLVM_DEBUG(dbgs() << "invalid range : 0x" << Twine::utohexstr(StartAddress)
40a34c753fSRafael Auler                       << " -> 0x" << Twine::utohexstr(EndAddress) << '\n');
41a34c753fSRafael Auler     ++NumSkippedRanges;
42a34c753fSRafael Auler     return;
43a34c753fSRafael Auler   }
44a34c753fSRafael Auler 
45a34c753fSRafael Auler   for (uint64_t Bucket = StartAddress / BucketSize;
46def464aaSAmir Ayupov        Bucket <= EndAddress / BucketSize; ++Bucket)
47a34c753fSRafael Auler     Map[Bucket] += Count;
48a34c753fSRafael Auler }
49a34c753fSRafael Auler 
50a34c753fSRafael Auler void Heatmap::print(StringRef FileName) const {
51a34c753fSRafael Auler   std::error_code EC;
52a34c753fSRafael Auler   raw_fd_ostream OS(FileName, EC, sys::fs::OpenFlags::OF_None);
53a34c753fSRafael Auler   if (EC) {
54a34c753fSRafael Auler     errs() << "error opening output file: " << EC.message() << '\n';
55a34c753fSRafael Auler     exit(1);
56a34c753fSRafael Auler   }
57a34c753fSRafael Auler   print(OS);
58a34c753fSRafael Auler }
59a34c753fSRafael Auler 
60a34c753fSRafael Auler void Heatmap::print(raw_ostream &OS) const {
61a34c753fSRafael Auler   const char FillChar = '.';
62a34c753fSRafael Auler 
63a34c753fSRafael Auler   const auto DefaultColor = raw_ostream::WHITE;
64a34c753fSRafael Auler   auto changeColor = [&](raw_ostream::Colors Color) -> void {
65a34c753fSRafael Auler     static auto CurrentColor = raw_ostream::BLACK;
66a34c753fSRafael Auler     if (CurrentColor == Color)
67a34c753fSRafael Auler       return;
68a34c753fSRafael Auler     OS.changeColor(Color);
69a34c753fSRafael Auler     CurrentColor = Color;
70a34c753fSRafael Auler   };
71a34c753fSRafael Auler 
72a34c753fSRafael Auler   const uint64_t BytesPerLine = opts::BucketsPerLine * BucketSize;
73a34c753fSRafael Auler 
74a34c753fSRafael Auler   // Calculate the max value for scaling.
75a34c753fSRafael Auler   uint64_t MaxValue = 0;
76def464aaSAmir Ayupov   for (const std::pair<const uint64_t, uint64_t> &Entry : Map)
77a34c753fSRafael Auler     MaxValue = std::max<uint64_t>(MaxValue, Entry.second);
78a34c753fSRafael Auler 
79a34c753fSRafael Auler   // Print start of the line and fill it with an empty space right before
80a34c753fSRafael Auler   // the Address.
81a34c753fSRafael Auler   auto startLine = [&](uint64_t Address, bool Empty = false) {
82a34c753fSRafael Auler     changeColor(DefaultColor);
83a34c753fSRafael Auler     const uint64_t LineAddress = Address / BytesPerLine * BytesPerLine;
84a34c753fSRafael Auler 
85a34c753fSRafael Auler     if (MaxAddress > 0xffffffff)
86a34c753fSRafael Auler       OS << format("0x%016" PRIx64 ": ", LineAddress);
87a34c753fSRafael Auler     else
88a34c753fSRafael Auler       OS << format("0x%08" PRIx64 ": ", LineAddress);
89a34c753fSRafael Auler 
90a34c753fSRafael Auler     if (Empty)
91a34c753fSRafael Auler       Address = LineAddress + BytesPerLine;
92def464aaSAmir Ayupov     for (uint64_t Fill = LineAddress; Fill < Address; Fill += BucketSize)
93a34c753fSRafael Auler       OS << FillChar;
94a34c753fSRafael Auler   };
95a34c753fSRafael Auler 
96a34c753fSRafael Auler   // Finish line after \p Address was printed.
97a34c753fSRafael Auler   auto finishLine = [&](uint64_t Address) {
98a34c753fSRafael Auler     const uint64_t End = alignTo(Address + 1, BytesPerLine);
99a34c753fSRafael Auler     for (uint64_t Fill = Address + BucketSize; Fill < End; Fill += BucketSize)
100a34c753fSRafael Auler       OS << FillChar;
101a34c753fSRafael Auler     OS << '\n';
102a34c753fSRafael Auler   };
103a34c753fSRafael Auler 
104a34c753fSRafael Auler   // Fill empty space in (Start, End) range.
105a34c753fSRafael Auler   auto fillRange = [&](uint64_t Start, uint64_t End) {
106a34c753fSRafael Auler     if ((Start / BytesPerLine) == (End / BytesPerLine)) {
107a34c753fSRafael Auler       for (uint64_t Fill = Start + BucketSize; Fill < End; Fill += BucketSize) {
108a34c753fSRafael Auler         changeColor(DefaultColor);
109a34c753fSRafael Auler         OS << FillChar;
110a34c753fSRafael Auler       }
111a34c753fSRafael Auler       return;
112a34c753fSRafael Auler     }
113a34c753fSRafael Auler 
114a34c753fSRafael Auler     changeColor(DefaultColor);
115a34c753fSRafael Auler     finishLine(Start);
116a34c753fSRafael Auler     Start = alignTo(Start, BytesPerLine);
117a34c753fSRafael Auler 
118a34c753fSRafael Auler     uint64_t NumEmptyLines = (End - Start) / BytesPerLine;
119a34c753fSRafael Auler 
120a34c753fSRafael Auler     if (NumEmptyLines > 32) {
121a34c753fSRafael Auler       OS << '\n';
122a34c753fSRafael Auler     } else {
123a34c753fSRafael Auler       while (NumEmptyLines--) {
124a34c753fSRafael Auler         startLine(Start, /*Empty=*/true);
125a34c753fSRafael Auler         OS << '\n';
126a34c753fSRafael Auler         Start += BytesPerLine;
127a34c753fSRafael Auler       }
128a34c753fSRafael Auler     }
129a34c753fSRafael Auler 
130a34c753fSRafael Auler     startLine(End);
131a34c753fSRafael Auler   };
132a34c753fSRafael Auler 
133a34c753fSRafael Auler   static raw_ostream::Colors Colors[] = {
13440c2e0faSMaksim Panchenko       raw_ostream::WHITE, raw_ostream::WHITE,  raw_ostream::CYAN,
13540c2e0faSMaksim Panchenko       raw_ostream::GREEN, raw_ostream::YELLOW, raw_ostream::RED};
136a34c753fSRafael Auler   constexpr size_t NumRanges = sizeof(Colors) / sizeof(Colors[0]);
137a34c753fSRafael Auler 
138a34c753fSRafael Auler   uint64_t Range[NumRanges];
139a34c753fSRafael Auler   for (uint64_t I = 0; I < NumRanges; ++I)
14040c2e0faSMaksim Panchenko     Range[I] = std::max(I + 1, (uint64_t)std::pow((double)MaxValue,
141a34c753fSRafael Auler                                                   (double)(I + 1) / NumRanges));
142a34c753fSRafael Auler   Range[NumRanges - 1] = std::max((uint64_t)NumRanges, MaxValue);
143a34c753fSRafael Auler 
144a34c753fSRafael Auler   // Print scaled value
14505aa2a16SFabian Parzefall   auto printValue = [&](uint64_t Value, char Character, bool ResetColor) {
146a34c753fSRafael Auler     assert(Value && "should only print positive values");
147a34c753fSRafael Auler     for (unsigned I = 0; I < sizeof(Range) / sizeof(Range[0]); ++I) {
148a34c753fSRafael Auler       if (Value <= Range[I]) {
149a34c753fSRafael Auler         changeColor(Colors[I]);
150a34c753fSRafael Auler         break;
151a34c753fSRafael Auler       }
152a34c753fSRafael Auler     }
153def464aaSAmir Ayupov     if (Value <= Range[0])
15405aa2a16SFabian Parzefall       OS << static_cast<char>(std::tolower(Character));
155def464aaSAmir Ayupov     else
15605aa2a16SFabian Parzefall       OS << static_cast<char>(std::toupper(Character));
157def464aaSAmir Ayupov 
158a34c753fSRafael Auler     if (ResetColor)
159a34c753fSRafael Auler       changeColor(DefaultColor);
160a34c753fSRafael Auler   };
161a34c753fSRafael Auler 
162a34c753fSRafael Auler   // Print against black background
163a34c753fSRafael Auler   OS.changeColor(raw_ostream::BLACK, /*Bold=*/false, /*Background=*/true);
164a34c753fSRafael Auler   changeColor(DefaultColor);
165a34c753fSRafael Auler 
166a34c753fSRafael Auler   // Print map legend
167a34c753fSRafael Auler   OS << "Legend:\n";
168*34433fdcSPaschalis Mpeis   OS << "\nRanges:\n";
169a34c753fSRafael Auler   uint64_t PrevValue = 0;
170a34c753fSRafael Auler   for (unsigned I = 0; I < sizeof(Range) / sizeof(Range[0]); ++I) {
171a34c753fSRafael Auler     const uint64_t Value = Range[I];
172a34c753fSRafael Auler     OS << "  ";
17305aa2a16SFabian Parzefall     printValue(Value, 'o', /*ResetColor=*/true);
174a34c753fSRafael Auler     OS << " : (" << PrevValue << ", " << Value << "]\n";
175a34c753fSRafael Auler     PrevValue = Value;
176a34c753fSRafael Auler   }
177*34433fdcSPaschalis Mpeis   if (opts::HeatmapPrintMappings) {
178*34433fdcSPaschalis Mpeis     OS << "\nSections:\n";
179*34433fdcSPaschalis Mpeis     unsigned SectionIdx = 0;
180*34433fdcSPaschalis Mpeis     for (auto TxtSeg : TextSections) {
181*34433fdcSPaschalis Mpeis       const char Upper = static_cast<char>('A' + ((SectionIdx++) % 26));
182*34433fdcSPaschalis Mpeis       const char Lower = static_cast<char>(std::tolower(Upper));
183*34433fdcSPaschalis Mpeis       OS << formatv("  {0}/{1} : {2,-10} ", Lower, Upper, TxtSeg.Name);
184*34433fdcSPaschalis Mpeis       if (MaxAddress > 0xffffffff)
185*34433fdcSPaschalis Mpeis         OS << format("0x%016" PRIx64, TxtSeg.BeginAddress) << "-"
186*34433fdcSPaschalis Mpeis            << format("0x%016" PRIx64, TxtSeg.EndAddress) << "\n";
187*34433fdcSPaschalis Mpeis       else
188*34433fdcSPaschalis Mpeis         OS << format("0x%08" PRIx64, TxtSeg.BeginAddress) << "-"
189*34433fdcSPaschalis Mpeis            << format("0x%08" PRIx64, TxtSeg.EndAddress) << "\n";
190*34433fdcSPaschalis Mpeis     }
191*34433fdcSPaschalis Mpeis     OS << "\n";
192*34433fdcSPaschalis Mpeis   }
193a34c753fSRafael Auler 
194a34c753fSRafael Auler   // Pos - character position from right in hex form.
195a34c753fSRafael Auler   auto printHeader = [&](unsigned Pos) {
196a34c753fSRafael Auler     OS << "            ";
197a34c753fSRafael Auler     if (MaxAddress > 0xffffffff)
198a34c753fSRafael Auler       OS << "        ";
199a34c753fSRafael Auler     unsigned PrevValue = unsigned(-1);
200a34c753fSRafael Auler     for (unsigned I = 0; I < BytesPerLine; I += BucketSize) {
201a34c753fSRafael Auler       const unsigned Value = (I & ((1 << Pos * 4) - 1)) >> (Pos - 1) * 4;
202a34c753fSRafael Auler       if (Value != PrevValue) {
203a34c753fSRafael Auler         OS << Twine::utohexstr(Value);
204a34c753fSRafael Auler         PrevValue = Value;
205a34c753fSRafael Auler       } else {
206a34c753fSRafael Auler         OS << ' ';
207a34c753fSRafael Auler       }
208a34c753fSRafael Auler     }
209a34c753fSRafael Auler     OS << '\n';
210a34c753fSRafael Auler   };
211a34c753fSRafael Auler   for (unsigned I = 5; I > 0; --I)
212a34c753fSRafael Auler     printHeader(I);
213a34c753fSRafael Auler 
21405aa2a16SFabian Parzefall   auto SectionStart = TextSections.begin();
215a34c753fSRafael Auler   uint64_t PrevAddress = 0;
216a34c753fSRafael Auler   for (auto MI = Map.begin(), ME = Map.end(); MI != ME; ++MI) {
217a34c753fSRafael Auler     const std::pair<const uint64_t, uint64_t> &Entry = *MI;
218a34c753fSRafael Auler     uint64_t Address = Entry.first * BucketSize;
21905aa2a16SFabian Parzefall     char Character = 'o';
22005aa2a16SFabian Parzefall 
22105aa2a16SFabian Parzefall     // Check if address is in the current or any later section.
22205aa2a16SFabian Parzefall     auto Section = std::find_if(
22305aa2a16SFabian Parzefall         SectionStart, TextSections.end(), [&](const SectionNameAndRange &S) {
22405aa2a16SFabian Parzefall           return Address >= S.BeginAddress && Address < S.EndAddress;
22505aa2a16SFabian Parzefall         });
22605aa2a16SFabian Parzefall     if (Section != TextSections.end()) {
22705aa2a16SFabian Parzefall       // Shift the section forward (if SectionStart is different from Section).
22805aa2a16SFabian Parzefall       // This works, because TextSections is sorted by start address.
22905aa2a16SFabian Parzefall       SectionStart = Section;
23005aa2a16SFabian Parzefall       Character = 'a' + ((Section - TextSections.begin()) % 26);
23105aa2a16SFabian Parzefall     }
232a34c753fSRafael Auler 
233def464aaSAmir Ayupov     if (PrevAddress)
234a34c753fSRafael Auler       fillRange(PrevAddress, Address);
235def464aaSAmir Ayupov     else
236a34c753fSRafael Auler       startLine(Address);
237a34c753fSRafael Auler 
23805aa2a16SFabian Parzefall     printValue(Entry.second, Character, /*ResetColor=*/false);
239a34c753fSRafael Auler 
240a34c753fSRafael Auler     PrevAddress = Address;
241a34c753fSRafael Auler   }
242a34c753fSRafael Auler 
243a34c753fSRafael Auler   if (PrevAddress) {
244a34c753fSRafael Auler     changeColor(DefaultColor);
245a34c753fSRafael Auler     finishLine(PrevAddress);
246a34c753fSRafael Auler   }
247a34c753fSRafael Auler }
248a34c753fSRafael Auler 
249a34c753fSRafael Auler void Heatmap::printCDF(StringRef FileName) const {
250a34c753fSRafael Auler   std::error_code EC;
251a34c753fSRafael Auler   raw_fd_ostream OS(FileName, EC, sys::fs::OpenFlags::OF_None);
252a34c753fSRafael Auler   if (EC) {
253a34c753fSRafael Auler     errs() << "error opening output file: " << EC.message() << '\n';
254a34c753fSRafael Auler     exit(1);
255a34c753fSRafael Auler   }
256a34c753fSRafael Auler   printCDF(OS);
257a34c753fSRafael Auler }
258a34c753fSRafael Auler 
259a34c753fSRafael Auler void Heatmap::printCDF(raw_ostream &OS) const {
260a34c753fSRafael Auler   uint64_t NumTotalCounts = 0;
261a34c753fSRafael Auler   std::vector<uint64_t> Counts;
262a34c753fSRafael Auler 
263a34c753fSRafael Auler   for (const std::pair<const uint64_t, uint64_t> &KV : Map) {
264a34c753fSRafael Auler     Counts.push_back(KV.second);
265a34c753fSRafael Auler     NumTotalCounts += KV.second;
266a34c753fSRafael Auler   }
267a34c753fSRafael Auler 
268d2c87699SAmir Ayupov   llvm::sort(Counts, std::greater<uint64_t>());
269a34c753fSRafael Auler 
270a34c753fSRafael Auler   double RatioLeftInKB = (1.0 * BucketSize) / 1024;
271a34c753fSRafael Auler   assert(NumTotalCounts > 0 &&
272a34c753fSRafael Auler          "total number of heatmap buckets should be greater than 0");
273a34c753fSRafael Auler   double RatioRightInPercent = 100.0 / NumTotalCounts;
274a34c753fSRafael Auler   uint64_t RunningCount = 0;
275a34c753fSRafael Auler 
276a34c753fSRafael Auler   OS << "Bucket counts, Size (KB), CDF (%)\n";
277a34c753fSRafael Auler   for (uint64_t I = 0; I < Counts.size(); I++) {
278a34c753fSRafael Auler     RunningCount += Counts[I];
279a34c753fSRafael Auler     OS << format("%llu", (I + 1)) << ", "
280a34c753fSRafael Auler        << format("%.4f", RatioLeftInKB * (I + 1)) << ", "
281a34c753fSRafael Auler        << format("%.4f", RatioRightInPercent * (RunningCount)) << "\n";
282a34c753fSRafael Auler   }
283a34c753fSRafael Auler 
284a34c753fSRafael Auler   Counts.clear();
285a34c753fSRafael Auler }
286a34c753fSRafael Auler 
287733dc3e5SRahman Lavaee void Heatmap::printSectionHotness(StringRef FileName) const {
288733dc3e5SRahman Lavaee   std::error_code EC;
289733dc3e5SRahman Lavaee   raw_fd_ostream OS(FileName, EC, sys::fs::OpenFlags::OF_None);
290733dc3e5SRahman Lavaee   if (EC) {
291733dc3e5SRahman Lavaee     errs() << "error opening output file: " << EC.message() << '\n';
292733dc3e5SRahman Lavaee     exit(1);
293733dc3e5SRahman Lavaee   }
294733dc3e5SRahman Lavaee   printSectionHotness(OS);
295733dc3e5SRahman Lavaee }
296733dc3e5SRahman Lavaee 
297733dc3e5SRahman Lavaee void Heatmap::printSectionHotness(raw_ostream &OS) const {
298733dc3e5SRahman Lavaee   uint64_t NumTotalCounts = 0;
299733dc3e5SRahman Lavaee   StringMap<uint64_t> SectionHotness;
300733dc3e5SRahman Lavaee   unsigned TextSectionIndex = 0;
301733dc3e5SRahman Lavaee 
302733dc3e5SRahman Lavaee   if (TextSections.empty())
303733dc3e5SRahman Lavaee     return;
304733dc3e5SRahman Lavaee 
305733dc3e5SRahman Lavaee   uint64_t UnmappedHotness = 0;
306733dc3e5SRahman Lavaee   auto RecordUnmappedBucket = [&](uint64_t Address, uint64_t Frequency) {
307733dc3e5SRahman Lavaee     errs() << "Couldn't map the address bucket [0x" << Twine::utohexstr(Address)
308733dc3e5SRahman Lavaee            << ", 0x" << Twine::utohexstr(Address + BucketSize)
309733dc3e5SRahman Lavaee            << "] containing " << Frequency
310733dc3e5SRahman Lavaee            << " samples to a text section in the binary.";
311733dc3e5SRahman Lavaee     UnmappedHotness += Frequency;
312733dc3e5SRahman Lavaee   };
313733dc3e5SRahman Lavaee 
314733dc3e5SRahman Lavaee   for (const std::pair<const uint64_t, uint64_t> &KV : Map) {
315733dc3e5SRahman Lavaee     NumTotalCounts += KV.second;
316733dc3e5SRahman Lavaee     // We map an address bucket to the first section (lowest address)
317733dc3e5SRahman Lavaee     // overlapping with that bucket.
318733dc3e5SRahman Lavaee     auto Address = KV.first * BucketSize;
319733dc3e5SRahman Lavaee     while (TextSectionIndex < TextSections.size() &&
320733dc3e5SRahman Lavaee            Address >= TextSections[TextSectionIndex].EndAddress)
321733dc3e5SRahman Lavaee       TextSectionIndex++;
322733dc3e5SRahman Lavaee     if (TextSectionIndex >= TextSections.size() ||
323733dc3e5SRahman Lavaee         Address + BucketSize < TextSections[TextSectionIndex].BeginAddress) {
324733dc3e5SRahman Lavaee       RecordUnmappedBucket(Address, KV.second);
325733dc3e5SRahman Lavaee       continue;
326733dc3e5SRahman Lavaee     }
327733dc3e5SRahman Lavaee     SectionHotness[TextSections[TextSectionIndex].Name] += KV.second;
328733dc3e5SRahman Lavaee   }
329733dc3e5SRahman Lavaee 
330733dc3e5SRahman Lavaee   assert(NumTotalCounts > 0 &&
331733dc3e5SRahman Lavaee          "total number of heatmap buckets should be greater than 0");
332733dc3e5SRahman Lavaee 
333733dc3e5SRahman Lavaee   OS << "Section Name, Begin Address, End Address, Percentage Hotness\n";
334733dc3e5SRahman Lavaee   for (auto &TextSection : TextSections) {
335733dc3e5SRahman Lavaee     OS << TextSection.Name << ", 0x"
336733dc3e5SRahman Lavaee        << Twine::utohexstr(TextSection.BeginAddress) << ", 0x"
337733dc3e5SRahman Lavaee        << Twine::utohexstr(TextSection.EndAddress) << ", "
338733dc3e5SRahman Lavaee        << format("%.4f",
339733dc3e5SRahman Lavaee                  100.0 * SectionHotness[TextSection.Name] / NumTotalCounts)
340733dc3e5SRahman Lavaee        << "\n";
341733dc3e5SRahman Lavaee   }
342733dc3e5SRahman Lavaee   if (UnmappedHotness > 0)
343733dc3e5SRahman Lavaee     OS << "[unmapped], 0x0, 0x0, "
344733dc3e5SRahman Lavaee        << format("%.4f", 100.0 * UnmappedHotness / NumTotalCounts) << "\n";
345733dc3e5SRahman Lavaee }
346a34c753fSRafael Auler } // namespace bolt
347a34c753fSRafael Auler } // namespace llvm
348