File indexing completed on 2024-05-19 05:44:27
0001 /* 0002 SPDX-FileCopyrightText: 2014-2016 Milian Wolff <mail@milianw.de> 0003 0004 SPDX-License-Identifier: LGPL-2.1-or-later 0005 */ 0006 0007 /** 0008 * @file heaptrack_print.cpp 0009 * 0010 * @brief Evaluate and print the collected heaptrack data. 0011 */ 0012 0013 #include <boost/program_options.hpp> 0014 0015 #include "analyze/accumulatedtracedata.h" 0016 #include "analyze/suppressions.h" 0017 0018 #include <future> 0019 #include <iomanip> 0020 #include <iostream> 0021 0022 #include <tsl/robin_set.h> 0023 0024 #include "util/config.h" 0025 0026 using namespace std; 0027 namespace po = boost::program_options; 0028 0029 namespace { 0030 0031 /** 0032 * Merged allocation information by instruction pointer outside of alloc funcs 0033 */ 0034 struct MergedAllocation : public AllocationData 0035 { 0036 // individual backtraces 0037 std::vector<Allocation> traces; 0038 // location 0039 IpIndex ipIndex; 0040 }; 0041 0042 class formatBytes 0043 { 0044 public: 0045 formatBytes(int64_t bytes, int width = 0) 0046 : m_bytes(bytes) 0047 , m_width(width) 0048 { 0049 } 0050 0051 friend ostream& operator<<(ostream& out, const formatBytes data); 0052 0053 private: 0054 int64_t m_bytes; 0055 int m_width; 0056 }; 0057 0058 template <typename Bytes> 0059 ostream& writeBytes(ostream& out, Bytes bytes, int width, const char* unit) 0060 { 0061 const auto unitLength = strlen(unit); 0062 if (width > static_cast<int>(unitLength)) { 0063 return out << fixed << setprecision(2) << setw(width - unitLength) << bytes << unit; 0064 } else { 0065 return out << fixed << setprecision(2) << bytes << *unit; 0066 } 0067 } 0068 0069 ostream& operator<<(ostream& out, const formatBytes data) 0070 { 0071 auto bytes = static_cast<double>(data.m_bytes); 0072 0073 static const auto units = {"B", "KB", "MB", "GB", "TB"}; 0074 auto unit = units.begin(); 0075 size_t i = 0; 0076 while (i < units.size() - 1 && std::abs(bytes) > 1000.) { 0077 bytes /= 1000.; 0078 ++i; 0079 ++unit; 0080 } 0081 0082 if (i == 0) { 0083 // no fractions for bytes 0084 return writeBytes(out, data.m_bytes, data.m_width, *unit); 0085 } else { 0086 return writeBytes(out, bytes, data.m_width, *unit); 0087 } 0088 } 0089 0090 enum CostType 0091 { 0092 Allocations, 0093 Temporary, 0094 Leaked, 0095 Peak 0096 }; 0097 0098 std::istream& operator>>(std::istream& in, CostType& type) 0099 { 0100 std::string token; 0101 in >> token; 0102 if (token == "allocations") 0103 type = Allocations; 0104 else if (token == "temporary") 0105 type = Temporary; 0106 else if (token == "leaked") 0107 type = Leaked; 0108 else if (token == "peak") 0109 type = Peak; 0110 else 0111 in.setstate(std::ios_base::failbit); 0112 return in; 0113 } 0114 0115 struct Printer final : public AccumulatedTraceData 0116 { 0117 void finalize() 0118 { 0119 applyLeakSuppressions(); 0120 filterAllocations(); 0121 mergedAllocations = mergeAllocations(allocations); 0122 } 0123 0124 void mergeAllocation(vector<MergedAllocation>* mergedAllocations, const Allocation& allocation) const 0125 { 0126 const auto trace = findTrace(allocation.traceIndex); 0127 const auto traceIp = findIp(trace.ipIndex); 0128 auto it = lower_bound(mergedAllocations->begin(), mergedAllocations->end(), traceIp, 0129 [this](const MergedAllocation& allocation, const InstructionPointer traceIp) -> bool { 0130 // Compare meta data without taking the instruction pointer address into account. 0131 // This is useful since sometimes, esp. when we lack debug symbols, the same 0132 // function allocates memory at different IP addresses which is pretty useless 0133 // information most of the time 0134 // TODO: make this configurable, but on-by-default 0135 const auto allocationIp = findIp(allocation.ipIndex); 0136 return allocationIp.compareWithoutAddress(traceIp); 0137 }); 0138 if (it == mergedAllocations->end() || !findIp(it->ipIndex).equalWithoutAddress(traceIp)) { 0139 MergedAllocation merged; 0140 merged.ipIndex = trace.ipIndex; 0141 it = mergedAllocations->insert(it, merged); 0142 } 0143 it->traces.push_back(allocation); 0144 } 0145 0146 // merge allocations so that different traces that point to the same 0147 // instruction pointer at the end where the allocation function is 0148 // called are combined 0149 vector<MergedAllocation> mergeAllocations(const vector<Allocation>& allocations) const 0150 { 0151 // TODO: merge deeper traces, i.e. A,B,C,D and A,B,C,F 0152 // should be merged to A,B,C: D & F 0153 // currently the below will only merge it to: A: B,C,D & B,C,F 0154 vector<MergedAllocation> ret; 0155 ret.reserve(allocations.size()); 0156 for (const Allocation& allocation : allocations) { 0157 mergeAllocation(&ret, allocation); 0158 } 0159 for (MergedAllocation& merged : ret) { 0160 for (const Allocation& allocation : merged.traces) { 0161 merged.allocations += allocation.allocations; 0162 merged.leaked += allocation.leaked; 0163 merged.peak += allocation.peak; 0164 merged.temporary += allocation.temporary; 0165 } 0166 } 0167 return ret; 0168 } 0169 0170 void filterAllocations() 0171 { 0172 if (filterBtFunction.empty()) { 0173 return; 0174 } 0175 allocations.erase(remove_if(allocations.begin(), allocations.end(), 0176 [&](const Allocation& allocation) -> bool { 0177 auto node = findTrace(allocation.traceIndex); 0178 while (node.ipIndex) { 0179 const auto& ip = findIp(node.ipIndex); 0180 if (isStopIndex(ip.frame.functionIndex)) { 0181 break; 0182 } 0183 auto matchFunction = [this](const Frame& frame) { 0184 return stringify(frame.functionIndex).find(filterBtFunction) 0185 != string::npos; 0186 }; 0187 if (matchFunction(ip.frame)) { 0188 return false; 0189 } 0190 for (const auto& inlined : ip.inlined) { 0191 if (matchFunction(inlined)) { 0192 return false; 0193 } 0194 } 0195 node = findTrace(node.parentIndex); 0196 }; 0197 return true; 0198 }), 0199 allocations.end()); 0200 } 0201 0202 void printIndent(ostream& out, size_t indent, const char* indentString = " ") const 0203 { 0204 while (indent--) { 0205 out << indentString; 0206 } 0207 } 0208 0209 void printIp(const IpIndex ip, ostream& out, const size_t indent = 0) const 0210 { 0211 printIp(findIp(ip), out, indent); 0212 } 0213 0214 void printIp(const InstructionPointer& ip, ostream& out, const size_t indent = 0, bool flameGraph = false) const 0215 { 0216 printIndent(out, indent); 0217 0218 if (ip.frame.functionIndex) { 0219 out << prettyFunction(stringify(ip.frame.functionIndex)); 0220 } else { 0221 out << "0x" << hex << ip.instructionPointer << dec; 0222 } 0223 0224 if (flameGraph) { 0225 // only print the file name but nothing else 0226 auto printFile = [this, &out](FileIndex fileIndex) { 0227 const auto& file = stringify(fileIndex); 0228 auto idx = file.find_last_of('/') + 1; 0229 out << " (" << file.substr(idx) << ")"; 0230 }; 0231 if (ip.frame.fileIndex) { 0232 printFile(ip.frame.fileIndex); 0233 } 0234 out << ';'; 0235 for (const auto& inlined : ip.inlined) { 0236 out << prettyFunction(stringify(inlined.functionIndex)); 0237 printFile(inlined.fileIndex); 0238 out << ';'; 0239 } 0240 return; 0241 } 0242 0243 out << '\n'; 0244 printIndent(out, indent + 1); 0245 0246 if (ip.frame.fileIndex) { 0247 out << "at " << stringify(ip.frame.fileIndex) << ':' << ip.frame.line << '\n'; 0248 printIndent(out, indent + 1); 0249 } 0250 0251 if (ip.moduleIndex) { 0252 out << "in " << stringify(ip.moduleIndex); 0253 } else { 0254 out << "in ??"; 0255 } 0256 out << '\n'; 0257 0258 for (const auto& inlined : ip.inlined) { 0259 printIndent(out, indent); 0260 out << prettyFunction(stringify(inlined.functionIndex)) << '\n'; 0261 printIndent(out, indent + 1); 0262 out << "at " << stringify(inlined.fileIndex) << ':' << inlined.line << '\n'; 0263 } 0264 } 0265 0266 void printBacktrace(const TraceIndex traceIndex, ostream& out, const size_t indent = 0, 0267 bool skipFirst = false) const 0268 { 0269 if (!traceIndex) { 0270 out << " ??"; 0271 return; 0272 } 0273 printBacktrace(findTrace(traceIndex), out, indent, skipFirst); 0274 } 0275 0276 void printBacktrace(TraceNode node, ostream& out, const size_t indent = 0, bool skipFirst = false) const 0277 { 0278 tsl::robin_set<TraceIndex> recursionGuard; 0279 while (node.ipIndex) { 0280 const auto& ip = findIp(node.ipIndex); 0281 if (!skipFirst) { 0282 printIp(ip, out, indent); 0283 } 0284 skipFirst = false; 0285 0286 if (isStopIndex(ip.frame.functionIndex)) { 0287 break; 0288 } 0289 0290 if (!recursionGuard.insert(node.parentIndex).second) { 0291 cerr << "Trace recursion detected - corrupt data file? " << node.parentIndex.index << endl; 0292 break; 0293 } 0294 node = findTrace(node.parentIndex); 0295 }; 0296 } 0297 0298 /** 0299 * recursive top-down printer in the format 0300 * 0301 * func1;func2 (file);func2 (file); 0302 */ 0303 void printFlamegraph(TraceNode node, ostream& out) const 0304 { 0305 if (!node.ipIndex) { 0306 return; 0307 } 0308 0309 const auto& ip = findIp(node.ipIndex); 0310 0311 if (!isStopIndex(ip.frame.functionIndex)) { 0312 printFlamegraph(findTrace(node.parentIndex), out); 0313 } 0314 printIp(ip, out, 0, true); 0315 } 0316 0317 template <typename T, typename LabelPrinter, typename SubLabelPrinter> 0318 void printAllocations(T AllocationData::*member, LabelPrinter label, SubLabelPrinter sublabel) 0319 { 0320 if (mergeBacktraces) { 0321 printMerged(member, label, sublabel); 0322 } else { 0323 printUnmerged(member, label); 0324 } 0325 } 0326 0327 template <typename T, typename LabelPrinter, typename SubLabelPrinter> 0328 void printMerged(T AllocationData::*member, LabelPrinter label, SubLabelPrinter sublabel) 0329 { 0330 auto sortOrder = [member](const AllocationData& l, const AllocationData& r) { 0331 return std::abs(l.*member) > std::abs(r.*member); 0332 }; 0333 sort(mergedAllocations.begin(), mergedAllocations.end(), sortOrder); 0334 for (size_t i = 0; i < min(peakLimit, mergedAllocations.size()); ++i) { 0335 auto& allocation = mergedAllocations[i]; 0336 if (!(allocation.*member)) { 0337 break; 0338 } 0339 label(allocation); 0340 printIp(allocation.ipIndex, cout); 0341 0342 if (!allocation.ipIndex) { 0343 continue; 0344 } 0345 0346 sort(allocation.traces.begin(), allocation.traces.end(), sortOrder); 0347 int64_t handled = 0; 0348 for (size_t j = 0; j < min(subPeakLimit, allocation.traces.size()); ++j) { 0349 const auto& trace = allocation.traces[j]; 0350 if (!(trace.*member)) { 0351 break; 0352 } 0353 sublabel(trace); 0354 handled += trace.*member; 0355 printBacktrace(trace.traceIndex, cout, 2, true); 0356 } 0357 if (allocation.traces.size() > subPeakLimit) { 0358 cout << " and "; 0359 if (member == &AllocationData::allocations) { 0360 cout << (allocation.*member - handled); 0361 } else { 0362 cout << formatBytes(allocation.*member - handled); 0363 } 0364 cout << " from " << (allocation.traces.size() - subPeakLimit) << " other places\n"; 0365 } 0366 cout << '\n'; 0367 } 0368 } 0369 0370 template <typename T, typename LabelPrinter> 0371 void printUnmerged(T AllocationData::*member, LabelPrinter label) 0372 { 0373 sort(allocations.begin(), allocations.end(), 0374 [member](const Allocation& l, const Allocation& r) { return std::abs(l.*member) > std::abs(r.*member); }); 0375 for (size_t i = 0; i < min(peakLimit, allocations.size()); ++i) { 0376 const auto& allocation = allocations[i]; 0377 if (!(allocation.*member)) { 0378 break; 0379 } 0380 label(allocation); 0381 printBacktrace(allocation.traceIndex, cout, 1); 0382 cout << '\n'; 0383 } 0384 cout << endl; 0385 } 0386 0387 void writeMassifHeader(const char* command) 0388 { 0389 // write massif header 0390 massifOut << "desc: heaptrack\n" 0391 << "cmd: " << command << '\n' 0392 << "time_unit: s\n"; 0393 } 0394 0395 void writeMassifSnapshot(size_t timeStamp, bool isLast) 0396 { 0397 if (!lastMassifPeak) { 0398 lastMassifPeak = totalCost.leaked; 0399 massifAllocations = allocations; 0400 } 0401 massifOut << "#-----------\n" 0402 << "snapshot=" << massifSnapshotId << '\n' 0403 << "#-----------\n" 0404 << "time=" << (0.001 * timeStamp) << '\n' 0405 << "mem_heap_B=" << lastMassifPeak << '\n' 0406 << "mem_heap_extra_B=0\n" 0407 << "mem_stacks_B=0\n"; 0408 0409 if (massifDetailedFreq && (isLast || !(massifSnapshotId % massifDetailedFreq))) { 0410 massifOut << "heap_tree=detailed\n"; 0411 const size_t threshold = double(lastMassifPeak) * massifThreshold * 0.01; 0412 writeMassifBacktrace(massifAllocations, lastMassifPeak, threshold, IpIndex()); 0413 } else { 0414 massifOut << "heap_tree=empty\n"; 0415 } 0416 0417 ++massifSnapshotId; 0418 lastMassifPeak = 0; 0419 } 0420 0421 void writeMassifBacktrace(const vector<Allocation>& allocations, size_t heapSize, size_t threshold, 0422 const IpIndex& location, size_t depth = 0) 0423 { 0424 int64_t skippedLeaked = 0; 0425 size_t numAllocs = 0; 0426 size_t skipped = 0; 0427 auto mergedAllocations = mergeAllocations(allocations); 0428 sort(mergedAllocations.begin(), mergedAllocations.end(), 0429 [](const MergedAllocation& l, const MergedAllocation& r) { return l.leaked > r.leaked; }); 0430 0431 const auto ip = findIp(location); 0432 0433 // skip anything below main 0434 const bool shouldStop = isStopIndex(ip.frame.functionIndex); 0435 if (!shouldStop) { 0436 for (auto& merged : mergedAllocations) { 0437 if (merged.leaked < 0) { 0438 // list is sorted, so we can bail out now - these entries are 0439 // uninteresting for massif 0440 break; 0441 } 0442 0443 // skip items below threshold 0444 if (static_cast<size_t>(merged.leaked) >= threshold) { 0445 ++numAllocs; 0446 // skip the first level of the backtrace, otherwise we'd endlessly 0447 // recurse 0448 for (auto& alloc : merged.traces) { 0449 alloc.traceIndex = findTrace(alloc.traceIndex).parentIndex; 0450 } 0451 } else { 0452 ++skipped; 0453 skippedLeaked += merged.leaked; 0454 } 0455 } 0456 } 0457 0458 // TODO: write inlined frames out to massif files 0459 printIndent(massifOut, depth, " "); 0460 massifOut << 'n' << (numAllocs + (skipped ? 1 : 0)) << ": " << heapSize; 0461 if (!depth) { 0462 massifOut << " (heap allocation functions) malloc/new/new[], " 0463 "--alloc-fns, etc.\n"; 0464 } else { 0465 massifOut << " 0x" << hex << ip.instructionPointer << dec << ": "; 0466 if (ip.frame.functionIndex) { 0467 massifOut << stringify(ip.frame.functionIndex); 0468 } else { 0469 massifOut << "???"; 0470 } 0471 0472 massifOut << " ("; 0473 if (ip.frame.fileIndex) { 0474 massifOut << stringify(ip.frame.fileIndex) << ':' << ip.frame.line; 0475 } else if (ip.moduleIndex) { 0476 massifOut << stringify(ip.moduleIndex); 0477 } else { 0478 massifOut << "???"; 0479 } 0480 massifOut << ")\n"; 0481 } 0482 0483 auto writeSkipped = [&] { 0484 if (skipped) { 0485 printIndent(massifOut, depth, " "); 0486 massifOut << " n0: " << skippedLeaked << " in " << skipped << " places, all below massif's threshold (" 0487 << massifThreshold << ")\n"; 0488 skipped = 0; 0489 } 0490 }; 0491 0492 if (!shouldStop) { 0493 for (const auto& merged : mergedAllocations) { 0494 if (merged.leaked > 0 && static_cast<size_t>(merged.leaked) >= threshold) { 0495 if (skippedLeaked > merged.leaked) { 0496 // manually inject this entry to keep the output sorted 0497 writeSkipped(); 0498 } 0499 writeMassifBacktrace(merged.traces, merged.leaked, threshold, merged.ipIndex, depth + 1); 0500 } 0501 } 0502 writeSkipped(); 0503 } 0504 } 0505 0506 void handleAllocation(const AllocationInfo& info, const AllocationInfoIndex /*index*/) override 0507 { 0508 if (printHistogram) { 0509 ++sizeHistogram[info.size]; 0510 } 0511 0512 if (totalCost.leaked > 0 && static_cast<size_t>(totalCost.leaked) > lastMassifPeak && massifOut.is_open()) { 0513 massifAllocations = allocations; 0514 lastMassifPeak = totalCost.leaked; 0515 } 0516 } 0517 0518 void handleTimeStamp(int64_t /*oldStamp*/, int64_t newStamp, bool isFinalTimeStamp, ParsePass pass) override 0519 { 0520 if (pass != ParsePass::FirstPass) { 0521 return; 0522 } 0523 if (massifOut.is_open()) { 0524 writeMassifSnapshot(newStamp, isFinalTimeStamp); 0525 } 0526 } 0527 0528 void handleDebuggee(const char* command) override 0529 { 0530 cout << "Debuggee command was: " << command << endl; 0531 if (massifOut.is_open()) { 0532 writeMassifHeader(command); 0533 } 0534 } 0535 0536 bool printHistogram = false; 0537 bool mergeBacktraces = true; 0538 0539 vector<MergedAllocation> mergedAllocations; 0540 0541 std::map<uint64_t, uint64_t> sizeHistogram; 0542 0543 uint64_t massifSnapshotId = 0; 0544 uint64_t lastMassifPeak = 0; 0545 vector<Allocation> massifAllocations; 0546 ofstream massifOut; 0547 double massifThreshold = 1; 0548 uint64_t massifDetailedFreq = 1; 0549 0550 string filterBtFunction; 0551 size_t peakLimit = 10; 0552 size_t subPeakLimit = 5; 0553 }; 0554 } 0555 0556 int main(int argc, char** argv) 0557 { 0558 po::options_description desc("Options", 120, 60); 0559 // clang-format off 0560 desc.add_options() 0561 ("file,f", po::value<string>(), 0562 "The heaptrack data file to print.") 0563 ("diff,d", po::value<string>()->default_value({}), 0564 "Find the differences to this file.") 0565 ("shorten-templates,t", po::value<bool>()->default_value(true)->implicit_value(true), 0566 "Shorten template identifiers.") 0567 ("merge-backtraces,m", po::value<bool>()->default_value(true)->implicit_value(true), 0568 "Merge backtraces.\nNOTE: the merged peak consumption is not correct.") 0569 ("print-peaks,p", po::value<bool>()->default_value(true)->implicit_value(true), 0570 "Print backtraces to top allocators, sorted by peak consumption.") 0571 ("print-allocators,a", po::value<bool>()->default_value(true)->implicit_value(true), 0572 "Print backtraces to top allocators, sorted by number of calls to allocation functions.") 0573 ("print-temporary,T", po::value<bool>()->default_value(true)->implicit_value(true), 0574 "Print backtraces to top allocators, sorted by number of temporary allocations.") 0575 ("print-leaks,l", po::value<bool>()->default_value(false)->implicit_value(true), 0576 "Print backtraces to leaked memory allocations.") 0577 ("peak-limit,n", po::value<size_t>()->default_value(10)->implicit_value(10), 0578 "Limit the number of reported peaks.") 0579 ("sub-peak-limit,s", po::value<size_t>()->default_value(5)->implicit_value(5), 0580 "Limit the number of reported backtraces of merged peak locations.") 0581 ("print-histogram,H", po::value<string>()->default_value(string()), 0582 "Path to output file where an allocation size histogram will be written to.") 0583 ("flamegraph-cost-type", po::value<CostType>()->default_value(Allocations), 0584 "The cost type to use when generating a flamegraph. Possible options are:\n" 0585 " - allocations: number of allocations\n" 0586 " - temporary: number of temporary allocations\n" 0587 " - leaked: bytes not deallocated at the end\n" 0588 " - peak: bytes consumed at highest total memory consumption") 0589 ("print-flamegraph,F", po::value<string>()->default_value(string()), 0590 "Path to output file where a flame-graph compatible stack file will be written to.\n" 0591 "To visualize the resulting file, use flamegraph.pl from " 0592 "https://github.com/brendangregg/FlameGraph:\n" 0593 " heaptrack_print heaptrack.someapp.PID.gz -F stacks.txt\n" 0594 " # optionally pass --reverse to flamegraph.pl\n" 0595 " flamegraph.pl --title \"heaptrack: allocations\" --colors mem \\\n" 0596 " --countname allocations < stacks.txt > heaptrack.someapp.PID.svg\n" 0597 " [firefox|chromium] heaptrack.someapp.PID.svg\n") 0598 ("print-massif,M", po::value<string>()->default_value(string()), 0599 "Path to output file where a massif compatible data file will be written to.") 0600 ("massif-threshold", po::value<double>()->default_value(1.), 0601 "Percentage of current memory usage, below which allocations are aggregated into a 'below threshold' entry.\n" 0602 "This is only used in the massif output file so far.\n") 0603 ("massif-detailed-freq", po::value<size_t>()->default_value(2), 0604 "Frequency of detailed snapshots in the massif output file. Increase this to reduce the file size.\n" 0605 "You can set the value to zero to disable detailed snapshots.\n") 0606 ("filter-bt-function", po::value<string>()->default_value(string()), 0607 "Only print allocations where the backtrace contains the given function.") 0608 ("suppressions", po::value<string>()->default_value(string()), 0609 "Load list of leak suppressions from the specified file. Specify one suppression per line, and start each line with 'leak:', i.e. use the LSAN suppression file format.") 0610 ("disable-embedded-suppressions", 0611 "Ignore suppression definitions that are embedded into the heaptrack data file. By default, heaptrack will copy the suppressions" 0612 "optionally defined via a `const char *__lsan_default_suppressions()` symbol in the debuggee application. These are then always " 0613 "applied when analyzing the data, unless this feature is explicitly disabled using this command line option.") 0614 ("disable-builtin-suppressions", 0615 "Ignore suppression definitions that are built into heaptrack. By default, heaptrack will suppress certain " 0616 "known leaks from common system libraries.") 0617 ("print-suppressions", po::value<bool>()->default_value(false)->implicit_value(true), 0618 "Show statistics for matched suppressions.") 0619 ("help,h", "Show this help message.") 0620 ("version,v", "Displays version information."); 0621 // clang-format on 0622 po::positional_options_description p; 0623 p.add("file", -1); 0624 0625 po::variables_map vm; 0626 try { 0627 po::store(po::command_line_parser(argc, argv).options(desc).positional(p).run(), vm); 0628 if (vm.count("help")) { 0629 cout << "heaptrack_print - analyze heaptrack data files.\n" 0630 << "\n" 0631 << "heaptrack is a heap memory profiler which records information\n" 0632 << "about calls to heap allocation functions such as malloc, " 0633 "operator new etc. pp.\n" 0634 << "This print utility can then be used to analyze the generated " 0635 "data files.\n\n" 0636 << desc << endl; 0637 return 0; 0638 } else if (vm.count("version")) { 0639 cout << "heaptrack_print " << HEAPTRACK_VERSION_STRING << endl; 0640 return 0; 0641 } 0642 po::notify(vm); 0643 } catch (const po::error& error) { 0644 cerr << "ERROR: " << error.what() << endl << endl << desc << endl; 0645 return 1; 0646 } 0647 0648 if (!vm.count("file")) { 0649 // NOTE: stay backwards compatible to old boost 1.41 available in RHEL 6 0650 // otherwise, we could simplify this by setting the file option 0651 // as ->required() using the new 1.42 boost API 0652 cerr << "ERROR: the option '--file' is required but missing\n\n" << desc << endl; 0653 return 1; 0654 } 0655 0656 Printer data; 0657 0658 const auto inputFile = vm["file"].as<string>(); 0659 const auto diffFile = vm["diff"].as<string>(); 0660 data.shortenTemplates = vm["shorten-templates"].as<bool>(); 0661 data.mergeBacktraces = vm["merge-backtraces"].as<bool>(); 0662 data.filterBtFunction = vm["filter-bt-function"].as<string>(); 0663 data.peakLimit = vm["peak-limit"].as<size_t>(); 0664 data.subPeakLimit = vm["sub-peak-limit"].as<size_t>(); 0665 const string printHistogram = vm["print-histogram"].as<string>(); 0666 data.printHistogram = !printHistogram.empty(); 0667 const string printFlamegraph = vm["print-flamegraph"].as<string>(); 0668 const auto flamegraphCostType = vm["flamegraph-cost-type"].as<CostType>(); 0669 const string printMassif = vm["print-massif"].as<string>(); 0670 if (!printMassif.empty()) { 0671 data.massifOut.open(printMassif, ios_base::out); 0672 if (!data.massifOut.is_open()) { 0673 cerr << "Failed to open massif output file \"" << printMassif << "\"." << endl; 0674 return 1; 0675 } 0676 data.massifThreshold = vm["massif-threshold"].as<double>(); 0677 data.massifDetailedFreq = vm["massif-detailed-freq"].as<size_t>(); 0678 } 0679 const bool printLeaks = vm["print-leaks"].as<bool>(); 0680 const bool printPeaks = vm["print-peaks"].as<bool>(); 0681 const bool printAllocs = vm["print-allocators"].as<bool>(); 0682 const bool printTemporary = vm["print-temporary"].as<bool>(); 0683 const auto printSuppressions = vm["print-suppressions"].as<bool>(); 0684 const auto suppressionsFile = vm["suppressions"].as<string>(); 0685 0686 data.filterParameters.disableEmbeddedSuppressions = vm.count("disable-embedded-suppressions"); 0687 data.filterParameters.disableBuiltinSuppressions = vm.count("disable-builtin-suppressions"); 0688 bool suppressionsOk = false; 0689 data.filterParameters.suppressions = parseSuppressions(suppressionsFile, &suppressionsOk); 0690 if (!suppressionsOk) { 0691 return 1; 0692 } 0693 0694 cout << "reading file \"" << inputFile << "\" - please wait, this might take some time..." << endl; 0695 0696 if (!diffFile.empty()) { 0697 cout << "reading diff file \"" << diffFile << "\" - please wait, this might take some time..." << endl; 0698 Printer diffData; 0699 auto diffRead = async(launch::async, [&diffData, diffFile]() { return diffData.read(diffFile, false); }); 0700 0701 if (!data.read(inputFile, false) || !diffRead.get()) { 0702 return 1; 0703 } 0704 0705 data.diff(diffData); 0706 } else if (!data.read(inputFile, false)) { 0707 return 1; 0708 } 0709 0710 data.finalize(); 0711 0712 cout << "finished reading file, now analyzing data:\n" << endl; 0713 0714 if (printAllocs) { 0715 // sort by amount of allocations 0716 cout << "MOST CALLS TO ALLOCATION FUNCTIONS\n"; 0717 data.printAllocations( 0718 &AllocationData::allocations, 0719 [](const AllocationData& data) { 0720 cout << data.allocations << " calls to allocation functions with " << formatBytes(data.peak) 0721 << " peak consumption from\n"; 0722 }, 0723 [](const AllocationData& data) { 0724 cout << data.allocations << " calls with " << formatBytes(data.peak) << " peak consumption from:\n"; 0725 }); 0726 cout << endl; 0727 } 0728 0729 if (printPeaks) { 0730 cout << "PEAK MEMORY CONSUMERS\n"; 0731 data.printAllocations( 0732 &AllocationData::peak, 0733 [](const AllocationData& data) { 0734 cout << formatBytes(data.peak) << " peak memory consumed over " << data.allocations << " calls from\n"; 0735 }, 0736 [](const AllocationData& data) { 0737 cout << formatBytes(data.peak) << " consumed over " << data.allocations << " calls from:\n"; 0738 }); 0739 cout << endl; 0740 } 0741 0742 if (printLeaks) { 0743 // sort by amount of leaks 0744 cout << "MEMORY LEAKS\n"; 0745 data.printAllocations( 0746 &AllocationData::leaked, 0747 [](const AllocationData& data) { 0748 cout << formatBytes(data.leaked) << " leaked over " << data.allocations << " calls from\n"; 0749 }, 0750 [](const AllocationData& data) { 0751 cout << formatBytes(data.leaked) << " leaked over " << data.allocations << " calls from:\n"; 0752 }); 0753 cout << endl; 0754 } 0755 0756 if (printTemporary) { 0757 // sort by amount of temporary allocations 0758 cout << "MOST TEMPORARY ALLOCATIONS\n"; 0759 data.printAllocations( 0760 &AllocationData::temporary, 0761 [](const AllocationData& data) { 0762 cout << data.temporary << " temporary allocations of " << data.allocations << " allocations in total (" 0763 << fixed << setprecision(2) << (float(data.temporary) * 100.f / data.allocations) << "%) from\n"; 0764 }, 0765 [](const AllocationData& data) { 0766 cout << data.temporary << " temporary allocations of " << data.allocations << " allocations in total (" 0767 << fixed << setprecision(2) << (float(data.temporary) * 100.f / data.allocations) << "%) from:\n"; 0768 }); 0769 cout << endl; 0770 } 0771 0772 const double totalTimeS = data.totalTime ? (1000. / data.totalTime) : 1.; 0773 cout << "total runtime: " << fixed << (data.totalTime / 1000.) << "s.\n" 0774 << "calls to allocation functions: " << data.totalCost.allocations << " (" 0775 << int64_t(data.totalCost.allocations * totalTimeS) << "/s)\n" 0776 << "temporary memory allocations: " << data.totalCost.temporary << " (" 0777 << int64_t(data.totalCost.temporary * totalTimeS) << "/s)\n" 0778 << "peak heap memory consumption: " << formatBytes(data.totalCost.peak) << '\n' 0779 << "peak RSS (including heaptrack overhead): " << formatBytes(data.peakRSS * data.systemInfo.pageSize) << '\n' 0780 << "total memory leaked: " << formatBytes(data.totalCost.leaked) << '\n'; 0781 if (data.totalLeakedSuppressed) { 0782 cout << "suppressed leaks: " << formatBytes(data.totalLeakedSuppressed) << '\n'; 0783 0784 if (printSuppressions) { 0785 cout << "Suppressions used:\n"; 0786 cout << setw(16) << "matches" << ' ' << setw(16) << "leaked" 0787 << " pattern\n"; 0788 for (const auto& suppression : data.suppressions) { 0789 if (!suppression.matches) { 0790 continue; 0791 } 0792 cout << setw(16) << suppression.matches << ' ' << formatBytes(suppression.leaked, 16) << ' ' 0793 << suppression.pattern << '\n'; 0794 } 0795 } 0796 } 0797 0798 if (!printHistogram.empty()) { 0799 ofstream histogram(printHistogram, ios_base::out); 0800 if (!histogram.is_open()) { 0801 cerr << "Failed to open histogram output file \"" << printHistogram << "\"." << endl; 0802 } else { 0803 for (auto entry : data.sizeHistogram) { 0804 histogram << entry.first << '\t' << entry.second << '\n'; 0805 } 0806 } 0807 } 0808 0809 if (!printFlamegraph.empty()) { 0810 ofstream flamegraph(printFlamegraph, ios_base::out); 0811 if (!flamegraph.is_open()) { 0812 cerr << "Failed to open flamegraph output file \"" << printFlamegraph << "\"." << endl; 0813 } else { 0814 for (const auto& allocation : data.allocations) { 0815 if (!allocation.traceIndex) { 0816 flamegraph << "??"; 0817 } else { 0818 data.printFlamegraph(data.findTrace(allocation.traceIndex), flamegraph); 0819 } 0820 flamegraph << ' '; 0821 switch (flamegraphCostType) { 0822 case Allocations: 0823 flamegraph << allocation.allocations; 0824 break; 0825 case Temporary: 0826 flamegraph << allocation.temporary; 0827 break; 0828 case Peak: 0829 flamegraph << allocation.peak; 0830 break; 0831 case Leaked: 0832 flamegraph << allocation.leaked; 0833 break; 0834 } 0835 flamegraph << '\n'; 0836 } 0837 } 0838 } 0839 0840 return 0; 0841 }