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: Christian Stenger <christian.stenger@theqtcompany.com>
Reviewed-by: David Schulz <david.schulz@theqtcompany.com>
This commit is contained in:
hjk
2015-12-16 17:17:38 +01:00
parent 768b775f52
commit 7de7eb6bca
19 changed files with 878 additions and 912 deletions

View File

@@ -37,12 +37,6 @@
#include <QDebug>
////////////////////////////////////////////////////////////////////
//
// WatchData
//
////////////////////////////////////////////////////////////////////
namespace Debugger {
namespace Internal {
@@ -118,8 +112,8 @@ bool isIntOrFloatType(const QByteArray &type)
return isIntType(type) || isFloatType(type);
}
WatchData::WatchData() :
id(WatchData::InvalidId),
WatchItem::WatchItem() :
id(WatchItem::InvalidId),
state(InitialState),
editformat(StopDisplay),
address(0),
@@ -128,6 +122,7 @@ WatchData::WatchData() :
bitpos(0),
bitsize(0),
elided(0),
arrayIndex(-1),
wantsChildren(false),
valueEnabled(true),
valueEditable(true),
@@ -135,16 +130,7 @@ WatchData::WatchData() :
{
}
bool WatchData::isAncestorOf(const QByteArray &childIName) const
{
if (iname.size() >= childIName.size())
return false;
if (!childIName.startsWith(iname))
return false;
return childIName.at(iname.size()) == '.';
}
bool WatchData::isVTablePointer() const
bool WatchItem::isVTablePointer() const
{
// First case: Cdb only. No user type can be named like this, this is safe.
// Second case: Python dumper only.
@@ -152,7 +138,7 @@ bool WatchData::isVTablePointer() const
|| (type.isEmpty() && name == QLatin1String("[vptr]"));
}
void WatchData::setError(const QString &msg)
void WatchItem::setError(const QString &msg)
{
setAllUnneeded();
value = msg;
@@ -161,7 +147,7 @@ void WatchData::setError(const QString &msg)
valueEditable = false;
}
void WatchData::setValue(const QString &value0)
void WatchItem::setValue(const QString &value0)
{
value = value0;
if (value == QLatin1String("{...}")) {
@@ -216,7 +202,7 @@ static GuessChildrenResult guessChildren(const QByteArray &type)
return HasPossiblyChildren;
}
void WatchData::setType(const QByteArray &str, bool guessChildrenFromType)
void WatchItem::setType(const QByteArray &str, bool guessChildrenFromType)
{
type = str.trimmed();
bool changed = true;
@@ -253,7 +239,7 @@ void WatchData::setType(const QByteArray &str, bool guessChildrenFromType)
}
}
QString WatchData::toString() const
QString WatchItem::toString() const
{
const char *doubleQuoteComma = "\",";
QString res;
@@ -299,6 +285,254 @@ QString WatchData::toString() const
return res + QLatin1Char('}');
}
QString WatchItem::msgNotInScope()
{
//: Value of variable in Debugger Locals display for variables out
//: of scope (stopped above initialization).
static const QString rc =
QCoreApplication::translate("Debugger::Internal::WatchItem", "<not in scope>");
return rc;
}
const QString &WatchItem::shadowedNameFormat()
{
//: Display of variables shadowed by variables of the same name
//: in nested scopes: Variable %1 is the variable name, %2 is a
//: simple count.
static const QString format =
QCoreApplication::translate("Debugger::Internal::WatchItem", "%1 <shadowed %2>");
return format;
}
QString WatchItem::shadowedName(const QString &name, int seen)
{
if (seen <= 0)
return name;
return shadowedNameFormat().arg(name).arg(seen);
}
QByteArray WatchItem::hexAddress() const
{
if (address)
return QByteArray("0x") + QByteArray::number(address, 16);
return QByteArray();
}
template <class T>
QString decodeItemHelper(const T &t)
{
return QString::number(t);
}
QString decodeItemHelper(const double &t)
{
return QString::number(t, 'g', 16);
}
template <class T>
void decodeArrayHelper(WatchItem *item, const QByteArray &rawData, int size, const QByteArray &childType)
{
const QByteArray ba = QByteArray::fromHex(rawData);
const T *p = (const T *) ba.data();
for (int i = 0, n = ba.size() / sizeof(T); i < n; ++i) {
WatchItem *child = new WatchItem;
child->arrayIndex = i;
child->value = decodeItemHelper(p[i]);
child->size = size;
child->type = childType;
child->setAllUnneeded();
item->appendChild(child);
}
}
static void decodeArrayData(WatchItem *item, const QByteArray &rawData,
const DebuggerEncoding &encoding, const QByteArray &childType)
{
switch (encoding.type) {
case DebuggerEncoding::HexEncodedSignedInteger:
switch (encoding.size) {
case 1:
return decodeArrayHelper<signed char>(item, rawData, encoding.size, childType);
case 2:
return decodeArrayHelper<short>(item, rawData, encoding.size, childType);
case 4:
return decodeArrayHelper<int>(item, rawData, encoding.size, childType);
case 8:
return decodeArrayHelper<qint64>(item, rawData, encoding.size, childType);
}
case DebuggerEncoding::HexEncodedUnsignedInteger:
switch (encoding.size) {
case 1:
return decodeArrayHelper<uchar>(item, rawData, encoding.size, childType);
case 2:
return decodeArrayHelper<ushort>(item, rawData, encoding.size, childType);
case 4:
return decodeArrayHelper<uint>(item, rawData, encoding.size, childType);
case 8:
return decodeArrayHelper<quint64>(item, rawData, encoding.size, childType);
}
break;
case DebuggerEncoding::HexEncodedFloat:
switch (encoding.size) {
case 4:
return decodeArrayHelper<float>(item, rawData, encoding.size, childType);
case 8:
return decodeArrayHelper<double>(item, rawData, encoding.size, childType);
}
default:
break;
}
qDebug() << "ENCODING ERROR: " << encoding.toString();
}
void WatchItem::parseHelper(const GdbMi &input)
{
setChildrenUnneeded();
GdbMi mi = input["type"];
if (mi.isValid())
setType(mi.data());
editvalue = input["editvalue"].data();
editformat = DebuggerDisplay(input["editformat"].toInt());
editencoding = DebuggerEncoding(input["editencoding"].data());
mi = input["valueelided"];
if (mi.isValid())
elided = mi.toInt();
mi = input["bitpos"];
if (mi.isValid())
bitpos = mi.toInt();
mi = input["bitsize"];
if (mi.isValid())
bitsize = mi.toInt();
mi = input["origaddr"];
if (mi.isValid())
origaddr = mi.toAddress();
mi = input["address"];
if (mi.isValid()) {
address = mi.toAddress();
if (exp.isEmpty()) {
if (iname.startsWith("local.") && iname.count('.') == 1)
// Solve one common case of adding 'class' in
// *(class X*)0xdeadbeef for gdb.
exp = name.toLatin1();
else
exp = "*(" + gdbQuoteTypes(type) + "*)" + hexAddress();
}
}
mi = input["value"];
QByteArray enc = input["valueencoded"].data();
if (mi.isValid() || !enc.isEmpty()) {
setValue(decodeData(mi.data(), enc));
} else {
setValueNeeded();
}
mi = input["size"];
if (mi.isValid())
size = mi.toInt();
mi = input["exp"];
if (mi.isValid())
exp = mi.data();
mi = input["valueenabled"];
if (mi.data() == "true")
valueEnabled = true;
else if (mi.data() == "false")
valueEnabled = false;
mi = input["valueeditable"];
if (mi.data() == "true")
valueEditable = true;
else if (mi.data() == "false")
valueEditable = false;
mi = input["numchild"]; // GDB/MI
if (mi.isValid())
setHasChildren(mi.toInt() > 0);
mi = input["haschild"]; // native-mixed
if (mi.isValid())
setHasChildren(mi.toInt() > 0);
mi = input["arraydata"];
if (mi.isValid()) {
DebuggerEncoding encoding(input["arrayencoding"].data());
QByteArray childType = input["childtype"].data();
decodeArrayData(this, mi.data(), encoding, childType);
} else {
const GdbMi children = input["children"];
if (children.isValid()) {
bool ok = false;
// Try not to repeat data too often.
const GdbMi childType = input["childtype"];
const GdbMi childNumChild = input["childnumchild"];
qulonglong addressBase = input["addrbase"].data().toULongLong(&ok, 0);
qulonglong addressStep = input["addrstep"].data().toULongLong(&ok, 0);
for (int i = 0, n = int(children.children().size()); i != n; ++i) {
const GdbMi &subinput = children.children().at(i);
WatchItem *child = new WatchItem;
if (childType.isValid())
child->setType(childType.data());
if (childNumChild.isValid())
child->setHasChildren(childNumChild.toInt() > 0);
GdbMi name = subinput["name"];
QByteArray nn;
if (name.isValid()) {
nn = name.data();
child->name = QString::fromLatin1(nn);
} else {
nn.setNum(i);
child->name = QString::fromLatin1("[%1]").arg(i);
}
GdbMi iname = subinput["iname"];
if (iname.isValid())
child->iname = iname.data();
else
child->iname = this->iname + '.' + nn;
if (addressStep) {
child->address = addressBase + i * addressStep;
child->exp = "*(" + gdbQuoteTypes(child->type) + "*)" + child->hexAddress();
}
QByteArray key = subinput["key"].data();
if (!key.isEmpty())
child->name = decodeData(key, subinput["keyencoded"].data());
child->parseHelper(subinput);
appendChild(child);
}
}
}
}
void WatchItem::parse(const GdbMi &data)
{
iname = data["iname"].data();
GdbMi wname = data["wname"];
if (wname.isValid()) // Happens (only) for watched expressions.
name = QString::fromUtf8(QByteArray::fromHex(wname.data()));
else
name = QString::fromLatin1(data["name"].data());
parseHelper(data);
if (wname.isValid())
exp = name.toUtf8();
}
WatchItem *WatchItem::parentItem() const
{
return static_cast<WatchItem *>(parent());
}
// Format a tooltip row with aligned colon.
static void formatToolTipRow(QTextStream &str, const QString &category, const QString &value)
{
@@ -310,13 +544,13 @@ static void formatToolTipRow(QTextStream &str, const QString &category, const QS
str << "</td><td>" << val << "</td></tr>";
}
QString WatchData::toToolTip() const
QString WatchItem::toToolTip() const
{
QString res;
QTextStream str(&res);
str << "<html><body><table>";
formatToolTipRow(str, tr("Name"), name);
formatToolTipRow(str, tr("Expression"), QLatin1String(exp));
formatToolTipRow(str, tr("Expression"), expression());
formatToolTipRow(str, tr("Internal Type"), QLatin1String(type));
bool ok;
const quint64 intValue = value.toULongLong(&ok);
@@ -334,367 +568,92 @@ QString WatchData::toToolTip() const
}
formatToolTipRow(str, tr("Value"), val);
}
if (address)
formatToolTipRow(str, tr("Object Address"), formatToolTipAddress(address));
if (realAddress())
formatToolTipRow(str, tr("Object Address"), formatToolTipAddress(realAddress()));
if (origaddr)
formatToolTipRow(str, tr("Pointer Address"), formatToolTipAddress(origaddr));
if (arrayIndex >= 0)
formatToolTipRow(str, tr("Array Index"), QString::number(arrayIndex));
if (size)
formatToolTipRow(str, tr("Static Object Size"), tr("%n bytes", 0, size));
formatToolTipRow(str, tr("Internal ID"), QLatin1String(iname));
formatToolTipRow(str, tr("Internal ID"), QLatin1String(internalName()));
str << "</table></body></html>";
return res;
}
QString WatchData::msgNotInScope()
bool WatchItem::isLocal() const
{
//: Value of variable in Debugger Locals display for variables out
//: of scope (stopped above initialization).
static const QString rc =
QCoreApplication::translate("Debugger::Internal::WatchData", "<not in scope>");
return rc;
if (arrayIndex >= 0)
if (const WatchItem *p = parentItem())
return p->isLocal();
return iname.startsWith("local.");
}
const QString &WatchData::shadowedNameFormat()
bool WatchItem::isWatcher() const
{
//: Display of variables shadowed by variables of the same name
//: in nested scopes: Variable %1 is the variable name, %2 is a
//: simple count.
static const QString format =
QCoreApplication::translate("Debugger::Internal::WatchData", "%1 <shadowed %2>");
return format;
if (arrayIndex >= 0)
if (const WatchItem *p = parentItem())
return p->isWatcher();
return iname.startsWith("watch.");
}
QString WatchData::shadowedName(const QString &name, int seen)
bool WatchItem::isInspect() const
{
if (seen <= 0)
return name;
return shadowedNameFormat().arg(name).arg(seen);
if (arrayIndex >= 0)
if (const WatchItem *p = parentItem())
return p->isInspect();
return iname.startsWith("inspect.");
}
QByteArray WatchData::hexAddress() const
quint64 WatchItem::realAddress() const
{
if (address)
return QByteArray("0x") + QByteArray::number(address, 16);
return QByteArray();
}
////////////////////////////////////////////////////
//
// Protocol convienience
//
////////////////////////////////////////////////////
void WatchData::updateValue(const GdbMi &item)
{
GdbMi value = item["value"];
GdbMi encoding = item["valueencoded"];
if (value.isValid() || encoding.isValid()) {
setValue(decodeData(value.data(), encoding.data()));
} else {
setValueNeeded();
if (arrayIndex >= 0) {
if (const WatchItem *p = parentItem())
return p->address + arrayIndex * size;
}
return address;
}
void WatchData::updateChildCount(const GdbMi &mi)
QByteArray WatchItem::internalName() const
{
if (mi.isValid())
setHasChildren(mi.toInt() > 0);
}
static void setWatchDataValueEnabled(WatchData &data, const GdbMi &mi)
{
if (mi.data() == "true")
data.valueEnabled = true;
else if (mi.data() == "false")
data.valueEnabled = false;
}
static void setWatchDataValueEditable(WatchData &data, const GdbMi &mi)
{
if (mi.data() == "true")
data.valueEditable = true;
else if (mi.data() == "false")
data.valueEditable = false;
}
static void setWatchDataAddress(WatchData &data, quint64 address)
{
data.address = address;
if (data.exp.isEmpty()) {
if (data.iname.startsWith("local.") && data.iname.count('.') == 1)
// Solve one common case of adding 'class' in
// *(class X*)0xdeadbeef for gdb.
data.exp = data.name.toLatin1();
else
data.exp = "*(" + gdbQuoteTypes(data.type) + "*)" + data.hexAddress();
if (arrayIndex >= 0) {
if (const WatchItem *p = parentItem())
return p->iname + '.' + QByteArray::number(arrayIndex);
}
return iname;
}
static void setWatchDataSize(WatchData &data, const GdbMi &mi)
QString WatchItem::realName() const
{
if (mi.isValid()) {
bool ok = false;
const unsigned size = mi.data().toUInt(&ok);
if (ok)
data.size = size;
if (arrayIndex >= 0)
return QString::fromLatin1("[%1]").arg(arrayIndex);
return name;
}
QString WatchItem::expression() const
{
if (!exp.isEmpty())
return QString::fromLatin1(exp);
if (quint64 addr = realAddress()) {
if (!type.isEmpty())
return QString::fromLatin1("*(%1*)0x%2").arg(QLatin1String(type)).arg(addr, 0, 16);
}
const WatchItem *p = parentItem();
if (p && !p->exp.isEmpty())
return QString::fromLatin1("(%1).%2").arg(QString::fromLatin1(p->exp), name);
return name;
}
void WatchData::updateType(const GdbMi &item)
WatchItem *WatchItem::findItem(const QByteArray &iname)
{
if (item.isValid())
setType(item.data());
}
// Utilities to decode string data returned by the dumper helpers.
template <class T>
QString decodeItemHelper(const T &t)
{
return QString::number(t);
}
QString decodeItemHelper(const double &t)
{
return QString::number(t, 'g', 16);
}
template <class T>
void decodeArrayHelper(std::function<void(const WatchData &)> itemHandler, const WatchData &tmplate,
const QByteArray &rawData)
{
const QByteArray ba = QByteArray::fromHex(rawData);
const T *p = (const T *) ba.data();
WatchData data;
const QByteArray exp = "*(" + gdbQuoteTypes(tmplate.type) + "*)0x";
for (int i = 0, n = ba.size() / sizeof(T); i < n; ++i) {
data = tmplate;
data.iname += QByteArray::number(i);
data.name = QString::fromLatin1("[%1]").arg(i);
data.value = decodeItemHelper(p[i]);
data.address += i * sizeof(T);
data.exp = exp + QByteArray::number(data.address, 16);
data.setAllUnneeded();
itemHandler(data);
if (internalName() == iname)
return this;
foreach (TreeItem *child, children()) {
auto witem = static_cast<WatchItem *>(child);
if (WatchItem *result = witem->findItem(iname))
return result;
}
}
void decodeArrayData(std::function<void(const WatchData &)> itemHandler, const WatchData &tmplate,
const QByteArray &rawData, const DebuggerEncoding &encoding)
{
switch (encoding.type) {
case DebuggerEncoding::HexEncodedSignedInteger:
switch (encoding.size) {
case 1:
return decodeArrayHelper<signed char>(itemHandler, tmplate, rawData);
case 2:
return decodeArrayHelper<short>(itemHandler, tmplate, rawData);
case 4:
return decodeArrayHelper<int>(itemHandler, tmplate, rawData);
case 8:
return decodeArrayHelper<qint64>(itemHandler, tmplate, rawData);
}
break;
case DebuggerEncoding::HexEncodedUnsignedInteger:
switch (encoding.size) {
case 1:
return decodeArrayHelper<uchar>(itemHandler, tmplate, rawData);
case 2:
return decodeArrayHelper<ushort>(itemHandler, tmplate, rawData);
case 4:
return decodeArrayHelper<uint>(itemHandler, tmplate, rawData);
case 8:
return decodeArrayHelper<quint64>(itemHandler, tmplate, rawData);
}
break;
case DebuggerEncoding::HexEncodedFloat:
switch (encoding.size) {
case 4:
return decodeArrayHelper<float>(itemHandler, tmplate, rawData);
case 8:
return decodeArrayHelper<double>(itemHandler, tmplate, rawData);
}
default:
break;
}
qDebug() << "ENCODING ERROR: " << encoding.toString();
}
void parseChildrenData(const WatchData &data0, const GdbMi &item,
std::function<void(const WatchData &)> itemHandler,
std::function<void(const WatchData &, const GdbMi &)> childHandler,
std::function<void(const WatchData &childTemplate, const QByteArray &encodedData,
const DebuggerEncoding &encoding)> arrayDecoder)
{
WatchData data = data0;
data.setChildrenUnneeded();
GdbMi children = item["children"];
data.updateType(item["type"]);
data.editvalue = item["editvalue"].data();
data.editformat = DebuggerDisplay(item["editformat"].toInt());
data.editencoding = DebuggerEncoding(item["editencoding"].data());
GdbMi mi = item["valueelided"];
if (mi.isValid())
data.elided = mi.toInt();
mi = item["bitpos"];
if (mi.isValid())
data.bitpos = mi.toInt();
mi = item["bitsize"];
if (mi.isValid())
data.bitsize = mi.toInt();
mi = item["origaddr"];
if (mi.isValid())
data.origaddr = mi.toAddress();
mi = item["address"];
if (mi.isValid())
setWatchDataAddress(data, mi.toAddress());
data.updateValue(item);
setWatchDataSize(data, item["size"]);
mi = item["exp"];
if (mi.isValid())
data.exp = mi.data();
setWatchDataValueEnabled(data, item["valueenabled"]);
setWatchDataValueEditable(data, item["valueeditable"]);
data.updateChildCount(item["numchild"]); // GDB/MI
data.updateChildCount(item["haschild"]); // native-mixed
itemHandler(data);
bool ok = false;
qulonglong addressBase = item["addrbase"].data().toULongLong(&ok, 0);
qulonglong addressStep = item["addrstep"].data().toULongLong(&ok, 0);
// Try not to repeat data too often.
WatchData childtemplate;
childtemplate.updateType(item["childtype"]);
childtemplate.updateChildCount(item["childnumchild"]);
mi = item["arraydata"];
if (mi.isValid()) {
DebuggerEncoding encoding(item["arrayencoding"].data());
childtemplate.iname = data.iname + '.';
childtemplate.address = addressBase;
arrayDecoder(childtemplate, mi.data(), encoding);
} else {
for (int i = 0, n = int(children.children().size()); i != n; ++i) {
const GdbMi &child = children.children().at(i);
WatchData data1 = childtemplate;
GdbMi name = child["name"];
if (name.isValid())
data1.name = QString::fromLatin1(name.data());
else
data1.name = QString::number(i);
GdbMi iname = child["iname"];
if (iname.isValid()) {
data1.iname = iname.data();
} else {
data1.iname = data.iname;
data1.iname += '.';
data1.iname += data1.name.toLatin1();
}
if (!data1.name.isEmpty() && data1.name.at(0).isDigit())
data1.name = QLatin1Char('[') + data1.name + QLatin1Char(']');
if (addressStep) {
setWatchDataAddress(data1, addressBase);
addressBase += addressStep;
}
QByteArray key = child["key"].data();
if (!key.isEmpty()) {
data1.name = decodeData(key, child["keyencoded"].data());
}
childHandler(data1, child);
}
}
}
void parseWatchData(const WatchData &data0, const GdbMi &input,
QList<WatchData> *list)
{
auto itemHandler = [list](const WatchData &data) {
list->append(data);
};
auto childHandler = [list](const WatchData &innerData, const GdbMi &innerInput) {
parseWatchData(innerData, innerInput, list);
};
auto arrayDecoder = [itemHandler](const WatchData &childTemplate,
const QByteArray &encodedData, const DebuggerEncoding &encoding) {
decodeArrayData(itemHandler, childTemplate, encodedData, encoding);
};
parseChildrenData(data0, input, itemHandler, childHandler, arrayDecoder);
}
template <class T>
void readNumericVectorHelper(std::vector<double> *v, const QByteArray &ba)
{
const T *p = (const T *) ba.data();
const int n = ba.size() / sizeof(T);
v->resize(n);
// Losing precision in case of 64 bit ints is ok here, as the result
// is only used to plot data.
for (int i = 0; i != n; ++i)
(*v)[i] = static_cast<double>(p[i]);
}
void readNumericVector(std::vector<double> *v, const QByteArray &rawData, const DebuggerEncoding &encoding)
{
switch (encoding.type) {
case DebuggerEncoding::HexEncodedSignedInteger:
switch (encoding.size) {
case 1:
readNumericVectorHelper<signed char>(v, rawData);
return;
case 2:
readNumericVectorHelper<short>(v, rawData);
return;
case 4:
readNumericVectorHelper<int>(v, rawData);
return;
case 8:
readNumericVectorHelper<qint64>(v, rawData);
return;
}
case DebuggerEncoding::HexEncodedUnsignedInteger:
switch (encoding.size) {
case 1:
readNumericVectorHelper<uchar>(v, rawData);
return;
case 2:
readNumericVectorHelper<ushort>(v, rawData);
return;
case 4:
readNumericVectorHelper<uint>(v, rawData);
return;
case 8:
readNumericVectorHelper<quint64>(v, rawData);
return;
}
case DebuggerEncoding::HexEncodedFloat:
switch (encoding.size) {
case 4:
readNumericVectorHelper<float>(v, rawData);
return;
case 8:
readNumericVectorHelper<double>(v, rawData);
return;
}
default:
break;
}
qDebug() << "ENCODING ERROR: " << encoding.toString();
return 0;
}
} // namespace Internal