Files
qt-creator/src/plugins/qmlprofiler/qmlprofilertraceclient.cpp
Ulf Hermann 0924ee5efb QmlProfiler: Move type resolution logic into trace client
This way we will be able to use server-provided type IDs for more
efficient lookup of event types.

Change-Id: I37cd592a7829e5f36c6cfc04e400013d1dc37155
Reviewed-by: Joerg Bornemann <joerg.bornemann@qt.io>
2016-05-25 13:52:38 +00:00

313 lines
10 KiB
C++

/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "qmlprofilertraceclient.h"
#include "qmltypedevent.h"
#include "qmlprofilerdatamodel.h"
#include <qmldebug/qmlenginecontrolclient.h>
#include <qmldebug/qdebugmessageclient.h>
#include <qmldebug/qpacketprotocol.h>
#include <utils/qtcassert.h>
namespace QmlProfiler {
inline static uint qHash(const QmlEventType &type)
{
return qHash(type.location.filename) ^
((type.location.line & 0xfff) | // 12 bits of line number
((type.message << 12) & 0xf000) | // 4 bits of message
((type.location.column << 16) & 0xff0000) | // 8 bits of column
((type.rangeType << 24) & 0xf000000) | // 4 bits of rangeType
((type.detailType << 28) & 0xf0000000)); // 4 bits of detailType
}
inline static bool operator==(const QmlEventType &type1,
const QmlEventType &type2)
{
return type1.message == type2.message && type1.rangeType == type2.rangeType &&
type1.detailType == type2.detailType && type1.location.line == type2.location.line &&
type1.location.column == type2.location.column &&
// compare filename last as it's expensive.
type1.location.filename == type2.location.filename;
}
class QmlProfilerTraceClientPrivate {
public:
QmlProfilerTraceClientPrivate(QmlProfilerTraceClient *_q, QmlDebug::QmlDebugConnection *client,
QmlProfilerDataModel *model)
: q(_q)
, model(model)
, engineControl(client)
, maximumTime(0)
, recording(false)
, requestedFeatures(0)
, recordedFeatures(0)
, flushInterval(0)
{
}
void sendRecordingStatus(int engineId);
bool updateFeatures(ProfileFeature feature);
int resolveType(const QmlEventType &type);
int resolveStackTop();
void processCurrentEvent();
QmlProfilerTraceClient *q;
QmlProfilerDataModel *model;
QmlDebug::QmlEngineControlClient engineControl;
QScopedPointer<QmlDebug::QDebugMessageClient> messageClient;
qint64 maximumTime;
bool recording;
quint64 requestedFeatures;
quint64 recordedFeatures;
quint32 flushInterval;
// Reuse the same event, so that we don't have to constantly reallocate all the data.
QmlTypedEvent currentEvent;
QHash<QmlEventType, int> eventTypeIds;
QStack<QmlTypedEvent> rangesInProgress;
};
int QmlProfilerTraceClientPrivate::resolveType(const QmlEventType &type)
{
QHash<QmlEventType, int>::ConstIterator it = eventTypeIds.constFind(type);
int typeIndex = -1;
if (it != eventTypeIds.constEnd()) {
typeIndex = it.value();
} else {
typeIndex = model->addEventType(type);
eventTypeIds[type] = typeIndex;
}
return typeIndex;
}
int QmlProfilerTraceClientPrivate::resolveStackTop()
{
if (rangesInProgress.isEmpty())
return -1;
QmlTypedEvent &typedEvent = rangesInProgress.top();
int typeIndex = typedEvent.event.typeIndex();
if (typeIndex >= 0)
return typeIndex;
typeIndex = resolveType(typedEvent.type);
typedEvent.event.setTypeIndex(typeIndex);
model->addEvent(typedEvent.event);
return typeIndex;
}
void QmlProfilerTraceClientPrivate::processCurrentEvent()
{
// RangeData and RangeLocation always apply to the range on the top of the stack. Furthermore,
// all ranges are perfectly nested. This is why we can defer the type resolution until either
// the range ends or a child range starts. With only the information in RangeStart we wouldn't
// be able to uniquely identify the event type.
Message rangeStage = currentEvent.type.rangeType == MaximumRangeType ?
currentEvent.type.message : currentEvent.event.rangeStage();
switch (rangeStage) {
case RangeStart:
resolveStackTop();
rangesInProgress.push(currentEvent);
break;
case RangeEnd: {
int typeIndex = resolveStackTop();
QTC_ASSERT(typeIndex != -1, break);
currentEvent.event.setTypeIndex(typeIndex);
model->addEvent(currentEvent.event);
rangesInProgress.pop();
break;
}
case RangeData:
rangesInProgress.top().type.data = currentEvent.type.data;
break;
case RangeLocation:
rangesInProgress.top().type.location = currentEvent.type.location;
break;
default: {
int typeIndex = resolveType(currentEvent.type);
currentEvent.event.setTypeIndex(typeIndex);
model->addEvent(currentEvent.event);
break;
}
}
}
void QmlProfilerTraceClientPrivate::sendRecordingStatus(int engineId)
{
QmlDebug::QPacket stream(q->connection()->currentDataStreamVersion());
stream << recording << engineId; // engineId -1 is OK. It means "all of them"
if (recording)
stream << requestedFeatures << flushInterval;
q->sendMessage(stream.data());
}
QmlProfilerTraceClient::QmlProfilerTraceClient(QmlDebug::QmlDebugConnection *client,
QmlProfilerDataModel *model,
quint64 features)
: QmlDebugClient(QLatin1String("CanvasFrameRate"), client)
, d(new QmlProfilerTraceClientPrivate(this, client, model))
{
setRequestedFeatures(features);
connect(&d->engineControl, &QmlDebug::QmlEngineControlClient::engineAboutToBeAdded,
this, &QmlProfilerTraceClient::newEngine);
}
QmlProfilerTraceClient::~QmlProfilerTraceClient()
{
//Disable profiling if started by client
//Profiling data will be lost!!
if (isRecording())
setRecording(false);
delete d;
}
void QmlProfilerTraceClient::clearData()
{
d->eventTypeIds.clear();
d->rangesInProgress.clear();
if (d->recordedFeatures != 0) {
d->recordedFeatures = 0;
emit recordedFeaturesChanged(0);
}
emit cleared();
}
void QmlProfilerTraceClient::sendRecordingStatus(int engineId)
{
d->sendRecordingStatus(engineId);
}
bool QmlProfilerTraceClient::isRecording() const
{
return d->recording;
}
void QmlProfilerTraceClient::setRecording(bool v)
{
if (v == d->recording)
return;
d->recording = v;
if (state() == Enabled)
sendRecordingStatus();
emit recordingChanged(v);
}
quint64 QmlProfilerTraceClient::recordedFeatures() const
{
return d->recordedFeatures;
}
void QmlProfilerTraceClient::setRequestedFeatures(quint64 features)
{
d->requestedFeatures = features;
if (features & static_cast<quint64>(1) << ProfileDebugMessages) {
d->messageClient.reset(new QmlDebug::QDebugMessageClient(connection()));
connect(d->messageClient.data(), &QmlDebug::QDebugMessageClient::message, this,
[this](QtMsgType type, const QString &text,
const QmlDebug::QDebugContextInfo &context)
{
d->updateFeatures(ProfileDebugMessages);
d->currentEvent.event.setTimestamp(context.timestamp);
d->currentEvent.event.setTypeIndex(-1);
d->currentEvent.event.setString(text);
d->currentEvent.type.location.filename = context.file;
d->currentEvent.type.location.line = context.line;
d->currentEvent.type.location.column = 1;
d->currentEvent.type.displayName.clear();
d->currentEvent.type.data.clear();
d->currentEvent.type.message = DebugMessage;
d->currentEvent.type.rangeType = MaximumRangeType;
d->currentEvent.type.detailType = type;
d->processCurrentEvent();
});
} else {
d->messageClient.reset();
}
}
void QmlProfilerTraceClient::setFlushInterval(quint32 flushInterval)
{
d->flushInterval = flushInterval;
}
void QmlProfilerTraceClient::setRecordingFromServer(bool v)
{
if (v == d->recording)
return;
d->recording = v;
emit recordingChanged(v);
}
bool QmlProfilerTraceClientPrivate::updateFeatures(ProfileFeature feature)
{
quint64 flag = 1ULL << feature;
if (!(requestedFeatures & flag))
return false;
if (!(recordedFeatures & flag)) {
recordedFeatures |= flag;
emit q->recordedFeaturesChanged(recordedFeatures);
}
return true;
}
void QmlProfilerTraceClient::stateChanged(State status)
{
if (status == Enabled)
sendRecordingStatus(-1);
}
void QmlProfilerTraceClient::messageReceived(const QByteArray &data)
{
QmlDebug::QPacket stream(connection()->currentDataStreamVersion(), data);
stream >> d->currentEvent;
d->maximumTime = qMax(d->currentEvent.event.timestamp(), d->maximumTime);
if (d->currentEvent.type.message == Complete) {
emit complete(d->maximumTime);
setRecordingFromServer(false);
} else if (d->currentEvent.type.message == Event
&& d->currentEvent.type.detailType == StartTrace) {
if (!d->recording)
setRecordingFromServer(true);
emit traceStarted(d->currentEvent.event.timestamp(),
d->currentEvent.event.numbers<QList<int>, qint32>());
} else if (d->currentEvent.type.message == Event
&& d->currentEvent.type.detailType == EndTrace) {
emit traceFinished(d->currentEvent.event.timestamp(),
d->currentEvent.event.numbers<QList<int>, qint32>());
} else if (d->updateFeatures(d->currentEvent.type.feature())) {
d->processCurrentEvent();
}
}
} // namespace QmlProfiler