File indexing completed on 2024-03-03 05:55:51

0001 /*
0002     SPDX-FileCopyrightText: 2019 David Faure <david.faure@kdab.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-or-later
0005 */
0006 
0007 #define DOCTEST_CONFIG_IMPLEMENT
0008 #include "3rdparty/doctest.h"
0009 
0010 #include "analyze/suppressions.h"
0011 #include "locationdata.h"
0012 #include "parser.h"
0013 #include "treemodel.h"
0014 #include "tst_config.h" // for SRC_DIR
0015 
0016 #include <KLocalizedString>
0017 
0018 #include <QDebug>
0019 #include <QSignalSpy>
0020 
0021 std::ostream& operator<<(std::ostream& os, const QString& value)
0022 {
0023     os << value.toStdString();
0024     return os;
0025 }
0026 
0027 struct TestParser
0028 {
0029     TestParser()
0030         : spySummary(&parser, &Parser::summaryAvailable)
0031         , spyCCD(&parser, &Parser::callerCalleeDataAvailable)
0032         , spyBottomUp(&parser, &Parser::bottomUpDataAvailable)
0033         , spyTopDown(&parser, &Parser::topDownDataAvailable)
0034         , spyFinished(&parser, &Parser::finished)
0035     {
0036         QObject::connect(&parser, &Parser::bottomUpDataAvailable, &parser,
0037                          [this](const auto& data) { resultData = data.resultData; });
0038     }
0039 
0040     ~TestParser()
0041     {
0042         if (spyFinished.isEmpty())
0043             REQUIRE(spyFinished.wait(20000));
0044     }
0045 
0046     CallerCalleeResults awaitCallerCallee()
0047     {
0048         if (spyCCD.isEmpty())
0049             REQUIRE(spyCCD.wait(20000));
0050 
0051         auto ccr = spyCCD.at(0).at(0).value<CallerCalleeResults>();
0052         REQUIRE(ccr.resultData);
0053         REQUIRE(ccr.resultData == resultData);
0054         return ccr;
0055     }
0056 
0057     TreeData awaitBottomUp()
0058     {
0059         if (spyBottomUp.isEmpty())
0060             REQUIRE(spyBottomUp.wait(20000));
0061 
0062         auto bottomUpData = spyBottomUp.at(0).at(0).value<TreeData>();
0063         REQUIRE(bottomUpData.resultData);
0064         REQUIRE(bottomUpData.resultData == resultData);
0065 
0066         if (qEnvironmentVariableIntValue("HEAPTRACK_DEBUG")) {
0067             qDebug() << "Bottom Up Data:";
0068             for (const RowData& row : bottomUpData.rows) {
0069                 qDebug() << symbolToString(row.symbol);
0070             }
0071         }
0072 
0073         return bottomUpData;
0074     }
0075 
0076     TreeData awaitTopDown()
0077     {
0078         if (spyTopDown.isEmpty())
0079             REQUIRE(spyTopDown.wait(20000));
0080 
0081         auto topDownData = spyTopDown.at(0).at(0).value<TreeData>();
0082         REQUIRE(topDownData.resultData);
0083         REQUIRE(topDownData.resultData == resultData);
0084 
0085         if (qEnvironmentVariableIntValue("HEAPTRACK_DEBUG")) {
0086             qDebug() << "Top Down Data:";
0087             for (const RowData& row : topDownData.rows) {
0088                 qDebug() << symbolToString(row.symbol);
0089             }
0090         }
0091 
0092         return topDownData;
0093     }
0094 
0095     SummaryData awaitSummary()
0096     {
0097         if (spySummary.isEmpty())
0098             REQUIRE(spySummary.wait(20000));
0099 
0100         return spySummary.at(0).at(0).value<SummaryData>();
0101     }
0102 
0103     Parser parser;
0104     std::shared_ptr<const ResultData> resultData;
0105 
0106     QString symbolToString(const Symbol& sym) const
0107     {
0108         const auto module = resultData->string(sym.moduleId);
0109         return resultData->string(sym.functionId) + '|' + Util::basename(module) + '|' + module;
0110     }
0111 
0112     QList<Symbol> sortedSymbols(const CallerCalleeResults& ccr) const
0113     {
0114         auto ccrSymbolList = ccr.entries.keys();
0115         std::sort(ccrSymbolList.begin(), ccrSymbolList.end(), [&](const Symbol& lhs, const Symbol& rhs) {
0116             // keep unresolved functions up front
0117             auto sortable = [&](const Symbol& symbol) {
0118                 auto str = [&](StringIndex stringId) { return ccr.resultData->string(stringId); };
0119                 return std::make_tuple(str(symbol.functionId), str(symbol.moduleId));
0120             };
0121             return sortable(lhs) < sortable(rhs);
0122         });
0123         if (qEnvironmentVariableIntValue("HEAPTRACK_DEBUG")) {
0124             qDebug() << "Sorted Symbols";
0125             int i = 0;
0126             for (const Symbol& sym : ccrSymbolList) {
0127                 qDebug() << i++ << symbolToString(sym);
0128             }
0129         }
0130         return ccrSymbolList;
0131     }
0132 
0133 private:
0134     QSignalSpy spySummary;
0135     QSignalSpy spyCCD;
0136     QSignalSpy spyBottomUp;
0137     QSignalSpy spyTopDown;
0138     QSignalSpy spyFinished;
0139 };
0140 
0141 TEST_CASE ("heaptrack.david.18594.gz") {
0142     TestParser parser;
0143 
0144     FilterParameters params;
0145     bool parsedSuppressions = false;
0146     params.suppressions = parseSuppressions(SRC_DIR "/suppressions.txt", &parsedSuppressions);
0147     REQUIRE(parsedSuppressions);
0148 
0149     parser.parser.parse(SRC_DIR "/heaptrack.david.18594.gz", QString(), params);
0150 
0151     // ---- Check Caller Callee Data
0152 
0153     const auto ccr = parser.awaitCallerCallee();
0154     const auto ccrSymbolList = parser.sortedSymbols(ccr);
0155 
0156     // Let's check a few items
0157     REQUIRE(parser.symbolToString(ccrSymbolList.at(0)) == "<unresolved function>||");
0158     REQUIRE(parser.symbolToString(ccrSymbolList.at(1))
0159             == "<unresolved function>|ld-linux-x86-64.so.2|/lib64/ld-linux-x86-64.so.2");
0160     REQUIRE(parser.symbolToString(ccrSymbolList.at(25))
0161             == "QByteArray::constData() const|libQt5Core.so.5|/d/qt/5/kde/build/qtbase/lib/libQt5Core.so.5");
0162     const int lastIndx = ccrSymbolList.size() - 1;
0163     REQUIRE(parser.symbolToString(ccrSymbolList.at(lastIndx))
0164             == "~QVarLengthArray|libQt5Core.so.5|/d/qt/5/kde/build/qtbase/lib/libQt5Core.so.5");
0165 
0166     REQUIRE(ccr.entries.count() == 365);
0167     REQUIRE(ccr.resultData->totalCosts().allocations == 2896);
0168 
0169     // ---- Check Bottom Up Data
0170 
0171     const auto bottomUpData = parser.awaitBottomUp();
0172 
0173     REQUIRE(bottomUpData.rows.size() == 54);
0174     REQUIRE(parser.symbolToString(bottomUpData.rows.at(3).symbol)
0175             == "<unresolved function>|libglib-2.0.so.0|/usr/lib64/libglib-2.0.so.0");
0176     REQUIRE(bottomUpData.rows.at(3).children.size() == 2);
0177     REQUIRE(bottomUpData.rows.at(3).cost.allocations == 17);
0178     REQUIRE(bottomUpData.rows.at(3).cost.peak == 2020);
0179     REQUIRE(parser.symbolToString(bottomUpData.rows.at(53).symbol)
0180             == "QThreadPool::QThreadPool(QObject*)|libQt5Core.so.5|/d/qt/5/kde/build/qtbase/lib/libQt5Core.so.5");
0181 
0182     // ---- Check Top Down Data
0183 
0184     const auto topDownData = parser.awaitTopDown();
0185     REQUIRE(topDownData.rows.size() == 5);
0186     REQUIRE(parser.symbolToString(topDownData.rows.at(2).symbol)
0187             == "<unresolved function>|ld-linux-x86-64.so.2|/lib64/ld-linux-x86-64.so.2");
0188     REQUIRE(topDownData.rows.at(2).children.size() == 1);
0189     REQUIRE(topDownData.rows.at(2).cost.allocations == 15);
0190     REQUIRE(topDownData.rows.at(2).cost.peak == 94496);
0191 
0192     // ---- Check Summary
0193 
0194     const auto summary = parser.awaitSummary();
0195     REQUIRE(summary.debuggee == "./david");
0196     REQUIRE(summary.cost.allocations == 2896);
0197     REQUIRE(summary.cost.temporary == 729);
0198     REQUIRE(summary.cost.leaked == 0);
0199     REQUIRE(summary.totalLeakedSuppressed == 30463);
0200     REQUIRE(summary.cost.peak == 996970);
0201     REQUIRE(summary.totalTime == 80);
0202     REQUIRE(summary.peakRSS == 76042240);
0203     REQUIRE(summary.peakTime == 0);
0204     REQUIRE(summary.totalSystemMemory == 16715239424);
0205     REQUIRE(summary.fromAttached == false);
0206 }
0207 
0208 TEST_CASE ("heaptrack.embedded_lsan_suppressions.84207.zst") {
0209     TestParser parser;
0210 
0211     parser.parser.parse(SRC_DIR "/heaptrack.embedded_lsan_suppressions.84207.zst", QString(), {});
0212 
0213     const auto summary = parser.awaitSummary();
0214     REQUIRE(summary.debuggee == "./tests/manual/embedded_lsan_suppressions");
0215     REQUIRE(summary.cost.allocations == 5);
0216     REQUIRE(summary.cost.temporary == 0);
0217     REQUIRE(summary.cost.leaked == 5);
0218     REQUIRE(summary.totalLeakedSuppressed == 5);
0219     REQUIRE(summary.cost.peak == 72714);
0220     REQUIRE(summary.totalSystemMemory == 33643876352);
0221 }
0222 
0223 TEST_CASE ("heaptrack.embedded_lsan_suppressions.84207.zst without suppressions") {
0224     TestParser parser;
0225 
0226     FilterParameters params;
0227     params.disableEmbeddedSuppressions = true;
0228     parser.parser.parse(SRC_DIR "/heaptrack.embedded_lsan_suppressions.84207.zst", QString(), params);
0229 
0230     const auto summary = parser.awaitSummary();
0231     REQUIRE(summary.debuggee == "./tests/manual/embedded_lsan_suppressions");
0232     REQUIRE(summary.cost.allocations == 5);
0233     REQUIRE(summary.cost.leaked == 10);
0234     REQUIRE(summary.totalLeakedSuppressed == 0);
0235 }
0236 
0237 TEST_CASE ("heaptrack.heaptrack_gui.99454.zst") {
0238     TestParser parser;
0239 
0240     FilterParameters params;
0241     params.disableBuiltinSuppressions = true;
0242 
0243     parser.parser.parse(SRC_DIR "/heaptrack.heaptrack_gui.99454.zst", QString(), params);
0244 
0245     const auto summary = parser.awaitSummary();
0246     REQUIRE(summary.debuggee == "heaptrack_gui heaptrack.trest_c.78689.zst");
0247     REQUIRE(summary.cost.allocations == 278534);
0248     REQUIRE(summary.cost.temporary == 35481);
0249     REQUIRE(summary.cost.leaked == 1047379);
0250     REQUIRE(summary.cost.peak == 12222213);
0251 
0252     const auto ccr = parser.awaitCallerCallee();
0253     const auto sortedSymbols = parser.sortedSymbols(ccr);
0254 
0255     const auto& sym = sortedSymbols[994];
0256     REQUIRE(parser.symbolToString(sym) == "QHashData::allocateNode(int)|libQt5Core.so.5|/usr/lib/libQt5Core.so.5");
0257     const auto& cost = ccr.entries[sym];
0258     CHECK(cost.inclusiveCost.allocations == 5214);
0259     CHECK(cost.inclusiveCost.temporary == 0);
0260     CHECK(cost.inclusiveCost.leaked == 32);
0261     CHECK(cost.inclusiveCost.peak == 56152);
0262     CHECK(cost.selfCost.allocations == 5214);
0263     CHECK(cost.selfCost.temporary == 0);
0264     CHECK(cost.selfCost.leaked == 32);
0265     CHECK(cost.selfCost.peak == 56152);
0266 }
0267 
0268 TEST_CASE ("heaptrack.heaptrack_gui.99529.zst") {
0269     TestParser parser;
0270 
0271     FilterParameters params;
0272     params.disableBuiltinSuppressions = true;
0273 
0274     parser.parser.parse(SRC_DIR "/heaptrack.heaptrack_gui.99529.zst", QString(), params);
0275 
0276     const auto summary = parser.awaitSummary();
0277     REQUIRE(summary.debuggee == "heaptrack_gui heaptrack.test_c.78689.zst");
0278     REQUIRE(summary.cost.allocations == 315255);
0279     REQUIRE(summary.cost.temporary == 40771);
0280     REQUIRE(summary.cost.leaked == 1046377);
0281     REQUIRE(summary.cost.peak == 64840134);
0282 
0283     const auto ccr = parser.awaitCallerCallee();
0284     const auto sortedSymbols = parser.sortedSymbols(ccr);
0285 
0286     const auto& sym = sortedSymbols[1103];
0287     REQUIRE(parser.symbolToString(sym) == "QHashData::allocateNode(int)|libQt5Core.so.5|/usr/lib/libQt5Core.so.5");
0288     const auto& cost = ccr.entries[sym];
0289     CHECK(cost.inclusiveCost.allocations == 5559);
0290     CHECK(cost.inclusiveCost.temporary == 0);
0291     CHECK(cost.inclusiveCost.leaked == 32);
0292     CHECK(cost.inclusiveCost.peak == 68952);
0293     CHECK(cost.selfCost.allocations == 5559);
0294     CHECK(cost.selfCost.temporary == 0);
0295     CHECK(cost.selfCost.leaked == 32);
0296     CHECK(cost.selfCost.peak == 68952);
0297 }
0298 
0299 TEST_CASE ("heaptrack.heaptrack_gui.{99454,99529}.zst diff") {
0300     TestParser parser;
0301 
0302     parser.parser.parse(SRC_DIR "/heaptrack.heaptrack_gui.99529.zst", SRC_DIR "/heaptrack.heaptrack_gui.99454.zst", {});
0303 
0304     const auto summary = parser.awaitSummary();
0305     REQUIRE(summary.debuggee == "heaptrack_gui heaptrack.test_c.78689.zst");
0306     REQUIRE(summary.cost.allocations == 36721);
0307     REQUIRE(summary.cost.temporary == 5290);
0308     REQUIRE(summary.cost.leaked == -1002);
0309     REQUIRE(summary.cost.peak == 52617921);
0310 
0311     const auto ccr = parser.awaitCallerCallee();
0312     const auto sortedSymbols = parser.sortedSymbols(ccr);
0313 
0314     const auto& sym = sortedSymbols[545];
0315     REQUIRE(parser.symbolToString(sym) == "QHashData::allocateNode(int)|libQt5Core.so.5|/usr/lib/libQt5Core.so.5");
0316     const auto& cost = ccr.entries[sym];
0317     CHECK(cost.inclusiveCost.allocations == (5559 - 5214));
0318     CHECK(cost.inclusiveCost.temporary == 0);
0319     CHECK(cost.inclusiveCost.leaked == 0);
0320     CHECK(cost.inclusiveCost.peak == (68952 - 56152));
0321     CHECK(cost.selfCost.allocations == (5559 - 5214));
0322     CHECK(cost.selfCost.temporary == 0);
0323     CHECK(cost.selfCost.leaked == 0);
0324     CHECK(cost.selfCost.peak == ((68952 - 56152)));
0325 }
0326 
0327 int main(int argc, char** argv)
0328 {
0329     QCoreApplication app(argc, argv);
0330 
0331     qRegisterMetaType<CallerCalleeResults>();
0332     KLocalizedString::setApplicationDomain("heaptrack");
0333 
0334     doctest::Context context;
0335     context.applyCommandLine(argc, argv);
0336 
0337     return context.run();
0338 }