Commit 7de7eb6b authored by hjk's avatar hjk

Debugger: Work on WatchModel performance

Don't instantiate repeating boilerplate item data in some
cases (such as large arrays).

This makes it necessary to access parent WatchItems in
a lot more cases than before and needs another separation of
WatchItem/WatchModel code to keep the dumper autotests
in a functional state.

For a plain std::vector<int> with 1 mio items this reduces
 extraction time from more than 2 minutes to about 3 seconds.

Change-Id: I175c5f6ee90434a6e85342d8bb71bd10a04dd271
Reviewed-by: default avatarChristian Stenger <christian.stenger@theqtcompany.com>
Reviewed-by: default avatarDavid Schulz <david.schulz@theqtcompany.com>
parent 768b775f
......@@ -354,7 +354,13 @@ class Dumper(DumperBase):
def canCallLocale(self):
return False if self.is32bit() else True
def reportTime(self, hint):
#from datetime import datetime
#warn("%s: %s" % (hint, datetime.now().time().isoformat()))
pass
def fetchVariables(self, args):
self.reportTime("begin fetch")
self.prepare(args)
partialVariable = args.get("partialvar", "")
isPartial = len(partialVariable) > 0
......@@ -390,6 +396,8 @@ class Dumper(DumperBase):
else:
locals = self.listOfLocals()
self.reportTime("locals")
# Take care of the return value of the last function call.
if len(self.resultVarName) > 0:
try:
......@@ -439,7 +447,9 @@ class Dumper(DumperBase):
self.output.append(',partial="%d"' % isPartial)
self.reportTime("before print: %s" % len(self.output))
safePrint(''.join(self.output))
self.reportTime("after print")
def enterSubItem(self, item):
if not item.iname:
......
......@@ -807,7 +807,10 @@ def qdump__std__vector(d, value):
base = d.pointerValue(start)
for i in d.childRange():
q = base + int(i / 8)
d.putBoolItem(str(i), (int(d.extractPointer(q)) >> (i % 8)) & 1)
with SubItem(d, i):
d.putValue((int(d.extractPointer(q)) >> (i % 8)) & 1)
d.putType("bool")
d.putNumChild(0)
else:
d.putPlotData(start, size, type)
......@@ -840,7 +843,10 @@ def qdump__std__vector__QNX(d, value):
with Children(d, size, maxNumChild=10000, childType=innerType):
for i in d.childRange():
q = start + int(i / storagesize)
d.putBoolItem(str(i), (q.dereference() >> (i % storagesize)) & 1)
with SubItem(d, i):
d.putValue((q.dereference() >> (i % storagesize)) & 1)
d.putType("bool")
d.putNumChild(0)
else:
d.putArrayData(start, size, innerType)
......
......@@ -1982,9 +1982,10 @@ void DebuggerEngine::updateLocalsView(const GdbMi &all)
GdbMi data = all["data"];
foreach (const GdbMi &child, data.children()) {
WatchItem *item = new WatchItem(child);
WatchItem *item = new WatchItem;
item->parse(child);
const TypeInfo ti = d->m_typeInfoCache.value(item->type);
if (ti.size)
if (ti.size && !item->size)
item->size = ti.size;
handler->insertItem(item);
......
......@@ -63,7 +63,6 @@ class DebuggerEnginePrivate;
class DebuggerPluginPrivate;
class DisassemblerAgent;
class MemoryAgent;
class WatchData;
class WatchItem;
class BreakHandler;
class LocationMark;
......
......@@ -1036,7 +1036,6 @@ DebuggerPluginPrivate::DebuggerPluginPrivate(DebuggerPlugin *plugin) :
m_dummyEngine(0),
m_globalDebuggerOptions(new GlobalDebuggerOptions)
{
qRegisterMetaType<WatchData>("WatchData");
qRegisterMetaType<ContextData>("ContextData");
qRegisterMetaType<DebuggerRunParameters>("DebuggerRunParameters");
......
......@@ -193,11 +193,11 @@ void DraggableLabel::mouseMoveEvent(QMouseEvent * event)
//
/////////////////////////////////////////////////////////////////////////
class ToolTipWatchItem : public Utils::TreeItem
class ToolTipWatchItem : public TreeItem
{
public:
ToolTipWatchItem() : expandable(false) {}
ToolTipWatchItem(WatchItem *item);
ToolTipWatchItem(TreeItem *item);
bool hasChildren() const { return expandable; }
bool canFetchMore() const { return childCount() == 0 && expandable && model(); }
......@@ -214,17 +214,19 @@ public:
QByteArray iname;
};
ToolTipWatchItem::ToolTipWatchItem(WatchItem *item)
ToolTipWatchItem::ToolTipWatchItem(TreeItem *item)
{
name = item->displayName();
value = item->displayValue();
type = item->displayType();
iname = item->iname;
valueColor = item->valueColor(1);
const TreeModel *model = item->model();
QModelIndex idx = model->indexForItem(item);
name = model->data(idx.sibling(idx.row(), 0), Qt::DisplayRole).toString();
value = model->data(idx.sibling(idx.row(), 1), Qt::DisplayRole).toString();
type = model->data(idx.sibling(idx.row(), 2), Qt::DisplayRole).toString();
iname = model->data(idx.sibling(idx.row(), 0), LocalsINameRole).toByteArray();
valueColor = model->data(idx.sibling(idx.row(), 1), Qt::ForegroundRole).value<QColor>();
expandable = item->hasChildren();
expression = item->expression();
expression = model->data(idx.sibling(idx.row(), 0), Qt::EditRole).toString();
foreach (TreeItem *child, item->children())
appendChild(new ToolTipWatchItem(static_cast<WatchItem *>(child)));
appendChild(new ToolTipWatchItem(child));
}
/////////////////////////////////////////////////////////////////////////
......@@ -1175,7 +1177,7 @@ static void slotTooltipOverrideRequested
purgeClosedToolTips();
// Prefer a filter on an existing local variable if it can be found.
const WatchData *localVariable = engine->watchHandler()->findCppLocalVariable(context.expression);
const WatchItem *localVariable = engine->watchHandler()->findCppLocalVariable(context.expression);
if (localVariable) {
context.expression = QLatin1String(localVariable->exp);
if (context.expression.isEmpty())
......
......@@ -52,7 +52,6 @@
namespace Debugger {
namespace Internal {
class WatchData;
class GdbMi;
/* A debugger engine interfacing the LLDB debugger
......
......@@ -518,8 +518,11 @@ void PdbEngine::refreshLocals(const GdbMi &vars)
WatchHandler *handler = watchHandler();
handler->resetValueCache();
foreach (const GdbMi &child, vars.children())
handler->insertItem(new WatchItem(child));
foreach (const GdbMi &child, vars.children()) {
WatchItem *item = new WatchItem;
item->parse(child);
handler->insertItem(item);
}
handler->notifyUpdateFinished();
......
......@@ -1089,11 +1089,11 @@ void QmlEngine::updateCurrentContext()
context = stackHandler()->currentFrame().function;
} else {
QModelIndex currentIndex = inspectorView()->currentIndex();
const WatchData *currentData = watchHandler()->watchItem(currentIndex);
const WatchItem *currentData = watchHandler()->watchItem(currentIndex);
if (!currentData)
return;
const WatchData *parentData = watchHandler()->watchItem(currentIndex.parent());
const WatchData *grandParentData = watchHandler()->watchItem(currentIndex.parent().parent());
const WatchItem *parentData = watchHandler()->watchItem(currentIndex.parent());
const WatchItem *grandParentData = watchHandler()->watchItem(currentIndex.parent().parent());
if (currentData->id != parentData->id)
context = currentData->name;
else if (parentData->id != grandParentData->id)
......@@ -1351,7 +1351,9 @@ void QmlEnginePrivate::handleEvaluateExpression(const QVariantMap &response,
QmlV8ObjectData body = extractData(bodyVal);
WatchHandler *watchHandler = engine->watchHandler();
auto item = new WatchItem(iname, exp);
auto item = new WatchItem;
item->iname = iname;
item->name = exp;
item->exp = exp.toLatin1();
item->id = body.handle;
bool success = response.value(_("success")).toBool();
......@@ -2170,9 +2172,11 @@ void QmlEnginePrivate::handleFrame(const QVariantMap &response)
{
QByteArray iname = "local.this";
QString exp = QLatin1String("this");
auto item = new WatchItem(iname, exp);
QmlV8ObjectData objectData = extractData(body.value(_("receiver")));
auto item = new WatchItem;
item->iname = iname;
item->name = exp;
item->id = objectData.handle;
item->type = objectData.type;
item->value = objectData.value.toString();
......
......@@ -40,7 +40,6 @@
namespace Debugger {
namespace Internal {
class WatchData;
class WatchItem;
class QmlEnginePrivate;
class QmlInspectorAgent;
......
......@@ -74,12 +74,12 @@ QmlInspectorAgent::QmlInspectorAgent(QmlEngine *engine, QmlDebugConnection *conn
, m_engineClient(0)
, m_engineQueryId(0)
, m_rootContextQueryId(0)
, m_objectToSelect(WatchData::InvalidId)
, m_objectToSelect(WatchItem::InvalidId)
, m_masterEngine(engine)
, m_toolsClient(0)
, m_targetToSync(NoTarget)
, m_debugIdToSelect(WatchData::InvalidId)
, m_currentSelectedDebugId(WatchData::InvalidId)
, m_debugIdToSelect(WatchItem::InvalidId)
, m_currentSelectedDebugId(WatchItem::InvalidId)
, m_toolsClientConnected(false)
, m_inspectorToolsContext("Debugger.QmlInspector")
, m_selectAction(new QAction(this))
......@@ -87,7 +87,7 @@ QmlInspectorAgent::QmlInspectorAgent(QmlEngine *engine, QmlDebugConnection *conn
, m_showAppOnTopAction(action(ShowAppOnTop))
, m_engineClientConnected(false)
{
m_debugIdToIname.insert(WatchData::InvalidId, QByteArray("inspect"));
m_debugIdToIname.insert(WatchItem::InvalidId, QByteArray("inspect"));
connect(action(ShowQmlObjectTree),
&Utils::SavedAction::valueChanged, this, &QmlInspectorAgent::updateState);
m_delayQueryTimer.setSingleShot(true);
......@@ -173,13 +173,13 @@ quint32 QmlInspectorAgent::queryExpressionResult(int debugId,
m_engine.debugId());
}
void QmlInspectorAgent::assignValue(const WatchData *data,
void QmlInspectorAgent::assignValue(const WatchItem *data,
const QString &expr, const QVariant &valueV)
{
qCDebug(qmlInspectorLog)
<< __FUNCTION__ << '(' << data->id << ')' << data->iname;
if (data->id != WatchData::InvalidId) {
if (data->id != WatchItem::InvalidId) {
QString val(valueV.toString());
if (valueV.type() == QVariant::String) {
val = val.replace(QLatin1Char('\"'), QLatin1String("\\\""));
......@@ -195,17 +195,17 @@ static int parentIdForIname(const QByteArray &iname)
// Extract the parent id
int lastIndex = iname.lastIndexOf('.');
int secondLastIndex = iname.lastIndexOf('.', lastIndex - 1);
int parentId = WatchData::InvalidId;
if (secondLastIndex != WatchData::InvalidId)
int parentId = WatchItem::InvalidId;
if (secondLastIndex != WatchItem::InvalidId)
parentId = iname.mid(secondLastIndex + 1, lastIndex - secondLastIndex - 1).toInt();
return parentId;
}
void QmlInspectorAgent::updateWatchData(const WatchData &data)
void QmlInspectorAgent::updateWatchData(const WatchItem &data)
{
qCDebug(qmlInspectorLog) << __FUNCTION__ << '(' << data.id << ')';
if (data.id != WatchData::InvalidId && !m_fetchDataIds.contains(data.id)) {
if (data.id != WatchItem::InvalidId && !m_fetchDataIds.contains(data.id)) {
// objects
m_fetchDataIds << data.id;
fetchObject(data.id);
......@@ -216,7 +216,7 @@ void QmlInspectorAgent::watchDataSelected(qint64 id)
{
qCDebug(qmlInspectorLog) << __FUNCTION__ << '(' << id << ')';
if (id != WatchData::InvalidId) {
if (id != WatchItem::InvalidId) {
QTC_ASSERT(m_debugIdLocations.keys().contains(id), return);
jumpToObjectDefinitionInEditor(m_debugIdLocations.value(id), id);
if (m_toolsClient)
......@@ -278,7 +278,7 @@ ObjectReference QmlInspectorAgent::objectForId(int objectDebugId) const
}
}
// TODO: Set correct parentId
return ObjectReference(objectDebugId, WatchData::InvalidId,
return ObjectReference(objectDebugId, WatchItem::InvalidId,
FileReference(QUrl::fromLocalFile(file), line, column));
}
......@@ -286,7 +286,7 @@ void QmlInspectorAgent::addObjectWatch(int objectDebugId)
{
qCDebug(qmlInspectorLog) << __FUNCTION__ << '(' << objectDebugId << ')';
if (objectDebugId == WatchData::InvalidId)
if (objectDebugId == WatchItem::InvalidId)
return;
if (!isConnected() || !boolSetting(ShowQmlObjectTree))
......@@ -470,7 +470,7 @@ void QmlInspectorAgent::verifyAndInsertObjectInTree(const ObjectReference &objec
// Find out the correct position in the tree
// Objects are inserted to the tree if they satisfy one of the two conditions.
// Condition 1: Object is a root object i.e. parentId == WatchData::InvalidId.
// Condition 1: Object is a root object i.e. parentId == WatchItem::InvalidId.
// Condition 2: Object has an expanded parent i.e. siblings are known.
// If the two conditions are not met then we push the object to a stack and recursively
// fetch parents till we find a previously expanded parent.
......@@ -480,7 +480,7 @@ void QmlInspectorAgent::verifyAndInsertObjectInTree(const ObjectReference &objec
const int objectDebugId = object.debugId();
if (m_debugIdToIname.contains(parentId)) {
QByteArray parentIname = m_debugIdToIname.value(parentId);
if (parentId != WatchData::InvalidId && !handler->isExpandedIName(parentIname)) {
if (parentId != WatchItem::InvalidId && !handler->isExpandedIName(parentIname)) {
m_objectStack.push(object);
handler->fetchMore(parentIname);
return; // recursive
......@@ -535,7 +535,7 @@ void QmlInspectorAgent::insertObjectInTree(const ObjectReference &object)
<< timeElapsed.elapsed() << " ms";
if (object.debugId() == m_debugIdToSelect) {
m_debugIdToSelect = WatchData::InvalidId;
m_debugIdToSelect = WatchItem::InvalidId;
selectObject(object, m_targetToSync);
}
......@@ -544,7 +544,7 @@ void QmlInspectorAgent::insertObjectInTree(const ObjectReference &object)
QByteArray iname = m_debugIdToIname.value(m_objectToSelect);
qCDebug(qmlInspectorLog) << " selecting" << iname << "in tree";
m_qmlEngine->watchHandler()->setCurrentItem(iname);
m_objectToSelect = WatchData::InvalidId;
m_objectToSelect = WatchItem::InvalidId;
}
m_qmlEngine->watchHandler()->updateWatchersWindow();
m_qmlEngine->watchHandler()->reexpandItems();
......@@ -613,7 +613,9 @@ void QmlInspectorAgent::addWatchData(const ObjectReference &obj,
return;
// object
auto objWatch = new WatchItem(objIname, name);
auto objWatch = new WatchItem;
objWatch->iname = objIname;
objWatch->name = name;
objWatch->id = objDebugId;
objWatch->exp = name.toLatin1();
objWatch->type = obj.className().toLatin1();
......@@ -643,7 +645,9 @@ void QmlInspectorAgent::addWatchData(const ObjectReference &obj,
// properties
if (append && obj.properties().count()) {
QByteArray iname = objIname + ".[properties]";
auto propertiesWatch = new WatchItem(iname, tr("Properties"));
auto propertiesWatch = new WatchItem;
propertiesWatch->iname = iname;
propertiesWatch->name = tr("Properties");
propertiesWatch->id = objDebugId;
propertiesWatch->value = _("list");
propertiesWatch->wantsChildren = true;
......@@ -653,7 +657,9 @@ void QmlInspectorAgent::addWatchData(const ObjectReference &obj,
const QString propertyName = property.name();
if (propertyName.isEmpty())
continue;
auto propertyWatch = new WatchItem(buildIName(iname, propertyName), propertyName);
auto propertyWatch = new WatchItem;
propertyWatch->iname = buildIName(iname, propertyName);
propertyWatch->name = propertyName;
propertyWatch->id = objDebugId;
propertyWatch->exp = propertyName.toLatin1();
propertyWatch->type = property.valueTypeName().toLatin1();
......@@ -700,7 +706,7 @@ void QmlInspectorAgent::clearObjectTree()
m_debugIdHash.clear();
m_debugIdHash.reserve(old_count + 1);
m_debugIdToIname.clear();
m_debugIdToIname.insert(WatchData::InvalidId, QByteArray("inspect"));
m_debugIdToIname.insert(WatchItem::InvalidId, QByteArray("inspect"));
m_objectStack.clear();
m_objectWatches.clear();
}
......@@ -870,7 +876,7 @@ void QmlInspectorAgent::jumpToObjectDefinitionInEditor(
const QString fileName = m_masterEngine->toFileInProject(objSource.url());
Core::EditorManager::openEditorAt(fileName, objSource.lineNumber());
if (debugId != WatchData::InvalidId && debugId != m_currentSelectedDebugId) {
if (debugId != WatchItem::InvalidId && debugId != m_currentSelectedDebugId) {
m_currentSelectedDebugId = debugId;
m_currentSelectedDebugName = displayName(debugId);
}
......
......@@ -52,7 +52,7 @@ namespace Internal {
class DebuggerEngine;
class QmlEngine;
class WatchData;
class WatchItem;
//map <filename, editorRevision> -> <lineNumber, columnNumber> -> debugId
typedef
......@@ -67,8 +67,8 @@ public:
void fetchObject(int debugId);
quint32 queryExpressionResult(int debugId, const QString &expression);
void assignValue(const WatchData *data, const QString &expression, const QVariant &valueV);
void updateWatchData(const WatchData &data);
void assignValue(const WatchItem *data, const QString &expression, const QVariant &valueV);
void updateWatchData(const WatchItem &data);
void watchDataSelected(qint64 id);
bool selectObjectInTree(int debugId);
void addObjectWatch(int objectDebugId);
......
......@@ -179,7 +179,7 @@ static void blockRecursion(const Overview &overview,
// Is the declaration on or past the current line, that is,
// the variable not initialized.
if (symbol->line() >= line)
uninitializedVariables->push_back(WatchData::shadowedName(name, it.value()));
uninitializedVariables->push_back(WatchItem::shadowedName(name, it.value()));
}
}
// Next block scope.
......
This diff is collapsed.
......@@ -33,10 +33,11 @@
#include "debuggerprotocol.h"
#include <utils/treemodel.h>
#include <QCoreApplication>
#include <QMetaType>
#include <functional>
#include <vector>
namespace Debugger {
......@@ -44,20 +45,35 @@ namespace Internal {
class GdbMi;
class WatchData
class WatchItem : public Utils::TreeItem
{
public:
WatchData();
WatchItem();
void parse(const GdbMi &input);
bool isLocal() const;
bool isWatcher() const;
bool isInspect() const;
QString expression() const;
QString realName() const;
quint64 realAddress() const;
QByteArray internalName() const;
QString toToolTip() const;
QVariant editValue() const;
int editType() const;
WatchItem *findItem(const QByteArray &iname);
WatchItem *parentItem() const;
enum State
{
HasChildrenNeeded = 1,
ValueNeeded = 2,
ChildrenNeeded = 8,
InitialState = ValueNeeded
| ChildrenNeeded
| HasChildrenNeeded
InitialState = ValueNeeded | ChildrenNeeded
};
static const qint64 InvalidId = -1;
......@@ -73,20 +89,14 @@ public:
void setChildrenUnneeded() { state = State(state & ~ChildrenNeeded); }
void setHasChildren(bool c) { wantsChildren = c; if (!c) setChildrenUnneeded(); }
bool isLocal() const { return iname.startsWith("local."); }
bool isWatcher() const { return iname.startsWith("watch."); }
bool isInspect() const { return iname.startsWith("inspect."); }
bool isValid() const { return !iname.isEmpty(); }
bool isVTablePointer() const;
bool isAncestorOf(const QByteArray &childIName) const;
void setError(const QString &);
void setValue(const QString &);
void setType(const QByteArray &, bool guessChildrenFromType = true);
QString toString() const;
QString toToolTip() const;
static QString msgNotInScope();
static QString shadowedName(const QString &name, int seen);
......@@ -94,11 +104,6 @@ public:
QByteArray hexAddress() const;
// Protocol interaction.
void updateValue(const GdbMi &item);
void updateChildCount(const GdbMi &mi);
void updateType(const GdbMi &item);
public:
qint64 id; // Token for the engine for internal mapping
qint32 state; // 'needed' flags;
......@@ -116,35 +121,18 @@ public:
uint bitpos; // Position within bit fields
uint bitsize; // Size in case of bit fields
int elided; // Full size if value was cut off, -1 if cut on unknown size, 0 otherwise
int arrayIndex; // -1 if not an array member
bool wantsChildren;
bool valueEnabled; // Value will be enabled or not
bool valueEditable; // Value will be editable
bool outdated; // \internal item is to be removed.
private:
void parseHelper(const GdbMi &input);
Q_DECLARE_TR_FUNCTIONS(Debugger::Internal::WatchHandler)
};
void decodeArrayData(std::function<void(const WatchData &)> itemHandler,
const WatchData &tmplate,
const QByteArray &rawData,
const DebuggerEncoding &encoding);
void readNumericVector(std::vector<double> *,
const QByteArray &rawData,
const DebuggerEncoding &encoding);
void parseChildrenData(const WatchData &parent, const GdbMi &child,
std::function<void(const WatchData &)> itemHandler,
std::function<void(const WatchData &, const GdbMi &)> childHandler,
std::function<void(const WatchData &, const QByteArray &, const DebuggerEncoding &)> arrayDecoder);
void parseWatchData(const WatchData &parent, const GdbMi &child,
QList<WatchData> *insertions);
} // namespace Internal
} // namespace Debugger
Q_DECLARE_METATYPE(Debugger::Internal::WatchData)
#endif // DEBUGGER_WATCHDATA_H
This diff is collapsed.
......@@ -33,8 +33,6 @@
#include "watchdata.h"
#include <utils/treemodel.h>
#include <QVector>
namespace Debugger {
......@@ -46,44 +44,6 @@ class WatchModel;
typedef QVector<DisplayFormat> DisplayFormats;
class WatchItem : public Utils::TreeItem, public WatchData
{
public:
WatchItem() {}
WatchItem(const QByteArray &i, const QString &n);
explicit WatchItem(const WatchData &data);
explicit WatchItem(const GdbMi &data);
void fetchMore();
QString displayName() const;
QString displayType() const;
QString displayValue() const;
QString formattedValue() const;
QString expression() const;
int itemFormat() const;
QVariant editValue() const;
int editType() const;
QColor valueColor(int column) const;
int requestedFormat() const;
WatchItem *findItem(const QByteArray &iname);
private:
WatchItem *parentItem() const;
const WatchModel *watchModel() const;
WatchModel *watchModel();
DisplayFormats typeFormatList() const;
bool canFetchMore() const;
QVariant data(int column, int role) const;
Qt::ItemFlags flags(int column) const;
void parseWatchData(const GdbMi &input);
};
class WatchModelBase : public Utils::TreeModel
{
Q_OBJECT
......
QT = core network
win32-msvc* {
QTC_LIB_DEPENDS += utils
}
QTC_LIB_DEPENDS += utils
include(../qttest.pri)
......
......@@ -1425,62 +1425,65 @@ void tst_Dumpers::dumper()
GdbMi actual;
actual.fromString(contents);
WatchData local;
WatchItem local;
local.iname = "local";
QList<WatchData> list;
foreach (const GdbMi &child, actual.children()) {
WatchData dummy;
dummy.iname = child["iname"].data();
dummy.name = QLatin1String(child["name"].data());
if (dummy.iname == "local.qtversion")
const QByteArray iname = child["iname"].data();
if (iname == "local.qtversion")<