File indexing completed on 2024-05-19 05:44:25

0001 /*
0002     SPDX-FileCopyrightText: 2015-2020 Milian Wolff <mail@milianw.de>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-or-later
0005 */
0006 
0007 #include "parser.h"
0008 
0009 #include <KLocalizedString>
0010 #include <ThreadWeaver/ThreadWeaver>
0011 
0012 #include <QDebug>
0013 #include <QElapsedTimer>
0014 #include <QThread>
0015 
0016 #include "analyze/accumulatedtracedata.h"
0017 
0018 #include <future>
0019 #include <tuple>
0020 #include <utility>
0021 #include <vector>
0022 
0023 #define TSL_NO_EXCEPTIONS 1
0024 #include <tsl/robin_map.h>
0025 #include <tsl/robin_set.h>
0026 
0027 #include <boost/functional/hash/hash.hpp>
0028 
0029 namespace std {
0030 template <>
0031 struct hash<std::pair<Symbol, Symbol>>
0032 {
0033     std::size_t operator()(const std::pair<Symbol, Symbol>& pair) const
0034     {
0035         return boost::hash_value(std::tie(pair.first.functionId.index, pair.first.moduleId.index,
0036                                           pair.second.functionId.index, pair.second.moduleId.index));
0037     }
0038 };
0039 }
0040 
0041 using namespace std;
0042 
0043 namespace {
0044 Symbol symbol(const Frame& frame, ModuleIndex moduleIndex)
0045 {
0046     return {frame.functionIndex, moduleIndex};
0047 }
0048 
0049 Symbol symbol(const InstructionPointer& ip)
0050 {
0051     return symbol(ip.frame, ip.moduleIndex);
0052 }
0053 
0054 struct Location
0055 {
0056     Symbol symbol;
0057     FileLine fileLine;
0058 };
0059 Location frameLocation(const Frame& frame, ModuleIndex moduleIndex)
0060 {
0061     return {symbol(frame, moduleIndex), {frame.fileIndex, frame.line}};
0062 }
0063 
0064 Location location(const InstructionPointer& ip)
0065 {
0066     return frameLocation(ip.frame, ip.moduleIndex);
0067 }
0068 
0069 struct ChartMergeData
0070 {
0071     IpIndex ip;
0072     qint64 consumed;
0073     qint64 allocations;
0074     qint64 temporary;
0075     bool operator<(const IpIndex rhs) const
0076     {
0077         return ip < rhs;
0078     }
0079 };
0080 
0081 const uint64_t MAX_CHART_DATAPOINTS = 500; // TODO: make this configurable via the GUI
0082 
0083 QVector<Suppression> toQt(const std::vector<Suppression>& suppressions)
0084 {
0085     QVector<Suppression> ret(suppressions.size());
0086     std::copy(suppressions.begin(), suppressions.end(), ret.begin());
0087     return ret;
0088 }
0089 }
0090 
0091 struct ParserData final : public AccumulatedTraceData
0092 {
0093     using TimestampCallback = std::function<void(const ParserData& data)>;
0094     ParserData(TimestampCallback timestampCallback)
0095         : timestampCallback(std::move(timestampCallback))
0096     {
0097     }
0098 
0099     void prepareBuildCharts(const std::shared_ptr<const ResultData>& resultData)
0100     {
0101         if (diffMode) {
0102             return;
0103         }
0104         consumedChartData.resultData = resultData;
0105         consumedChartData.rows.reserve(MAX_CHART_DATAPOINTS);
0106         allocationsChartData.resultData = resultData;
0107         allocationsChartData.rows.reserve(MAX_CHART_DATAPOINTS);
0108         temporaryChartData.resultData = resultData;
0109         temporaryChartData.rows.reserve(MAX_CHART_DATAPOINTS);
0110         // start off with null data at the origin
0111         lastTimeStamp = filterParameters.minTime;
0112         ChartRows origin;
0113         origin.timeStamp = lastTimeStamp;
0114         consumedChartData.rows.push_back(origin);
0115         allocationsChartData.rows.push_back(origin);
0116         temporaryChartData.rows.push_back(origin);
0117         // index 0 indicates the total row
0118         consumedChartData.labels[0] = {};
0119         allocationsChartData.labels[0] = {};
0120         temporaryChartData.labels[0] = {};
0121 
0122         buildCharts = true;
0123         maxConsumedSinceLastTimeStamp = 0;
0124         vector<ChartMergeData> merged;
0125         merged.reserve(instructionPointers.size());
0126         // merge the allocation cost by instruction pointer
0127         // TODO: traverse the merged call stack up until the first fork
0128         for (const auto& alloc : allocations) {
0129             const auto ip = findTrace(alloc.traceIndex).ipIndex;
0130             auto it = lower_bound(merged.begin(), merged.end(), ip);
0131             if (it == merged.end() || it->ip != ip) {
0132                 it = merged.insert(it, {ip, 0, 0, 0});
0133             }
0134             it->consumed += alloc.peak; // we want to track the top peaks in the chart
0135             it->allocations += alloc.allocations;
0136             it->temporary += alloc.temporary;
0137         }
0138         // find the top hot spots for the individual data members and remember their
0139         // IP and store the label
0140         tsl::robin_map<IpIndex, LabelIds> ipToLabelIds;
0141         auto findTopChartEntries = [&](qint64 ChartMergeData::*member, int LabelIds::*label, ChartData* data) {
0142             sort(merged.begin(), merged.end(), [=](const ChartMergeData& left, const ChartMergeData& right) {
0143                 return std::abs(left.*member) > std::abs(right.*member);
0144             });
0145             for (size_t i = 0; i < min(size_t(ChartRows::MAX_NUM_COST - 2), merged.size()); ++i) {
0146                 const auto& alloc = merged[i];
0147                 if (!(alloc.*member)) {
0148                     break;
0149                 }
0150                 (ipToLabelIds[alloc.ip].*label) = i + 1;
0151                 data->labels[i + 1] = symbol(findIp(alloc.ip));
0152                 Q_ASSERT(data->labels.size() < ChartRows::MAX_NUM_COST);
0153             }
0154         };
0155         ipToLabelIds.reserve(3 * ChartRows::MAX_NUM_COST);
0156         findTopChartEntries(&ChartMergeData::consumed, &LabelIds::consumed, &consumedChartData);
0157         findTopChartEntries(&ChartMergeData::allocations, &LabelIds::allocations, &allocationsChartData);
0158         findTopChartEntries(&ChartMergeData::temporary, &LabelIds::temporary, &temporaryChartData);
0159 
0160         // now iterate the allocations once to build the list of allocations
0161         // we need to look at when we are building the charts in handleTimeStamp
0162         // instead of doing this lookup every time we are handling a time stamp
0163         for (uint32_t i = 0, c = allocations.size(); i < c; ++i) {
0164             const auto ip = findTrace(allocations[i].traceIndex).ipIndex;
0165             auto it = ipToLabelIds.find(ip);
0166             if (it == ipToLabelIds.end())
0167                 continue;
0168             auto ids = it->second;
0169             ids.allocationIndex.index = i;
0170             labelIds.push_back(ids);
0171         }
0172     }
0173 
0174     void handleTimeStamp(int64_t /*oldStamp*/, int64_t newStamp, bool isFinalTimeStamp, ParsePass pass) override
0175     {
0176         if (timestampCallback) {
0177             timestampCallback(*this);
0178         }
0179         if (pass == ParsePass::FirstPass) {
0180             return;
0181         }
0182         if (!buildCharts || diffMode) {
0183             return;
0184         }
0185         maxConsumedSinceLastTimeStamp = max(maxConsumedSinceLastTimeStamp, totalCost.leaked);
0186         const auto timeSpan = (filterParameters.maxTime - filterParameters.minTime);
0187         const int64_t diffBetweenTimeStamps = timeSpan / MAX_CHART_DATAPOINTS;
0188         if (!isFinalTimeStamp && (newStamp - lastTimeStamp) < diffBetweenTimeStamps) {
0189             return;
0190         }
0191         const auto nowConsumed = maxConsumedSinceLastTimeStamp;
0192         maxConsumedSinceLastTimeStamp = 0;
0193         lastTimeStamp = newStamp;
0194 
0195         // create the rows
0196         auto createRow = [newStamp](int64_t totalCost) {
0197             ChartRows row;
0198             row.timeStamp = newStamp;
0199             row.cost[0] = totalCost;
0200             return row;
0201         };
0202         auto consumed = createRow(nowConsumed);
0203         auto allocs = createRow(totalCost.allocations);
0204         auto temporary = createRow(totalCost.temporary);
0205 
0206         // if the cost is non-zero and the ip corresponds to a hotspot function
0207         // selected in the labels, we add the cost to the rows column
0208         auto addDataToRow = [](int64_t cost, int labelId, ChartRows* rows) {
0209             if (!cost || labelId == -1) {
0210                 return;
0211             }
0212             rows->cost[labelId] += cost;
0213         };
0214         for (const auto& ids : labelIds) {
0215             const auto alloc = allocations[ids.allocationIndex.index];
0216             addDataToRow(alloc.leaked, ids.consumed, &consumed);
0217             addDataToRow(alloc.allocations, ids.allocations, &allocs);
0218             addDataToRow(alloc.temporary, ids.temporary, &temporary);
0219         }
0220         // add the rows for this time stamp
0221         consumedChartData.rows << consumed;
0222         allocationsChartData.rows << allocs;
0223         temporaryChartData.rows << temporary;
0224     }
0225 
0226     void handleAllocation(const AllocationInfo& info, const AllocationInfoIndex index) override
0227     {
0228         maxConsumedSinceLastTimeStamp = max(maxConsumedSinceLastTimeStamp, totalCost.leaked);
0229 
0230         if (index.index == allocationInfoCounter.size()) {
0231             allocationInfoCounter.push_back({info, 1});
0232         } else {
0233             ++allocationInfoCounter[index.index].allocations;
0234         }
0235     }
0236 
0237     void handleDebuggee(const char* command) override
0238     {
0239         debuggee = command;
0240     }
0241 
0242     void clearForReparse()
0243     {
0244         // data moved to size histogram
0245         {
0246             // we have to reset the allocation count
0247             for (auto& info : allocationInfoCounter)
0248                 info.allocations = 0;
0249             // and restore the order to allow fast direct access
0250             std::sort(allocationInfoCounter.begin(), allocationInfoCounter.end(),
0251                       [](const ParserData::CountedAllocationInfo& lhs, const ParserData::CountedAllocationInfo& rhs) {
0252                           return lhs.info.allocationIndex < rhs.info.allocationIndex;
0253                       });
0254         }
0255         // data moved to chart models
0256         consumedChartData = {};
0257         allocationsChartData = {};
0258         temporaryChartData = {};
0259         labelIds.clear();
0260         maxConsumedSinceLastTimeStamp = 0;
0261         lastTimeStamp = 0;
0262         buildCharts = false;
0263     }
0264 
0265     string debuggee;
0266 
0267     struct CountedAllocationInfo
0268     {
0269         AllocationInfo info;
0270         int64_t allocations;
0271         bool operator<(const CountedAllocationInfo& rhs) const
0272         {
0273             return tie(info.size, allocations) < tie(rhs.info.size, rhs.allocations);
0274         }
0275     };
0276     /// counts how often a given allocation info is encountered based on its index
0277     /// used to build the size histogram
0278     vector<CountedAllocationInfo> allocationInfoCounter;
0279 
0280     ChartData consumedChartData;
0281     ChartData allocationsChartData;
0282     ChartData temporaryChartData;
0283     // here we store the indices into ChartRows::cost for those IpIndices that
0284     // are within the top hotspots. This way, we can do one hash lookup in the
0285     // handleTimeStamp function instead of three when we'd store this data
0286     // in a per-ChartData hash.
0287     struct LabelIds
0288     {
0289         AllocationIndex allocationIndex;
0290         int consumed = -1;
0291         int allocations = -1;
0292         int temporary = -1;
0293     };
0294     vector<LabelIds> labelIds;
0295     int64_t maxConsumedSinceLastTimeStamp = 0;
0296     int64_t lastTimeStamp = 0;
0297 
0298     bool buildCharts = false;
0299     bool diffMode = false;
0300 
0301     TimestampCallback timestampCallback;
0302     QElapsedTimer parseTimer;
0303 
0304     QVector<QString> qtStrings;
0305 };
0306 
0307 namespace {
0308 void setParents(QVector<RowData>& children, const RowData* parent)
0309 {
0310     children.squeeze();
0311     for (auto& row : children) {
0312         row.parent = parent;
0313         setParents(row.children, &row);
0314     }
0315 }
0316 
0317 void addCallerCalleeEvent(const Location& location, const AllocationData& cost, tsl::robin_set<Symbol>* recursionGuard,
0318                           CallerCalleeResults* callerCalleeResult)
0319 {
0320     const auto isLeaf = recursionGuard->empty();
0321     if (!recursionGuard->insert(location.symbol).second) {
0322         return;
0323     }
0324 
0325     auto& entry = callerCalleeResult->entries[location.symbol];
0326     auto& locationCost = entry.sourceMap[location.fileLine];
0327 
0328     locationCost.inclusiveCost += cost;
0329     if (isLeaf) {
0330         // increment self cost for leaf
0331         locationCost.selfCost += cost;
0332     }
0333 }
0334 
0335 std::pair<TreeData, CallerCalleeResults> mergeAllocations(Parser* parser, const ParserData& data,
0336                                                           std::shared_ptr<const ResultData> resultData)
0337 {
0338     CallerCalleeResults callerCalleeResults;
0339     TreeData topRows;
0340     tsl::robin_set<TraceIndex> traceRecursionGuard;
0341     traceRecursionGuard.reserve(128);
0342     tsl::robin_set<Symbol> symbolRecursionGuard;
0343     symbolRecursionGuard.reserve(128);
0344     auto addRow = [&symbolRecursionGuard, &callerCalleeResults](QVector<RowData>* rows, const Location& location,
0345                                                                 const Allocation& cost) -> QVector<RowData>* {
0346         auto it = lower_bound(rows->begin(), rows->end(), location.symbol);
0347         if (it != rows->end() && it->symbol == location.symbol) {
0348             it->cost += cost;
0349         } else {
0350             it = rows->insert(it, {cost, location.symbol, nullptr, {}});
0351         }
0352         addCallerCalleeEvent(location, cost, &symbolRecursionGuard, &callerCalleeResults);
0353         return &it->children;
0354     };
0355     const auto allocationCount = data.allocations.size();
0356     const auto onePercent = std::max<size_t>(1, allocationCount / 100);
0357     auto progress = 0;
0358     // merge allocations, leave parent pointers invalid (their location may change)
0359     for (const auto& allocation : data.allocations) {
0360         auto traceIndex = allocation.traceIndex;
0361         auto rows = &topRows.rows;
0362         traceRecursionGuard.clear();
0363         traceRecursionGuard.insert(traceIndex);
0364         symbolRecursionGuard.clear();
0365         bool first = true;
0366         while (traceIndex || first) {
0367             first = false;
0368             const auto& trace = data.findTrace(traceIndex);
0369             const auto& ip = data.findIp(trace.ipIndex);
0370             rows = addRow(rows, location(ip), allocation);
0371             for (const auto& inlined : ip.inlined) {
0372                 const auto& inlinedLocation = frameLocation(inlined, ip.moduleIndex);
0373                 rows = addRow(rows, inlinedLocation, allocation);
0374             }
0375             if (data.isStopIndex(ip.frame.functionIndex)) {
0376                 break;
0377             }
0378             traceIndex = trace.parentIndex;
0379             if (!traceRecursionGuard.insert(traceIndex).second) {
0380                 qWarning() << "Trace recursion detected - corrupt data file?";
0381                 break;
0382             }
0383         }
0384         ++progress;
0385         if ((progress % onePercent) == 0) {
0386             const int percent = progress * 100 / allocationCount;
0387             emit parser->progressMessageAvailable(i18n("merging allocations... %1%", percent));
0388         }
0389     }
0390     // now set the parents, the data is constant from here on
0391     setParents(topRows.rows, nullptr);
0392 
0393     topRows.resultData = std::move(resultData);
0394     return {topRows, callerCalleeResults};
0395 }
0396 
0397 RowData* findBySymbol(Symbol symbol, QVector<RowData>* data)
0398 {
0399     auto it = std::find_if(data->begin(), data->end(), [symbol](const RowData& row) { return row.symbol == symbol; });
0400     return it == data->end() ? nullptr : &(*it);
0401 }
0402 
0403 AllocationData buildTopDown(const QVector<RowData>& bottomUpData, QVector<RowData>* topDownData)
0404 {
0405     AllocationData totalCost;
0406     for (const auto& row : bottomUpData) {
0407         // recurse and find the cost attributed to children
0408         const auto childCost = buildTopDown(row.children, topDownData);
0409         if (childCost != row.cost) {
0410             // this row is (partially) a leaf
0411             const auto cost = row.cost - childCost;
0412 
0413             // bubble up the parent chain to build a top-down tree
0414             auto node = &row;
0415             auto stack = topDownData;
0416             while (node) {
0417                 auto data = findBySymbol(node->symbol, stack);
0418                 if (!data) {
0419                     // create an empty top-down item for this bottom-up node
0420                     *stack << RowData {{}, node->symbol, nullptr, {}};
0421                     data = &stack->back();
0422                 }
0423                 // always use the leaf node's cost and propagate that one up the chain
0424                 // otherwise we'd count the cost of some nodes multiple times
0425                 data->cost += cost;
0426                 stack = &data->children;
0427                 node = node->parent;
0428             }
0429         }
0430         totalCost += row.cost;
0431     }
0432     return totalCost;
0433 }
0434 
0435 TreeData toTopDownData(const TreeData& bottomUpData)
0436 {
0437     TreeData topRows;
0438     topRows.resultData = bottomUpData.resultData;
0439     buildTopDown(bottomUpData.rows, &topRows.rows);
0440     // now set the parents, the data is constant from here on
0441     setParents(topRows.rows, nullptr);
0442     return topRows;
0443 }
0444 
0445 struct ReusableGuardBuffer
0446 {
0447     ReusableGuardBuffer()
0448     {
0449         recursionGuard.reserve(128);
0450         callerCalleeRecursionGuard.reserve(128);
0451     }
0452 
0453     void reset()
0454     {
0455         recursionGuard.clear();
0456         callerCalleeRecursionGuard.clear();
0457     }
0458 
0459     tsl::robin_set<Symbol> recursionGuard;
0460     tsl::robin_set<std::pair<Symbol, Symbol>> callerCalleeRecursionGuard;
0461 };
0462 
0463 AllocationData buildCallerCallee(const QVector<RowData>& bottomUpData, CallerCalleeResults* callerCalleeResults,
0464                                  ReusableGuardBuffer* guardBuffer)
0465 {
0466     AllocationData totalCost;
0467     for (const auto& row : bottomUpData) {
0468         // recurse to find a leaf
0469         const auto childCost = buildCallerCallee(row.children, callerCalleeResults, guardBuffer);
0470         if (childCost != row.cost) {
0471             // this row is (partially) a leaf
0472             const auto cost = row.cost - childCost;
0473 
0474             // leaf node found, bubble up the parent chain to add cost for all frames
0475             // to the caller/callee data. this is done top-down since we must not count
0476             // symbols more than once in the caller-callee data
0477             guardBuffer->reset();
0478             auto& recursionGuard = guardBuffer->recursionGuard;
0479             auto& callerCalleeRecursionGuard = guardBuffer->callerCalleeRecursionGuard;
0480 
0481             auto node = &row;
0482 
0483             Symbol lastSymbol;
0484             CallerCalleeEntry* lastEntry = nullptr;
0485 
0486             while (node) {
0487                 const auto symbol = node->symbol;
0488                 // aggregate caller-callee data
0489                 auto& entry = callerCalleeResults->entries[symbol];
0490                 if (recursionGuard.insert(symbol).second) {
0491                     // only increment inclusive cost once for a given stack
0492                     entry.inclusiveCost += cost;
0493                 }
0494                 if (!node->parent) {
0495                     // always increment the self cost
0496                     entry.selfCost += cost;
0497                 }
0498                 // add current entry as callee to last entry
0499                 // and last entry as caller to current entry
0500                 if (lastEntry) {
0501                     if (callerCalleeRecursionGuard.insert({symbol, lastSymbol}).second) {
0502                         lastEntry->callees[symbol] += cost;
0503                         entry.callers[lastSymbol] += cost;
0504                     }
0505                 }
0506 
0507                 node = node->parent;
0508                 lastSymbol = symbol;
0509                 lastEntry = &entry;
0510             }
0511         }
0512         totalCost += row.cost;
0513     }
0514     return totalCost;
0515 }
0516 
0517 CallerCalleeResults toCallerCalleeData(const TreeData& bottomUpData, const CallerCalleeResults& results, bool diffMode)
0518 {
0519     // copy the source map and continue from there
0520     auto callerCalleeResults = results;
0521     ReusableGuardBuffer guardBuffer;
0522     buildCallerCallee(bottomUpData.rows, &callerCalleeResults, &guardBuffer);
0523 
0524     if (diffMode) {
0525         // remove rows without cost
0526         for (auto it = callerCalleeResults.entries.begin(); it != callerCalleeResults.entries.end();) {
0527             if (it->inclusiveCost == AllocationData() && it->selfCost == AllocationData()) {
0528                 it = callerCalleeResults.entries.erase(it);
0529             } else {
0530                 ++it;
0531             }
0532         }
0533     }
0534 
0535     callerCalleeResults.resultData = bottomUpData.resultData;
0536     return callerCalleeResults;
0537 }
0538 
0539 struct MergedHistogramColumnData
0540 {
0541     Symbol symbol;
0542     int64_t allocations;
0543     int64_t totalAllocated;
0544     bool operator<(const Symbol& rhs) const
0545     {
0546         return symbol < rhs;
0547     }
0548 };
0549 
0550 HistogramData buildSizeHistogram(ParserData& data, std::shared_ptr<const ResultData> resultData)
0551 {
0552     HistogramData ret;
0553     if (data.allocationInfoCounter.empty()) {
0554         return ret;
0555     }
0556     sort(data.allocationInfoCounter.begin(), data.allocationInfoCounter.end());
0557     const auto totalLabel = i18n("total");
0558     HistogramRow row;
0559     const pair<uint64_t, QString> buckets[] = {{8, i18n("0B to 8B")},
0560                                                {16, i18n("9B to 16B")},
0561                                                {32, i18n("17B to 32B")},
0562                                                {64, i18n("33B to 64B")},
0563                                                {128, i18n("65B to 128B")},
0564                                                {256, i18n("129B to 256B")},
0565                                                {512, i18n("257B to 512B")},
0566                                                {1024, i18n("512B to 1KB")},
0567                                                {numeric_limits<uint64_t>::max(), i18n("more than 1KB")}};
0568     uint bucketIndex = 0;
0569     row.size = buckets[bucketIndex].first;
0570     row.sizeLabel = buckets[bucketIndex].second;
0571     vector<MergedHistogramColumnData> columnData;
0572     columnData.reserve(128);
0573     auto insertColumns = [&]() {
0574         sort(columnData.begin(), columnData.end(),
0575              [](const MergedHistogramColumnData& lhs, const MergedHistogramColumnData& rhs) {
0576                  return std::tie(lhs.allocations, lhs.totalAllocated) > std::tie(rhs.allocations, rhs.totalAllocated);
0577              });
0578         // -1 to account for total row
0579         for (size_t i = 0; i < min(columnData.size(), size_t(HistogramRow::NUM_COLUMNS - 1)); ++i) {
0580             const auto& column = columnData[i];
0581             row.columns[i + 1] = {column.allocations, column.totalAllocated, column.symbol};
0582         }
0583     };
0584     for (const auto& info : data.allocationInfoCounter) {
0585         if (info.info.size > row.size) {
0586             insertColumns();
0587             columnData.clear();
0588             ret.rows << row;
0589             ++bucketIndex;
0590             row.size = buckets[bucketIndex].first;
0591             row.sizeLabel = buckets[bucketIndex].second;
0592             row.columns[0] = {info.allocations, static_cast<qint64>(info.info.size * info.allocations), {}};
0593         } else {
0594             auto& column = row.columns[0];
0595             column.allocations += info.allocations;
0596             column.totalAllocated += info.info.size * info.allocations;
0597         }
0598         const auto& allocation = data.allocations[info.info.allocationIndex.index];
0599         const auto& ipIndex = data.findTrace(allocation.traceIndex).ipIndex;
0600         const auto& ip = data.findIp(ipIndex);
0601         const auto& sym = symbol(ip);
0602         auto it = lower_bound(columnData.begin(), columnData.end(), sym);
0603         if (it == columnData.end() || it->symbol != sym) {
0604             columnData.insert(it, {sym, info.allocations, static_cast<qint64>(info.info.size * info.allocations)});
0605         } else {
0606             it->allocations += info.allocations;
0607             it->totalAllocated += static_cast<qint64>(info.info.size * info.allocations);
0608         }
0609     }
0610     insertColumns();
0611     ret.rows << row;
0612     ret.resultData = std::move(resultData);
0613     return ret;
0614 }
0615 }
0616 
0617 Parser::Parser(QObject* parent)
0618     : QObject(parent)
0619 {
0620     qRegisterMetaType<SummaryData>();
0621 }
0622 
0623 Parser::~Parser() = default;
0624 
0625 bool Parser::isFiltered() const
0626 {
0627     if (!m_data)
0628         return false;
0629     return m_data->filterParameters.isFilteredByTime(m_data->totalTime);
0630 }
0631 
0632 void Parser::parse(const QString& path, const QString& diffBase, const FilterParameters& filterParameters,
0633                    StopAfter stopAfter)
0634 {
0635     parseImpl(path, diffBase, filterParameters, stopAfter);
0636 }
0637 
0638 void Parser::parseImpl(const QString& path, const QString& diffBase, const FilterParameters& filterParameters,
0639                        StopAfter stopAfter)
0640 {
0641     auto oldData = std::move(m_data);
0642     using namespace ThreadWeaver;
0643     stream() << make_job([this, oldData, path, diffBase, filterParameters, stopAfter]() {
0644         const auto isReparsing = (path == m_path && oldData && diffBase.isEmpty());
0645         auto parsingMsg = isReparsing ? i18n("reparsing data") : i18n("parsing data");
0646 
0647         auto updateProgress = [this, parsingMsg, lastPassCompletion = 0.f](const ParserData& data) mutable {
0648             auto passCompletion = 1.0 * data.parsingState.readCompressedByte / data.parsingState.fileSize;
0649             if (std::abs(lastPassCompletion - passCompletion) < 0.001) {
0650                 // don't spam the progress bar
0651                 return;
0652             }
0653 
0654             lastPassCompletion = passCompletion;
0655             const auto numPasses = data.diffMode ? 2 : 3;
0656             auto totalCompletion = (data.parsingState.pass + passCompletion) / numPasses;
0657             auto spentTime_ms = data.parseTimer.elapsed();
0658             auto totalRemainingTime_ms = (spentTime_ms / totalCompletion) * (1.0 - totalCompletion);
0659             auto message = i18n("%1 pass: %2/%3  spent: %4  remaining: %5", parsingMsg, data.parsingState.pass + 1,
0660                                 numPasses, Util::formatTime(spentTime_ms), Util::formatTime(totalRemainingTime_ms));
0661 
0662             emit progressMessageAvailable(message);
0663             emit progress(1000 * totalCompletion); // range is set as 0 to 1000 for fractional % bar display
0664         };
0665 
0666         const auto stdPath = path.toStdString();
0667         auto data = isReparsing ? oldData : make_shared<ParserData>(updateProgress);
0668         data->filterParameters = filterParameters;
0669 
0670         emit progressMessageAvailable(parsingMsg);
0671         data->parseTimer.start();
0672 
0673         if (!diffBase.isEmpty()) {
0674             ParserData diffData(nullptr); // currently we don't track the progress of diff parsing
0675             auto readBase = async(launch::async, [&diffData, diffBase, isReparsing]() {
0676                 return diffData.read(diffBase.toStdString(), isReparsing);
0677             });
0678             if (!data->read(stdPath, isReparsing)) {
0679                 emit failedToOpen(path);
0680                 return;
0681             }
0682             if (!readBase.get()) {
0683                 emit failedToOpen(diffBase);
0684                 return;
0685             }
0686             data->diff(diffData);
0687             data->diffMode = true;
0688         } else {
0689             if (!data->read(stdPath, isReparsing)) {
0690                 emit failedToOpen(path);
0691                 return;
0692             }
0693         }
0694 
0695         if (!isReparsing) {
0696             data->qtStrings.resize(data->strings.size());
0697             std::transform(data->strings.begin(), data->strings.end(), data->qtStrings.begin(),
0698                            [](const std::string& string) { return QString::fromStdString(string); });
0699         }
0700 
0701         data->applyLeakSuppressions();
0702 
0703         const auto resultData = std::make_shared<const ResultData>(data->totalCost, data->qtStrings);
0704 
0705         emit summaryAvailable({QString::fromStdString(data->debuggee), data->totalCost, data->totalTime,
0706                                data->filterParameters, data->peakTime, data->peakRSS * data->systemInfo.pageSize,
0707                                data->systemInfo.pages * data->systemInfo.pageSize, data->fromAttached,
0708                                data->totalLeakedSuppressed, toQt(data->suppressions)});
0709 
0710         if (stopAfter == StopAfter::Summary) {
0711             emit finished();
0712             return;
0713         }
0714 
0715         emit progressMessageAvailable(i18n("merging allocations..."));
0716         // merge allocations before modifying the data again
0717         const auto mergedAllocations = mergeAllocations(this, *data, resultData);
0718         emit bottomUpDataAvailable(mergedAllocations.first);
0719 
0720         if (stopAfter == StopAfter::BottomUp) {
0721             emit finished();
0722             return;
0723         }
0724 
0725         // also calculate the size histogram
0726         emit progressMessageAvailable(i18n("building size histogram..."));
0727         const auto sizeHistogram = buildSizeHistogram(*data, resultData);
0728         emit sizeHistogramDataAvailable(sizeHistogram);
0729         // now data can be modified again for the chart data evaluation
0730 
0731         if (stopAfter == StopAfter::SizeHistogram) {
0732             emit finished();
0733             return;
0734         }
0735 
0736         emit progress(0);
0737 
0738         const auto diffMode = data->diffMode;
0739         emit progressMessageAvailable(i18n("building charts..."));
0740         auto parallel = new Collection;
0741         *parallel << make_job([this, mergedAllocations, resultData]() {
0742             const auto topDownData = toTopDownData(mergedAllocations.first);
0743             emit topDownDataAvailable(topDownData);
0744         }) << make_job([this, mergedAllocations, diffMode]() {
0745             emit callerCalleeDataAvailable(
0746                 toCallerCalleeData(mergedAllocations.first, mergedAllocations.second, diffMode));
0747         });
0748         if (!data->diffMode && stopAfter != StopAfter::TopDownAndCallerCallee) {
0749             // only build charts when we are not diffing
0750             *parallel << make_job([this, data, stdPath, isReparsing, resultData]() {
0751                 // this mutates data, and thus anything running in parallel must
0752                 // not access data
0753                 data->prepareBuildCharts(resultData);
0754                 data->read(stdPath, AccumulatedTraceData::ThirdPass, isReparsing);
0755                 emit consumedChartDataAvailable(data->consumedChartData);
0756                 emit allocationsChartDataAvailable(data->allocationsChartData);
0757                 emit temporaryChartDataAvailable(data->temporaryChartData);
0758             });
0759         }
0760 
0761         emit progress(0);
0762 
0763         auto sequential = new Sequence;
0764         *sequential << parallel << make_job([this, data, path]() {
0765             QMetaObject::invokeMethod(this, [this, data, path]() {
0766                 Q_ASSERT(QThread::currentThread() == thread());
0767                 m_data = data;
0768                 m_data->clearForReparse();
0769                 m_path = path;
0770                 emit finished();
0771             });
0772         });
0773 
0774         stream() << sequential;
0775     });
0776 }
0777 
0778 void Parser::reparse(const FilterParameters& parameters_)
0779 {
0780     if (!m_data || m_data->diffMode)
0781         return;
0782 
0783     auto filterParameters = parameters_;
0784     filterParameters.minTime = std::max(int64_t(0), filterParameters.minTime);
0785     filterParameters.maxTime = std::min(m_data->totalTime, filterParameters.maxTime);
0786 
0787     parseImpl(m_path, {}, filterParameters, StopAfter::Finished);
0788 }
0789 
0790 #include "moc_parser.cpp"