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