File indexing completed on 2024-04-28 17:02:20
0001 /* 0002 This file is part of Massif Visualizer 0003 0004 Copyright 2010 Milian Wolff <mail@milianw.de> 0005 0006 This library is free software; you can redistribute it and/or 0007 modify it under the terms of the GNU Lesser General Public 0008 License as published by the Free Software Foundation; either 0009 version 2.1 of the License, or (at your option) version 3, or any 0010 later version accepted by the membership of KDE e.V. (or its 0011 successor approved by the membership of KDE e.V.), which shall 0012 act as a proxy defined in Section 6 of version 3 of the license. 0013 0014 This library is distributed in the hope that it will be useful, 0015 but WITHOUT ANY WARRANTY; without even the implied warranty of 0016 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0017 Lesser General Public License for more details. 0018 0019 You should have received a copy of the GNU Lesser General Public 0020 License along with this library. If not, see <http://www.gnu.org/licenses/>. 0021 */ 0022 0023 #include "parserprivate.h" 0024 0025 #include "filedata.h" 0026 #include "snapshotitem.h" 0027 #include "treeleafitem.h" 0028 #include "util.h" 0029 #include "parser.h" 0030 0031 #include <QtCore/QIODevice> 0032 0033 #include <QtCore/QDebug> 0034 0035 using namespace Massif; 0036 0037 #define VALIDATE(l, x) if (!(x)) { m_errorLineString = l; m_error = Invalid; return; } 0038 0039 #define VALIDATE_RETURN(l, x, y) if (!(x)) { m_errorLineString = l; m_error = Invalid; return y; } 0040 0041 static QByteArray midRef(const QByteArray& line, int offset, int length = -1) 0042 { 0043 return QByteArray::fromRawData(line.data() + offset, length == -1 ? (line.length() - offset) : length); 0044 } 0045 0046 ParserPrivate::ParserPrivate(Parser* parser, QIODevice* file, FileData* data, 0047 const QStringList& customAllocators, 0048 QAtomicInt* shouldStop) 0049 : m_parser(parser) 0050 , m_file(file) 0051 , m_data(data) 0052 , m_nextLine(FileDesc) 0053 , m_currentLine(0) 0054 , m_error(NoError) 0055 , m_snapshot(0) 0056 , m_parentItem(0) 0057 , m_hadCustomAllocators(false) 0058 , m_expectedSnapshots(100) 0059 { 0060 foreach(const QString& allocator, customAllocators) { 0061 if (allocator.contains(QLatin1Char('*'))) { 0062 m_allocators << QRegExp(allocator, Qt::CaseSensitive, QRegExp::Wildcard); 0063 } else { 0064 m_plainAllocators << allocator.toLatin1(); 0065 } 0066 } 0067 0068 while (!file->atEnd()) { 0069 if (shouldStop && *shouldStop) { 0070 m_error = Stopped; 0071 return; 0072 } 0073 if (file->size()) { 0074 // use pos to determine progress when reading the file, won't work for compressed files 0075 parser->setProgress(static_cast<int>(double(file->pos() * 100) / file->size())); 0076 } 0077 const QByteArray& line = readLine(); 0078 switch (m_nextLine) { 0079 case FileDesc: 0080 parseFileDesc(line); 0081 break; 0082 case FileCmd: 0083 parseFileCmd(line); 0084 break; 0085 case FileTimeUnit: 0086 parseFileTimeUnit(line); 0087 break; 0088 case Snapshot: 0089 parseSnapshot(line); 0090 break; 0091 case SnapshotHeapTree: 0092 parseSnapshotHeapTree(line); 0093 break; 0094 case SnapshotMemHeap: 0095 parseSnapshotMemHeap(line); 0096 break; 0097 case SnapshotMemHeapExtra: 0098 parseSnapshotMemHeapExtra(line); 0099 break; 0100 case SnapshotTime: 0101 parseSnapshotTime(line); 0102 break; 0103 case SnapshotMemStacks: 0104 parseSnapshotMemStacks(line); 0105 break; 0106 case HeapTreeLeaf: 0107 parseHeapTreeLeaf(line); 0108 break; 0109 } 0110 if (m_error != NoError) { 0111 qWarning() << "invalid line" << m_currentLine << line; 0112 m_error = Invalid; 0113 if (m_errorLineString.isEmpty()) { 0114 m_errorLineString = line; 0115 } 0116 // we use fromRawData, so better detach here 0117 m_errorLineString.detach(); 0118 break; 0119 } 0120 } 0121 if (!file->atEnd()) { 0122 m_error = Invalid; 0123 } 0124 } 0125 0126 ParserPrivate::~ParserPrivate() 0127 { 0128 } 0129 0130 ParserPrivate::Error ParserPrivate::error() const 0131 { 0132 return m_error; 0133 } 0134 0135 int ParserPrivate::errorLine() const 0136 { 0137 if (m_error == Invalid) { 0138 return m_currentLine; 0139 } else { 0140 return -1; 0141 } 0142 } 0143 0144 QByteArray ParserPrivate::errorLineString() const 0145 { 0146 return m_errorLineString; 0147 } 0148 0149 //BEGIN Parser Functions 0150 void ParserPrivate::parseFileDesc(const QByteArray& line) 0151 { 0152 // desc: ... 0153 VALIDATE(line, line.startsWith("desc: ")) 0154 0155 m_data->setDescription(QString::fromUtf8(line.mid(6))); 0156 m_nextLine = FileCmd; 0157 0158 if (!m_file->size()) { 0159 // for zipped files, parse the desc line for a --max-snapshots parameter 0160 // and use that number for the progress bar. read the manual to know that 0161 // this might not be a good measure, but better than nothing. 0162 QRegExp pattern(QStringLiteral("--max-snapshots=([0-9]+)"), Qt::CaseSensitive, QRegExp::RegExp2); 0163 if (pattern.indexIn(m_data->description()) != -1) { 0164 m_expectedSnapshots = pattern.cap(1).toInt(); 0165 } 0166 } 0167 } 0168 0169 void ParserPrivate::parseFileCmd(const QByteArray& line) 0170 { 0171 // cmd: ... 0172 VALIDATE(line, line.startsWith("cmd: ")) 0173 0174 m_data->setCmd(QString::fromUtf8(line.mid(5))); 0175 m_nextLine = FileTimeUnit; 0176 } 0177 0178 void ParserPrivate::parseFileTimeUnit(const QByteArray& line) 0179 { 0180 // time_unit: ... 0181 VALIDATE(line, line.startsWith("time_unit: ")) 0182 0183 m_data->setTimeUnit(QString::fromUtf8(line.mid(11))); 0184 m_nextLine = Snapshot; 0185 } 0186 0187 void ParserPrivate::parseSnapshot(const QByteArray& line) 0188 { 0189 VALIDATE(line, line == "#-----------") 0190 0191 // snapshot=N 0192 QByteArray nextLine = readLine(); 0193 VALIDATE(nextLine, nextLine.startsWith("snapshot=")) 0194 bool ok; 0195 uint number = midRef(nextLine, 9).toUInt(&ok); 0196 VALIDATE(nextLine, ok) 0197 nextLine = readLine(); 0198 VALIDATE(nextLine, nextLine == "#-----------") 0199 0200 m_snapshot = new SnapshotItem; 0201 m_data->addSnapshot(m_snapshot); 0202 m_snapshot->setNumber(number); 0203 m_nextLine = SnapshotTime; 0204 0205 if (!m_file->size()) { 0206 // see above: progress calculation for compressed files 0207 m_parser->setProgress(number * 100 / m_expectedSnapshots); 0208 } 0209 } 0210 0211 void ParserPrivate::parseSnapshotTime(const QByteArray& line) 0212 { 0213 VALIDATE(line, line.startsWith("time=")) 0214 bool ok; 0215 double time = midRef(line, 5).toDouble(&ok); 0216 VALIDATE(line, ok) 0217 m_snapshot->setTime(time); 0218 m_nextLine = SnapshotMemHeap; 0219 } 0220 0221 void ParserPrivate::parseSnapshotMemHeap(const QByteArray& line) 0222 { 0223 VALIDATE(line, line.startsWith("mem_heap_B=")) 0224 bool ok; 0225 quint64 bytes = midRef(line, 11).toULongLong(&ok); 0226 VALIDATE(line, ok) 0227 m_snapshot->setMemHeap(bytes); 0228 m_nextLine = SnapshotMemHeapExtra; 0229 } 0230 0231 void ParserPrivate::parseSnapshotMemHeapExtra(const QByteArray& line) 0232 { 0233 VALIDATE(line, line.startsWith("mem_heap_extra_B=")) 0234 bool ok; 0235 quint64 bytes = midRef(line, 17).toULongLong(&ok); 0236 VALIDATE(line, ok) 0237 m_snapshot->setMemHeapExtra(bytes); 0238 m_nextLine = SnapshotMemStacks; 0239 } 0240 0241 void ParserPrivate::parseSnapshotMemStacks(const QByteArray& line) 0242 { 0243 VALIDATE(line, line.startsWith("mem_stacks_B=")) 0244 bool ok; 0245 quint64 bytes = midRef(line, 13).toULongLong(&ok); 0246 VALIDATE(line, ok) 0247 m_snapshot->setMemStacks(bytes); 0248 m_nextLine = SnapshotHeapTree; 0249 } 0250 0251 void ParserPrivate::parseSnapshotHeapTree(const QByteArray& line) 0252 { 0253 VALIDATE(line, line.startsWith("heap_tree=")) 0254 QByteArray valueRef = midRef(line, 10); 0255 if (valueRef == "empty") { 0256 m_nextLine = Snapshot; 0257 } else if (valueRef == "detailed") { 0258 m_nextLine = HeapTreeLeaf; 0259 } else if (valueRef == "peak") { 0260 m_nextLine = HeapTreeLeaf; 0261 m_data->setPeak(m_snapshot); 0262 } else { 0263 m_error = Invalid; 0264 return; 0265 } 0266 } 0267 0268 bool sortLeafsByCost(TreeLeafItem* l, TreeLeafItem* r) 0269 { 0270 return l->cost() > r->cost(); 0271 } 0272 0273 void ParserPrivate::parseHeapTreeLeaf(const QByteArray& line) 0274 { 0275 parseheapTreeLeafInternal(line, 0); 0276 m_nextLine = Snapshot; 0277 // we need to do some post processing if we had custom allocators: 0278 // - sort by cost 0279 // - merge "in XYZ places all below threshold" 0280 if (m_hadCustomAllocators) { 0281 Q_ASSERT(m_snapshot->heapTree()); 0282 QVector<TreeLeafItem*> newChildren = m_snapshot->heapTree()->children(); 0283 TreeLeafItem* belowThreshold = 0; 0284 uint places = 0; 0285 QString oldPlaces; 0286 ///TODO: is massif translateable? 0287 QRegExp matchBT(QStringLiteral("in ([0-9]+) places, all below massif's threshold"), 0288 Qt::CaseSensitive, QRegExp::RegExp2); 0289 QVector<TreeLeafItem*>::iterator it = newChildren.begin(); 0290 while(it != newChildren.end()) { 0291 TreeLeafItem* child = *it; 0292 if (matchBT.indexIn(QString::fromLatin1(child->label())) != -1) { 0293 places += matchBT.cap(1).toUInt(); 0294 if (belowThreshold) { 0295 // merge with previously found node 0296 belowThreshold->setCost(belowThreshold->cost() + child->cost()); 0297 delete child; 0298 it = newChildren.erase(it); 0299 continue; 0300 } else { 0301 belowThreshold = child; 0302 oldPlaces = matchBT.cap(1); 0303 // no break, see above 0304 } 0305 } 0306 ++it; 0307 } 0308 if (belowThreshold) { 0309 QByteArray label = belowThreshold->label(); 0310 label.replace(oldPlaces.toUtf8(), QByteArray::number(places)); 0311 belowThreshold->setLabel(label); 0312 } 0313 qSort(newChildren.begin(), newChildren.end(), sortLeafsByCost); 0314 m_snapshot->heapTree()->setChildren(newChildren); 0315 } 0316 m_parentItem = 0; 0317 } 0318 0319 struct SaveAndRestoreItem 0320 { 0321 SaveAndRestoreItem(TreeLeafItem** item, TreeLeafItem* val) 0322 : m_item(item) 0323 { 0324 m_oldVal = *m_item; 0325 *m_item = val; 0326 } 0327 ~SaveAndRestoreItem() 0328 { 0329 *m_item = m_oldVal; 0330 } 0331 TreeLeafItem** m_item; 0332 TreeLeafItem* m_oldVal; 0333 }; 0334 0335 QByteArray ParserPrivate::getLabel(const QByteArray& original) 0336 { 0337 QSet<QByteArray>::const_iterator it = m_labels.constFind(original); 0338 if (it != m_labels.constEnd()) { 0339 // reuse known label to leverage implicit sharing 0340 return *it; 0341 } else { 0342 m_labels.insert(original); 0343 return original; 0344 } 0345 } 0346 0347 0348 bool ParserPrivate::parseheapTreeLeafInternal(const QByteArray& line, int depth) 0349 { 0350 VALIDATE_RETURN(line, line.length() > depth + 1 && line.at(depth) == 'n', false) 0351 int colonPos = line.indexOf(':', depth); 0352 VALIDATE_RETURN(line, colonPos != -1, false) 0353 bool ok; 0354 0355 unsigned int children = midRef(line, depth + 1, colonPos - depth - 1).toUInt(&ok); 0356 VALIDATE_RETURN(line, ok, false) 0357 0358 int spacePos = line.indexOf(' ', colonPos + 2); 0359 VALIDATE_RETURN(line, spacePos != -1, false) 0360 quint64 cost = midRef(line, colonPos + 2, spacePos - colonPos - 2).toULongLong(&ok); 0361 VALIDATE_RETURN(line, ok, false) 0362 0363 if (!cost && !children) { 0364 // ignore these empty entries 0365 return true; 0366 } 0367 0368 const QByteArray label = getLabel(line.mid(spacePos + 1)); 0369 0370 bool isCustomAlloc = false; 0371 0372 if (depth > 0 && !m_allocators.isEmpty()) { 0373 const QByteArray func = functionInLabel(label); 0374 foreach(const QByteArray& allocator, m_plainAllocators) { 0375 if (func.contains(allocator)) { 0376 isCustomAlloc = true; 0377 break; 0378 } 0379 } 0380 if (!isCustomAlloc) { 0381 const QString funcString = QString::fromLatin1(func); 0382 foreach(const QRegExp& allocator, m_allocators) { 0383 if (allocator.indexIn(funcString) != -1) { 0384 isCustomAlloc = true; 0385 break; 0386 } 0387 } 0388 } 0389 } 0390 0391 TreeLeafItem* newParent = 0; 0392 if (!isCustomAlloc) { 0393 TreeLeafItem* leaf = new TreeLeafItem; 0394 leaf->setCost(cost); 0395 leaf->setLabel(label); 0396 0397 if (!depth) { 0398 m_snapshot->setHeapTree(leaf); 0399 } else { 0400 Q_ASSERT(m_parentItem); 0401 m_parentItem->addChild(leaf); 0402 } 0403 0404 newParent = leaf; 0405 } else { 0406 // the first line/heaptree start must never be a custom allocator, e.g.: 0407 // n11: 1776070 (heap allocation functions) malloc/new/new[], --alloc-fns, etc. 0408 Q_ASSERT(depth); 0409 Q_ASSERT(m_snapshot->heapTree()); 0410 newParent = m_snapshot->heapTree(); 0411 m_hadCustomAllocators = true; 0412 } 0413 0414 SaveAndRestoreItem lastParent(&m_parentItem, newParent); 0415 0416 for (unsigned int i = 0; i < children; ++i) { 0417 QByteArray nextLine = readLine(); 0418 if (nextLine.isEmpty()) { 0419 // fail gracefully if the tree is not complete, esp. useful for cases where 0420 // an app run with massif crashes and massif doesn't finish the full tree dump. 0421 return true; 0422 } 0423 if (!parseheapTreeLeafInternal(nextLine, depth + 1)) { 0424 return false; 0425 } 0426 } 0427 0428 return true; 0429 } 0430 0431 QByteArray ParserPrivate::readLine() 0432 { 0433 const int read = m_file->readLine(m_lineBuffer, BUF_SIZE); 0434 ++m_currentLine; 0435 return QByteArray::fromRawData(m_lineBuffer, read - 1 /* -1 to remove trailing \n */); 0436 } 0437 0438 //END Parser Functions