Introduce TimeTracker debugging tool for plasma

TimeTracker is a small class that keeps track of an object the changes in
the properties of an object and relates them to a point in time. This way
we can see how things change and react to each other. Then, this information
is exported into a json file to analyze.

REVIEW: 119199
This commit is contained in:
Aleix Pol 2014-07-10 19:20:59 +02:00
parent 8afadc6410
commit 92a8007489
7 changed files with 236 additions and 0 deletions

View File

@ -50,6 +50,7 @@ set(Plasma_LIB_SRCS
private/applet_p.cpp
private/associatedapplicationmanager.cpp
private/containment_p.cpp
private/timetracker.cpp
#Dataengines, services
datacontainer.cpp

View File

@ -65,6 +65,12 @@ class Package;
class PLASMA_EXPORT Applet : public QObject
{
Q_OBJECT
Q_PROPERTY(Types::ItemStatus status READ status WRITE setStatus NOTIFY statusChanged)
Q_PROPERTY(Types::ImmutabilityType immutability READ immutability WRITE setImmutability NOTIFY immutabilityChanged)
Q_PROPERTY(Types::FormFactor formFactor READ formFactor NOTIFY formFactorChanged)
Q_PROPERTY(Types::Location location READ location NOTIFY locationChanged)
Q_PROPERTY(QString title READ title WRITE setTitle FINAL)
Q_PROPERTY(QString icon READ icon WRITE setIcon FINAL)
public:
//CONSTRUCTORS

View File

@ -59,6 +59,7 @@ class PLASMA_EXPORT Containment : public Applet
{
Q_OBJECT
Q_PROPERTY(QString wallpaper READ wallpaper WRITE setWallpaper NOTIFY wallpaperChanged)
Q_PROPERTY(bool isUiReady READ isUiReady NOTIFY uiReadyChanged)
public:
/**

View File

@ -41,6 +41,7 @@
#include "scripting/scriptengine.h"
#include "scripting/appletscript.h"
#include "private/containment_p.h"
#include "timetracker.h"
namespace Plasma
{
@ -76,6 +77,9 @@ AppletPrivate::AppletPrivate(KService::Ptr service, const KPluginInfo *info, int
}
QObject::connect(actions->action("configure"), SIGNAL(triggered()),
q, SLOT(requestConfiguration()));
#ifndef NDEBUG
new TimeTracker(q);
#endif
}
AppletPrivate::~AppletPrivate()

View File

@ -34,6 +34,7 @@
#include "pluginloader.h"
#include "private/applet_p.h"
#include "timetracker.h"
namespace Plasma
{
@ -53,6 +54,10 @@ ContainmentPrivate::ContainmentPrivate(Containment *c):
if (appletParent) {
QObject::connect(appletParent->containment(), &Containment::screenChanged, c, &Containment::screenChanged);
}
#ifndef NDEBUG
new TimeTracker(q);
#endif
}
Plasma::ContainmentPrivate::~ContainmentPrivate()

View File

@ -0,0 +1,142 @@
/*
* Copyright 2014 by Aleix Pol Gonzalez <aleixpol@blue-systems.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2, 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 Library General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "timetracker.h"
#include <containment.h>
#include <QDateTime>
#include <qmetaobject.h>
#include <QFile>
#include <qjsondocument.h>
#include <qjsonarray.h>
#include <QTimer>
using namespace Plasma;
struct TimeTrackerWriter : QObject {
Q_OBJECT
public:
TimeTrackerWriter() {
QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, static_cast<void (TimeTrackerWriter::*)()>(&TimeTrackerWriter::print));
}
void print() {
QJsonArray array;
Q_FOREACH(const ObjectHistory& history, m_data) {
QVariantMap map;
map["events"] = serializeEvents(history.events);
map["initial"] = history.initial;
array.append(QJsonValue::fromVariant(map));
}
Q_ASSERT(array.count() == m_data.count());
QJsonDocument doc;
doc.setArray(array);
QFile f(QStringLiteral("/tmp/debug-")+qgetenv("USER")+".json");
bool b = f.open(QFile::WriteOnly);
Q_ASSERT(b);
f.write(doc.toJson());
}
void feed(QObject* obj, const ObjectHistory& tracker)
{
m_data[obj] = tracker;
}
QHash<QObject*, ObjectHistory> m_data;
private:
QVariantList serializeEvents(const QVector<TimeEvent>& events) const {
QVariantList ret;
Q_ASSERT(!events.isEmpty());
foreach(const TimeEvent& ev, events) {
QVariantMap map;
map["comment"] = ev.comment;
map["time"] = ev.moment.toMSecsSinceEpoch();
ret.append(map);
}
Q_ASSERT(ret.count() == events.count());
return ret;
}
};
Q_GLOBAL_STATIC(TimeTrackerWriter, s_writer);
TimeTracker::TimeTracker(QObject* o)
: QObject(o)
{
m_history.events.append(TimeEvent { QDateTime::currentDateTime(), QString("constructed %1 %2").arg(o->metaObject()->className()).arg(o->objectName()) });
QTimer* t = new QTimer(this);
t->setInterval(2000);
t->setSingleShot(false);
connect(t, SIGNAL(timeout()), this, SLOT(sync()));
t->start();
QMetaObject::invokeMethod(this, "init", Qt::QueuedConnection);
}
void TimeTracker::init()
{
QMetaMethod propChange = metaObject()->method(metaObject()->indexOfSlot("propertyChanged()"));
Q_ASSERT(propChange.isValid() && metaObject()->indexOfSlot("propertyChanged()")>=0);
QObject* o = parent();
for (int i = 0, pc = o->metaObject()->propertyCount(); i<pc; ++i) {
QMetaProperty prop = o->metaObject()->property(i);
if (prop.isFinal() || prop.isConstant())
m_history.initial[prop.name()] = prop.read(o);
if (prop.hasNotifySignal())
connect(o, prop.notifySignal(), this, propChange);
}
}
TimeTracker::~TimeTracker()
{
sync();
}
void TimeTracker::sync()
{
s_writer->feed(parent(), m_history);
}
void TimeTracker::propertyChanged()
{
Q_ASSERT(sender() == parent());
const QMetaObject* mo = parent()->metaObject();
for (int i = 0, pc = mo->propertyCount(); i<pc; ++i) {
QMetaProperty prop = mo->property(i);
if (prop.notifySignalIndex() == senderSignalIndex()) {
QString val;
QVariant var = prop.read(parent());
if(var.canConvert<QString>()) {
val = var.toString();
} else {
val = QString("<unknown %1>").arg(var.typeName());
}
m_history.events.append(TimeEvent { QDateTime::currentDateTime(), QString("property %1 changed to %2").arg(prop.name()).arg(val)});
break;
}
}
}
#include "timetracker.moc"

View File

@ -0,0 +1,77 @@
/*
* Copyright 2014 by Aleix Pol Gonzalez <aleixpol@blue-systems.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2, 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 Library General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef TIMETRACKER_H
#define TIMETRACKER_H
#include <QObject>
#include <QVariantMap>
#include <QDateTime>
#include <QVector>
#include "plasma/plasma_export.h"
namespace Plasma
{
class Containment;
class Applet;
struct TimeEvent
{
QDateTime moment;
QString comment;
};
struct ObjectHistory
{
QVariantMap initial;
QVector<TimeEvent> events;
};
/**
* This debugging class is meant to provide an overview of how the objects change
* over time and hopefully provide the information required to detect buggy initialization.
*
* To use it, you'll pass the object you want to track to the constructor and the TimeTracker
* will use Qt introspection to read the properties and check what values they have and how they
* change.
*
* To analyze the results one can read the generated json file /tmp/debug-$USER, as soon
* as the process has quit.
*/
class PLASMA_EXPORT TimeTracker : QObject
{
Q_OBJECT
public:
TimeTracker(QObject* applet);
virtual ~TimeTracker();
private Q_SLOTS:
void init();
void sync();
void propertyChanged();
private:
ObjectHistory m_history;
};
}
#endif // TIMETRACKER_H