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"