forked from qt-creator/qt-creator
CtfVisualizer: Fix multithreading issues
Simplify the CtfVisualizerTool::loadJson(). Don't create a QThread manually, but use TaskTree with AsyncTask instead. Move pure parsing into the separate thread, and leave the nlohmann::json event handling in the main thread. Avoid moving m_modelAggregator between threads. Fixes: QTCREATORBUG-29657 Change-Id: I0c6a9a4ea8298dbbdbafcddd338d39ad73c3f82b Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
This commit is contained in:
@@ -8,17 +8,11 @@
|
|||||||
#include "ctfvisualizertr.h"
|
#include "ctfvisualizertr.h"
|
||||||
|
|
||||||
#include <coreplugin/icore.h>
|
#include <coreplugin/icore.h>
|
||||||
|
|
||||||
#include <tracing/timelinemodelaggregator.h>
|
#include <tracing/timelinemodelaggregator.h>
|
||||||
|
|
||||||
#include <QByteArray>
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QFile>
|
|
||||||
#include <QList>
|
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
|
|
||||||
#include <fstream>
|
|
||||||
|
|
||||||
|
|
||||||
namespace CtfVisualizer {
|
namespace CtfVisualizer {
|
||||||
namespace Internal {
|
namespace Internal {
|
||||||
|
|
||||||
@@ -26,48 +20,6 @@ using json = nlohmann::json;
|
|||||||
|
|
||||||
using namespace Constants;
|
using namespace Constants;
|
||||||
|
|
||||||
|
|
||||||
class CtfJsonParserCallback
|
|
||||||
{
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
explicit CtfJsonParserCallback(CtfTraceManager *traceManager)
|
|
||||||
: m_traceManager(traceManager)
|
|
||||||
{}
|
|
||||||
|
|
||||||
bool callback(int depth, nlohmann::json::parse_event_t event, nlohmann::json &parsed)
|
|
||||||
{
|
|
||||||
if ((event == json::parse_event_t::array_start && depth == 0)
|
|
||||||
|| (event == json::parse_event_t::key && depth == 1 && parsed == json(CtfTraceEventsKey))) {
|
|
||||||
m_isInTraceArray = true;
|
|
||||||
m_traceArrayDepth = depth;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (m_isInTraceArray && event == json::parse_event_t::array_end && depth == m_traceArrayDepth) {
|
|
||||||
m_isInTraceArray = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (m_isInTraceArray && event == json::parse_event_t::object_end && depth == m_traceArrayDepth + 1) {
|
|
||||||
m_traceManager->addEvent(parsed);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (m_isInTraceArray || (event == json::parse_event_t::object_start && depth == 0)) {
|
|
||||||
// keep outer object and values in trace objects:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// discard any objects outside of trace array:
|
|
||||||
// TODO: parse other data, e.g. stack frames
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
CtfTraceManager *m_traceManager;
|
|
||||||
|
|
||||||
bool m_isInTraceArray = false;
|
|
||||||
int m_traceArrayDepth = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
CtfTraceManager::CtfTraceManager(QObject *parent,
|
CtfTraceManager::CtfTraceManager(QObject *parent,
|
||||||
Timeline::TimelineModelAggregator *modelAggregator,
|
Timeline::TimelineModelAggregator *modelAggregator,
|
||||||
CtfStatisticsModel *statisticsModel)
|
CtfStatisticsModel *statisticsModel)
|
||||||
@@ -75,7 +27,6 @@ CtfTraceManager::CtfTraceManager(QObject *parent,
|
|||||||
, m_modelAggregator(modelAggregator)
|
, m_modelAggregator(modelAggregator)
|
||||||
, m_statisticsModel(statisticsModel)
|
, m_statisticsModel(statisticsModel)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
qint64 CtfTraceManager::traceDuration() const
|
qint64 CtfTraceManager::traceDuration() const
|
||||||
@@ -142,26 +93,6 @@ void CtfTraceManager::addEvent(const json &event)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CtfTraceManager::load(const QString &filename)
|
|
||||||
{
|
|
||||||
clearAll();
|
|
||||||
|
|
||||||
std::ifstream file(filename.toStdString());
|
|
||||||
if (!file.is_open()) {
|
|
||||||
QMessageBox::warning(Core::ICore::dialogParent(),
|
|
||||||
Tr::tr("CTF Visualizer"),
|
|
||||||
Tr::tr("Cannot read the CTF file."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
CtfJsonParserCallback ctfParser(this);
|
|
||||||
json::parser_callback_t callback = [&ctfParser](int depth, json::parse_event_t event, json &parsed) {
|
|
||||||
return ctfParser.callback(depth, event, parsed);
|
|
||||||
};
|
|
||||||
json unusedValues = json::parse(file, callback, /*allow_exceptions*/ false);
|
|
||||||
file.close();
|
|
||||||
updateStatistics();
|
|
||||||
}
|
|
||||||
|
|
||||||
void CtfTraceManager::finalize()
|
void CtfTraceManager::finalize()
|
||||||
{
|
{
|
||||||
bool userConsentToIgnoreDeepTraces = false;
|
bool userConsentToIgnoreDeepTraces = false;
|
||||||
|
|||||||
@@ -5,13 +5,11 @@
|
|||||||
#include "json/json.hpp"
|
#include "json/json.hpp"
|
||||||
|
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
|
#include <QList>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QVector>
|
|
||||||
|
|
||||||
namespace Timeline {
|
namespace Timeline { class TimelineModelAggregator; }
|
||||||
class TimelineModelAggregator;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace CtfVisualizer {
|
namespace CtfVisualizer {
|
||||||
namespace Internal {
|
namespace Internal {
|
||||||
@@ -34,7 +32,6 @@ public:
|
|||||||
|
|
||||||
void addEvent(const nlohmann::json &event);
|
void addEvent(const nlohmann::json &event);
|
||||||
|
|
||||||
void load(const QString &filename);
|
|
||||||
void finalize();
|
void finalize();
|
||||||
|
|
||||||
bool isEmpty() const;
|
bool isEmpty() const;
|
||||||
@@ -46,6 +43,9 @@ public:
|
|||||||
void setThreadRestriction(const QString &tid, bool restrictToThisThread);
|
void setThreadRestriction(const QString &tid, bool restrictToThisThread);
|
||||||
bool isRestrictedTo(const QString &tid) const;
|
bool isRestrictedTo(const QString &tid) const;
|
||||||
|
|
||||||
|
void updateStatistics();
|
||||||
|
void clearAll();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void detailsRequested(const QString &title);
|
void detailsRequested(const QString &title);
|
||||||
|
|
||||||
@@ -53,10 +53,6 @@ protected:
|
|||||||
void addModelForThread(const QString &threadId, const QString &processId);
|
void addModelForThread(const QString &threadId, const QString &processId);
|
||||||
void addModelsToAggregator();
|
void addModelsToAggregator();
|
||||||
|
|
||||||
void updateStatistics();
|
|
||||||
|
|
||||||
void clearAll();
|
|
||||||
|
|
||||||
Timeline::TimelineModelAggregator *const m_modelAggregator;
|
Timeline::TimelineModelAggregator *const m_modelAggregator;
|
||||||
CtfStatisticsModel *const m_statisticsModel;
|
CtfStatisticsModel *const m_statisticsModel;
|
||||||
|
|
||||||
|
|||||||
@@ -13,30 +13,29 @@
|
|||||||
#include <coreplugin/actionmanager/actioncontainer.h>
|
#include <coreplugin/actionmanager/actioncontainer.h>
|
||||||
#include <coreplugin/actionmanager/actionmanager.h>
|
#include <coreplugin/actionmanager/actionmanager.h>
|
||||||
#include <coreplugin/icore.h>
|
#include <coreplugin/icore.h>
|
||||||
#include <coreplugin/progressmanager/progressmanager.h>
|
#include <coreplugin/progressmanager/taskprogress.h>
|
||||||
|
|
||||||
#include <debugger/analyzer/analyzerconstants.h>
|
#include <debugger/analyzer/analyzerconstants.h>
|
||||||
|
|
||||||
|
#include <utils/async.h>
|
||||||
#include <utils/stylehelper.h>
|
#include <utils/stylehelper.h>
|
||||||
#include <utils/utilsicons.h>
|
#include <utils/utilsicons.h>
|
||||||
|
|
||||||
#include <QAction>
|
|
||||||
#include <QApplication>
|
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QFutureInterface>
|
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QThread>
|
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
using namespace Core;
|
using namespace Core;
|
||||||
using namespace CtfVisualizer::Constants;
|
using namespace CtfVisualizer::Constants;
|
||||||
|
using namespace Utils;
|
||||||
|
|
||||||
namespace CtfVisualizer {
|
namespace CtfVisualizer {
|
||||||
namespace Internal {
|
namespace Internal {
|
||||||
|
|
||||||
CtfVisualizerTool::CtfVisualizerTool()
|
CtfVisualizerTool::CtfVisualizerTool()
|
||||||
: QObject (nullptr)
|
: QObject (nullptr)
|
||||||
, m_isLoading(false)
|
|
||||||
, m_loadJson(nullptr)
|
, m_loadJson(nullptr)
|
||||||
, m_traceView(nullptr)
|
, m_traceView(nullptr)
|
||||||
, m_modelAggregator(new Timeline::TimelineModelAggregator(this))
|
, m_modelAggregator(new Timeline::TimelineModelAggregator(this))
|
||||||
@@ -150,34 +149,84 @@ Timeline::TimelineZoomControl *CtfVisualizerTool::zoomControl() const
|
|||||||
return m_zoomControl.get();
|
return m_zoomControl.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CtfVisualizerTool::loadJson(const QString &filename)
|
class CtfJsonParserFunctor
|
||||||
{
|
{
|
||||||
if (m_isLoading)
|
public:
|
||||||
return;
|
CtfJsonParserFunctor(QPromise<nlohmann::json> &promise)
|
||||||
|
: m_promise(promise) {}
|
||||||
|
|
||||||
if (filename.isEmpty()) {
|
bool operator()(int depth, nlohmann::json::parse_event_t event, nlohmann::json &parsed)
|
||||||
m_isLoading = false;
|
{
|
||||||
|
using json = nlohmann::json;
|
||||||
|
if ((event == json::parse_event_t::array_start && depth == 0)
|
||||||
|
|| (event == json::parse_event_t::key && depth == 1 && parsed == json(CtfTraceEventsKey))) {
|
||||||
|
m_isInTraceArray = true;
|
||||||
|
m_traceArrayDepth = depth;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (m_isInTraceArray && event == json::parse_event_t::array_end && depth == m_traceArrayDepth) {
|
||||||
|
m_isInTraceArray = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (m_isInTraceArray && event == json::parse_event_t::object_end && depth == m_traceArrayDepth + 1) {
|
||||||
|
m_promise.addResult(parsed);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (m_isInTraceArray || (event == json::parse_event_t::object_start && depth == 0)) {
|
||||||
|
// keep outer object and values in trace objects:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// discard any objects outside of trace array:
|
||||||
|
// TODO: parse other data, e.g. stack frames
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QPromise<nlohmann::json> &m_promise;
|
||||||
|
bool m_isInTraceArray = false;
|
||||||
|
int m_traceArrayDepth = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void load(QPromise<nlohmann::json> &promise, const QString &fileName)
|
||||||
|
{
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
std::ifstream file(fileName.toStdString());
|
||||||
|
if (!file.is_open()) {
|
||||||
|
promise.future().cancel();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_isLoading = true;
|
CtfJsonParserFunctor functor(promise);
|
||||||
|
json::parser_callback_t callback = [&functor](int depth, json::parse_event_t event, json &parsed) {
|
||||||
|
return functor(depth, event, parsed);
|
||||||
|
};
|
||||||
|
|
||||||
auto *futureInterface = new QFutureInterface<void>();
|
try {
|
||||||
auto *task = new QFuture<void>(futureInterface);
|
json unusedValues = json::parse(file, callback, /*allow_exceptions*/ false);
|
||||||
|
} catch (...) {
|
||||||
|
// nlohmann::json can throw exceptions when requesting type that is wrong
|
||||||
|
}
|
||||||
|
|
||||||
QThread *thread = QThread::create([this, filename, futureInterface]() {
|
file.close();
|
||||||
try {
|
}
|
||||||
m_traceManager->load(filename);
|
|
||||||
} catch (...) {
|
|
||||||
// nlohmann::json can throw exceptions when requesting type that is wrong
|
|
||||||
}
|
|
||||||
m_modelAggregator->moveToThread(QApplication::instance()->thread());
|
|
||||||
m_modelAggregator->setParent(this);
|
|
||||||
futureInterface->reportFinished();
|
|
||||||
});
|
|
||||||
|
|
||||||
connect(thread, &QThread::finished, this, [this, thread, task, futureInterface]() {
|
void CtfVisualizerTool::loadJson(const QString &fileName)
|
||||||
// in main thread:
|
{
|
||||||
|
using namespace Tasking;
|
||||||
|
|
||||||
|
if (m_loader || fileName.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto onSetup = [this, fileName](Async<nlohmann::json> &async) {
|
||||||
|
m_traceManager->clearAll();
|
||||||
|
async.setConcurrentCallData(load, fileName);
|
||||||
|
connect(&async, &AsyncBase::resultReadyAt, this, [this, asyncPtr = &async](int index) {
|
||||||
|
m_traceManager->addEvent(asyncPtr->resultAt(index));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const auto onDone = [this] {
|
||||||
|
m_traceManager->updateStatistics();
|
||||||
if (m_traceManager->isEmpty()) {
|
if (m_traceManager->isEmpty()) {
|
||||||
QMessageBox::warning(Core::ICore::dialogParent(),
|
QMessageBox::warning(Core::ICore::dialogParent(),
|
||||||
Tr::tr("CTF Visualizer"),
|
Tr::tr("CTF Visualizer"),
|
||||||
@@ -189,17 +238,22 @@ void CtfVisualizerTool::loadJson(const QString &filename)
|
|||||||
zoomControl()->setRange(m_traceManager->traceBegin(), m_traceManager->traceEnd() + m_traceManager->traceDuration() / 20);
|
zoomControl()->setRange(m_traceManager->traceBegin(), m_traceManager->traceEnd() + m_traceManager->traceDuration() / 20);
|
||||||
}
|
}
|
||||||
setAvailableThreads(m_traceManager->getSortedThreads());
|
setAvailableThreads(m_traceManager->getSortedThreads());
|
||||||
thread->deleteLater();
|
m_loader.release()->deleteLater();
|
||||||
delete task;
|
};
|
||||||
delete futureInterface;
|
const auto onError = [this] {
|
||||||
m_isLoading = false;
|
QMessageBox::warning(Core::ICore::dialogParent(),
|
||||||
}, Qt::QueuedConnection);
|
Tr::tr("CTF Visualizer"),
|
||||||
|
Tr::tr("Cannot read the CTF file."));
|
||||||
|
m_loader.release()->deleteLater();
|
||||||
|
};
|
||||||
|
|
||||||
m_modelAggregator->setParent(nullptr);
|
const Group recipe { AsyncTask<nlohmann::json>(onSetup) };
|
||||||
m_modelAggregator->moveToThread(thread);
|
m_loader.reset(new TaskTree(recipe));
|
||||||
|
connect(m_loader.get(), &TaskTree::done, this, onDone);
|
||||||
thread->start();
|
connect(m_loader.get(), &TaskTree::errorOccurred, this, onError);
|
||||||
Core::ProgressManager::addTask(*task, Tr::tr("Loading CTF File"), CtfVisualizerTaskLoadJson);
|
auto progress = new TaskProgress(m_loader.get());
|
||||||
|
progress->setDisplayName(Tr::tr("Loading CTF File"));
|
||||||
|
m_loader->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Internal
|
} // namespace Internal
|
||||||
|
|||||||
@@ -6,12 +6,15 @@
|
|||||||
#include "ctfvisualizerconstants.h"
|
#include "ctfvisualizerconstants.h"
|
||||||
|
|
||||||
#include <debugger/debuggermainwindow.h>
|
#include <debugger/debuggermainwindow.h>
|
||||||
|
|
||||||
#include <tracing/timelinemodelaggregator.h>
|
#include <tracing/timelinemodelaggregator.h>
|
||||||
#include <tracing/timelinezoomcontrol.h>
|
#include <tracing/timelinezoomcontrol.h>
|
||||||
|
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QScopedPointer>
|
#include <QScopedPointer>
|
||||||
|
|
||||||
|
namespace Tasking { class TaskTree; }
|
||||||
|
|
||||||
namespace CtfVisualizer {
|
namespace CtfVisualizer {
|
||||||
namespace Internal {
|
namespace Internal {
|
||||||
|
|
||||||
@@ -21,7 +24,6 @@ class CtfStatisticsView;
|
|||||||
class CtfTimelineModel;
|
class CtfTimelineModel;
|
||||||
class CtfVisualizerTraceView;
|
class CtfVisualizerTraceView;
|
||||||
|
|
||||||
|
|
||||||
class CtfVisualizerTool : public QObject
|
class CtfVisualizerTool : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -34,7 +36,7 @@ public:
|
|||||||
CtfTraceManager *traceManager() const;
|
CtfTraceManager *traceManager() const;
|
||||||
Timeline::TimelineZoomControl *zoomControl() const;
|
Timeline::TimelineZoomControl *zoomControl() const;
|
||||||
|
|
||||||
void loadJson(const QString &filename);
|
void loadJson(const QString &fileName);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void createViews();
|
void createViews();
|
||||||
@@ -45,11 +47,11 @@ private:
|
|||||||
void setAvailableThreads(const QList<CtfTimelineModel *> &threads);
|
void setAvailableThreads(const QList<CtfTimelineModel *> &threads);
|
||||||
void toggleThreadRestriction(QAction *action);
|
void toggleThreadRestriction(QAction *action);
|
||||||
|
|
||||||
Utils::Perspective m_perspective{Constants::CtfVisualizerPerspectiveId,
|
Utils::Perspective m_perspective{CtfVisualizer::Constants::CtfVisualizerPerspectiveId,
|
||||||
QCoreApplication::translate("QtC::CtfVisualizer",
|
QCoreApplication::translate("QtC::CtfVisualizer",
|
||||||
"Chrome Trace Format Visualizer")};
|
"Chrome Trace Format Visualizer")};
|
||||||
|
|
||||||
bool m_isLoading;
|
std::unique_ptr<Tasking::TaskTree> m_loader;
|
||||||
QScopedPointer<QAction> m_loadJson;
|
QScopedPointer<QAction> m_loadJson;
|
||||||
|
|
||||||
CtfVisualizerTraceView *m_traceView;
|
CtfVisualizerTraceView *m_traceView;
|
||||||
|
|||||||
Reference in New Issue
Block a user