2022-08-19 15:59:36 +02:00
|
|
|
// Copyright (C) 2019 Klarälvdalens Datakonsult AB, a KDAB Group company,
|
2022-12-21 10:12:09 +01:00
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
2019-08-29 11:45:45 +02:00
|
|
|
#include "ctftracemanager.h"
|
|
|
|
|
|
|
|
|
|
#include "ctfstatisticsmodel.h"
|
2022-09-01 12:13:45 +02:00
|
|
|
#include "ctftimelinemodel.h"
|
2019-08-29 11:45:45 +02:00
|
|
|
#include "ctfvisualizerconstants.h"
|
2022-09-01 12:13:45 +02:00
|
|
|
#include "ctfvisualizertr.h"
|
2019-08-29 11:45:45 +02:00
|
|
|
|
|
|
|
|
#include <coreplugin/icore.h>
|
2023-09-27 16:16:43 +02:00
|
|
|
|
2019-08-29 11:45:45 +02:00
|
|
|
#include <tracing/timelinemodelaggregator.h>
|
|
|
|
|
|
|
|
|
|
#include <QMessageBox>
|
|
|
|
|
|
|
|
|
|
namespace CtfVisualizer {
|
|
|
|
|
namespace Internal {
|
|
|
|
|
|
|
|
|
|
using json = nlohmann::json;
|
|
|
|
|
|
|
|
|
|
using namespace Constants;
|
|
|
|
|
|
|
|
|
|
CtfTraceManager::CtfTraceManager(QObject *parent,
|
|
|
|
|
Timeline::TimelineModelAggregator *modelAggregator,
|
|
|
|
|
CtfStatisticsModel *statisticsModel)
|
|
|
|
|
: QObject(parent)
|
|
|
|
|
, m_modelAggregator(modelAggregator)
|
|
|
|
|
, m_statisticsModel(statisticsModel)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
qint64 CtfTraceManager::traceDuration() const
|
|
|
|
|
{
|
|
|
|
|
return qint64((m_traceEnd - m_traceBegin) * 1000);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
qint64 CtfTraceManager::traceBegin() const
|
|
|
|
|
{
|
|
|
|
|
return qint64((m_traceBegin - m_timeOffset) * 1000);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
qint64 CtfTraceManager::traceEnd() const
|
|
|
|
|
{
|
|
|
|
|
return qint64((m_traceEnd - m_timeOffset) * 1000);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CtfTraceManager::addEvent(const json &event)
|
|
|
|
|
{
|
|
|
|
|
const double timestamp = event.value(CtfTracingClockTimestampKey, -1.0);
|
|
|
|
|
if (timestamp < 0) {
|
|
|
|
|
// events without or with negative timestamp will be ignored
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (m_timeOffset < 0) {
|
|
|
|
|
// the timestamp of the first event is used as the global offset
|
|
|
|
|
m_timeOffset = timestamp;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-08 10:17:29 +02:00
|
|
|
static const auto getStringValue = [](const json &event, const char *key, const QString &def) {
|
|
|
|
|
if (!event.contains(key))
|
|
|
|
|
return def;
|
|
|
|
|
const json val = event[key];
|
|
|
|
|
if (val.is_string())
|
|
|
|
|
return QString::fromStdString(val);
|
|
|
|
|
if (val.is_number()) {
|
|
|
|
|
return QString::number(int(val));
|
|
|
|
|
}
|
|
|
|
|
return def;
|
|
|
|
|
};
|
|
|
|
|
const QString processId = getStringValue(event, CtfProcessIdKey, "0");
|
|
|
|
|
const QString threadId = getStringValue(event, CtfThreadIdKey, processId);
|
2019-08-29 11:45:45 +02:00
|
|
|
if (!m_threadModels.contains(threadId)) {
|
|
|
|
|
addModelForThread(threadId, processId);
|
|
|
|
|
}
|
|
|
|
|
if (event.value(CtfEventPhaseKey, "") == CtfEventTypeMetadata) {
|
|
|
|
|
const std::string name = event[CtfEventNameKey];
|
|
|
|
|
if (name == "thread_name") {
|
|
|
|
|
m_threadNames[threadId] = QString::fromStdString(event["args"]["name"]);
|
|
|
|
|
} else if (name == "process_name") {
|
|
|
|
|
m_processNames[processId] = QString::fromStdString(event["args"]["name"]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const QPair<bool, qint64> result = m_threadModels[threadId]->addEvent(event, m_timeOffset);
|
|
|
|
|
const bool visibleOnTimeline = result.first;
|
|
|
|
|
if (visibleOnTimeline) {
|
|
|
|
|
m_traceBegin = std::min(m_traceBegin, timestamp);
|
|
|
|
|
m_traceEnd = std::max(m_traceEnd, timestamp);
|
|
|
|
|
} else if (m_timeOffset == timestamp) {
|
|
|
|
|
// this timestamp was used as the time offset but it is not a visible element
|
|
|
|
|
// -> reset the time offset again:
|
|
|
|
|
m_timeOffset = -1.0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CtfTraceManager::finalize()
|
|
|
|
|
{
|
|
|
|
|
bool userConsentToIgnoreDeepTraces = false;
|
2020-12-08 15:41:46 +01:00
|
|
|
auto it = m_threadModels.begin();
|
|
|
|
|
while (it != m_threadModels.end()) {
|
|
|
|
|
if (it.value()->m_maxStackSize > 512) {
|
2019-08-29 11:45:45 +02:00
|
|
|
if (!userConsentToIgnoreDeepTraces) {
|
2020-06-02 09:10:40 +02:00
|
|
|
QMessageBox::StandardButton answer
|
|
|
|
|
= QMessageBox::question(Core::ICore::dialogParent(),
|
2022-09-01 12:13:45 +02:00
|
|
|
Tr::tr("CTF Visualizer"),
|
|
|
|
|
Tr::tr("The trace contains threads with stack depth > "
|
2020-06-02 09:10:40 +02:00
|
|
|
"512.\nDo you want to display them anyway?"),
|
|
|
|
|
QMessageBox::Yes | QMessageBox::No,
|
|
|
|
|
QMessageBox::No);
|
2019-08-29 11:45:45 +02:00
|
|
|
if (answer == QMessageBox::No) {
|
|
|
|
|
userConsentToIgnoreDeepTraces = true;
|
|
|
|
|
} else {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-12-08 15:41:46 +01:00
|
|
|
m_threadRestrictions.remove(it.key());
|
|
|
|
|
it = m_threadModels.erase(it);
|
|
|
|
|
} else {
|
|
|
|
|
++it;
|
2019-08-29 11:45:45 +02:00
|
|
|
}
|
|
|
|
|
}
|
2022-10-07 14:46:06 +02:00
|
|
|
for (CtfTimelineModel *model: std::as_const(m_threadModels)) {
|
2019-08-29 11:45:45 +02:00
|
|
|
model->finalize(m_traceBegin, m_traceEnd,
|
|
|
|
|
m_processNames[model->m_processId], m_threadNames[model->m_threadId]);
|
|
|
|
|
}
|
|
|
|
|
// TimelineModelAggregator::addModel() is called here because it
|
|
|
|
|
// needs to be run in the main thread
|
|
|
|
|
addModelsToAggregator();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CtfTraceManager::isEmpty() const
|
|
|
|
|
{
|
|
|
|
|
return m_threadModels.isEmpty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int CtfTraceManager::getSelectionId(const std::string &name)
|
|
|
|
|
{
|
|
|
|
|
auto it = m_name2selectionId.find(name);
|
|
|
|
|
if (it == m_name2selectionId.end())
|
|
|
|
|
it = m_name2selectionId.insert(name, m_name2selectionId.size());
|
|
|
|
|
return *it;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-17 13:36:37 +02:00
|
|
|
QList<CtfTimelineModel *> CtfTraceManager::getSortedThreads() const
|
|
|
|
|
{
|
|
|
|
|
QList<CtfTimelineModel *> models = m_threadModels.values();
|
2023-09-08 10:17:29 +02:00
|
|
|
std::sort(models.begin(),
|
|
|
|
|
models.end(),
|
|
|
|
|
[](const CtfTimelineModel *a, const CtfTimelineModel *b) -> bool {
|
|
|
|
|
return (a->m_processId != b->m_processId) ? (a->m_processId < b->m_processId)
|
|
|
|
|
: (a->m_threadId < b->m_threadId);
|
|
|
|
|
});
|
2019-10-17 13:36:37 +02:00
|
|
|
return models;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-08 10:17:29 +02:00
|
|
|
void CtfTraceManager::setThreadRestriction(const QString &tid, bool restrictToThisThread)
|
2019-10-17 13:36:37 +02:00
|
|
|
{
|
|
|
|
|
if (m_threadRestrictions.value(tid) == restrictToThisThread)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
m_threadRestrictions[tid] = restrictToThisThread;
|
|
|
|
|
addModelsToAggregator();
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-08 10:17:29 +02:00
|
|
|
bool CtfTraceManager::isRestrictedTo(const QString &tid) const
|
2019-10-17 13:36:37 +02:00
|
|
|
{
|
|
|
|
|
return m_threadRestrictions.value(tid);
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-08 10:17:29 +02:00
|
|
|
void CtfTraceManager::addModelForThread(const QString &threadId, const QString &processId)
|
2019-08-29 11:45:45 +02:00
|
|
|
{
|
|
|
|
|
CtfTimelineModel *model = new CtfTimelineModel(m_modelAggregator, this, threadId, processId);
|
|
|
|
|
m_threadModels.insert(threadId, model);
|
2019-10-17 13:36:37 +02:00
|
|
|
m_threadRestrictions.insert(threadId, false);
|
2019-08-29 11:45:45 +02:00
|
|
|
connect(model, &CtfTimelineModel::detailsRequested, this,
|
|
|
|
|
&CtfTraceManager::detailsRequested);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CtfTraceManager::addModelsToAggregator()
|
|
|
|
|
{
|
2019-10-17 13:36:37 +02:00
|
|
|
const QList<CtfTimelineModel *> models = getSortedThreads();
|
|
|
|
|
|
|
|
|
|
const bool showAll = std::none_of(m_threadRestrictions.begin(), m_threadRestrictions.end(), [](bool value) {
|
|
|
|
|
return value;
|
2019-08-29 11:45:45 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
QVariantList modelsToAdd;
|
|
|
|
|
for (CtfTimelineModel *model: models) {
|
2019-10-17 13:36:37 +02:00
|
|
|
if (showAll || isRestrictedTo(model->tid()))
|
|
|
|
|
modelsToAdd.append(QVariant::fromValue(model));
|
2019-08-29 11:45:45 +02:00
|
|
|
}
|
|
|
|
|
m_modelAggregator->setModels(modelsToAdd);
|
2019-10-17 16:20:13 +02:00
|
|
|
updateStatistics();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CtfTraceManager::updateStatistics()
|
|
|
|
|
{
|
|
|
|
|
const bool showAll = std::none_of(m_threadRestrictions.begin(), m_threadRestrictions.end(), [](bool value) {
|
|
|
|
|
return value;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
m_statisticsModel->beginLoading();
|
2022-10-07 14:46:06 +02:00
|
|
|
for (auto thread : std::as_const(m_threadModels)) {
|
2019-10-17 16:20:13 +02:00
|
|
|
if (showAll || m_threadRestrictions[thread->tid()])
|
|
|
|
|
{
|
|
|
|
|
const int eventCount = thread->count();
|
|
|
|
|
for (int i = 0; i < eventCount; ++i) {
|
|
|
|
|
QString title = thread->eventTitle(i);
|
|
|
|
|
m_statisticsModel->addEvent(title, thread->duration(i));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-10-17 17:08:08 +02:00
|
|
|
m_statisticsModel->setMeasurementDuration(qint64((m_traceEnd - m_traceBegin) * 1000));
|
2019-10-17 16:20:13 +02:00
|
|
|
m_statisticsModel->endLoading();
|
2019-08-29 11:45:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CtfTraceManager::clearAll()
|
|
|
|
|
{
|
|
|
|
|
m_modelAggregator->clear();
|
2022-10-07 14:46:06 +02:00
|
|
|
for (CtfTimelineModel *model: std::as_const(m_threadModels)) {
|
2019-08-29 11:45:45 +02:00
|
|
|
model->deleteLater();
|
|
|
|
|
}
|
|
|
|
|
m_threadModels.clear();
|
|
|
|
|
m_traceBegin = std::numeric_limits<double>::max();
|
|
|
|
|
m_traceEnd = std::numeric_limits<double>::min();
|
|
|
|
|
m_timeOffset = -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} // namespace Internal
|
|
|
|
|
} // namespace CtfVisualizer
|