diff -Nru hotspot-1.1.0+git20180816/3rdparty/perfparser/app/perfdata.cpp hotspot-1.1.0+git20190211/3rdparty/perfparser/app/perfdata.cpp --- hotspot-1.1.0+git20180816/3rdparty/perfparser/app/perfdata.cpp 2018-08-27 09:38:36.000000000 +0000 +++ hotspot-1.1.0+git20190211/3rdparty/perfparser/app/perfdata.cpp 2019-02-13 08:12:02.000000000 +0000 @@ -62,6 +62,8 @@ bool sampleIdAll = attrs.sampleIdAll(); quint64 sampleType = attrs.sampleType(); + const auto oldPos = stream.device()->pos(); + switch (m_eventHeader.type) { case PERF_RECORD_MMAP: { PerfRecordMmap mmap(&m_eventHeader, sampleType, sampleIdAll); @@ -178,6 +180,12 @@ break; } + const auto parsedContentSize = stream.device()->pos() - oldPos; + if (parsedContentSize != contentSize) { + qWarning() << "Event not fully parsed" << m_eventHeader.type << contentSize << parsedContentSize; + stream.skipRawData(contentSize - parsedContentSize); + } + m_eventHeader.size = 0; return SignalFinished; @@ -536,6 +544,7 @@ PerfRecordSample::BranchEntry entry; while (numBranches-- > 0) { stream >> entry.from >> entry.to; + stream.readRawData(reinterpret_cast(&entry.flags), sizeof(entry.flags)); record.m_branchStack << entry; } } diff -Nru hotspot-1.1.0+git20180816/3rdparty/perfparser/app/perfdata.h hotspot-1.1.0+git20190211/3rdparty/perfparser/app/perfdata.h --- hotspot-1.1.0+git20180816/3rdparty/perfparser/app/perfdata.h 2018-08-27 09:38:36.000000000 +0000 +++ hotspot-1.1.0+git20190211/3rdparty/perfparser/app/perfdata.h 2019-02-13 08:12:02.000000000 +0000 @@ -409,12 +409,24 @@ QList readFormats() const { return m_readFormats; } -private: + struct BranchFlags { + quint64 mispred: 1; + quint64 predicted: 1; + quint64 in_tx: 1; + quint64 abort: 1; + quint64 cycles: 16; + quint64 type: 4; + quint64 reserved: 40; + }; struct BranchEntry { quint64 from; quint64 to; + BranchFlags flags; }; + const QList &branchStack() const { return m_branchStack; } + +private: quint64 m_readFormat; quint64 m_registerMask; diff -Nru hotspot-1.1.0+git20180816/3rdparty/perfparser/app/perfelfmap.cpp hotspot-1.1.0+git20190211/3rdparty/perfparser/app/perfelfmap.cpp --- hotspot-1.1.0+git20180816/3rdparty/perfparser/app/perfelfmap.cpp 2018-08-27 09:38:36.000000000 +0000 +++ hotspot-1.1.0+git20190211/3rdparty/perfparser/app/perfelfmap.cpp 2019-02-13 08:12:02.000000000 +0000 @@ -30,10 +30,17 @@ << "isFile=" << info.isFile() << ", " << "originalFileName=" << info.originalFileName << ", " << "originalPath=" << info.originalPath << ", " - << "addr=" << hex << info.addr << dec << ", " - << "len=" << info.length << ", " - << "pgoff=" << info.pgoff << ", " - << "}"; + << "addr=" << hex << info.addr << ", " + << "len=" << hex << info.length << ", " + << "pgoff=" << hex << info.pgoff << ", " + << "baseAddr="; + + if (info.hasBaseAddr()) + stream << hex << info.baseAddr; + else + stream << "n/a"; + + stream << "}"; return stream.space(); } @@ -59,18 +66,37 @@ PerfElfMap::~PerfElfMap() = default; -void PerfElfMap::registerElf(const quint64 addr, const quint64 len, quint64 pgoff, +void PerfElfMap::registerElf(quint64 addr, quint64 len, quint64 pgoff, const QFileInfo &fullPath, const QByteArray &originalFileName, const QByteArray &originalPath) { - const quint64 addrEnd = addr + len; + quint64 addrEnd = addr + len; QVarLengthArray newElfs; QVarLengthArray removedElfs; for (auto i = m_elfs.begin(), end = m_elfs.end(); i != end && i->addr < addrEnd; ++i) { const quint64 iEnd = i->addr + i->length; - if (iEnd <= addr) + if (iEnd < addr) + continue; + + if (addr - pgoff == i->addr - i->pgoff && originalPath == i->originalPath) { + // Remapping parts of the same file in the same place: Extend to maximum continuous 71 + // address range and check if we already have that. + addr = qMin(addr, i->addr); + pgoff = qMin(pgoff, i->pgoff); + addrEnd = qMax(addrEnd, iEnd); + len = addrEnd - addr; + if (addr == i->addr && len == i->length) { + // New mapping is fully contained in old one: Nothing to do. + Q_ASSERT(newElfs.isEmpty()); + Q_ASSERT(removedElfs.isEmpty()); + return; + } + } else if (iEnd == addr) { + // Directly adjacent sections of the same file can be merged. Ones of different files + // don't bother each other. continue; + } // Newly added elf overwrites existing one. Mark the existing one as overwritten and // reinsert any fragments of it that remain. @@ -93,7 +119,14 @@ for (auto it = removedElfs.rbegin(), end = removedElfs.rend(); it != end; ++it) m_elfs.remove(*it); - newElfs.push_back(ElfInfo(fullPath, addr, len, pgoff, originalFileName, originalPath)); + ElfInfo elf(fullPath, addr, len, pgoff, originalFileName, originalPath); + + if (!pgoff) + m_lastBase = elf; + else if (m_lastBase.originalPath == originalPath) + elf.baseAddr = m_lastBase.addr; + + newElfs.push_back(elf); for (const auto &elf : newElfs) { auto it = std::lower_bound(m_elfs.begin(), m_elfs.end(), diff -Nru hotspot-1.1.0+git20180816/3rdparty/perfparser/app/perfelfmap.h hotspot-1.1.0+git20190211/3rdparty/perfparser/app/perfelfmap.h --- hotspot-1.1.0+git20180816/3rdparty/perfparser/app/perfelfmap.h 2018-08-27 09:38:36.000000000 +0000 +++ hotspot-1.1.0+git20190211/3rdparty/perfparser/app/perfelfmap.h 2019-02-13 08:12:02.000000000 +0000 @@ -23,12 +23,16 @@ #include #include +#include class PerfElfMap : public QObject { Q_OBJECT public: struct ElfInfo { + enum { + INVALID_BASE_ADDR = std::numeric_limits::max() + }; explicit ElfInfo(const QFileInfo &localFile = QFileInfo(), quint64 addr = 0, quint64 length = 0, quint64 pgoff = 0, const QByteArray &originalFileName = {}, @@ -40,7 +44,7 @@ originalPath(originalPath.isEmpty() ? localFile.absoluteFilePath().toLocal8Bit() : originalPath), - addr(addr), length(length), pgoff(pgoff) + addr(addr), length(length), pgoff(pgoff), baseAddr(INVALID_BASE_ADDR) {} bool isValid() const @@ -53,6 +57,11 @@ return localFile.isFile(); } + bool hasBaseAddr() const + { + return baseAddr != INVALID_BASE_ADDR; + } + bool operator==(const ElfInfo& rhs) const { return isFile() == rhs.isFile() @@ -61,7 +70,8 @@ && originalPath == rhs.originalPath && addr == rhs.addr && length == rhs.length - && pgoff == rhs.pgoff; + && pgoff == rhs.pgoff + && baseAddr == rhs.baseAddr; } QFileInfo localFile; @@ -70,6 +80,7 @@ quint64 addr; quint64 length; quint64 pgoff; + quint64 baseAddr; }; explicit PerfElfMap(QObject *parent = nullptr); @@ -94,6 +105,8 @@ private: // elf sorted by start address QVector m_elfs; + // last registered elf with zero pgoff + ElfInfo m_lastBase; }; QT_BEGIN_NAMESPACE diff -Nru hotspot-1.1.0+git20180816/3rdparty/perfparser/app/perfsymboltable.cpp hotspot-1.1.0+git20190211/3rdparty/perfparser/app/perfsymboltable.cpp --- hotspot-1.1.0+git20180816/3rdparty/perfparser/app/perfsymboltable.cpp 2018-08-27 09:38:36.000000000 +0000 +++ hotspot-1.1.0+git20190211/3rdparty/perfparser/app/perfsymboltable.cpp 2019-02-13 08:12:02.000000000 +0000 @@ -480,9 +480,9 @@ } } -static void reportError(const PerfElfMap::ElfInfo& info, const char *message) +static void reportError(qint32 pid, const PerfElfMap::ElfInfo& info, const char *message) { - qWarning() << "failed to report" << info << dec << ":" << message; + qWarning() << "failed to report elf for pid =" << pid << ':' << info << ":" << message; } Dwfl_Module *PerfSymbolTable::reportElf(const PerfElfMap::ElfInfo& info) @@ -490,18 +490,13 @@ if (!info.isValid() || !info.isFile()) return nullptr; - if (info.pgoff > 0) { - reportError(info, "Cannot report file fragments"); - return nullptr; - } - dwfl_report_begin_add(m_dwfl); Dwfl_Module *ret = dwfl_report_elf( m_dwfl, info.originalFileName.constData(), - info.localFile.absoluteFilePath().toLocal8Bit().constData(), -1, info.addr, + info.localFile.absoluteFilePath().toLocal8Bit().constData(), -1, info.addr - info.pgoff, false); if (!ret) { - reportError(info, dwfl_errmsg(dwfl_errno())); + reportError(m_pid, info, dwfl_errmsg(dwfl_errno())); m_cacheIsDirty = true; } else { // set symbol table as user data, cf. find_debuginfo callback in perfunwind.cpp @@ -526,6 +521,13 @@ if (!m_dwfl) return nullptr; + if (elf.pgoff && elf.hasBaseAddr()) { + const auto base = m_elfs.findElf(elf.baseAddr); + if (base.addr == elf.baseAddr && !base.pgoff && elf.originalPath == base.originalPath) + return module(addr, base); + qWarning() << "stale base mapping referenced:" << elf << base << dec << m_pid << hex << addr; + } + Dwfl_Module *mod = dwfl_addrmodule(m_dwfl, addr); if (!mod) { @@ -533,7 +535,7 @@ // by dwfl. If that is the case, then we would invalidate the cache and // re-report the library again - essentially recreating the current state // for no gain, except wasting time - mod = dwfl_addrmodule(m_dwfl, elf.addr); + mod = dwfl_addrmodule(m_dwfl, elf.addr - elf.pgoff); } if (mod) { @@ -543,7 +545,7 @@ Dwarf_Addr mod_start = 0; dwfl_module_info(mod, nullptr, &mod_start, nullptr, nullptr, nullptr, nullptr, nullptr); - if (elf.addr == mod_start) + if (elf.addr - elf.pgoff == mod_start) return mod; } return reportElf(elf); @@ -942,7 +944,7 @@ if (!str || str == QLatin1String(".text")) return {}; - if (str == QLatin1String(".plt")) { + if (str == QLatin1String(".plt") && entsize > 0) { const auto *pltSymbol = findPltSymbol(elf, addr / entsize); if (pltSymbol) return demangle(pltSymbol) + "@plt"; diff -Nru hotspot-1.1.0+git20180816/3rdparty/perfparser/app/perfsymboltable.h hotspot-1.1.0+git20190211/3rdparty/perfparser/app/perfsymboltable.h --- hotspot-1.1.0+git20180816/3rdparty/perfparser/app/perfsymboltable.h 2018-08-27 09:38:36.000000000 +0000 +++ hotspot-1.1.0+git20190211/3rdparty/perfparser/app/perfsymboltable.h 2019-02-13 08:12:02.000000000 +0000 @@ -60,9 +60,6 @@ PerfElfMap::ElfInfo findElf(quint64 ip) const; - // Report an mmap to dwfl and parse it for symbols and inlines, or simply return it if dwfl has - // it already - Dwfl_Module *reportElf(const PerfElfMap::ElfInfo& elf); // Find the module for the given address and report it if needed Dwfl_Module *module(quint64 addr); Dwfl_Module *module(quint64 addr, const PerfElfMap::ElfInfo &elf); @@ -82,6 +79,9 @@ bool cacheIsDirty() const { return m_cacheIsDirty; } private: + // Report an mmap to dwfl and parse it for symbols and inlines, or simply return it if dwfl has + // it already + Dwfl_Module *reportElf(const PerfElfMap::ElfInfo& elf); QFileInfo findFile(const char *path, const QString &fileName, const QByteArray &buildId = QByteArray()) const; diff -Nru hotspot-1.1.0+git20180816/3rdparty/perfparser/app/perfunwind.cpp hotspot-1.1.0+git20190211/3rdparty/perfparser/app/perfunwind.cpp --- hotspot-1.1.0+git20180816/3rdparty/perfparser/app/perfunwind.cpp 2018-08-27 09:38:36.000000000 +0000 +++ hotspot-1.1.0+git20190211/3rdparty/perfparser/app/perfunwind.cpp 2019-02-13 08:12:02.000000000 +0000 @@ -207,12 +207,10 @@ const QList &ids) { auto filteredIds = ids; - if (filteredIds.isEmpty()) { - // If we only get one attribute, it doesn't have an ID. - // The default ID for samples is 0, so we assign that here, - // in order to look it up in analyze(). - filteredIds = {0}; - } + // If we only get one attribute, it doesn't have an ID. + // The default ID for samples is 0, so we assign that here, + // in order to look it up in analyze(). + filteredIds << 0; { // remove attributes that are known already @@ -406,8 +404,21 @@ { bool isKernel = false; PerfSymbolTable *symbols = symbolTable(m_currentUnwind.sample->pid()); - for (int i = 0; i < m_currentUnwind.sample->callchain().length(); ++i) { + + auto reportIp = [&](quint64 ip) -> bool { + symbols->attachDwfl(&m_currentUnwind); + m_currentUnwind.frames.append(symbols->lookupFrame(ip, isKernel, + &m_currentUnwind.isInterworking)); + return !symbols->cacheIsDirty(); + }; + + // when we have a non-empty branch stack, we need to skip any non-kernel IPs + // in the normal callchain. The branch stack contains the non-kernel IPs then. + const bool hasBranchStack = !m_currentUnwind.sample->branchStack().isEmpty(); + + for (int i = 0, c = m_currentUnwind.sample->callchain().size(); i < c; ++i) { quint64 ip = m_currentUnwind.sample->callchain()[i]; + if (ip > PERF_CONTEXT_MAX) { switch (ip) { case PERF_CONTEXT_HV: // hypervisor @@ -424,28 +435,39 @@ } break; default: - qWarning() << "invalid callchain context" << ip; + qWarning() << "invalid callchain context" << hex << ip; return; } + continue; } + // prefer user frames from branch stack if available + if (hasBranchStack && !isKernel) + break; + // sometimes it skips the first user frame. if (i == 0 && !isKernel && ip != m_currentUnwind.sample->ip()) { - symbols->attachDwfl(&m_currentUnwind); - m_currentUnwind.frames.append(symbols->lookupFrame( - m_currentUnwind.sample->ip(), false, - &m_currentUnwind.isInterworking)); + if (!reportIp(m_currentUnwind.sample->ip())) + return; } - if (ip <= PERF_CONTEXT_MAX) { - symbols->attachDwfl(&m_currentUnwind); - m_currentUnwind.frames.append(symbols->lookupFrame( - ip, isKernel, - &m_currentUnwind.isInterworking)); - } + if (!reportIp(ip)) + return; + }; - if (symbols->cacheIsDirty()) - break; + // when we are still in the kernel, we cannot have a meaningful branch stack + if (isKernel) + return; + + // if available, also resolve the callchain stored in the branch stack: + // caller is stored in "from", callee is stored in "to" + // so the branch is made up of the first callee and all callers + for (int i = 0, c = m_currentUnwind.sample->branchStack().size(); i < c; ++i) { + const auto& entry = m_currentUnwind.sample->branchStack()[i]; + if (i == 0 && !reportIp(entry.to)) + return; + if (!reportIp(entry.from)) + return; } } @@ -534,7 +556,7 @@ m_currentUnwind.frames.clear(); userSymbols->updatePerfMap(); - if (sample.callchain().length() > 0) + if (!sample.callchain().isEmpty() || !sample.branchStack().isEmpty()) resolveCallchain(); bool userDirty = userSymbols->cacheIsDirty(); @@ -811,9 +833,12 @@ void PerfUnwind::flushEventBuffer(uint desiredBufferSize) { - std::sort(m_mmapBuffer.begin(), m_mmapBuffer.end(), sortByTime); - std::sort(m_sampleBuffer.begin(), m_sampleBuffer.end(), sortByTime); - std::sort(m_taskEventsBuffer.begin(), m_taskEventsBuffer.end(), sortByTime); + // stable sort here to keep order of events with the same time + // esp. when we runtime-attach, we will get lots of mmap events with time 0 + // which we must not shuffle + std::stable_sort(m_mmapBuffer.begin(), m_mmapBuffer.end(), sortByTime); + std::stable_sort(m_sampleBuffer.begin(), m_sampleBuffer.end(), sortByTime); + std::stable_sort(m_taskEventsBuffer.begin(), m_taskEventsBuffer.end(), sortByTime); if (m_stats.enabled) { for (const auto &sample : m_sampleBuffer) { diff -Nru hotspot-1.1.0+git20180816/3rdparty/perfparser/tests/auto/elfmap/tst_elfmap.cpp hotspot-1.1.0+git20190211/3rdparty/perfparser/tests/auto/elfmap/tst_elfmap.cpp --- hotspot-1.1.0+git20180816/3rdparty/perfparser/tests/auto/elfmap/tst_elfmap.cpp 2018-08-27 09:38:36.000000000 +0000 +++ hotspot-1.1.0+git20190211/3rdparty/perfparser/tests/auto/elfmap/tst_elfmap.cpp 2019-02-13 08:12:02.000000000 +0000 @@ -154,6 +154,32 @@ QVERIFY(map.isAddressInRange(29)); } + void testExtendMapping() + { + PerfElfMap map; + const PerfElfMap::ElfInfo first({}, 0, 5000, 0, "lalala.so", "/tmp/lalala.so"); + registerElf(&map, first); + QCOMPARE(map.findElf(100), first); + + // fully contained in the first mapping + const PerfElfMap::ElfInfo second({}, 20, 500, 20, "lalala.so", "/tmp/lalala.so"); + registerElf(&map, second); + QCOMPARE(map.findElf(100), first); + + const PerfElfMap::ElfInfo third({}, 2000, 8000, 2000, "lalala.so", "/tmp/lalala.so"); + registerElf(&map, third); + const PerfElfMap::ElfInfo extended({}, 0, 10000, 0, "lalala.so", "/tmp/lalala.so"); + QCOMPARE(map.findElf(100), extended); + QCOMPARE(map.findElf(2200), extended); + + PerfElfMap::ElfInfo fourth({}, 11000, 100, 500, "lalala.so", "/tmp/lalala.so"); + registerElf(&map, fourth); + QVERIFY(!fourth.hasBaseAddr()); + fourth.baseAddr = 0; + QVERIFY(fourth.hasBaseAddr()); + QCOMPARE(map.findElf(11000), fourth); + } + void benchRegisterElfDisjunct() { QFETCH(int, numElfMaps); diff -Nru hotspot-1.1.0+git20180816/debian/changelog hotspot-1.1.0+git20190211/debian/changelog --- hotspot-1.1.0+git20180816/debian/changelog 2019-01-07 08:06:12.000000000 +0000 +++ hotspot-1.1.0+git20190211/debian/changelog 2019-02-13 08:20:45.000000000 +0000 @@ -1,3 +1,10 @@ +hotspot (1.1.0+git20190211-1) unstable; urgency=high + + * New release for debian. + * Update perfparser. (Closes: #921431) + + -- Yanhao Mo Wed, 13 Feb 2019 16:20:45 +0800 + hotspot (1.1.0+git20180816-2) unstable; urgency=medium * d/patches: Add a patch to disable testProc temporarily diff -Nru hotspot-1.1.0+git20180816/.dockerignore hotspot-1.1.0+git20190211/.dockerignore --- hotspot-1.1.0+git20180816/.dockerignore 1970-01-01 00:00:00.000000000 +0000 +++ hotspot-1.1.0+git20190211/.dockerignore 2019-02-13 09:13:10.000000000 +0000 @@ -0,0 +1,6 @@ +build*/ +.git +*.AppImage +**/perf.data +**/perf.data.old +**/a.out \ No newline at end of file diff -Nru hotspot-1.1.0+git20180816/.gitignore hotspot-1.1.0+git20190211/.gitignore --- hotspot-1.1.0+git20180816/.gitignore 1970-01-01 00:00:00.000000000 +0000 +++ hotspot-1.1.0+git20190211/.gitignore 2019-02-13 09:13:10.000000000 +0000 @@ -0,0 +1,4 @@ +build/ +/.project +/CMakeLists.txt.user +.kdev4 \ No newline at end of file diff -Nru hotspot-1.1.0+git20180816/.gitmodules hotspot-1.1.0+git20190211/.gitmodules --- hotspot-1.1.0+git20180816/.gitmodules 1970-01-01 00:00:00.000000000 +0000 +++ hotspot-1.1.0+git20190211/.gitmodules 2019-02-13 09:13:10.000000000 +0000 @@ -0,0 +1,4 @@ +[submodule "3rdparty/perfparser"] + path = 3rdparty/perfparser + url = https://github.com/KDAB/perfparser.git +# url = git://code.qt.io/qt-creator/perfparser.git diff -Nru hotspot-1.1.0+git20180816/hotspot-config.h.cmake hotspot-1.1.0+git20190211/hotspot-config.h.cmake --- hotspot-1.1.0+git20180816/hotspot-config.h.cmake 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/hotspot-config.h.cmake 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2016-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2016-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Milian Wolff Licensees holding valid commercial KDAB Hotspot licenses may use this file in diff -Nru hotspot-1.1.0+git20180816/LICENSE.GPL.txt hotspot-1.1.0+git20190211/LICENSE.GPL.txt --- hotspot-1.1.0+git20180816/LICENSE.GPL.txt 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/LICENSE.GPL.txt 2019-02-13 09:13:10.000000000 +0000 @@ -1,4 +1,4 @@ - The Hotspot software is Copyright (C) 2016-2018 Klaralvdalens Datakonsult AB. + The Hotspot software is Copyright (C) 2016-2019 Klaralvdalens Datakonsult AB. You may use, distribute and copy the Hotspot software under the terms of the GNU General Public License version 2 or under the terms of GNU General diff -Nru hotspot-1.1.0+git20180816/LICENSE.txt hotspot-1.1.0+git20190211/LICENSE.txt --- hotspot-1.1.0+git20180816/LICENSE.txt 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/LICENSE.txt 2019-02-13 09:13:10.000000000 +0000 @@ -5,7 +5,7 @@ Copyright of this license text (C) 2001 Trolltech AS and (C) 2002-2016 Klarälvdalens Datakonsult AB. All rights reserved. License text used with kind permission of Trolltech AS. The software and accompanying -material is Copyright (C) 2010-2018 Klarälvdalens Datakonsult AB. +material is Copyright (C) 2010-2019 Klarälvdalens Datakonsult AB. This non-exclusive non-transferable License Agreement ("Agreement") is between you ("Licensee") and Klarälvdalens Datakonsult AB (KDAB), and diff -Nru hotspot-1.1.0+git20180816/README.md hotspot-1.1.0+git20190211/README.md --- hotspot-1.1.0+git20180816/README.md 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/README.md 2019-02-13 09:13:10.000000000 +0000 @@ -160,6 +160,10 @@ hotspot is available in AUR (https://aur.archlinux.org/packages/hotspot). +### Gentoo + +hotspot ebuilds are available from our overlay (https://github.com/KDAB/kdab-overlay). + ### For any Linux distro: AppImage Head over to our [download page](https://github.com/KDAB/hotspot/releases) and download the latest [AppImage](http://appimage.org/) release and just run it. diff -Nru hotspot-1.1.0+git20180816/scripts/build_appimage_in_docker.sh hotspot-1.1.0+git20190211/scripts/build_appimage_in_docker.sh --- hotspot-1.1.0+git20180816/scripts/build_appimage_in_docker.sh 1970-01-01 00:00:00.000000000 +0000 +++ hotspot-1.1.0+git20190211/scripts/build_appimage_in_docker.sh 2019-02-13 09:13:10.000000000 +0000 @@ -0,0 +1,12 @@ +#!/bin/sh + +cd $(dirname $0)/../ + +if [ ! -d hotspot-appimage-artifacts ]; then + mkdir /tmp/hotspot-appimage-artifacts +fi + +sudo docker build -t hotspot_appimage -f scripts/Dockerfile . || exit 1 +sudo docker run -v /tmp/hotspot-appimage-artifacts:/artifacts -it hotspot_appimage +mv /tmp/hotspot-appimage-artifacts/hotspot-x86_64.AppImage hotspot-$(git describe)-x86_64.AppImage +ls -latr hotspot-*.AppImage | tail -n 1 diff -Nru hotspot-1.1.0+git20180816/scripts/build_appimage.sh hotspot-1.1.0+git20190211/scripts/build_appimage.sh --- hotspot-1.1.0+git20180816/scripts/build_appimage.sh 1970-01-01 00:00:00.000000000 +0000 +++ hotspot-1.1.0+git20190211/scripts/build_appimage.sh 2019-02-13 09:13:10.000000000 +0000 @@ -0,0 +1,75 @@ +#!/bin/sh + +cd $(dirname $0)/../ + +OUTDIR=$PWD + +PREFIX=/opt + +if [ ! -z "$1" ]; then + PREFIX=$1 +fi + +if [ ! -z "$2" ]; then + OUTDIR="$2" +fi + +if [ -z "$(which linuxdeployqt)" ]; then + echo "ERROR: cannot find linuxdeployqt in PATH" + exit 1 +fi + +if [ -z "$(which appimagetool)" ]; then + echo "ERROR: cannot find appimagetool in PATH" + exit 1 +fi + +if [ ! -d build-appimage ]; then + mkdir build-appimage +fi + +cd build-appimage + +cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$PREFIX -DAPPIMAGE_BUILD=ON +make -j$(nproc) +make DESTDIR=appdir install + +# FIXME: Do in CMakeLists.txt +mkdir -p appdir/$PREFIX/share/applications/ +cp ../hotspot.desktop appdir/$PREFIX/share/applications/ + +unset QTDIR +unset QT_PLUGIN_PATH +unset LD_LIBRARY_PATH +export LD_LIBRARY_PATH=/opt/qt510/lib/x86_64-linux-gnu # make sure this path is known so all Qt/KF5 libs are found +linuxdeployqt ./appdir/$PREFIX/share/applications/*.desktop -bundle-non-qt-libs +# workaround for https://github.com/KDAB/hotspot/issues/87 +linuxdeployqt ./appdir/$PREFIX/lib/x86_64-linux-gnu/libexec/hotspot-perfparser -bundle-non-qt-libs -no-plugins + +unset LD_LIBRARY_PATH +# also include the elfutils backend library ABI specific implementations +cp -va $PREFIX/lib/x86_64-linux-gnu/elfutils/* ./appdir/$PREFIX/lib/x86_64-linux-gnu/libexec/lib/ + +# Share libraries to reduce image size +mv ./appdir/$PREFIX/lib/x86_64-linux-gnu/libexec/lib/* ./appdir/$PREFIX/lib/ +rmdir ./appdir/$PREFIX/lib/x86_64-linux-gnu/libexec/lib +ln -sr ./appdir/$PREFIX/lib/ ./appdir/$PREFIX/lib/x86_64-linux-gnu/libexec/lib + +# include breeze icons +if [ -d /opt/qt*/share/icons/breeze ]; then + cp -va /opt/qt*/share/icons/breeze ./appdir/$PREFIX/share/icons/ +fi + +# Ensure we prefer the bundled libs also when calling dlopen, cf.: https://github.com/KDAB/hotspot/issues/89 +rm ./appdir/AppRun +cat << WRAPPER_SCRIPT > ./appdir/AppRun +#!/bin/bash +f="\$(readlink -f "\${0}")" +d="\$(dirname "\$f")" +bin="\$d/$PREFIX/bin" +LD_LIBRARY_PATH="\$d/$PREFIX/lib":\$LD_LIBRARY_PATH "\$bin/hotspot" "\$@" +WRAPPER_SCRIPT +chmod +x ./appdir/AppRun + +# Actually create the final image +appimagetool ./appdir $OUTDIR/hotspot-x86_64.AppImage diff -Nru hotspot-1.1.0+git20180816/scripts/create_tarballs.sh hotspot-1.1.0+git20190211/scripts/create_tarballs.sh --- hotspot-1.1.0+git20180816/scripts/create_tarballs.sh 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/scripts/create_tarballs.sh 2019-02-13 09:13:10.000000000 +0000 @@ -2,7 +2,7 @@ # # This file is part of Hotspot, the Qt GUI for performance analysis. # -# Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com +# Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com # Author: Milian Wolff # # Licensees holding valid commercial KDAB Hotspot licenses may use this file in diff -Nru hotspot-1.1.0+git20180816/scripts/Dockerfile hotspot-1.1.0+git20190211/scripts/Dockerfile --- hotspot-1.1.0+git20180816/scripts/Dockerfile 1970-01-01 00:00:00.000000000 +0000 +++ hotspot-1.1.0+git20190211/scripts/Dockerfile 2019-02-13 09:13:10.000000000 +0000 @@ -0,0 +1,51 @@ +# trusty +FROM ubuntu:14.04 as hotspot_appimage_intermediate + +# install dependencies +ARG DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && \ + apt-get -y upgrade && \ + apt-get install -y software-properties-common build-essential curl git wget \ + autotools-dev autoconf libtool liblzma-dev libz-dev gettext && \ + add-apt-repository ppa:beineri/opt-qt-5.10.1-trusty -y && \ + apt-get update && \ + apt-get install -y qt510base qt510svg qt510x11extras cmake3 libdwarf-dev mesa-common-dev \ + libboost-iostreams-dev libboost-program-options-dev libboost-system-dev libboost-filesystem-dev + +WORKDIR /opt + +# download prebuild KF5 libraries and ECM + +RUN wget -c "https://github.com/chigraph/precompiled-kf5-linux/releases/download/precompiled/kf5-gcc6-linux64-release.tar.xz" && \ + tar --strip-components=2 -xf kf5-gcc6-linux64-release.tar.xz -C /opt/qt510/ + +# Precompiled version of elfutils in newer version (0.170) +RUN wget -c "https://swanson.kdab.com/owncloud/index.php/s/RzRwIkFuTKI30t3/download" -O elfutils.tar.bz2 && \ + sudo tar -xf elfutils.tar.bz2 -C / + +# download AppImage tools and extract them, to remove fuse dependency + +RUN wget -c "https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage" -O /tmp/linuxdeployqt && \ + wget -c "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" -O /tmp/appimagetool && \ + chmod a+x /tmp/linuxdeployqt /tmp/appimagetool && \ + /tmp/linuxdeployqt --appimage-extract && mv squashfs-root linuxdeployqt && \ + /tmp/appimagetool --appimage-extract && mv squashfs-root appimagetool && \ + mkdir /opt/bin && \ + ln -s /opt/linuxdeployqt/AppRun /opt/bin/linuxdeployqt && \ + ln -s /opt/appimagetool/AppRun /opt/bin/appimagetool + +# setup env + +ENV PATH="/opt/bin:/opt/qt510/bin:${PATH}" \ + PKG_CONFIG_PATH="/opt/qt510/lib/pkgconfig:${PKG_CONFIG_PATH}" \ + LD_LIBRARY_PATH="/opt/qt510/lib:/opt/qt510/lib/x86_64-linux-gnu" + +# setup hotspot build environment + +FROM hotspot_appimage_intermediate + +ADD . /opt/hotspot +WORKDIR /opt/hotspot + +CMD ./scripts/build_appimage.sh /opt /artifacts diff -Nru hotspot-1.1.0+git20180816/scripts/elevate_perf_privileges.sh hotspot-1.1.0+git20190211/scripts/elevate_perf_privileges.sh --- hotspot-1.1.0+git20180816/scripts/elevate_perf_privileges.sh 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/scripts/elevate_perf_privileges.sh 2019-02-13 09:13:10.000000000 +0000 @@ -7,7 +7,7 @@ # # This file is part of Hotspot, the Qt GUI for performance analysis. # -# Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com +# Copyright (C) 2018-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com # Author: Milian Wolff # # Licensees holding valid commercial KDAB Hotspot licenses may use this file in diff -Nru hotspot-1.1.0+git20180816/src/aboutdialog.cpp hotspot-1.1.0+git20190211/src/aboutdialog.cpp --- hotspot-1.1.0+git20180816/src/aboutdialog.cpp 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/aboutdialog.cpp 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2013-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2013-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Volker Krause Licensees holding valid commercial KDAB Hotspot licenses may use this file in diff -Nru hotspot-1.1.0+git20180816/src/aboutdialog.h hotspot-1.1.0+git20190211/src/aboutdialog.h --- hotspot-1.1.0+git20180816/src/aboutdialog.h 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/aboutdialog.h 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2013-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2013-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Volker Krause Licensees holding valid commercial KDAB Hotspot licenses may use this file in diff -Nru hotspot-1.1.0+git20180816/src/flamegraph.cpp hotspot-1.1.0+git20190211/src/flamegraph.cpp --- hotspot-1.1.0+git20180816/src/flamegraph.cpp 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/flamegraph.cpp 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Milian Wolff Licensees holding valid commercial KDAB Hotspot licenses may use this file in @@ -42,6 +42,7 @@ #include #include #include +#include #include #include #include @@ -53,6 +54,7 @@ #include #include "resultsutil.h" +#include "models/filterandzoomstack.h" namespace { enum SearchMatchType @@ -401,7 +403,17 @@ m_view->viewport()->setMouseTracking(true); m_view->setFont(QFont(QStringLiteral("monospace"))); + m_backButton = new QPushButton(this); + m_backButton->setIcon(QIcon::fromTheme(QStringLiteral("go-previous"))); + m_backButton->setToolTip(QStringLiteral("Go back in symbol view history")); + m_forwardButton = new QPushButton(this); + m_forwardButton->setIcon(QIcon::fromTheme(QStringLiteral("go-next"))); + m_forwardButton->setToolTip(QStringLiteral("Go forward in symbol view history")); + auto bottomUpCheckbox = new QCheckBox(i18n("Bottom-Up View"), this); + connect(this, &FlameGraph::uiResetRequested, bottomUpCheckbox, [bottomUpCheckbox](){ + bottomUpCheckbox->setChecked(false); + }); bottomUpCheckbox->setToolTip(i18n( "Enable the bottom-up flame graph view. When this is unchecked, the top-down view is enabled by default.")); bottomUpCheckbox->setChecked(m_showBottomUpData); @@ -411,6 +423,9 @@ }); auto collapseRecursionCheckbox = new QCheckBox(i18n("Collapse Recursion"), this); + connect(this, &FlameGraph::uiResetRequested, collapseRecursionCheckbox, [collapseRecursionCheckbox](){ + collapseRecursionCheckbox->setChecked(false); + }); collapseRecursionCheckbox->setChecked(m_collapseRecursion); collapseRecursionCheckbox->setToolTip( i18n("Collapse stack frames for functions calling themselves. " @@ -426,7 +441,10 @@ costThreshold->setMaximum(99.90); costThreshold->setPrefix(i18n("Cost Threshold: ")); costThreshold->setSuffix(QStringLiteral("%")); - costThreshold->setValue(m_costThreshold); + costThreshold->setValue(DEFAULT_COST_THRESHOLD); + connect(this, &FlameGraph::uiResetRequested, costThreshold, [costThreshold](){ + costThreshold->setValue(DEFAULT_COST_THRESHOLD); + }); costThreshold->setSingleStep(0.01); costThreshold->setToolTip( i18n("The cost threshold defines a fractional cut-off value. " @@ -444,9 +462,14 @@ m_searchInput->setToolTip(i18n("Search the flame graph for a symbol.")); m_searchInput->setClearButtonEnabled(true); connect(m_searchInput, &QLineEdit::textChanged, this, &FlameGraph::setSearchValue); + connect(this, &FlameGraph::uiResetRequested, this, [this](){ + m_searchInput->clear(); + }); auto controls = new QWidget(this); controls->setLayout(new QHBoxLayout); + controls->layout()->addWidget(m_backButton); + controls->layout()->addWidget(m_forwardButton); controls->layout()->addWidget(m_costSource); controls->layout()->addWidget(bottomUpCheckbox); controls->layout()->addWidget(collapseRecursionCheckbox); @@ -469,8 +492,12 @@ m_backAction = KStandardAction::back(this, SLOT(navigateBack()), this); addAction(m_backAction); + connect(m_backButton, &QPushButton::released, m_backAction, &QAction::trigger); + m_forwardAction = KStandardAction::forward(this, SLOT(navigateForward()), this); addAction(m_forwardAction); + connect(m_forwardButton, &QPushButton::released, m_forwardAction, &QAction::trigger); + m_resetAction = new QAction(QIcon::fromTheme(QStringLiteral("go-first")), tr("Reset View"), this); m_resetAction->setShortcut(Qt::Key_Escape); connect(m_resetAction, &QAction::triggered, this, [this]() { selectItem(0); }); @@ -480,6 +507,11 @@ FlameGraph::~FlameGraph() = default; +void FlameGraph::setFilterStack(FilterAndZoomStack* filterStack) +{ + m_filterStack = filterStack; +} + bool FlameGraph::eventFilter(QObject* object, QEvent* event) { bool ret = QObject::eventFilter(object, event); @@ -522,18 +554,19 @@ auto item = static_cast(m_view->itemAt(m_view->mapFromGlobal(contextEvent->globalPos()))); QMenu contextMenu; - QAction* viewCallerCallee = nullptr; if (item) { - viewCallerCallee = contextMenu.addAction(tr("View Caller/Callee")); + auto* viewCallerCallee = contextMenu.addAction(tr("View Caller/Callee")); + connect(viewCallerCallee, &QAction::triggered, this, [this, item](){ + emit jumpToCallerCallee(item->symbol()); + }); contextMenu.addSeparator(); } + ResultsUtil::addFilterActions(&contextMenu, item ? item->symbol() : Data::Symbol(), m_filterStack); + contextMenu.addSeparator(); contextMenu.addActions(actions()); - QAction* action = contextMenu.exec(QCursor::pos()); - - if (action && action == viewCallerCallee) { - emit jumpToCallerCallee(item->symbol()); - } + contextMenu.exec(QCursor::pos()); + return true; } else if (event->type() == QEvent::ToolTip) { const auto& tooltip = m_displayLabel->toolTip(); if (tooltip.isEmpty()) { @@ -570,6 +603,11 @@ &FlameGraph::showData); } +void FlameGraph::clear() +{ + emit uiResetRequested(); +} + void FlameGraph::showData() { auto showBottomUpData = m_showBottomUpData; @@ -598,6 +636,7 @@ } QMetaObject::invokeMethod(this, "setData", Qt::QueuedConnection, Q_ARG(FrameGraphicsItem*, parsedData)); }); + updateNavigationActions(); } void FlameGraph::setTooltipItem(const FrameGraphicsItem* item) @@ -724,7 +763,11 @@ void FlameGraph::updateNavigationActions() { - m_backAction->setEnabled(m_selectedItem > 0); - m_forwardAction->setEnabled(m_selectedItem + 1 < m_selectionHistory.size()); - m_resetAction->setEnabled(m_selectedItem > 0); + const bool hasItems = m_selectedItem > 0; + const bool isNotLastItem = m_selectedItem + 1 < m_selectionHistory.size(); + m_backAction->setEnabled(hasItems); + m_forwardAction->setEnabled(isNotLastItem); + m_resetAction->setEnabled(hasItems); + m_backButton->setEnabled(hasItems); + m_forwardButton->setEnabled(isNotLastItem); } diff -Nru hotspot-1.1.0+git20180816/src/flamegraph.h hotspot-1.1.0+git20190211/src/flamegraph.h --- hotspot-1.1.0+git20180816/src/flamegraph.h 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/flamegraph.h 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Milian Wolff Licensees holding valid commercial KDAB Hotspot licenses may use this file in @@ -38,8 +38,10 @@ class QComboBox; class QLabel; class QLineEdit; +class QPushButton; class FrameGraphicsItem; +class FilterAndZoomStack; class FlameGraph : public QWidget { @@ -48,8 +50,10 @@ explicit FlameGraph(QWidget* parent = nullptr, Qt::WindowFlags flags = {}); ~FlameGraph(); + void setFilterStack(FilterAndZoomStack *filterStack); void setTopDownData(const Data::TopDownResults& topDownData); void setBottomUpData(const Data::BottomUpResults& bottomUpData); + void clear(); protected: bool eventFilter(QObject* object, QEvent* event) override; @@ -62,6 +66,7 @@ signals: void jumpToCallerCallee(const Data::Symbol& symbol); + void uiResetRequested(); private: void setTooltipItem(const FrameGraphicsItem* item); @@ -74,6 +79,7 @@ Data::TopDownResults m_topDownData; Data::BottomUpResults m_bottomUpData; + FilterAndZoomStack* m_filterStack = nullptr; QComboBox* m_costSource; QGraphicsScene* m_scene; QGraphicsView* m_view; @@ -83,6 +89,8 @@ QAction* m_forwardAction = nullptr; QAction* m_backAction = nullptr; QAction* m_resetAction = nullptr; + QPushButton* m_backButton = nullptr; + QPushButton* m_forwardButton = nullptr; const FrameGraphicsItem* m_tooltipItem = nullptr; FrameGraphicsItem* m_rootItem = nullptr; QVector m_selectionHistory; @@ -92,7 +100,8 @@ bool m_collapseRecursion = false; bool m_buildingScene = false; // cost threshold in percent, items below that value will not be shown - double m_costThreshold = 0.1; + static const constexpr double DEFAULT_COST_THRESHOLD = 0.1; + double m_costThreshold = DEFAULT_COST_THRESHOLD; }; #endif // FLAMEGRAPH_H diff -Nru hotspot-1.1.0+git20180816/src/main.cpp hotspot-1.1.0+git20190211/src/main.cpp --- hotspot-1.1.0+git20180816/src/main.cpp 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/main.cpp 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2016-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2016-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Milian Wolff Licensees holding valid commercial KDAB Hotspot licenses may use this file in @@ -33,6 +33,9 @@ #include "mainwindow.h" #include "models/data.h" +#include +#include + int main(int argc, char** argv) { QCoreApplication::setOrganizationName(QStringLiteral("KDAB")); @@ -102,6 +105,8 @@ parser.process(app); + ThreadWeaver::Queue::instance()->setMaximumNumberOfThreads(QThread::idealThreadCount()); + auto applyCliArgs = [&](MainWindow* window) { if (parser.isSet(sysroot)) { window->setSysroot(parser.value(sysroot)); diff -Nru hotspot-1.1.0+git20180816/src/mainwindow.cpp hotspot-1.1.0+git20190211/src/mainwindow.cpp --- hotspot-1.1.0+git20180816/src/mainwindow.cpp 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/mainwindow.cpp 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2016-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2016-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Nate Rogers Licensees holding valid commercial KDAB Hotspot licenses may use this file in @@ -148,6 +148,15 @@ connect(ui->actionAbout_KDAB, &QAction::triggered, this, &MainWindow::aboutKDAB); connect(ui->actionAbout_Hotspot, &QAction::triggered, this, &MainWindow::aboutHotspot); + auto *showTimelineAction = ui->viewMenu->addAction(tr("Show Timeline")); + showTimelineAction->setCheckable(true); + showTimelineAction->setChecked(true); + showTimelineAction->setShortcut(tr("Ctrl+T")); + connect(showTimelineAction, &QAction::toggled, m_resultsPage, &ResultsPage::setTimelineVisible); + + ui->viewMenu->addSeparator(); + ui->viewMenu->addActions(m_resultsPage->filterMenu()->actions()); + setupCodeNavigationMenu(); setupPathSettingsMenu(); @@ -235,6 +244,7 @@ m_pageStack->setCurrentWidget(m_startPage); m_recordPage->stopRecording(); m_resultsPage->selectSummaryTab(); + m_resultsPage->clear(); } void MainWindow::openFile(const QString& path) diff -Nru hotspot-1.1.0+git20180816/src/mainwindow.h hotspot-1.1.0+git20190211/src/mainwindow.h --- hotspot-1.1.0+git20180816/src/mainwindow.h 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/mainwindow.h 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2016-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2016-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Nate Rogers Licensees holding valid commercial KDAB Hotspot licenses may use this file in diff -Nru hotspot-1.1.0+git20180816/src/mainwindow.ui hotspot-1.1.0+git20190211/src/mainwindow.ui --- hotspot-1.1.0+git20180816/src/mainwindow.ui 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/mainwindow.ui 2019-02-13 09:13:10.000000000 +0000 @@ -44,8 +44,14 @@ Setti&ngs + + + View + + + diff -Nru hotspot-1.1.0+git20180816/src/models/callercalleemodel.cpp hotspot-1.1.0+git20190211/src/models/callercalleemodel.cpp --- hotspot-1.1.0+git20180816/src/models/callercalleemodel.cpp 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/models/callercalleemodel.cpp 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Nate Rogers Licensees holding valid commercial KDAB Hotspot licenses may use this file in diff -Nru hotspot-1.1.0+git20180816/src/models/callercalleemodel.h hotspot-1.1.0+git20190211/src/models/callercalleemodel.h --- hotspot-1.1.0+git20180816/src/models/callercalleemodel.h 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/models/callercalleemodel.h 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Nate Rogers Licensees holding valid commercial KDAB Hotspot licenses may use this file in diff -Nru hotspot-1.1.0+git20180816/src/models/CMakeLists.txt hotspot-1.1.0+git20190211/src/models/CMakeLists.txt --- hotspot-1.1.0+git20180816/src/models/CMakeLists.txt 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/models/CMakeLists.txt 2019-02-13 09:13:10.000000000 +0000 @@ -9,6 +9,7 @@ processlist_unix.cpp timelinedelegate.cpp eventmodel.cpp + filterandzoomstack.cpp ../util.cpp ) diff -Nru hotspot-1.1.0+git20180816/src/models/costdelegate.cpp hotspot-1.1.0+git20190211/src/models/costdelegate.cpp --- hotspot-1.1.0+git20180816/src/models/costdelegate.cpp 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/models/costdelegate.cpp 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2016-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2016-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Milian Wolff Licensees holding valid commercial KDAB Hotspot licenses may use this file in diff -Nru hotspot-1.1.0+git20180816/src/models/costdelegate.h hotspot-1.1.0+git20190211/src/models/costdelegate.h --- hotspot-1.1.0+git20180816/src/models/costdelegate.h 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/models/costdelegate.h 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2016-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2016-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Milian Wolff Licensees holding valid commercial KDAB Hotspot licenses may use this file in diff -Nru hotspot-1.1.0+git20180816/src/models/data.cpp hotspot-1.1.0+git20190211/src/models/data.cpp --- hotspot-1.1.0+git20180816/src/models/data.cpp 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/models/data.cpp 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Milian Wolff Licensees holding valid commercial KDAB Hotspot licenses may use this file in @@ -200,46 +200,3 @@ return nullptr; } - -BottomUp* Data::BottomUpResults::addFrame(BottomUp* parent, qint32 locationId, int type, quint64 period, - const FrameCallback& frameCallback) -{ - bool skipNextFrame = false; - while (locationId != -1) { - const auto& location = locations.value(locationId); - if (skipNextFrame) { - locationId = location.parentLocationId; - skipNextFrame = false; - continue; - } - - auto symbol = symbols.value(locationId); - if (!symbol.isValid()) { - // we get function entry points from the perfparser but - // those are imo not interesting - skip them - symbol = symbols.value(location.parentLocationId); - skipNextFrame = true; - } - - auto ret = parent->entryForSymbol(symbol, &maxBottomUpId); - costs.add(type, ret->id, period); - - frameCallback(symbol, location.location); - - parent = ret; - locationId = location.parentLocationId; - } - - return parent; -} - -const BottomUp* Data::BottomUpResults::addEvent(int type, quint64 cost, const QVector& frames, - const FrameCallback& frameCallback) -{ - costs.addTotalCost(type, cost); - auto parent = &root; - for (auto id : frames) { - parent = addFrame(parent, id, type, cost, frameCallback); - } - return parent; -} diff -Nru hotspot-1.1.0+git20180816/src/models/data.h hotspot-1.1.0+git20190211/src/models/data.h --- hotspot-1.1.0+git20180816/src/models/data.h 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/models/data.h 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2016-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2016-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Milian Wolff Licensees holding valid commercial KDAB Hotspot licenses may use this file in @@ -32,6 +32,7 @@ #include #include #include +#include #include "../util.h" @@ -367,14 +368,63 @@ QVector symbols; QVector locations; - using FrameCallback = std::function; + // callback should return true to continue iteration or false otherwise + template + void foreachFrame(const QVector& frames, FrameCallback frameCallback) const + { + for (auto id : frames) { + if (!handleFrame(id, frameCallback)) { + break; + } + } + } - const BottomUp* addEvent(int type, quint64 cost, const QVector& frames, const FrameCallback& frameCallback); + // callback return type is ignored, all frames will be iterated over + template + const BottomUp* addEvent(int type, quint64 cost, const QVector& frames, FrameCallback frameCallback) + { + costs.addTotalCost(type, cost); + auto parent = &root; + foreachFrame(frames, [this, type, cost, &parent, frameCallback](const Data::Symbol &symbol, const Data::Location &location) { + parent = parent->entryForSymbol(symbol, &maxBottomUpId); + costs.add(type, parent->id, cost); + frameCallback(symbol, location); + return true; + }); + return parent; + } private: quint32 maxBottomUpId = 0; - BottomUp* addFrame(BottomUp* parent, qint32 locationId, int type, quint64 period, - const FrameCallback& frameCallback); + + template + bool handleFrame(qint32 locationId, FrameCallback frameCallback) const + { + bool skipNextFrame = false; + while (locationId != -1) { + const auto& location = locations.value(locationId); + if (skipNextFrame) { + locationId = location.parentLocationId; + skipNextFrame = false; + continue; + } + + auto symbol = symbols.value(locationId); + if (!symbol.isValid()) { + // we get function entry points from the perfparser but + // those are imo not interesting - skip them + symbol = symbols.value(location.parentLocationId); + skipNextFrame = true; + } + + if (!frameCallback(symbol, location.location)) { + return false; + } + + locationId = location.parentLocationId; + } + return true; + } }; struct TopDown : SymbolTree @@ -491,14 +541,64 @@ using Events = QVector; +struct TimeRange +{ + constexpr TimeRange() = default; + constexpr TimeRange(quint64 start, quint64 end) + : start(start) + , end(end) + { + } + + quint64 start = 0; + quint64 end = 0; + + bool isValid() const + { + return start > 0 || end > 0; + } + + bool isEmpty() const + { + return start != end; + } + + quint64 delta() const + { + return end - start; + } + + bool contains(quint64 time) const + { + return time >= start && time <= end; + } + + TimeRange normalized() const + { + if (end < start) + return {end, start}; + return *this; + } + + bool operator==(const TimeRange& rhs) const + { + return std::tie(start, end) == std::tie(rhs.start, rhs.end); + } + + bool operator!=(const TimeRange& rhs) const + { + return !operator==(rhs); + } +}; + const constexpr auto MAX_TIME = std::numeric_limits::max(); +const constexpr auto MAX_TIME_RANGE = TimeRange {0, MAX_TIME}; struct ThreadEvents { qint32 pid = INVALID_PID; qint32 tid = INVALID_TID; - quint64 timeStart = 0; - quint64 timeEnd = MAX_TIME; + TimeRange time = MAX_TIME_RANGE; Events events; QString name; quint64 lastSwitchTime = MAX_TIME; @@ -513,9 +613,9 @@ bool operator==(const ThreadEvents& rhs) const { - return std::tie(pid, tid, timeStart, timeEnd, events, name, lastSwitchTime, offCpuTime, state) - == std::tie(rhs.pid, rhs.tid, rhs.timeStart, rhs.timeEnd, rhs.events, rhs.name, rhs.lastSwitchTime, - rhs.offCpuTime, rhs.state); + return std::tie(pid, tid, time, events, name, lastSwitchTime, offCpuTime, state) + == std::tie(rhs.pid, rhs.tid, rhs.time, rhs.events, rhs.name, rhs.lastSwitchTime, rhs.offCpuTime, + rhs.state); } }; @@ -602,14 +702,34 @@ struct FilterAction { - quint64 startTime = 0; - quint64 endTime = 0; - qint32 processId = Data::INVALID_PID; - qint32 threadId = Data::INVALID_PID; + TimeRange time; + qint32 processId = INVALID_PID; + qint32 threadId = INVALID_PID; quint32 cpuId = INVALID_CPU_ID; QVector excludeProcessIds; QVector excludeThreadIds; QVector excludeCpuIds; + QSet includeSymbols; + QSet excludeSymbols; + + bool isValid() const + { + return time.isValid() || processId != INVALID_PID + || threadId != INVALID_PID || cpuId != INVALID_CPU_ID + || !excludeProcessIds.isEmpty() || !excludeThreadIds.isEmpty() + || !excludeCpuIds.isEmpty() || !includeSymbols.isEmpty() + || !excludeSymbols.isEmpty(); + } +}; + +struct ZoomAction +{ + TimeRange time; + + bool isValid() const + { + return time.isValid(); + } }; } @@ -663,4 +783,8 @@ Q_DECLARE_METATYPE(Data::EventResults) Q_DECLARE_TYPEINFO(Data::EventResults, Q_MOVABLE_TYPE); +Q_DECLARE_METATYPE(Data::TimeRange) +Q_DECLARE_TYPEINFO(Data::TimeRange, Q_MOVABLE_TYPE); + Q_DECLARE_TYPEINFO(Data::FilterAction, Q_MOVABLE_TYPE); +Q_DECLARE_TYPEINFO(Data::ZoomAction, Q_MOVABLE_TYPE); diff -Nru hotspot-1.1.0+git20180816/src/models/eventmodel.cpp hotspot-1.1.0+git20190211/src/models/eventmodel.cpp --- hotspot-1.1.0+git20180816/src/models/eventmodel.cpp 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/models/eventmodel.cpp 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Milian Wolff Licensees holding valid commercial KDAB Hotspot licenses may use this file in @@ -115,9 +115,9 @@ } if (role == MaxTimeRole) { - return m_maxTime; + return m_time.end; } else if (role == MinTimeRole) { - return m_minTime; + return m_time.start; } else if (role == MaxCostRole) { return m_maxCost; } else if (role == NumProcessesRole) { @@ -162,9 +162,9 @@ } if (role == ThreadStartRole) { - return thread ? thread->timeStart : m_minTime; + return thread ? thread->time.start : m_time.start; } else if (role == ThreadEndRole) { - return thread ? thread->timeEnd : m_maxTime; + return thread ? thread->time.end : m_time.end; } else if (role == ThreadNameRole) { return thread ? thread->name : tr("CPU #%1").arg(cpu->cpuId); } else if (role == ThreadIdRole) { @@ -191,8 +191,8 @@ : tr("Thread %1, tid = %2, pid = %3\n") .arg(thread->name, QString::number(thread->tid), QString::number(thread->pid)); if (thread) { - const auto runtime = thread->timeEnd - thread->timeStart; - const auto totalRuntime = m_maxTime - m_minTime; + const auto runtime = thread->time.delta(); + const auto totalRuntime = m_time.delta(); tooltip += tr("Runtime: %1 (%2% of total runtime)\n") .arg(Util::formatTimeString(runtime), Util::formatCostRelative(runtime, totalRuntime)); if (m_totalOffCpuTime > 0) { @@ -235,18 +235,16 @@ m_totalOnCpuTime = 0; m_totalOffCpuTime = 0; if (data.threads.isEmpty()) { - m_minTime = 0; - m_maxTime = 0; + m_time = {}; } else { - m_minTime = data.threads.first().timeStart; - m_maxTime = data.threads.first().timeEnd; + m_time = data.threads.first().time; QSet processes; QSet threads; for (const auto& thread : data.threads) { - m_minTime = std::min(thread.timeStart, m_minTime); - m_maxTime = std::max(thread.timeEnd, m_maxTime); + m_time.start = std::min(thread.time.start, m_time.start); + m_time.end = std::max(thread.time.end, m_time.end); m_totalOffCpuTime += thread.offCpuTime; - m_totalOnCpuTime += thread.timeEnd - thread.timeStart - thread.offCpuTime; + m_totalOnCpuTime += thread.time.delta() - thread.offCpuTime; m_totalEvents += thread.events.size(); processes.insert(thread.pid); threads.insert(thread.tid); diff -Nru hotspot-1.1.0+git20180816/src/models/eventmodel.h hotspot-1.1.0+git20190211/src/models/eventmodel.h --- hotspot-1.1.0+git20180816/src/models/eventmodel.h 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/models/eventmodel.h 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Milian Wolff Licensees holding valid commercial KDAB Hotspot licenses may use this file in @@ -78,8 +78,7 @@ private: Data::EventResults m_data; - quint64 m_minTime = 0; - quint64 m_maxTime = 0; + Data::TimeRange m_time; quint64 m_totalOnCpuTime = 0; quint64 m_totalOffCpuTime = 0; quint64 m_totalEvents = 0; diff -Nru hotspot-1.1.0+git20180816/src/models/filterandzoomstack.cpp hotspot-1.1.0+git20190211/src/models/filterandzoomstack.cpp --- hotspot-1.1.0+git20180816/src/models/filterandzoomstack.cpp 1970-01-01 00:00:00.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/models/filterandzoomstack.cpp 2019-02-13 09:13:10.000000000 +0000 @@ -0,0 +1,233 @@ +/* + filterandzoomstack.cpp + + This file is part of Hotspot, the Qt GUI for performance analysis. + + Copyright (C) 2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Author: Milian Wolff + + Licensees holding valid commercial KDAB Hotspot licenses may use this file in + accordance with Hotspot Commercial License Agreement provided with the Software. + + Contact info@kdab.com if any conditions of this licensing are not clear to you. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "filterandzoomstack.h" + +#include +#include +#include + +FilterAndZoomStack::FilterAndZoomStack(QObject* parent) + : QObject(parent) +{ + m_actions.filterOut = new QAction(QIcon::fromTheme(QStringLiteral("kt-remove-filters")), tr("Filter Out"), this); + connect(m_actions.filterOut, &QAction::triggered, this, &FilterAndZoomStack::filterOut); + m_actions.filterOut->setToolTip(tr("Undo the last filter and show more data in the views.")); + + m_actions.resetFilter = new QAction(QIcon::fromTheme(QStringLiteral("view-filter")), tr("Reset Filter"), this); + connect(m_actions.resetFilter, &QAction::triggered, this, &FilterAndZoomStack::resetFilter); + m_actions.resetFilter->setToolTip(tr("Reset all filters and show the full data in the views.")); + + m_actions.zoomOut = new QAction(QIcon::fromTheme(QStringLiteral("zoom-out")), tr("Zoom Out"), this); + connect(m_actions.zoomOut, &QAction::triggered, this, &FilterAndZoomStack::zoomOut); + m_actions.zoomOut->setToolTip(tr("Undo the last zoom operation and show a larger range in the time line.")); + + m_actions.resetZoom = new QAction(QIcon::fromTheme(QStringLiteral("zoom-original")), tr("Reset Zoom"), this); + connect(m_actions.resetZoom, &QAction::triggered, this, &FilterAndZoomStack::resetZoom); + m_actions.resetZoom->setToolTip(tr("Reset the zoom level to show the full range in the time line.")); + + m_actions.resetFilterAndZoom = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear")), tr("Reset Zoom And Filter"), this); + connect(m_actions.resetFilterAndZoom, &QAction::triggered, this, &FilterAndZoomStack::resetFilterAndZoom); + m_actions.resetFilterAndZoom->setToolTip(tr("Reset both, filters and zoom level to show the full data in both, views and timeline.")); + + m_actions.filterInBySymbol = new QAction(QIcon::fromTheme(QStringLiteral("view-filter")), tr("Filter In By Symbol"), this); + connect(m_actions.filterInBySymbol, &QAction::triggered, this, [this](){ + const auto data = m_actions.filterInBySymbol->data(); + Q_ASSERT(data.canConvert()); + filterInBySymbol(data.value()); + }); + + m_actions.filterOutBySymbol = new QAction(QIcon::fromTheme(QStringLiteral("view-filter")), tr("Filter Out By Symbol")); + connect(m_actions.filterOutBySymbol, &QAction::triggered, this, [this](){ + const auto data = m_actions.filterInBySymbol->data(); + Q_ASSERT(data.canConvert()); + filterOutBySymbol(data.value()); + }); + + connect(this, &FilterAndZoomStack::filterChanged, this, &FilterAndZoomStack::updateActions); + connect(this, &FilterAndZoomStack::zoomChanged, this, &FilterAndZoomStack::updateActions); + updateActions(); +} + +FilterAndZoomStack::~FilterAndZoomStack() = default; + +Data::FilterAction FilterAndZoomStack::filter() const +{ + return m_filterStack.isEmpty() ? Data::FilterAction{} : m_filterStack.last(); +} + +Data::ZoomAction FilterAndZoomStack::zoom() const +{ + return m_zoomStack.isEmpty() ? Data::ZoomAction{} : m_zoomStack.last(); +} + +FilterAndZoomStack::Actions FilterAndZoomStack::actions() const +{ + return m_actions; +} + +void FilterAndZoomStack::filterInByTime(const Data::TimeRange &time) +{ + zoomIn(time); + + Data::FilterAction filter; + filter.time = time.normalized(); + applyFilter(filter); +} + +void FilterAndZoomStack::filterInByProcess(qint32 processId) +{ + Data::FilterAction filter; + filter.processId = processId; + applyFilter(filter); +} + +void FilterAndZoomStack::filterOutByProcess(qint32 processId) +{ + Data::FilterAction filter; + filter.excludeThreadIds.push_back(processId); + applyFilter(filter); +} + +void FilterAndZoomStack::filterInByThread(qint32 threadId) +{ + Data::FilterAction filter; + filter.threadId = threadId; + applyFilter(filter); +} + +void FilterAndZoomStack::filterOutByThread(qint32 threadId) +{ + Data::FilterAction filter; + filter.excludeThreadIds.push_back(threadId); + applyFilter(filter); +} + +void FilterAndZoomStack::filterInByCpu(quint32 cpuId) +{ + Data::FilterAction filter; + filter.cpuId = cpuId; + applyFilter(filter); +} + +void FilterAndZoomStack::filterOutByCpu(quint32 cpuId) +{ + Data::FilterAction filter; + filter.excludeCpuIds.push_back(cpuId); + applyFilter(filter); +} + +void FilterAndZoomStack::filterInBySymbol(const Data::Symbol &symbol) +{ + Data::FilterAction filter; + filter.includeSymbols.insert(symbol); + applyFilter(filter); +} + +void FilterAndZoomStack::filterOutBySymbol(const Data::Symbol &symbol) +{ + Data::FilterAction filter; + filter.excludeSymbols.insert(symbol); + applyFilter(filter); +} + +void FilterAndZoomStack::applyFilter(Data::FilterAction filter) +{ + if (!m_filterStack.isEmpty()) { + // apply previous filter state + const auto& lastFilter = m_filterStack.last(); + if (!filter.time.start) + filter.time.start = lastFilter.time.start; + if (!filter.time.end) + filter.time.end = lastFilter.time.end; + if (filter.processId == Data::INVALID_PID) + filter.processId = lastFilter.processId; + if (filter.threadId == Data::INVALID_TID) + filter.threadId = lastFilter.threadId; + if (filter.cpuId == Data::INVALID_CPU_ID) + filter.cpuId = lastFilter.cpuId; + filter.excludeProcessIds += lastFilter.excludeProcessIds; + filter.excludeThreadIds += lastFilter.excludeThreadIds; + filter.excludeCpuIds += lastFilter.excludeCpuIds; + filter.excludeSymbols += lastFilter.excludeSymbols; + filter.includeSymbols += lastFilter.includeSymbols; + filter.includeSymbols.subtract(filter.excludeSymbols); + } + + m_filterStack.push_back(filter); + + emit filterChanged(filter); +} + +void FilterAndZoomStack::resetFilter() +{ + m_filterStack.clear(); + emit filterChanged({}); +} + +void FilterAndZoomStack::filterOut() +{ + m_filterStack.removeLast(); + emit filterChanged(filter()); +} + +void FilterAndZoomStack::zoomIn(const Data::TimeRange &time) +{ + m_zoomStack.append({time.normalized()}); + emit zoomChanged(m_zoomStack.constLast()); +} + +void FilterAndZoomStack::resetZoom() +{ + m_zoomStack.clear(); + emit zoomChanged({}); +} + +void FilterAndZoomStack::zoomOut() +{ + m_zoomStack.removeLast(); + emit zoomChanged(zoom()); +} + +void FilterAndZoomStack::resetFilterAndZoom() +{ + resetFilter(); + resetZoom(); +} + +void FilterAndZoomStack::updateActions() +{ + const bool isFiltered = filter().isValid(); + m_actions.filterOut->setEnabled(isFiltered); + m_actions.resetFilter->setEnabled(isFiltered); + + const bool isZoomed = zoom().isValid(); + m_actions.zoomOut->setEnabled(isZoomed); + m_actions.resetZoom->setEnabled(isZoomed); + + m_actions.resetFilterAndZoom->setEnabled(isZoomed || isFiltered); +} diff -Nru hotspot-1.1.0+git20180816/src/models/filterandzoomstack.h hotspot-1.1.0+git20190211/src/models/filterandzoomstack.h --- hotspot-1.1.0+git20180816/src/models/filterandzoomstack.h 1970-01-01 00:00:00.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/models/filterandzoomstack.h 2019-02-13 09:13:10.000000000 +0000 @@ -0,0 +1,87 @@ +/* + filterandzoomstack.h + + This file is part of Hotspot, the Qt GUI for performance analysis. + + Copyright (C) 2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Author: Milian Wolff + + Licensees holding valid commercial KDAB Hotspot licenses may use this file in + accordance with Hotspot Commercial License Agreement provided with the Software. + + Contact info@kdab.com if any conditions of this licensing are not clear to you. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include + +#include "data.h" + +class QAction; + +class FilterAndZoomStack : public QObject +{ + Q_OBJECT +public: + explicit FilterAndZoomStack(QObject* parent = nullptr); + ~FilterAndZoomStack(); + + Data::FilterAction filter() const; + Data::ZoomAction zoom() const; + + struct Actions + { + QAction* filterOut = nullptr; + QAction* resetFilter = nullptr; + QAction* zoomOut = nullptr; + QAction* resetZoom = nullptr; + QAction* resetFilterAndZoom = nullptr; + QAction* filterInBySymbol = nullptr; + QAction* filterOutBySymbol = nullptr; + }; + + Actions actions() const; + +public slots: + void filterInByTime(const Data::TimeRange &time); + void filterInByProcess(qint32 processId); + void filterOutByProcess(qint32 processId); + void filterInByThread(qint32 threadId); + void filterOutByThread(qint32 threadId); + void filterInByCpu(quint32 cpuId); + void filterOutByCpu(quint32 cpuId); + void filterInBySymbol(const Data::Symbol &symbol); + void filterOutBySymbol(const Data::Symbol &symbol); + void applyFilter(Data::FilterAction filter); + void resetFilter(); + void filterOut(); + void zoomIn(const Data::TimeRange &time); + void resetZoom(); + void zoomOut(); + void resetFilterAndZoom(); + +signals: + void filterChanged(const Data::FilterAction& filter); + void zoomChanged(const Data::ZoomAction& zoom); + +private: + void updateActions(); + + Actions m_actions; + QVector m_filterStack; + QVector m_zoomStack; +}; diff -Nru hotspot-1.1.0+git20180816/src/models/hashmodel.cpp hotspot-1.1.0+git20190211/src/models/hashmodel.cpp --- hotspot-1.1.0+git20180816/src/models/hashmodel.cpp 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/models/hashmodel.cpp 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2016-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2016-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Milian Wolff Licensees holding valid commercial KDAB Hotspot licenses may use this file in diff -Nru hotspot-1.1.0+git20180816/src/models/hashmodel.h hotspot-1.1.0+git20190211/src/models/hashmodel.h --- hotspot-1.1.0+git20180816/src/models/hashmodel.h 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/models/hashmodel.h 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2016-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2016-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Milian Wolff Licensees holding valid commercial KDAB Hotspot licenses may use this file in diff -Nru hotspot-1.1.0+git20180816/src/models/processfiltermodel.cpp hotspot-1.1.0+git20190211/src/models/processfiltermodel.cpp --- hotspot-1.1.0+git20180816/src/models/processfiltermodel.cpp 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/models/processfiltermodel.cpp 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Authors: Milian Wolff Nate Rogers diff -Nru hotspot-1.1.0+git20180816/src/models/processfiltermodel.h hotspot-1.1.0+git20190211/src/models/processfiltermodel.h --- hotspot-1.1.0+git20180816/src/models/processfiltermodel.h 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/models/processfiltermodel.h 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Authors: Milian Wolff Nate Rogers diff -Nru hotspot-1.1.0+git20180816/src/models/processmodel.cpp hotspot-1.1.0+git20190211/src/models/processmodel.cpp --- hotspot-1.1.0+git20180816/src/models/processmodel.cpp 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/models/processmodel.cpp 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Authors: Milian Wolff Nate Rogers diff -Nru hotspot-1.1.0+git20180816/src/models/processmodel.h hotspot-1.1.0+git20190211/src/models/processmodel.h --- hotspot-1.1.0+git20180816/src/models/processmodel.h 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/models/processmodel.h 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Authors: Milian Wolff Nate Rogers diff -Nru hotspot-1.1.0+git20180816/src/models/timelinedelegate.cpp hotspot-1.1.0+git20190211/src/models/timelinedelegate.cpp --- hotspot-1.1.0+git20180816/src/models/timelinedelegate.cpp 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/models/timelinedelegate.cpp 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Milian Wolff Licensees holding valid commercial KDAB Hotspot licenses may use this file in @@ -37,40 +37,38 @@ #include "../util.h" #include "eventmodel.h" +#include "filterandzoomstack.h" #include #include TimeLineData::TimeLineData() - : TimeLineData({}, 0, 0, 0, 0, 0, {}) + : TimeLineData({}, 0, {}, {}, {}) { } -TimeLineData::TimeLineData(const Data::Events& events, quint64 maxCost, quint64 minTime, quint64 maxTime, - quint64 threadStartTime, quint64 threadEndTime, QRect rect) +TimeLineData::TimeLineData(const Data::Events& events, quint64 maxCost, const Data::TimeRange& time, + const Data::TimeRange& threadTime, QRect rect) : events(events) , maxCost(maxCost) - , minTime(minTime) - , maxTime(maxTime) - , timeDelta(maxTime - minTime) - , threadStartTime(threadStartTime) - , threadEndTime(threadEndTime) + , time(time) + , threadTime(threadTime) , h(rect.height() - 2 * padding) , w(rect.width() - 2 * padding) - , xMultiplicator(double(w) / timeDelta) + , xMultiplicator(double(w) / time.delta()) , yMultiplicator(double(h) / maxCost) { } -int TimeLineData::mapTimeToX(quint64 time) const +int TimeLineData::mapTimeToX(quint64 t) const { - return minTime > time ? 0 : int(double(time - minTime) * xMultiplicator); + return time.start > t ? 0 : int(double(t - time.start) * xMultiplicator); } quint64 TimeLineData::mapXToTime(int x) const { - return quint64(double(x) / xMultiplicator) + minTime; + return quint64(double(x) / xMultiplicator) + time.start; } int TimeLineData::mapCostToY(quint64 cost) const @@ -78,26 +76,24 @@ return double(cost) * yMultiplicator; } -void TimeLineData::zoom(quint64 newMinTime, quint64 newMaxTime) +void TimeLineData::zoom(const Data::TimeRange& t) { - const auto newTimeDelta = (newMaxTime - newMinTime); - minTime = newMinTime; - maxTime = newMaxTime; - timeDelta = newTimeDelta; - xMultiplicator = double(w) / newTimeDelta; + time = t; + xMultiplicator = double(w) / time.delta(); } namespace { -TimeLineData dataFromIndex(const QModelIndex& index, QRect rect, const QVector>& zoomStack) +TimeLineData dataFromIndex(const QModelIndex& index, QRect rect, const Data::ZoomAction &zoom) { TimeLineData data( index.data(EventModel::EventsRole).value(), index.data(EventModel::MaxCostRole).value(), - index.data(EventModel::MinTimeRole).value(), index.data(EventModel::MaxTimeRole).value(), - index.data(EventModel::ThreadStartRole).value(), - index.data(EventModel::ThreadEndRole).value(), rect); - if (!zoomStack.isEmpty()) { - data.zoom(zoomStack.last().first, zoomStack.last().second); + {index.data(EventModel::MinTimeRole).value(), index.data(EventModel::MaxTimeRole).value()}, + {index.data(EventModel::ThreadStartRole).value(), + index.data(EventModel::ThreadEndRole).value()}, + rect); + if (zoom.isValid()) { + data.zoom(zoom.time); } return data; } @@ -109,41 +105,22 @@ } } -TimeLineDelegate::TimeLineDelegate(QAbstractItemView* view) +TimeLineDelegate::TimeLineDelegate(FilterAndZoomStack* filterAndZoomStack, QAbstractItemView* view) : QStyledItemDelegate(view) + , m_filterAndZoomStack(filterAndZoomStack) , m_view(view) - , m_filterMenu(new QMenu) { m_view->viewport()->installEventFilter(this); - m_filterOutAction = m_filterMenu->addAction(QIcon::fromTheme(QStringLiteral("kt-remove-filters")), tr("Filter Out"), - this, &TimeLineDelegate::filterOut); - m_resetFilterAction = m_filterMenu->addAction(QIcon::fromTheme(QStringLiteral("view-filter")), tr("Reset Filter"), - this, &TimeLineDelegate::resetFilter); - - m_filterMenu->addSeparator(); - - m_zoomOutAction = m_filterMenu->addAction(QIcon::fromTheme(QStringLiteral("zoom-out")), tr("Zoom Out"), this, - &TimeLineDelegate::zoomOut); - m_resetZoomAction = m_filterMenu->addAction(QIcon::fromTheme(QStringLiteral("zoom-original")), tr("Reset Zoom"), - this, &TimeLineDelegate::resetZoom); - m_resetZoomAndFilterAction = - m_filterMenu->addAction(QIcon::fromTheme(QStringLiteral("edit-clear")), tr("Reset Zoom And Filter"), this, - &TimeLineDelegate::resetZoomAndFilter); - - updateFilterActions(); + connect(filterAndZoomStack, &FilterAndZoomStack::filterChanged, this, &TimeLineDelegate::updateView); + connect(filterAndZoomStack, &FilterAndZoomStack::zoomChanged, this, &TimeLineDelegate::updateZoomState); } TimeLineDelegate::~TimeLineDelegate() = default; -QMenu* TimeLineDelegate::filterMenu() const -{ - return m_filterMenu.data(); -} - void TimeLineDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { - const auto data = dataFromIndex(index, option.rect, m_zoomStack); + const auto data = dataFromIndex(index, option.rect, m_filterAndZoomStack->zoom()); const auto offCpuCostId = index.data(EventModel::EventResultsRole).value().offCpuTimeCostId; const bool is_alternate = option.features & QStyleOptionViewItem::Alternate; const auto& palette = option.palette; @@ -161,7 +138,7 @@ // visualize the time where the thread was active // i.e. paint events for threads that have any in the selected time range auto threadTimeRect = - QRect(QPoint(data.mapTimeToX(data.threadStartTime), 0), QPoint(data.mapTimeToX(data.threadEndTime), data.h)); + QRect(QPoint(data.mapTimeToX(data.threadTime.start), 0), QPoint(data.mapTimeToX(data.threadTime.end), data.h)); if (threadTimeRect.left() < option.rect.width() && threadTimeRect.right() > 0) { if (threadTimeRect.left() < 0) threadTimeRect.setLeft(0); @@ -223,11 +200,11 @@ } } - if (m_timeSliceStart != m_timeSliceEnd) { + if (m_timeSlice.isValid()) { // the painter is translated to option.rect.topLeft // clamp to available width to prevent us from painting over the other columns - const auto startX = std::max(data.mapTimeToX(m_timeSliceStart), 0); - const auto endX = std::min(data.mapTimeToX(m_timeSliceEnd), data.w); + const auto startX = std::max(data.mapTimeToX(m_timeSlice.start), 0); + const auto endX = std::min(data.mapTimeToX(m_timeSlice.end), data.w); // undo vertical padding manually to fill complete height QRect timeSlice(startX, -data.padding, endX - startX, option.rect.height()); @@ -245,7 +222,7 @@ const QModelIndex& index) { if (event->type() == QEvent::ToolTip) { - const auto data = dataFromIndex(index, option.rect, m_zoomStack); + const auto data = dataFromIndex(index, option.rect, m_filterAndZoomStack->zoom()); const auto localX = event->pos().x(); const auto mappedX = localX - option.rect.x() - data.padding; const auto time = data.mapXToTime(mappedX); @@ -298,7 +275,7 @@ found = findSamples(offCpuCostId, true); } - const auto formattedTime = Util::formatTimeString(time - data.minTime); + const auto formattedTime = Util::formatTimeString(time - data.time.start); const auto totalCosts = index.data(EventModel::TotalCostsRole).value>(); if (found.numSamples > 0 && found.type == offCpuCostId) { QToolTip::showText(event->globalPos(), @@ -344,25 +321,26 @@ const auto alwaysValidIndex = m_view->model()->index(0, EventModel::EventsColumn); const auto visualRect = m_view->visualRect(alwaysValidIndex); const bool inEventsColumn = visualRect.left() < pos.x(); - const bool isZoomed = !m_zoomStack.isEmpty(); - const bool isFiltered = !m_filterStack.isEmpty(); + const auto zoom = m_filterAndZoomStack->zoom(); + const auto filter = m_filterAndZoomStack->filter(); + const bool isZoomed = zoom.isValid(); + const bool isFiltered = filter.isValid(); if (isLeftButtonEvent && inEventsColumn) { - const auto data = dataFromIndex(alwaysValidIndex, visualRect, m_zoomStack); + const auto data = dataFromIndex(alwaysValidIndex, visualRect, zoom); const auto time = data.mapXToTime(pos.x() - visualRect.left() - data.padding); if (isButtonPress) { - m_timeSliceStart = time; + m_timeSlice.start = time; } - m_timeSliceEnd = time; + m_timeSlice.end = time; + m_timeSlice = m_timeSlice.normalized(); // trigger an update of the viewport, to ensure our paint method gets called again - m_view->viewport()->update(); + updateView(); } - const auto timeSliceStart = std::min(m_timeSliceStart, m_timeSliceEnd); - const auto timeSliceEnd = std::max(m_timeSliceStart, m_timeSliceEnd); - const bool isTimeSpanSelected = m_timeSliceStart != m_timeSliceEnd; + const bool isTimeSpanSelected = m_timeSlice.isEmpty(); const auto index = m_view->indexAt(pos.toPoint()); const bool haveContextInfo = index.isValid() || isZoomed || isFiltered; const bool showContextMenu = isButtonRelease @@ -385,95 +363,91 @@ const auto cpuId = index.data(EventModel::CpuIdRole).value(); const auto numCpus = index.data(EventModel::NumCpusRole).value(); - if (isTimeSpanSelected && (minTime != timeSliceStart || maxTime != timeSliceEnd)) { + if (isTimeSpanSelected && (minTime != m_timeSlice.start || maxTime != m_timeSlice.end)) { contextMenu->addAction(QIcon::fromTheme(QStringLiteral("zoom-in")), tr("Zoom In On Selection"), this, - [this]() { zoomIn(m_timeSliceStart, m_timeSliceEnd); }); + [this]() { m_filterAndZoomStack->zoomIn(m_timeSlice); }); } if (isRightButtonEvent && index.isValid() && threadStartTime != threadEndTime && numThreads > 1 && threadId != Data::INVALID_TID && ((!isZoomed && !isMainThread) - || (isZoomed && m_zoomStack.last().first != threadStartTime - && m_zoomStack.last().second != threadEndTime))) { + || (isZoomed && zoom.time.start != threadStartTime && zoom.time.end != threadEndTime))) { contextMenu->addAction( QIcon::fromTheme(QStringLiteral("zoom-in")), tr("Zoom In On Thread #%1 By Time").arg(threadId), this, - [this, threadStartTime, threadEndTime]() { zoomIn(threadStartTime, threadEndTime); }); + [this, threadStartTime, threadEndTime]() { m_filterAndZoomStack->zoomIn({threadStartTime, threadEndTime}); }); } if (isRightButtonEvent && isZoomed) { - contextMenu->addAction(m_zoomOutAction); - contextMenu->addAction(m_resetZoomAction); + contextMenu->addAction(m_filterAndZoomStack->actions().zoomOut); + contextMenu->addAction(m_filterAndZoomStack->actions().resetZoom); } contextMenu->addSeparator(); if (isTimeSpanSelected - && (!isFiltered || m_filterStack.last().startTime != timeSliceStart - || m_filterStack.last().endTime != timeSliceEnd)) { + && (!isFiltered || filter.time.end != m_timeSlice.start || filter.time.end != m_timeSlice.end)) { contextMenu->addAction(QIcon::fromTheme(QStringLiteral("kt-add-filters")), tr("Filter In On Selection"), - this, [this]() { filterInByTime(m_timeSliceStart, m_timeSliceEnd); }); + this, [this]() { m_filterAndZoomStack->filterInByTime(m_timeSlice); }); } if (isRightButtonEvent && index.isValid() && numThreads > 1 && threadId != Data::INVALID_TID) { if ((!isFiltered && !isMainThread) - || (isFiltered && m_filterStack.last().startTime != threadStartTime - && m_filterStack.last().endTime != threadEndTime)) { + || (isFiltered && filter.time.end != threadStartTime && filter.time.end != threadEndTime)) { contextMenu->addAction( QIcon::fromTheme(QStringLiteral("kt-add-filters")), tr("Filter In On Thread #%1 By Time").arg(threadId), this, - [this, threadStartTime, threadEndTime]() { filterInByTime(threadStartTime, threadEndTime); }); + [this, threadStartTime, threadEndTime]() { m_filterAndZoomStack->filterInByTime({threadStartTime, threadEndTime}); }); } - if ((!isFiltered || m_filterStack.last().threadId == Data::INVALID_TID)) { + if ((!isFiltered || filter.threadId == Data::INVALID_TID)) { contextMenu->addAction(QIcon::fromTheme(QStringLiteral("kt-add-filters")), tr("Filter In On Thread #%1").arg(threadId), this, - [this, threadId]() { filterInByThread(threadId); }); + [this, threadId]() { m_filterAndZoomStack->filterInByThread(threadId); }); contextMenu->addAction(QIcon::fromTheme(QStringLiteral("kt-add-filters")), tr("Exclude Thread #%1").arg(threadId), this, - [this, threadId]() { filterOutByThread(threadId); }); + [this, threadId]() { m_filterAndZoomStack->filterOutByThread(threadId); }); } if (numProcesses > 1 && (!isFiltered - || (m_filterStack.last().processId == Data::INVALID_PID - && m_filterStack.last().threadId == Data::INVALID_TID))) { + || (filter.processId == Data::INVALID_PID && filter.threadId == Data::INVALID_TID))) { contextMenu->addAction(QIcon::fromTheme(QStringLiteral("kt-add-filters")), tr("Filter In On Process #%1").arg(processId), this, - [this, processId]() { filterInByProcess(processId); }); + [this, processId]() { m_filterAndZoomStack->filterInByProcess(processId); }); contextMenu->addAction(QIcon::fromTheme(QStringLiteral("kt-add-filters")), tr("Exclude Process #%1").arg(processId), this, - [this, processId]() { filterOutByProcess(processId); }); + [this, processId]() { m_filterAndZoomStack->filterOutByProcess(processId); }); } } if (isRightButtonEvent && index.isValid() && cpuId != Data::INVALID_CPU_ID && numCpus > 1 - && (!isFiltered || m_filterStack.last().cpuId != cpuId)) { + && (!isFiltered || filter.cpuId != cpuId)) { contextMenu->addAction(QIcon::fromTheme(QStringLiteral("kt-add-filters")), tr("Filter In On CPU #%1").arg(cpuId), this, - [this, cpuId]() { filterInByCpu(cpuId); }); + [this, cpuId]() { m_filterAndZoomStack->filterInByCpu(cpuId); }); contextMenu->addAction(QIcon::fromTheme(QStringLiteral("kt-add-filters")), tr("Exclude CPU #%1").arg(cpuId), - this, [this, cpuId]() { filterOutByCpu(cpuId); }); + this, [this, cpuId]() { m_filterAndZoomStack->filterOutByCpu(cpuId); }); } if (isRightButtonEvent && isFiltered) { - contextMenu->addAction(m_filterOutAction); - contextMenu->addAction(m_resetFilterAction); + contextMenu->addAction(m_filterAndZoomStack->actions().filterOut); + contextMenu->addAction(m_filterAndZoomStack->actions().resetFilter); } if (isRightButtonEvent && (isFiltered || isZoomed)) { contextMenu->addSeparator(); - contextMenu->addAction(m_resetZoomAndFilterAction); + contextMenu->addAction(m_filterAndZoomStack->actions().resetFilterAndZoom); } contextMenu->popup(mouseEvent->globalPos()); return true; } else if (isTimeSpanSelected && isLeftButtonEvent) { const auto& data = alwaysValidIndex.data(EventModel::EventResultsRole).value(); - const auto timeDelta = timeSliceEnd - timeSliceStart; + const auto timeDelta = m_timeSlice.delta(); quint64 cost = 0; quint64 numEvents = 0; QSet threads; QSet processes; for (const auto& thread : data.threads) { - const auto start = findEvent(thread.events.begin(), thread.events.end(), timeSliceStart); - const auto end = findEvent(start, thread.events.end(), timeSliceEnd); + const auto start = findEvent(thread.events.begin(), thread.events.end(), m_timeSlice.start); + const auto end = findEvent(start, thread.events.end(), m_timeSlice.end); if (start != end) { threads.insert(thread.tid); processes.insert(thread.pid); @@ -501,155 +475,19 @@ return false; } -void TimeLineDelegate::filterInByTime(quint64 startTime, quint64 endTime) -{ - if (endTime < startTime) - std::swap(endTime, startTime); - - zoomIn(startTime, endTime); - - Data::FilterAction filter; - filter.startTime = startTime; - filter.endTime = endTime; - applyFilter(filter); -} - -void TimeLineDelegate::filterInByProcess(qint32 processId) -{ - Data::FilterAction filter; - filter.processId = processId; - applyFilter(filter); -} - -void TimeLineDelegate::filterOutByProcess(qint32 processId) -{ - Data::FilterAction filter; - filter.excludeThreadIds.push_back(processId); - applyFilter(filter); -} - -void TimeLineDelegate::filterInByThread(qint32 threadId) -{ - Data::FilterAction filter; - filter.threadId = threadId; - applyFilter(filter); -} - -void TimeLineDelegate::filterOutByThread(qint32 threadId) -{ - Data::FilterAction filter; - filter.excludeThreadIds.push_back(threadId); - applyFilter(filter); -} - -void TimeLineDelegate::filterInByCpu(quint32 cpuId) -{ - Data::FilterAction filter; - filter.cpuId = cpuId; - applyFilter(filter); -} - -void TimeLineDelegate::filterOutByCpu(quint32 cpuId) -{ - Data::FilterAction filter; - filter.excludeCpuIds.push_back(cpuId); - applyFilter(filter); -} - -void TimeLineDelegate::applyFilter(Data::FilterAction filter) -{ - if (!m_filterStack.isEmpty()) { - // apply previous filter state - const auto& lastFilter = m_filterStack.last(); - if (!filter.startTime) - filter.startTime = lastFilter.startTime; - if (!filter.endTime) - filter.endTime = lastFilter.endTime; - if (filter.processId == Data::INVALID_PID) - filter.processId = lastFilter.processId; - if (filter.threadId == Data::INVALID_TID) - filter.threadId = lastFilter.threadId; - if (filter.cpuId == Data::INVALID_CPU_ID) - filter.cpuId = lastFilter.cpuId; - filter.excludeProcessIds += lastFilter.excludeProcessIds; - filter.excludeThreadIds += lastFilter.excludeThreadIds; - filter.excludeCpuIds += lastFilter.excludeCpuIds; - } - - m_filterStack.push_back(filter); - updateFilterActions(); - - emit filterRequested(filter); -} - -void TimeLineDelegate::zoomIn(quint64 startTime, quint64 endTime) -{ - if (endTime < startTime) - std::swap(endTime, startTime); - - m_zoomStack.append({startTime, endTime}); - updateZoomState(); -} - -void TimeLineDelegate::updateZoomState() -{ - m_timeSliceStart = 0; - m_timeSliceEnd = 0; - m_view->viewport()->update(); - updateFilterActions(); -} - void TimeLineDelegate::setEventType(int type) { m_eventType = type; - m_view->viewport()->update(); -} - -void TimeLineDelegate::resetFilter() -{ - m_filterStack.clear(); - emit filterRequested({}); - updateFilterActions(); -} - -void TimeLineDelegate::filterOut() -{ - m_filterStack.removeLast(); - if (m_filterStack.isEmpty()) { - emit filterRequested({}); - } else { - emit filterRequested(m_filterStack.last()); - } - updateFilterActions(); -} - -void TimeLineDelegate::resetZoom() -{ - m_zoomStack.clear(); - updateZoomState(); + updateView(); } -void TimeLineDelegate::zoomOut() +void TimeLineDelegate::updateView() { - m_zoomStack.removeLast(); - updateZoomState(); -} - -void TimeLineDelegate::resetZoomAndFilter() -{ - resetFilter(); - resetZoom(); + m_view->viewport()->update(); } -void TimeLineDelegate::updateFilterActions() +void TimeLineDelegate::updateZoomState() { - const bool isFiltered = !m_filterStack.isEmpty(); - m_filterOutAction->setEnabled(isFiltered); - m_resetFilterAction->setEnabled(isFiltered); - - const bool isZoomed = !m_zoomStack.isEmpty(); - m_zoomOutAction->setEnabled(isZoomed); - m_resetZoomAction->setEnabled(isZoomed); - - m_resetZoomAndFilterAction->setEnabled(isZoomed || isFiltered); + m_timeSlice = {}; + updateView(); } diff -Nru hotspot-1.1.0+git20180816/src/models/timelinedelegate.h hotspot-1.1.0+git20190211/src/models/timelinedelegate.h --- hotspot-1.1.0+git20180816/src/models/timelinedelegate.h 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/models/timelinedelegate.h 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Milian Wolff Licensees holding valid commercial KDAB Hotspot licenses may use this file in @@ -34,15 +34,16 @@ #include "data.h" class QAbstractItemView; -class QMenu; class QAction; +class FilterAndZoomStack; + struct TimeLineData { TimeLineData(); - TimeLineData(const Data::Events& events, quint64 maxCost, quint64 minTime, quint64 maxTime, quint64 threadStartTime, - quint64 threadEndTime, QRect rect); + TimeLineData(const Data::Events& events, quint64 maxCost, const Data::TimeRange& time, + const Data::TimeRange& threadTime, QRect rect); int mapTimeToX(quint64 time) const; @@ -50,16 +51,13 @@ int mapCostToY(quint64 cost) const; - void zoom(quint64 timeStart, quint64 timeEnd); + void zoom(const Data::TimeRange &time); static const constexpr int padding = 2; Data::Events events; quint64 maxCost; - quint64 minTime; - quint64 maxTime; - quint64 timeDelta; - quint64 threadStartTime; - quint64 threadEndTime; + Data::TimeRange time; + Data::TimeRange threadTime; int h; int w; double xMultiplicator; @@ -71,7 +69,7 @@ { Q_OBJECT public: - explicit TimeLineDelegate(QAbstractItemView* view); + explicit TimeLineDelegate(FilterAndZoomStack* filterAndZoomStack, QAbstractItemView* view); virtual ~TimeLineDelegate(); void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; @@ -81,44 +79,15 @@ void setEventType(int type); - QMenu* filterMenu() const; - -signals: - // emitted when user wants to filter by time, process id or thread id - // a zero for any of the values means "show everything" - void filterRequested(const Data::FilterAction& filterAction); - protected: bool eventFilter(QObject* watched, QEvent* event) override; private: - void filterInByTime(quint64 timeStart, quint64 timeEnd); - void filterInByProcess(qint32 processId); - void filterOutByProcess(qint32 processId); - void filterInByThread(qint32 threadId); - void filterOutByThread(qint32 threadId); - void filterInByCpu(quint32 cpuId); - void filterOutByCpu(quint32 cpuId); - void applyFilter(Data::FilterAction filter); - void zoomIn(quint64 startTime, quint64 endTime); + void updateView(); void updateZoomState(); - void resetFilter(); - void filterOut(); - void resetZoom(); - void zoomOut(); - void resetZoomAndFilter(); - void updateFilterActions(); + FilterAndZoomStack* m_filterAndZoomStack = nullptr; QAbstractItemView* m_view = nullptr; - quint64 m_timeSliceStart = 0; - quint64 m_timeSliceEnd = 0; - QVector> m_zoomStack; - QVector m_filterStack; + Data::TimeRange m_timeSlice; int m_eventType = 0; - QScopedPointer m_filterMenu; - QAction* m_filterOutAction; - QAction* m_resetFilterAction; - QAction* m_zoomOutAction; - QAction* m_resetZoomAction; - QAction* m_resetZoomAndFilterAction; }; diff -Nru hotspot-1.1.0+git20180816/src/models/topproxy.cpp hotspot-1.1.0+git20190211/src/models/topproxy.cpp --- hotspot-1.1.0+git20180816/src/models/topproxy.cpp 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/models/topproxy.cpp 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Milian Wolff Licensees holding valid commercial KDAB Hotspot licenses may use this file in diff -Nru hotspot-1.1.0+git20180816/src/models/topproxy.h hotspot-1.1.0+git20190211/src/models/topproxy.h --- hotspot-1.1.0+git20180816/src/models/topproxy.h 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/models/topproxy.h 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Milian Wolff Licensees holding valid commercial KDAB Hotspot licenses may use this file in diff -Nru hotspot-1.1.0+git20180816/src/models/treemodel.cpp hotspot-1.1.0+git20190211/src/models/treemodel.cpp --- hotspot-1.1.0+git20180816/src/models/treemodel.cpp 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/models/treemodel.cpp 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2016-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2016-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Milian Wolff Licensees holding valid commercial KDAB Hotspot licenses may use this file in diff -Nru hotspot-1.1.0+git20180816/src/models/treemodel.h hotspot-1.1.0+git20190211/src/models/treemodel.h --- hotspot-1.1.0+git20180816/src/models/treemodel.h 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/models/treemodel.h 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2016-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2016-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Milian Wolff Licensees holding valid commercial KDAB Hotspot licenses may use this file in diff -Nru hotspot-1.1.0+git20180816/src/parsers/perf/perfparser.cpp hotspot-1.1.0+git20190211/src/parsers/perf/perfparser.cpp --- hotspot-1.1.0+git20180816/src/parsers/perf/perfparser.cpp 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/parsers/perf/perfparser.cpp 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2016-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2016-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Milian Wolff Licensees holding valid commercial KDAB Hotspot licenses may use this file in @@ -642,6 +642,7 @@ case EventType::Sample45: qCWarning(LOG_PERFPARSER) << "unexpected legacy type encountered" << eventType; break; + case EventType::TracePointSample: case EventType::Sample: { Sample sample; stream >> sample; @@ -657,6 +658,9 @@ addRecord(sample); addSample(sample); + + if (static_cast(eventType) == EventType::TracePointSample) + return true; // TODO: read full data break; } case EventType::ThreadStart: { @@ -665,7 +669,7 @@ qCDebug(LOG_PERFPARSER) << "parsed:" << threadStart; addRecord(threadStart); // override start time explicitly - addThread(threadStart)->timeStart = threadStart.time; + addThread(threadStart)->time.start = threadStart.time; break; } case EventType::ThreadEnd: { @@ -750,10 +754,8 @@ break; } case EventType::TracePointFormat: - case EventType::TracePointSample: { // TODO: implement me return true; - } case EventType::InvalidType: break; } @@ -771,7 +773,7 @@ { Data::BottomUp::initializeParents(&bottomUpResult.root); - summaryResult.applicationRunningTime = applicationEndTime - applicationStartTime; + summaryResult.applicationRunningTime = applicationTime.delta(); summaryResult.threadCount = uniqueThreads.size(); summaryResult.processCount = uniqueProcess.size(); @@ -779,8 +781,8 @@ buildCallerCalleeResult(); for (auto& thread : eventResult.threads) { - thread.timeStart = std::max(thread.timeStart, applicationStartTime); - thread.timeEnd = std::min(thread.timeEnd, applicationEndTime); + thread.time.start = std::max(thread.time.start, applicationTime.start); + thread.time.end = std::min(thread.time.end, applicationTime.end); if (thread.name.isEmpty()) { thread.name = PerfParser::tr("#%1").arg(thread.tid); } @@ -788,13 +790,12 @@ // we may have been switched out before detaching perf, so increment // the off-CPU time in this case if (thread.state == Data::ThreadEvents::OffCpu) { - thread.offCpuTime += thread.timeEnd - thread.lastSwitchTime; + thread.offCpuTime += thread.time.end - thread.lastSwitchTime; } if (thread.offCpuTime > 0) { - const auto runTime = thread.timeEnd - thread.timeStart; summaryResult.offCpuTime += thread.offCpuTime; - summaryResult.onCpuTime += runTime - thread.offCpuTime; + summaryResult.onCpuTime += thread.time.delta() - thread.offCpuTime; } } @@ -848,7 +849,7 @@ // when we encounter a thread the first time it was probably alive when // we started the application, otherwise we override the start time when // we encounter a ThreadStart event - thread.timeStart = applicationStartTime; + thread.time.start = applicationTime.start; thread.name = commands.value(thread.pid).value(thread.tid); eventResult.threads.push_back(thread); return &eventResult.threads.last(); @@ -858,7 +859,7 @@ { auto* thread = eventResult.findThread(threadEnd.pid, threadEnd.tid); if (thread) { - thread->timeEnd = threadEnd.time; + thread->time.end = threadEnd.time; } } @@ -1009,11 +1010,11 @@ uniqueProcess.insert(record.pid); uniqueThreads.insert(record.tid); - if (record.time < applicationStartTime || applicationStartTime == 0) { - applicationStartTime = record.time; + if (record.time < applicationTime.start || applicationTime.start == 0) { + applicationTime.start = record.time; } - if (record.time > applicationEndTime || applicationEndTime == 0) { - applicationEndTime = record.time; + if (record.time > applicationTime.end || applicationTime.end == 0) { + applicationTime.end = record.time; } } @@ -1162,8 +1163,7 @@ QVector strings; QProcess process; Data::Summary summaryResult; - quint64 applicationStartTime = 0; - quint64 applicationEndTime = 0; + Data::TimeRange applicationTime; QSet uniqueThreads; QSet uniqueProcess; Data::BottomUpResults bottomUpResult; @@ -1380,12 +1380,14 @@ Data::BottomUpResults bottomUp; Data::EventResults events = m_events; Data::CallerCalleeResults callerCallee; - const bool filterByTime = filter.startTime != 0 && filter.endTime != 0; + const bool filterByTime = filter.time.isValid(); const bool filterByCpu = filter.cpuId != std::numeric_limits::max(); const bool excludeByCpu = !filter.excludeCpuIds.isEmpty(); - if (!filterByTime && filter.processId == Data::INVALID_PID && filter.threadId == Data::INVALID_TID - && !filterByCpu && !excludeByCpu && filter.excludeProcessIds.isEmpty() - && filter.excludeThreadIds.isEmpty()) { + const bool includeBySymbol = !filter.includeSymbols.isEmpty(); + const bool excludeBySymbol = !filter.excludeSymbols.isEmpty(); + const bool filterByStack = includeBySymbol || excludeBySymbol; + + if (!filter.isValid()) { bottomUp = m_bottomUpResults; callerCallee = m_callerCalleeResults; } else { @@ -1400,6 +1402,30 @@ cpu.events.clear(); } + // we filter all available stacks and then remember the stack ids that should be + // included, which is hopefully less work than filtering the stack for every event + QVector filterStacks; + if (filterByStack) { + filterStacks.resize(m_events.stacks.size()); + // TODO: parallelize + for (qint32 stackId = 0, c = m_events.stacks.size(); stackId < c; ++stackId) { + // if empty, then all include filters are matched + auto included = filter.includeSymbols; + // if false, then none of the exclude filters matched + bool excluded = false; + m_bottomUpResults.foreachFrame(m_events.stacks.at(stackId), [&included, &excluded, &filter](const Data::Symbol& symbol, const Data::Location& /*location*/){ + excluded = filter.excludeSymbols.contains(symbol); + if (excluded) { + return false; + } + included.remove(symbol); + // only stop when we included everything and no exclude filter is set + return !included.isEmpty() || !filter.excludeSymbols.isEmpty(); + }); + filterStacks[stackId] = !excluded && included.isEmpty(); + } + } + // remove events that lie outside the selected time span // TODO: parallelize for (auto& thread : events.threads) { @@ -1410,22 +1436,24 @@ if ((filter.processId != Data::INVALID_PID && thread.pid != filter.processId) || (filter.threadId != Data::INVALID_TID && thread.tid != filter.threadId) - || (filterByTime && (thread.timeStart > filter.endTime || thread.timeEnd < filter.startTime)) + || (filterByTime && (thread.time.start > filter.time.end || thread.time.end < filter.time.start)) || filter.excludeProcessIds.contains(thread.pid) || filter.excludeThreadIds.contains(thread.tid)) { thread.events.clear(); continue; } - if (filterByTime || filterByCpu || excludeByCpu) { + if (filterByTime || filterByCpu || excludeByCpu || filterByStack) { auto it = std::remove_if( thread.events.begin(), thread.events.end(), - [filter, filterByTime, filterByCpu, excludeByCpu](const Data::Event& event) { - if (filterByTime && (event.time < filter.startTime || event.time >= filter.endTime)) { + [filter, filterByTime, filterByCpu, excludeByCpu, filterByStack, filterStacks](const Data::Event& event) { + if (filterByTime && filter.time.contains(event.time)) { return true; } else if (filterByCpu && event.cpuId != filter.cpuId) { return true; } else if (excludeByCpu && filter.excludeCpuIds.contains(event.cpuId)) { return true; + } else if (filterByStack && !filterStacks[event.stackId]) { + return true; } return false; }); diff -Nru hotspot-1.1.0+git20180816/src/parsers/perf/perfparser.h hotspot-1.1.0+git20190211/src/parsers/perf/perfparser.h --- hotspot-1.1.0+git20180816/src/parsers/perf/perfparser.h 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/parsers/perf/perfparser.h 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2016-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2016-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Milian Wolff Licensees holding valid commercial KDAB Hotspot licenses may use this file in diff -Nru hotspot-1.1.0+git20180816/src/perfrecord.cpp hotspot-1.1.0+git20190211/src/perfrecord.cpp --- hotspot-1.1.0+git20180816/src/perfrecord.cpp 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/perfrecord.cpp 2019-02-13 09:14:27.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Nate Rogers Licensees holding valid commercial KDAB Hotspot licenses may use this file in @@ -365,3 +365,8 @@ { return perfRecordHelp().contains("--switch-events"); } + +bool PerfRecord::isPerfInstalled() +{ + return !QStandardPaths::findExecutable(QStringLiteral("perf")).isEmpty(); +} diff -Nru hotspot-1.1.0+git20180816/src/perfrecord.h hotspot-1.1.0+git20190211/src/perfrecord.h --- hotspot-1.1.0+git20180816/src/perfrecord.h 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/perfrecord.h 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Nate Rogers Licensees holding valid commercial KDAB Hotspot licenses may use this file in @@ -59,6 +59,8 @@ static QStringList offCpuProfilingOptions(); + static bool isPerfInstalled(); + signals: void recordingStarted(const QString& perfBinary, const QStringList& arguments); void recordingFinished(const QString& fileLocation); diff -Nru hotspot-1.1.0+git20180816/src/recordpage.cpp hotspot-1.1.0+git20190211/src/recordpage.cpp --- hotspot-1.1.0+git20180816/src/recordpage.cpp 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/recordpage.cpp 2019-02-13 09:14:27.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Nate Rogers Licensees holding valid commercial KDAB Hotspot licenses may use this file in @@ -86,6 +86,13 @@ void updateStartRecordingButtonState(const QScopedPointer& ui) { + if (!PerfRecord::isPerfInstalled()) { + ui->startRecordingButton->setEnabled(false); + ui->applicationRecordErrorMessage->setText(QObject::tr("Please install perf before trying to record.")); + ui->applicationRecordErrorMessage->setVisible(true); + return; + } + bool enabled = false; switch (selectedRecordType(ui)) { case LaunchApplication: @@ -633,8 +640,9 @@ void RecordPage::appendOutput(const QString& text) { - ui->perfResultsTextEdit->insertPlainText(text); - ui->perfResultsTextEdit->moveCursor(QTextCursor::End); + QTextCursor cursor(ui->perfResultsTextEdit->document()); + cursor.movePosition(QTextCursor::End); + cursor.insertText(text); } void RecordPage::setError(const QString& message) diff -Nru hotspot-1.1.0+git20180816/src/recordpage.h hotspot-1.1.0+git20190211/src/recordpage.h --- hotspot-1.1.0+git20180816/src/recordpage.h 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/recordpage.h 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Nate Rogers Licensees holding valid commercial KDAB Hotspot licenses may use this file in diff -Nru hotspot-1.1.0+git20180816/src/resultsbottomuppage.cpp hotspot-1.1.0+git20190211/src/resultsbottomuppage.cpp --- hotspot-1.1.0+git20180816/src/resultsbottomuppage.cpp 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/resultsbottomuppage.cpp 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Nate Rogers Licensees holding valid commercial KDAB Hotspot licenses may use this file in @@ -40,7 +40,7 @@ #include "models/topproxy.h" #include "models/treemodel.h" -ResultsBottomUpPage::ResultsBottomUpPage(PerfParser* parser, QWidget* parent) +ResultsBottomUpPage::ResultsBottomUpPage(FilterAndZoomStack* filterStack, PerfParser* parser, QWidget* parent) : QWidget(parent) , ui(new Ui::ResultsBottomUpPage) { @@ -49,7 +49,7 @@ auto bottomUpCostModel = new BottomUpModel(this); ResultsUtil::setupTreeView(ui->bottomUpTreeView, ui->bottomUpSearch, bottomUpCostModel); ResultsUtil::setupCostDelegate(bottomUpCostModel, ui->bottomUpTreeView); - ResultsUtil::setupContextMenu(ui->bottomUpTreeView, bottomUpCostModel, + ResultsUtil::setupContextMenu(ui->bottomUpTreeView, bottomUpCostModel, filterStack, [this](const Data::Symbol& symbol) { emit jumpToCallerCallee(symbol); }); auto topHotspotsProxy = new TopProxy(this); @@ -63,3 +63,8 @@ } ResultsBottomUpPage::~ResultsBottomUpPage() = default; + +void ResultsBottomUpPage::clear() +{ + ui->bottomUpSearch->setText({}); +} diff -Nru hotspot-1.1.0+git20180816/src/resultsbottomuppage.h hotspot-1.1.0+git20190211/src/resultsbottomuppage.h --- hotspot-1.1.0+git20180816/src/resultsbottomuppage.h 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/resultsbottomuppage.h 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Nate Rogers Licensees holding valid commercial KDAB Hotspot licenses may use this file in @@ -40,14 +40,17 @@ class QTreeView; class PerfParser; +class FilterAndZoomStack; class ResultsBottomUpPage : public QWidget { Q_OBJECT public: - explicit ResultsBottomUpPage(PerfParser* parser, QWidget* parent = nullptr); + explicit ResultsBottomUpPage(FilterAndZoomStack* filterStack, PerfParser* parser, QWidget* parent = nullptr); ~ResultsBottomUpPage(); + void clear(); + signals: void jumpToCallerCallee(const Data::Symbol& symbol); diff -Nru hotspot-1.1.0+git20180816/src/resultscallercalleepage.cpp hotspot-1.1.0+git20190211/src/resultscallercalleepage.cpp --- hotspot-1.1.0+git20180816/src/resultscallercalleepage.cpp 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/resultscallercalleepage.cpp 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Nate Rogers Licensees holding valid commercial KDAB Hotspot licenses may use this file in @@ -41,6 +41,7 @@ #include "models/costdelegate.h" #include "models/hashmodel.h" #include "models/treemodel.h" +#include "models/filterandzoomstack.h" namespace { template @@ -69,7 +70,7 @@ } } -ResultsCallerCalleePage::ResultsCallerCalleePage(PerfParser* parser, QWidget* parent) +ResultsCallerCalleePage::ResultsCallerCalleePage(FilterAndZoomStack* filterStack, PerfParser* parser, QWidget* parent) : QWidget(parent) , ui(new Ui::ResultsCallerCalleePage) { @@ -82,6 +83,7 @@ ui->callerCalleeFilter->setProxy(m_callerCalleeProxy); ui->callerCalleeTableView->setSortingEnabled(true); ui->callerCalleeTableView->setModel(m_callerCalleeProxy); + ResultsUtil::setupContextMenu(ui->callerCalleeTableView, CallerCalleeModel::SymbolRole, filterStack, {}); ResultsUtil::stretchFirstColumn(ui->callerCalleeTableView); ResultsUtil::setupCostDelegate(m_callerCalleeCostModel, ui->callerCalleeTableView); @@ -117,6 +119,8 @@ }; connectCallerOrCalleeModel(ui->calleesView, m_callerCalleeCostModel, selectCallerCaleeeIndex); connectCallerOrCalleeModel(ui->callersView, m_callerCalleeCostModel, selectCallerCaleeeIndex); + ResultsUtil::setupContextMenu(ui->calleesView, CalleeModel::SymbolRole, filterStack, {}); + ResultsUtil::setupContextMenu(ui->callersView, CallerModel::SymbolRole, filterStack, {}); ui->sourceMapView->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui->sourceMapView, &QTreeView::customContextMenuRequested, this, @@ -205,6 +209,11 @@ m_appPath = path; } +void ResultsCallerCalleePage::clear() +{ + ui->callerCalleeFilter->setText({}); +} + void ResultsCallerCalleePage::jumpToCallerCallee(const Data::Symbol& symbol) { auto callerCalleeIndex = m_callerCalleeProxy->mapFromSource(m_callerCalleeCostModel->indexForSymbol(symbol)); diff -Nru hotspot-1.1.0+git20180816/src/resultscallercalleepage.h hotspot-1.1.0+git20190211/src/resultscallercalleepage.h --- hotspot-1.1.0+git20180816/src/resultscallercalleepage.h 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/resultscallercalleepage.h 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Nate Rogers Licensees holding valid commercial KDAB Hotspot licenses may use this file in @@ -42,16 +42,18 @@ class PerfParser; class CallerCalleeModel; +class FilterAndZoomStack; class ResultsCallerCalleePage : public QWidget { Q_OBJECT public: - explicit ResultsCallerCalleePage(PerfParser* parser, QWidget* parent = nullptr); + explicit ResultsCallerCalleePage(FilterAndZoomStack* filterStack, PerfParser* parser, QWidget* parent = nullptr); ~ResultsCallerCalleePage(); void setSysroot(const QString& path); void setAppPath(const QString& path); + void clear(); void jumpToCallerCallee(const Data::Symbol& symbol); diff -Nru hotspot-1.1.0+git20180816/src/resultsflamegraphpage.cpp hotspot-1.1.0+git20190211/src/resultsflamegraphpage.cpp --- hotspot-1.1.0+git20180816/src/resultsflamegraphpage.cpp 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/resultsflamegraphpage.cpp 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Nate Rogers Licensees holding valid commercial KDAB Hotspot licenses may use this file in @@ -30,11 +30,12 @@ #include "parsers/perf/perfparser.h" -ResultsFlameGraphPage::ResultsFlameGraphPage(PerfParser* parser, QWidget* parent) +ResultsFlameGraphPage::ResultsFlameGraphPage(FilterAndZoomStack* filterStack, PerfParser* parser, QWidget* parent) : QWidget(parent) , ui(new Ui::ResultsFlameGraphPage) { ui->setupUi(this); + ui->flameGraph->setFilterStack(filterStack); connect(parser, &PerfParser::bottomUpDataAvailable, this, [this](const Data::BottomUpResults& data) { ui->flameGraph->setBottomUpData(data); }); @@ -45,4 +46,9 @@ connect(ui->flameGraph, &FlameGraph::jumpToCallerCallee, this, &ResultsFlameGraphPage::jumpToCallerCallee); } +void ResultsFlameGraphPage::clear() +{ + ui->flameGraph->clear(); +} + ResultsFlameGraphPage::~ResultsFlameGraphPage() = default; diff -Nru hotspot-1.1.0+git20180816/src/resultsflamegraphpage.h hotspot-1.1.0+git20190211/src/resultsflamegraphpage.h --- hotspot-1.1.0+git20180816/src/resultsflamegraphpage.h 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/resultsflamegraphpage.h 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Nate Rogers Licensees holding valid commercial KDAB Hotspot licenses may use this file in @@ -38,14 +38,17 @@ } class PerfParser; +class FilterAndZoomStack; class ResultsFlameGraphPage : public QWidget { Q_OBJECT public: - explicit ResultsFlameGraphPage(PerfParser* parser, QWidget* parent = nullptr); + explicit ResultsFlameGraphPage(FilterAndZoomStack* filterStack, PerfParser* parser, QWidget* parent = nullptr); ~ResultsFlameGraphPage(); + void clear(); + signals: void jumpToCallerCallee(const Data::Symbol& symbol); diff -Nru hotspot-1.1.0+git20180816/src/resultspage.cpp hotspot-1.1.0+git20190211/src/resultspage.cpp --- hotspot-1.1.0+git20180816/src/resultspage.cpp 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/resultspage.cpp 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Nate Rogers Licensees holding valid commercial KDAB Hotspot licenses may use this file in @@ -39,6 +39,7 @@ #include "models/eventmodel.h" #include "models/timelinedelegate.h" +#include "models/filterandzoomstack.h" #include #include @@ -46,21 +47,39 @@ #include #include #include +#include + +static const int SUMMARY_TABINDEX = 0; ResultsPage::ResultsPage(PerfParser* parser, QWidget* parent) : QWidget(parent) , ui(new Ui::ResultsPage) - , m_resultsSummaryPage(new ResultsSummaryPage(parser, this)) - , m_resultsBottomUpPage(new ResultsBottomUpPage(parser, this)) - , m_resultsTopDownPage(new ResultsTopDownPage(parser, this)) - , m_resultsFlameGraphPage(new ResultsFlameGraphPage(parser, this)) - , m_resultsCallerCalleePage(new ResultsCallerCalleePage(parser, this)) + , m_filterAndZoomStack(new FilterAndZoomStack(this)) + , m_resultsSummaryPage(new ResultsSummaryPage(m_filterAndZoomStack, parser, this)) + , m_resultsBottomUpPage(new ResultsBottomUpPage(m_filterAndZoomStack, parser, this)) + , m_resultsTopDownPage(new ResultsTopDownPage(m_filterAndZoomStack, parser, this)) + , m_resultsFlameGraphPage(new ResultsFlameGraphPage(m_filterAndZoomStack, parser, this)) + , m_resultsCallerCalleePage(new ResultsCallerCalleePage(m_filterAndZoomStack, parser, this)) + , m_filterMenu(new QMenu(this)) + , m_timeLineDelegate(nullptr) , m_filterBusyIndicator(nullptr) // create after we setup the UI to keep it on top + , m_timelineVisible(true) { + { + const auto actions = m_filterAndZoomStack->actions(); + m_filterMenu->addAction(actions.filterOut); + m_filterMenu->addAction(actions.resetFilter); + m_filterMenu->addSeparator(); + m_filterMenu->addAction(actions.zoomOut); + m_filterMenu->addAction(actions.resetZoom); + m_filterMenu->addSeparator(); + m_filterMenu->addAction(actions.resetFilterAndZoom); + } + ui->setupUi(this); ui->resultsTabWidget->setFocus(); - const int summaryTabIndex = ui->resultsTabWidget->addTab(m_resultsSummaryPage, tr("Summary")); + ui->resultsTabWidget->addTab(m_resultsSummaryPage, tr("Summary")); ui->resultsTabWidget->addTab(m_resultsBottomUpPage, tr("Bottom Up")); ui->resultsTabWidget->addTab(m_resultsTopDownPage, tr("Top Down")); ui->resultsTabWidget->addTab(m_resultsFlameGraphPage, tr("Flame Graph")); @@ -86,9 +105,9 @@ // due to the increased width leading to a zoom effect ui->timeLineView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); - auto* timeLineDelegate = new TimeLineDelegate(ui->timeLineView); - ui->timeLineEventFilterButton->setMenu(timeLineDelegate->filterMenu()); - ui->timeLineView->setItemDelegateForColumn(EventModel::EventsColumn, timeLineDelegate); + m_timeLineDelegate = new TimeLineDelegate(m_filterAndZoomStack, ui->timeLineView); + ui->timeLineEventFilterButton->setMenu(m_filterMenu); + ui->timeLineView->setItemDelegateForColumn(EventModel::EventsColumn, m_timeLineDelegate); connect(timeLineProxy, &QAbstractItemModel::rowsInserted, this, [this]() { ui->timeLineView->expandToDepth(1); }); connect(timeLineProxy, &QAbstractItemModel::modelReset, this, [this]() { ui->timeLineView->expandToDepth(1); }); @@ -109,17 +128,17 @@ } } }); - connect(timeLineDelegate, &TimeLineDelegate::filterRequested, parser, &PerfParser::filterResults); + connect(m_filterAndZoomStack, &FilterAndZoomStack::filterChanged, parser, &PerfParser::filterResults); connect(ui->timeLineEventSource, static_cast(&QComboBox::currentIndexChanged), this, - [this, timeLineDelegate](int index) { + [this](int index) { const auto typeId = ui->timeLineEventSource->itemData(index).toInt(); - timeLineDelegate->setEventType(typeId); + m_timeLineDelegate->setEventType(typeId); }); ui->timeLineArea->hide(); connect(ui->resultsTabWidget, &QTabWidget::currentChanged, this, - [this, summaryTabIndex](int index) { ui->timeLineArea->setVisible(index != summaryTabIndex); }); + [this](int index) { ui->timeLineArea->setVisible(index != SUMMARY_TABINDEX && m_timelineVisible); }); connect(parser, &PerfParser::parsingStarted, this, [this]() { // disable when we apply a filter // TODO: show some busy indicator? @@ -186,6 +205,19 @@ ui->resultsTabWidget->setCurrentWidget(m_resultsSummaryPage); } +void ResultsPage::clear() +{ + m_resultsBottomUpPage->clear(); + m_resultsTopDownPage->clear(); + m_resultsCallerCalleePage->clear(); + m_resultsFlameGraphPage->clear(); +} + +QMenu* ResultsPage::filterMenu() const +{ + return m_filterMenu; +} + bool ResultsPage::eventFilter(QObject* watched, QEvent* event) { if (watched == ui->timeLineArea && event->type() == QEvent::Resize) { @@ -203,3 +235,9 @@ QRect mapped(ui->timeLineView->mapTo(this, rect.topLeft()), ui->timeLineView->mapTo(this, rect.bottomRight())); m_filterBusyIndicator->setGeometry(mapped); } + +void ResultsPage::setTimelineVisible(bool visible) +{ + m_timelineVisible = visible; + ui->timeLineArea->setVisible(visible && ui->resultsTabWidget->currentIndex() != SUMMARY_TABINDEX); +} diff -Nru hotspot-1.1.0+git20180816/src/resultspage.h hotspot-1.1.0+git20190211/src/resultspage.h --- hotspot-1.1.0+git20180816/src/resultspage.h 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/resultspage.h 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Nate Rogers Licensees holding valid commercial KDAB Hotspot licenses may use this file in @@ -29,6 +29,8 @@ #include +class QMenu; + namespace Ui { class ResultsPage; } @@ -43,6 +45,8 @@ class ResultsTopDownPage; class ResultsFlameGraphPage; class ResultsCallerCalleePage; +class TimeLineDelegate; +class FilterAndZoomStack; class ResultsPage : public QWidget { @@ -55,10 +59,13 @@ void setAppPath(const QString& path); void selectSummaryTab(); + void clear(); + QMenu* filterMenu() const; public slots: void onNavigateToCode(const QString& url, int lineNumber, int columnNumber); void onJumpToCallerCallee(const Data::Symbol& symbol); + void setTimelineVisible(bool visible); signals: void navigateToCode(const QString& url, int lineNumber, int columnNumber); @@ -69,10 +76,14 @@ QScopedPointer ui; + FilterAndZoomStack* m_filterAndZoomStack; ResultsSummaryPage* m_resultsSummaryPage; ResultsBottomUpPage* m_resultsBottomUpPage; ResultsTopDownPage* m_resultsTopDownPage; ResultsFlameGraphPage* m_resultsFlameGraphPage; ResultsCallerCalleePage* m_resultsCallerCalleePage; + QMenu* m_filterMenu; + TimeLineDelegate* m_timeLineDelegate; QWidget* m_filterBusyIndicator; + bool m_timelineVisible; }; diff -Nru hotspot-1.1.0+git20180816/src/resultssummarypage.cpp hotspot-1.1.0+git20190211/src/resultssummarypage.cpp --- hotspot-1.1.0+git20180816/src/resultssummarypage.cpp 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/resultssummarypage.cpp 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Nate Rogers Licensees holding valid commercial KDAB Hotspot licenses may use this file in @@ -46,7 +46,7 @@ #include "models/topproxy.h" #include "models/treemodel.h" -ResultsSummaryPage::ResultsSummaryPage(PerfParser* parser, QWidget* parent) +ResultsSummaryPage::ResultsSummaryPage(FilterAndZoomStack* filterStack, PerfParser* parser, QWidget* parent) : QWidget(parent) , ui(new Ui::ResultsSummaryPage) { @@ -64,7 +64,7 @@ ui->topHotspotsTableView->setModel(topHotspotsProxy); ResultsUtil::setupCostDelegate(bottomUpCostModel, ui->topHotspotsTableView); ResultsUtil::stretchFirstColumn(ui->topHotspotsTableView); - ResultsUtil::setupContextMenu(ui->topHotspotsTableView, bottomUpCostModel, + ResultsUtil::setupContextMenu(ui->topHotspotsTableView, bottomUpCostModel, filterStack, [this](const Data::Symbol& symbol) { emit jumpToCallerCallee(symbol); }); connect(ui->eventSourceComboBox, static_cast(&QComboBox::currentIndexChanged), this, @@ -74,7 +74,7 @@ }); connect(parser, &PerfParser::bottomUpDataAvailable, this, - [this, bottomUpCostModel, topHotspotsProxy](const Data::BottomUpResults& data) { + [this, bottomUpCostModel](const Data::BottomUpResults& data) { bottomUpCostModel->setData(data); ResultsUtil::hideEmptyColumns(data.costs, ui->topHotspotsTableView, BottomUpModel::NUM_BASE_COLUMNS); ResultsUtil::fillEventSourceComboBox(ui->eventSourceComboBox, data.costs, diff -Nru hotspot-1.1.0+git20180816/src/resultssummarypage.h hotspot-1.1.0+git20190211/src/resultssummarypage.h --- hotspot-1.1.0+git20180816/src/resultssummarypage.h 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/resultssummarypage.h 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Nate Rogers Licensees holding valid commercial KDAB Hotspot licenses may use this file in @@ -38,12 +38,13 @@ } class PerfParser; +class FilterAndZoomStack; class ResultsSummaryPage : public QWidget { Q_OBJECT public: - explicit ResultsSummaryPage(PerfParser* parser, QWidget* parent = nullptr); + explicit ResultsSummaryPage(FilterAndZoomStack* filterStack, PerfParser* parser, QWidget* parent = nullptr); ~ResultsSummaryPage(); signals: diff -Nru hotspot-1.1.0+git20180816/src/resultstopdownpage.cpp hotspot-1.1.0+git20190211/src/resultstopdownpage.cpp --- hotspot-1.1.0+git20180816/src/resultstopdownpage.cpp 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/resultstopdownpage.cpp 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Nate Rogers Licensees holding valid commercial KDAB Hotspot licenses may use this file in @@ -34,7 +34,7 @@ #include "models/hashmodel.h" #include "models/treemodel.h" -ResultsTopDownPage::ResultsTopDownPage(PerfParser* parser, QWidget* parent) +ResultsTopDownPage::ResultsTopDownPage(FilterAndZoomStack* filterStack, PerfParser* parser, QWidget* parent) : QWidget(parent) , ui(new Ui::ResultsTopDownPage) { @@ -43,7 +43,7 @@ auto topDownCostModel = new TopDownModel(this); ResultsUtil::setupTreeView(ui->topDownTreeView, ui->topDownSearch, topDownCostModel); ResultsUtil::setupCostDelegate(topDownCostModel, ui->topDownTreeView); - ResultsUtil::setupContextMenu(ui->topDownTreeView, topDownCostModel, + ResultsUtil::setupContextMenu(ui->topDownTreeView, topDownCostModel, filterStack, [this](const Data::Symbol& symbol) { emit jumpToCallerCallee(symbol); }); connect(parser, &PerfParser::topDownDataAvailable, this, @@ -56,3 +56,8 @@ } ResultsTopDownPage::~ResultsTopDownPage() = default; + +void ResultsTopDownPage::clear() +{ + ui->topDownSearch->setText({}); +} diff -Nru hotspot-1.1.0+git20180816/src/resultstopdownpage.h hotspot-1.1.0+git20190211/src/resultstopdownpage.h --- hotspot-1.1.0+git20180816/src/resultstopdownpage.h 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/resultstopdownpage.h 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Nate Rogers Licensees holding valid commercial KDAB Hotspot licenses may use this file in @@ -40,14 +40,17 @@ class QTreeView; class PerfParser; +class FilterAndZoomStack; class ResultsTopDownPage : public QWidget { Q_OBJECT public: - explicit ResultsTopDownPage(PerfParser* parser, QWidget* parent = nullptr); + explicit ResultsTopDownPage(FilterAndZoomStack* filterStack, PerfParser* parser, QWidget* parent = nullptr); ~ResultsTopDownPage(); + void clear(); + signals: void jumpToCallerCallee(const Data::Symbol& symbol); diff -Nru hotspot-1.1.0+git20180816/src/resultsutil.cpp hotspot-1.1.0+git20190211/src/resultsutil.cpp --- hotspot-1.1.0+git20180816/src/resultsutil.cpp 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/resultsutil.cpp 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Nate Rogers Licensees holding valid commercial KDAB Hotspot licenses may use this file in @@ -39,6 +39,7 @@ #include "models/costdelegate.h" #include "models/data.h" +#include "models/filterandzoomstack.h" namespace ResultsUtil { @@ -63,25 +64,43 @@ stretchFirstColumn(view); } -void setupContextMenu(QTreeView* view, int symbolRole, std::function callback) +void addFilterActions(QMenu* menu, const Data::Symbol& symbol, FilterAndZoomStack* filterStack) +{ + if (symbol.isValid()) { + auto filterActions = filterStack->actions(); + filterActions.filterInBySymbol->setData(QVariant::fromValue(symbol)); + filterActions.filterOutBySymbol->setData(filterActions.filterInBySymbol->data()); + + menu->addAction(filterActions.filterInBySymbol); + menu->addAction(filterActions.filterOutBySymbol); + menu->addSeparator(); + } + + menu->addAction(filterStack->actions().filterOut); + menu->addAction(filterStack->actions().resetFilter); +} + +void setupContextMenu(QTreeView* view, int symbolRole, FilterAndZoomStack* filterStack, + std::function callback) { view->setContextMenuPolicy(Qt::CustomContextMenu); QObject::connect( - view, &QTreeView::customContextMenuRequested, view, [view, symbolRole, callback](const QPoint& point) { + view, &QTreeView::customContextMenuRequested, view, [view, symbolRole, filterStack, callback](const QPoint& point) { const auto index = view->indexAt(point); - if (!index.isValid()) { - return; - } + const auto symbol = index.data(symbolRole).value(); QMenu contextMenu; - auto* viewCallerCallee = contextMenu.addAction(QCoreApplication::translate("Util", "View Caller/Callee")); - auto* action = contextMenu.exec(QCursor::pos()); - if (action == viewCallerCallee) { - const auto symbol = index.data(symbolRole).value(); - - if (symbol.isValid()) { + if (callback && symbol.isValid()) { + auto* viewCallerCallee = contextMenu.addAction(QCoreApplication::translate("Util", "View Caller/Callee")); + QObject::connect(viewCallerCallee, &QAction::triggered, &contextMenu, [symbol, callback](){ callback(symbol); - } + }); + contextMenu.addSeparator(); + } + addFilterActions(&contextMenu, symbol, filterStack); + + if (!contextMenu.actions().isEmpty()) { + contextMenu.exec(QCursor::pos()); } }); } diff -Nru hotspot-1.1.0+git20180816/src/resultsutil.h hotspot-1.1.0+git20190211/src/resultsutil.h --- hotspot-1.1.0+git20180816/src/resultsutil.h 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/resultsutil.h 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Nate Rogers Licensees holding valid commercial KDAB Hotspot licenses may use this file in @@ -29,6 +29,7 @@ #include +class QMenu; class QTreeView; class QComboBox; class KFilterProxySearchLine; @@ -40,6 +41,8 @@ struct Symbol; } +class FilterAndZoomStack; + namespace ResultsUtil { void stretchFirstColumn(QTreeView* view); @@ -60,12 +63,14 @@ setupCostDelegate(model, view, Model::SortRole, Model::TotalCostRole, Model::NUM_BASE_COLUMNS); } -void setupContextMenu(QTreeView* view, int symbolRole, std::function callback); +void addFilterActions(QMenu* menu, const Data::Symbol &symbol, FilterAndZoomStack* filterStack); + +void setupContextMenu(QTreeView* view, int symbolRole, FilterAndZoomStack* filterStack, std::function callback); template -void setupContextMenu(QTreeView* view, Model* /*model*/, std::function callback) +void setupContextMenu(QTreeView* view, Model* /*model*/, FilterAndZoomStack* filterStack, std::function callback) { - setupContextMenu(view, Model::SymbolRole, callback); + setupContextMenu(view, Model::SymbolRole, filterStack, callback); } void hideEmptyColumns(const Data::Costs& costs, QTreeView* view, int numBaseColumns); diff -Nru hotspot-1.1.0+git20180816/src/startpage.cpp hotspot-1.1.0+git20190211/src/startpage.cpp --- hotspot-1.1.0+git20180816/src/startpage.cpp 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/startpage.cpp 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Nate Rogers Licensees holding valid commercial KDAB Hotspot licenses may use this file in diff -Nru hotspot-1.1.0+git20180816/src/startpage.h hotspot-1.1.0+git20190211/src/startpage.h --- hotspot-1.1.0+git20180816/src/startpage.h 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/startpage.h 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Nate Rogers Licensees holding valid commercial KDAB Hotspot licenses may use this file in diff -Nru hotspot-1.1.0+git20180816/src/util.cpp hotspot-1.1.0+git20190211/src/util.cpp --- hotspot-1.1.0+git20180816/src/util.cpp 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/util.cpp 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2016-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2016-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Milian Wolff Licensees holding valid commercial KDAB Hotspot licenses may use this file in diff -Nru hotspot-1.1.0+git20180816/src/util.h hotspot-1.1.0+git20190211/src/util.h --- hotspot-1.1.0+git20180816/src/util.h 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/src/util.h 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2016-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2016-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Milian Wolff Licensees holding valid commercial KDAB Hotspot licenses may use this file in diff -Nru hotspot-1.1.0+git20180816/tests/integrationtests/dump_perf_data.cpp hotspot-1.1.0+git20190211/tests/integrationtests/dump_perf_data.cpp --- hotspot-1.1.0+git20180816/tests/integrationtests/dump_perf_data.cpp 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/tests/integrationtests/dump_perf_data.cpp 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Nate Rogers Licensees holding valid commercial KDAB Hotspot licenses may use this file in diff -Nru hotspot-1.1.0+git20180816/tests/integrationtests/tst_perfparser.cpp hotspot-1.1.0+git20190211/tests/integrationtests/tst_perfparser.cpp --- hotspot-1.1.0+git20180816/tests/integrationtests/tst_perfparser.cpp 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/tests/integrationtests/tst_perfparser.cpp 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Nate Rogers Licensees holding valid commercial KDAB Hotspot licenses may use this file in @@ -109,7 +109,7 @@ private slots: void initTestCase() { - if (QStandardPaths::findExecutable(QStringLiteral("perf")).isEmpty()) { + if (!PerfRecord::isPerfInstalled()) { QSKIP("perf is not available, cannot run integration tests."); } @@ -379,8 +379,8 @@ quint64 lastTime = 0; for (int i = 0; i < 11; ++i) { const auto& thread = m_eventData.threads[i]; - QVERIFY(thread.timeStart > lastTime); - lastTime = thread.timeStart; + QVERIFY(thread.time.start > lastTime); + lastTime = thread.time.start; if (i == 0) { QCOMPARE(thread.name, QStringLiteral("cpp-threadnames")); QVERIFY(thread.offCpuTime > 1E9); // sleeps about 1s in total @@ -389,7 +389,7 @@ QVERIFY(thread.offCpuTime > 1E8); QVERIFY(thread.offCpuTime < 1E9); } - QVERIFY((thread.timeEnd - thread.timeStart) > thread.offCpuTime); + QVERIFY(thread.time.delta() > thread.offCpuTime); } } @@ -648,9 +648,9 @@ VERIFY_OR_THROW(!thread.name.isEmpty()); VERIFY_OR_THROW(thread.pid != 0); VERIFY_OR_THROW(thread.tid != 0); - VERIFY_OR_THROW(thread.timeStart != 0); - VERIFY_OR_THROW(thread.timeEnd > thread.timeStart); - VERIFY_OR_THROW(thread.offCpuTime == 0 || thread.offCpuTime < (thread.timeEnd - thread.timeStart)); + VERIFY_OR_THROW(thread.time.isValid()); + VERIFY_OR_THROW(thread.time.end > thread.time.start); + VERIFY_OR_THROW(thread.offCpuTime == 0 || thread.offCpuTime < thread.time.delta()); } VERIFY_OR_THROW(!m_eventData.totalCosts.isEmpty()); for (const auto& costs : m_eventData.totalCosts) { diff -Nru hotspot-1.1.0+git20180816/tests/modeltests/tst_models.cpp hotspot-1.1.0+git20190211/tests/modeltests/tst_models.cpp --- hotspot-1.1.0+git20180816/tests/modeltests/tst_models.cpp 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/tests/modeltests/tst_models.cpp 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Milian Wolff Licensees holding valid commercial KDAB Hotspot licenses may use this file in @@ -214,16 +214,14 @@ { thread1.pid = 1234; thread1.tid = 1234; - thread1.timeStart = 0; - thread1.timeEnd = endTime; + thread1.time = {0, endTime}; thread1.name = "foobar"; } auto& thread2 = events.threads[1]; { thread2.pid = 1234; thread2.tid = 1235; - thread2.timeStart = deltaTime; - thread2.timeEnd = endTime - deltaTime; + thread2.time = {deltaTime, endTime - deltaTime}; thread2.name = "asdf"; } @@ -241,7 +239,7 @@ }; for (quint64 time = 0; time < endTime; time += deltaTime) { thread1.events << generateEvent(time, 0); - if (time >= thread2.timeStart && time <= thread2.timeEnd) { + if (thread2.time.contains(time)) { thread2.events << generateEvent(time, 2); } } @@ -311,8 +309,8 @@ } else { const auto& thread = events.threads[j]; QCOMPARE(rowEvents, thread.events); - QCOMPARE(threadStart, thread.timeStart); - QCOMPARE(threadEnd, thread.timeEnd); + QCOMPARE(threadStart, thread.time.start); + QCOMPARE(threadEnd, thread.time.end); QCOMPARE(threadId, thread.tid); QCOMPARE(processId, thread.pid); QCOMPARE(cpuId, Data::INVALID_CPU_ID); diff -Nru hotspot-1.1.0+git20180816/tests/modeltests/tst_timelinedelegate.cpp hotspot-1.1.0+git20190211/tests/modeltests/tst_timelinedelegate.cpp --- hotspot-1.1.0+git20180816/tests/modeltests/tst_timelinedelegate.cpp 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/tests/modeltests/tst_timelinedelegate.cpp 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Milian Wolff Licensees holding valid commercial KDAB Hotspot licenses may use this file in @@ -48,12 +48,11 @@ QFETCH(TimeLineData, data); QFETCH(int, x); QFETCH(quint64, time); - QFETCH(quint64, minTime); - QFETCH(quint64, maxTime); + QFETCH(Data::TimeRange, timeRange); // make sure the mapping is within a certain threshold const auto relativeErrorThreshold = 0.01; // 1% - const auto timeErrorThreshold = relativeErrorThreshold * data.maxTime; + const auto timeErrorThreshold = relativeErrorThreshold * data.time.end; const auto xErrorThreshold = relativeErrorThreshold * data.w; auto validate = [&](quint64 value, quint64 expected, double threshold) { const auto actual = std::abs(static_cast(value - expected)); @@ -64,9 +63,9 @@ return true; }; - QVERIFY(validate(data.minTime, minTime, timeErrorThreshold)); - QVERIFY(validate(data.maxTime, maxTime, timeErrorThreshold)); - QVERIFY(validate(data.timeDelta, maxTime - minTime, timeErrorThreshold)); + QVERIFY(validate(data.time.start, timeRange.start, timeErrorThreshold)); + QVERIFY(validate(data.time.end, timeRange.end, timeErrorThreshold)); + QVERIFY(validate(data.time.delta(), timeRange.delta(), timeErrorThreshold)); QVERIFY(validate(data.mapXToTime(x), time, timeErrorThreshold)); QVERIFY(validate(data.mapTimeToX(time), x, xErrorThreshold)); @@ -77,36 +76,33 @@ QTest::addColumn("data"); QTest::addColumn("x"); QTest::addColumn("time"); - QTest::addColumn("minTime"); - QTest::addColumn("maxTime"); + QTest::addColumn("timeRange"); QRect rect(0, 0, 1000, 10); - quint64 minTime = 1000; - const quint64 maxTime = minTime + 10000; - TimeLineData data({}, 0, minTime, maxTime, minTime, maxTime, rect); + auto time = Data::TimeRange(1000, 1000 + 10000); + TimeLineData data({}, 0, time, time, rect); QCOMPARE(data.w, rect.width() - 2 * data.padding); QCOMPARE(data.h, rect.height() - 2 * data.padding); - QTest::newRow("minTime") << data << 0 << minTime << minTime << maxTime; - QTest::newRow("halfTime") << data << (rect.width() / 2) << (minTime + (maxTime - minTime) / 2) << minTime - << maxTime; - QTest::newRow("maxTime") << data << rect.width() << maxTime << minTime << maxTime; + QTest::newRow("minTime") << data << 0 << time.start << time; + QTest::newRow("halfTime") << data << (rect.width() / 2) << (time.start + time.delta() / 2) << time; + QTest::newRow("maxTime") << data << rect.width() << time.end << time; // zoom into the 2nd half - minTime = 6000; - data.zoom(minTime, maxTime); - QTest::newRow("minTime_zoom_2nd_half") << data << 0 << minTime << minTime << maxTime; + time.start = 6000; + data.zoom(time); + QTest::newRow("minTime_zoom_2nd_half") << data << 0 << time.start << time; QTest::newRow("halfTime_zoom_2nd_half") - << data << (rect.width() / 2) << (minTime + (maxTime - minTime) / 2) << minTime << maxTime; - QTest::newRow("maxTime_zoom_2nd_half") << data << rect.width() << maxTime << minTime << maxTime; + << data << (rect.width() / 2) << (time.start + time.delta() / 2) << time; + QTest::newRow("maxTime_zoom_2nd_half") << data << rect.width() << time.end << time; // zoom into the 4th quadrant - minTime = 8500; - data.zoom(minTime, maxTime); - QTest::newRow("minTime_zoom_4th_quadrant") << data << 0 << minTime << minTime << maxTime; + time.start = 8500; + data.zoom(time); + QTest::newRow("minTime_zoom_4th_quadrant") << data << 0 << time.start << time; QTest::newRow("halfTime_zoom_4th_quadrant") - << data << (rect.width() / 2) << (minTime + (maxTime - minTime) / 2) << minTime << maxTime; - QTest::newRow("maxTime_zoom_4th_quadrant") << data << rect.width() << maxTime << minTime << maxTime; + << data << (rect.width() / 2) << (time.start + time.delta() / 2) << time; + QTest::newRow("maxTime_zoom_4th_quadrant") << data << rect.width() << time.end << time; } }; diff -Nru hotspot-1.1.0+git20180816/tests/test-clients/cpp-inlining/main.cpp hotspot-1.1.0+git20190211/tests/test-clients/cpp-inlining/main.cpp --- hotspot-1.1.0+git20180816/tests/test-clients/cpp-inlining/main.cpp 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/tests/test-clients/cpp-inlining/main.cpp 2019-02-13 09:13:10.000000000 +0000 @@ -1,7 +1,7 @@ /* This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Milian Wolff Licensees holding valid commercial KDAB Hotspot licenses may use this file in diff -Nru hotspot-1.1.0+git20180816/tests/test-clients/cpp-locking/main.cpp hotspot-1.1.0+git20190211/tests/test-clients/cpp-locking/main.cpp --- hotspot-1.1.0+git20180816/tests/test-clients/cpp-locking/main.cpp 2018-08-27 09:31:08.000000000 +0000 +++ hotspot-1.1.0+git20190211/tests/test-clients/cpp-locking/main.cpp 2019-02-13 09:13:10.000000000 +0000 @@ -1,7 +1,7 @@ /* This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Milian Wolff Licensees holding valid commercial KDAB Hotspot licenses may use this file in diff -Nru hotspot-1.1.0+git20180816/tests/test-clients/cpp-minimal-static/main.cpp hotspot-1.1.0+git20190211/tests/test-clients/cpp-minimal-static/main.cpp --- hotspot-1.1.0+git20180816/tests/test-clients/cpp-minimal-static/main.cpp 2018-08-27 09:31:08.000000000 +0000 +++ hotspot-1.1.0+git20190211/tests/test-clients/cpp-minimal-static/main.cpp 2019-02-13 09:13:10.000000000 +0000 @@ -1,7 +1,7 @@ /* This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Milian Wolff Licensees holding valid commercial KDAB Hotspot licenses may use this file in diff -Nru hotspot-1.1.0+git20180816/tests/test-clients/cpp-parallel/main.cpp hotspot-1.1.0+git20190211/tests/test-clients/cpp-parallel/main.cpp --- hotspot-1.1.0+git20180816/tests/test-clients/cpp-parallel/main.cpp 2018-08-27 09:31:08.000000000 +0000 +++ hotspot-1.1.0+git20190211/tests/test-clients/cpp-parallel/main.cpp 2019-02-13 09:13:10.000000000 +0000 @@ -1,7 +1,7 @@ /* This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Milian Wolff Licensees holding valid commercial KDAB Hotspot licenses may use this file in diff -Nru hotspot-1.1.0+git20180816/tests/test-clients/cpp-recursion/main.cpp hotspot-1.1.0+git20190211/tests/test-clients/cpp-recursion/main.cpp --- hotspot-1.1.0+git20180816/tests/test-clients/cpp-recursion/main.cpp 2018-08-27 09:31:08.000000000 +0000 +++ hotspot-1.1.0+git20190211/tests/test-clients/cpp-recursion/main.cpp 2019-02-13 09:13:10.000000000 +0000 @@ -1,7 +1,7 @@ /* This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Milian Wolff Licensees holding valid commercial KDAB Hotspot licenses may use this file in diff -Nru hotspot-1.1.0+git20180816/tests/test-clients/cpp-sleep/main.cpp hotspot-1.1.0+git20190211/tests/test-clients/cpp-sleep/main.cpp --- hotspot-1.1.0+git20180816/tests/test-clients/cpp-sleep/main.cpp 2018-08-27 09:31:08.000000000 +0000 +++ hotspot-1.1.0+git20190211/tests/test-clients/cpp-sleep/main.cpp 2019-02-13 09:13:10.000000000 +0000 @@ -1,7 +1,7 @@ /* This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Milian Wolff Licensees holding valid commercial KDAB Hotspot licenses may use this file in diff -Nru hotspot-1.1.0+git20180816/tests/test-clients/cpp-stdin/main.cpp hotspot-1.1.0+git20190211/tests/test-clients/cpp-stdin/main.cpp --- hotspot-1.1.0+git20180816/tests/test-clients/cpp-stdin/main.cpp 2018-08-27 09:31:08.000000000 +0000 +++ hotspot-1.1.0+git20190211/tests/test-clients/cpp-stdin/main.cpp 2019-02-13 09:13:10.000000000 +0000 @@ -1,7 +1,7 @@ /* This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Milian Wolff Licensees holding valid commercial KDAB Hotspot licenses may use this file in diff -Nru hotspot-1.1.0+git20180816/tests/test-clients/cpp-threadnames/main.cpp hotspot-1.1.0+git20190211/tests/test-clients/cpp-threadnames/main.cpp --- hotspot-1.1.0+git20180816/tests/test-clients/cpp-threadnames/main.cpp 2018-08-27 09:31:08.000000000 +0000 +++ hotspot-1.1.0+git20190211/tests/test-clients/cpp-threadnames/main.cpp 2019-02-13 09:13:10.000000000 +0000 @@ -1,7 +1,7 @@ /* This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Milian Wolff Licensees holding valid commercial KDAB Hotspot licenses may use this file in diff -Nru hotspot-1.1.0+git20180816/tests/test-clients/c-syscalls/main.c hotspot-1.1.0+git20190211/tests/test-clients/c-syscalls/main.c --- hotspot-1.1.0+git20180816/tests/test-clients/c-syscalls/main.c 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/tests/test-clients/c-syscalls/main.c 2019-02-13 09:13:10.000000000 +0000 @@ -1,7 +1,7 @@ /* This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Milian Wolff Licensees holding valid commercial KDAB Hotspot licenses may use this file in diff -Nru hotspot-1.1.0+git20180816/tests/testutils.h hotspot-1.1.0+git20190211/tests/testutils.h --- hotspot-1.1.0+git20180816/tests/testutils.h 2018-08-27 09:31:08.000000000 +0000 +++ hotspot-1.1.0+git20190211/tests/testutils.h 2019-02-13 09:13:10.000000000 +0000 @@ -3,7 +3,7 @@ This file is part of Hotspot, the Qt GUI for performance analysis. - Copyright (C) 2017-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (C) 2017-2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Milian Wolff Licensees holding valid commercial KDAB Hotspot licenses may use this file in diff -Nru hotspot-1.1.0+git20180816/.travis.yml hotspot-1.1.0+git20190211/.travis.yml --- hotspot-1.1.0+git20180816/.travis.yml 2018-08-27 09:31:07.000000000 +0000 +++ hotspot-1.1.0+git20190211/.travis.yml 2019-02-13 09:13:10.000000000 +0000 @@ -4,11 +4,11 @@ dist: trusty before_install: - - sudo add-apt-repository ppa:beineri/opt-qt593-trusty -y + - sudo add-apt-repository ppa:beineri/opt-qt-5.10.1-trusty -y - sudo apt-get update -qq install: - - sudo apt-get -y install qt59base qt59svg qt59x11extras + - sudo apt-get -y install qt510base qt510svg qt510x11extras - source /opt/qt*/bin/qt*-env.sh - git clone git://anongit.kde.org/extra-cmake-modules - cd extra-cmake-modules @@ -31,18 +31,21 @@ - wget -c "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" - chmod a+x appimagetool-*.AppImage - sudo mv appimagetool-*.AppImage /usr/bin/appimagetool + - wget -c https://github.com/probonopd/uploadtool/raw/master/upload.sh + - chmod a+x upload.sh + - sudo mv upload.sh /usr/bin/github-upload script: - set -e # Exit immediately if anything fails - mkdir build - cd build - - cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DAPPIMAGE_BUILD=ON + - cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DAPPIMAGE_BUILD=ON - make -j$(nproc) - make DESTDIR=appdir install ; find appdir/ - mkdir -p appdir/usr/share/applications/ # FIXME: Do in CMakeLists.txt - cp ../hotspot.desktop appdir/usr/share/applications/ # FIXME: Do in CMakeLists.txt - unset QTDIR; unset QT_PLUGIN_PATH ; unset LD_LIBRARY_PATH - - export LD_LIBRARY_PATH=/opt/qt59/lib/x86_64-linux-gnu # make sure this path is known so all Qt/KF5 libs are found + - export LD_LIBRARY_PATH=/opt/qt510/lib/x86_64-linux-gnu # make sure this path is known so all Qt/KF5 libs are found - linuxdeployqt ./appdir/usr/share/applications/*.desktop -bundle-non-qt-libs - # workaround for https://github.com/KDAB/hotspot/issues/87 - linuxdeployqt ./appdir/usr/lib/x86_64-linux-gnu/libexec/hotspot-perfparser -bundle-non-qt-libs -no-plugins @@ -61,8 +64,15 @@ - chmod +x ./appdir/AppRun - # Actually create the final image - appimagetool ./appdir/ - - curl --upload-file ./Hotspot-*.AppImage https://transfer.sh/Hotspot-git.$(git rev-parse --short HEAD)-x86_64.AppImage + - # upload the appimage to GitHub + - mv Hotspot-*.AppImage Hotspot-git.$(git rev-parse --short HEAD)-x86_64.AppImage + - github-upload ./Hotspot-git.*-x86_64.AppImage - # finally run unit tests (we may want to get access to the app image independently from test failures) - set +e - source /opt/qt*/bin/qt*-env.sh - ctest -VV . + +branches: + except: + - # Do not build tags that we create when we upload to GitHub Releases + - /^(?i:continuous)$/