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 }