VBoxDbgStatsQt4.cpp revision 4d4f336b656d46f8d301603114bb99ce635aafc0
/* $Id$ */
/** @file
* VBox Debugger GUI - Statistics.
*/
/*
* Copyright (C) 2006-2010 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* you can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#define LOG_GROUP LOG_GROUP_DBGG
#include "VBoxDbgStatsQt4.h"
#include <QLocale>
#include <QPushButton>
#include <QSpinBox>
#include <QLabel>
#include <QClipboard>
#include <QApplication>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QKeySequence>
#include <QAction>
#include <QContextMenuEvent>
#include <QHeaderView>
#include <VBox/err.h>
#include <VBox/log.h>
#include <iprt/string.h>
#include <iprt/mem.h>
#include <iprt/assert.h>
/*******************************************************************************
* Defined Constants And Macros *
*******************************************************************************/
/** The number of column. */
#define DBGGUI_STATS_COLUMNS 9
/*******************************************************************************
* Structures and Typedefs *
*******************************************************************************/
/**
* The state of a statistics sample node.
*
* This is used for two pass refresh (1. get data, 2. update the view) and
* for saving the result of a diff.
*/
typedef enum DBGGUISTATSNODESTATE
{
/** The typical invalid zeroth entry. */
kDbgGuiStatsNodeState_kInvalid = 0,
/** The node is the root node. */
kDbgGuiStatsNodeState_kRoot,
/** The node is visible. */
kDbgGuiStatsNodeState_kVisible,
/** The node should be refreshed. */
kDbgGuiStatsNodeState_kRefresh,
/** diff: The node equals. */
kDbgGuiStatsNodeState_kDiffEqual,
/** diff: The node in set 1 is less than the one in set 2. */
kDbgGuiStatsNodeState_kDiffSmaller,
/** diff: The node in set 1 is greater than the one in set 2. */
kDbgGuiStatsNodeState_kDiffGreater,
/** diff: The node is only in set 1. */
kDbgGuiStatsNodeState_kDiffOnlyIn1,
/** diff: The node is only in set 2. */
kDbgGuiStatsNodeState_kDiffOnlyIn2,
/** The end of the valid state values. */
kDbgGuiStatsNodeState_kEnd
} DBGGUISTATENODESTATE;
/**
* A tree node representing a statistic sample.
*
* The nodes carry a reference to the parent and to its position among its
* siblings. Both of these need updating when the grand parent or parent adds a
* new child. This will hopefully not be too expensive but rather pay off when
* we need to create a parent index.
*/
typedef struct DBGGUISTATSNODE
{
/** Pointer to the parent. */
PDBGGUISTATSNODE pParent;
/** Array of pointers to the child nodes. */
PDBGGUISTATSNODE *papChildren;
/** The number of children. */
uint32_t cChildren;
/** Our index among the parent's children. */
uint32_t iSelf;
/** The unit. */
STAMUNIT enmUnit;
/** The data type.
* For filler nodes not containing data, this will be set to STAMTYPE_INVALID. */
STAMTYPE enmType;
/** The data at last update. */
union
{
/** STAMTYPE_COUNTER. */
STAMCOUNTER Counter;
/** STAMTYPE_PROFILE. */
STAMPROFILE Profile;
/** STAMTYPE_PROFILE_ADV. */
STAMPROFILEADV ProfileAdv;
/** STAMTYPE_RATIO_U32. */
STAMRATIOU32 RatioU32;
/** STAMTYPE_U8 & STAMTYPE_U8_RESET. */
uint8_t u8;
/** STAMTYPE_U16 & STAMTYPE_U16_RESET. */
uint16_t u16;
/** STAMTYPE_U32 & STAMTYPE_U32_RESET. */
uint32_t u32;
/** STAMTYPE_U64 & STAMTYPE_U64_RESET. */
uint64_t u64;
/** STAMTYPE_BOOL and STAMTYPE_BOOL_RESET. */
bool f;
/** STAMTYPE_CALLBACK. */
QString *pStr;
} Data;
/** The delta. */
int64_t i64Delta;
/** The name. */
char *pszName;
/** The length of the name. */
size_t cchName;
/** The description string. */
QString *pDescStr;
/** The node state. */
DBGGUISTATENODESTATE enmState;
} DBGGUISTATSNODE;
/**
* Recursion stack.
*/
typedef struct DBGGUISTATSSTACK
{
/** The top stack entry. */
int32_t iTop;
/** The stack array. */
struct DBGGUISTATSSTACKENTRY
{
/** The node. */
PDBGGUISTATSNODE pNode;
/** The current child. */
int32_t iChild;
} a[32];
} DBGGUISTATSSTACK;
/**
* The item model for the statistics tree view.
*
* This manages the DBGGUISTATSNODE trees.
*/
class VBoxDbgStatsModel : public QAbstractItemModel
{
protected:
/** The root of the sample tree. */
PDBGGUISTATSNODE m_pRoot;
private:
/** Next update child. This is UINT32_MAX when invalid. */
uint32_t m_iUpdateChild;
/** Pointer to the node m_szUpdateParent represent and m_iUpdateChild refers to. */
PDBGGUISTATSNODE m_pUpdateParent;
/** The length of the path. */
size_t m_cchUpdateParent;
/** The path to the current update parent, including a trailing slash. */
char m_szUpdateParent[1024];
/** Inserted or/and removed nodes during the update. */
bool m_fUpdateInsertRemove;
public:
/**
* Constructor.
*
* @param a_pParent The parent object. See QAbstractItemModel in the Qt
* docs for details.
*/
VBoxDbgStatsModel(QObject *a_pParent);
/**
* Destructor.
*
* This will free all the data the model holds.
*/
virtual ~VBoxDbgStatsModel();
/**
* Updates the data matching the specified pattern.
*
* This will should invoke updatePrep, updateCallback and updateDone.
*
* It is vitally important that updateCallback is fed the data in the right
* order. The code make very definite ASSUMPTIONS about the ordering being
* strictly sorted and taking the slash into account when doing so.
*
* @returns true if we reset the model and it's necessary to set the root index.
* @param a_rPatStr The selection pattern.
*
* @remarks The default implementation is an empty stub.
*/
virtual bool updateStatsByPattern(const QString &a_rPatStr);
/**
* Similar to updateStatsByPattern, except that it only works on a sub-tree and
* will not remove anything that's outside that tree.
*
* @param a_rIndex The sub-tree root. Invalid index means root.
*
* @todo Create a default implementation using updateStatsByPattern.
*/
virtual void updateStatsByIndex(QModelIndex const &a_rIndex);
/**
* Reset the stats matching the specified pattern.
*
* @param a_rPatStr The selection pattern.
*
* @remarks The default implementation is an empty stub.
*/
virtual void resetStatsByPattern(QString const &a_rPatStr);
/**
* Reset the stats of a sub-tree.
*
* @param a_rIndex The sub-tree root. Invalid index means root.
* @param a_fSubTree Whether to reset the sub-tree as well. Default is true.
*
* @remarks The default implementation makes use of resetStatsByPattern
*/
virtual void resetStatsByIndex(QModelIndex const &a_rIndex, bool a_fSubTree = true);
/**
* Gets the model index of the root node.
*
* @returns root index.
*/
QModelIndex getRootIndex(void) const;
protected:
/**
* Set the root node.
*
* This will free all the current data before taking the ownership of the new
* root node and its children.
*
* @param a_pRoot The new root node.
*/
void setRootNode(PDBGGUISTATSNODE a_pRoot);
/** Creates the root node. */
static PDBGGUISTATSNODE createRootNode(void);
/** Creates and insert a node under the given parent. */
static PDBGGUISTATSNODE createAndInsertNode(PDBGGUISTATSNODE pParent, const char *pszName, size_t cchName, uint32_t iPosition);
/** Creates and insert a node under the given parent with correct Qt
* signalling. */
PDBGGUISTATSNODE createAndInsert(PDBGGUISTATSNODE pParent, const char *pszName, size_t cchName, uint32_t iPosition);
/**
* Resets the node to a pristine state.
*
* @param pNode The node.
*/
static void resetNode(PDBGGUISTATSNODE pNode);
/**
* Initializes a pristine node.
*/
static int initNode(PDBGGUISTATSNODE pNode, STAMTYPE enmType, void *pvSample, STAMUNIT enmUnit, const char *pszDesc);
/**
* Updates (or reinitializes if you like) a node.
*/
static void updateNode(PDBGGUISTATSNODE pNode, STAMTYPE enmType, void *pvSample, STAMUNIT enmUnit, const char *pszDesc);
/**
* Called by updateStatsByPattern(), makes the necessary preparations.
*
* @returns Success indicator.
*/
bool updatePrepare(void);
/**
* Called by updateStatsByPattern(), finalizes the update.
*
* @return See updateStatsByPattern().
*
* @param a_fSuccess Whether the update was successful or not.
*
*/
bool updateDone(bool a_fSuccess);
/**
* updateCallback() worker taking care of in-tree inserts and removals.
*
* @returns The current node.
* @param pszName The name of the tree element to update.
*/
PDBGGUISTATSNODE updateCallbackHandleOutOfOrder(const char *pszName);
/**
* updateCallback() worker taking care of tail insertions.
*
* @returns The current node.
* @param pszName The name of the tree element to update.
*/
PDBGGUISTATSNODE updateCallbackHandleTail(const char *pszName);
/**
* updateCallback() worker that advances the update state to the next data node
* in anticipation of the next updateCallback call.
*
* @returns The current node.
* @param pNode The current node.
*/
void updateCallbackAdvance(PDBGGUISTATSNODE pNode);
/** Callback used by updateStatsByPattern() and updateStatsByIndex() to feed
* changes.
* @copydoc FNSTAMR3ENUM */
static DECLCALLBACK(int) updateCallback(const char *pszName, STAMTYPE enmType, void *pvSample, STAMUNIT enmUnit,
STAMVISIBILITY enmVisibility, const char *pszDesc, void *pvUser);
/**
* Calculates the full path of a node.
*
* @returns Number of bytes returned, negative value on buffer overflow
*
* @param pNode The node.
* @param psz The output buffer.
* @param cch The size of the buffer.
*/
static ssize_t getNodePath(PCDBGGUISTATSNODE pNode, char *psz, ssize_t cch);
/**
* Check if the first node is an ancestor to the second one.
*
* @returns true/false.
* @param pAncestor The first node, the alleged ancestor.
* @param pDescendant The second node, the alleged descendant.
*/
static bool isNodeAncestorOf(PCDBGGUISTATSNODE pAncestor, PCDBGGUISTATSNODE pDescendant);
/**
* Advance to the next node in the tree.
*
* @returns Pointer to the next node, NULL if we've reached the end or
* was handed a NULL node.
* @param pNode The current node.
*/
static PDBGGUISTATSNODE nextNode(PDBGGUISTATSNODE pNode);
/**
* Advance to the next node in the tree that contains data.
*
* @returns Pointer to the next data node, NULL if we've reached the end or
* was handed a NULL node.
* @param pNode The current node.
*/
static PDBGGUISTATSNODE nextDataNode(PDBGGUISTATSNODE pNode);
/**
* Advance to the previous node in the tree.
*
* @returns Pointer to the previous node, NULL if we've reached the end or
* was handed a NULL node.
* @param pNode The current node.
*/
static PDBGGUISTATSNODE prevNode(PDBGGUISTATSNODE pNode);
/**
* Advance to the previous node in the tree that contains data.
*
* @returns Pointer to the previous data node, NULL if we've reached the end or
* was handed a NULL node.
* @param pNode The current node.
*/
static PDBGGUISTATSNODE prevDataNode(PDBGGUISTATSNODE pNode);
/**
* Removes a node from the tree.
*
* @returns pNode.
* @param pNode The node.
*/
static PDBGGUISTATSNODE removeNode(PDBGGUISTATSNODE pNode);
/**
* Removes a node from the tree and destroys it and all its descendants.
*
* @param pNode The node.
*/
static void removeAndDestroyNode(PDBGGUISTATSNODE pNode);
/** Removes a node from the tree and destroys it and all its descendants
* performing the required Qt signalling. */
void removeAndDestroy(PDBGGUISTATSNODE pNode);
/**
* Destroys a statistics tree.
*
* @param a_pRoot The root of the tree. NULL is fine.
*/
static void destroyTree(PDBGGUISTATSNODE a_pRoot);
/**
* Stringifies exactly one node, no children.
*
* This is for logging and clipboard.
*
* @param a_pNode The node.
* @param a_rString The string to append the stringified node to.
*/
static void stringifyNodeNoRecursion(PDBGGUISTATSNODE a_pNode, QString &a_rString);
/**
* Stringifies a node and its children.
*
* This is for logging and clipboard.
*
* @param a_pNode The node.
* @param a_rString The string to append the stringified node to.
*/
static void stringifyNode(PDBGGUISTATSNODE a_pNode, QString &a_rString);
public:
/**
* Converts the specified tree to string.
*
* This is for logging and clipboard.
*
* @param a_rRoot Where to start. Use QModelIndex() to start at the root.
* @param a_rString Where to return to return the string dump.
*/
void stringifyTree(QModelIndex &a_rRoot, QString &a_rString) const;
/**
* Dumps the given (sub-)tree as XML.
*
* @param a_rRoot Where to start. Use QModelIndex() to start at the root.
* @param a_rString Where to return to return the XML dump.
*/
void xmlifyTree(QModelIndex &a_rRoot, QString &a_rString) const;
/**
* Puts the stringified tree on the clipboard.
*
* @param a_rRoot Where to start. Use QModelIndex() to start at the root.
*/
void copyTreeToClipboard(QModelIndex &a_rRoot) const;
protected:
/** Worker for logTree. */
static void logNode(PDBGGUISTATSNODE a_pNode, bool a_fReleaseLog);
public:
/** Logs a (sub-)tree.
*
* @param a_rRoot Where to start. Use QModelIndex() to start at the root.
* @param a_fReleaseLog Whether to use the release log (true) or the debug log (false).
*/
void logTree(QModelIndex &a_rRoot, bool a_fReleaseLog) const;
protected:
/** Gets the unit. */
static QString strUnit(PCDBGGUISTATSNODE pNode);
/** Gets the value/times. */
static QString strValueTimes(PCDBGGUISTATSNODE pNode);
/** Gets the minimum value. */
static QString strMinValue(PCDBGGUISTATSNODE pNode);
/** Gets the average value. */
static QString strAvgValue(PCDBGGUISTATSNODE pNode);
/** Gets the maximum value. */
static QString strMaxValue(PCDBGGUISTATSNODE pNode);
/** Gets the total value. */
static QString strTotalValue(PCDBGGUISTATSNODE pNode);
/** Gets the delta value. */
static QString strDeltaValue(PCDBGGUISTATSNODE pNode);
/**
* Destroys a node and all its children.
*
* @param a_pNode The node to destroy.
*/
static void destroyNode(PDBGGUISTATSNODE a_pNode);
/**
* Converts an index to a node pointer.
*
* @returns Pointer to the node, NULL if invalid reference.
* @param a_rIndex Reference to the index
*/
inline PDBGGUISTATSNODE nodeFromIndex(const QModelIndex &a_rIndex) const
{
if (RT_LIKELY(a_rIndex.isValid()))
return (PDBGGUISTATSNODE)a_rIndex.internalPointer();
return NULL;
}
public:
/** @name Overridden QAbstractItemModel methods
* @{ */
virtual int columnCount(const QModelIndex &a_rParent) const;
virtual QVariant data(const QModelIndex &a_rIndex, int a_eRole) const;
virtual Qt::ItemFlags flags(const QModelIndex &a_rIndex) const;
virtual bool hasChildren(const QModelIndex &a_rParent) const;
virtual QVariant headerData(int a_iSection, Qt::Orientation a_ePrientation, int a_eRole) const;
virtual QModelIndex index(int a_iRow, int a_iColumn, const QModelIndex &a_rParent) const;
virtual QModelIndex parent(const QModelIndex &a_rChild) const;
virtual int rowCount(const QModelIndex &a_rParent) const;
///virtual void sort(int a_iColumn, Qt::SortOrder a_eOrder);
/** @} */
};
/**
* Model using the VM / STAM interface as data source.
*/
class VBoxDbgStatsModelVM : public VBoxDbgStatsModel, public VBoxDbgBase
{
public:
/**
* Constructor.
*
* @param a_pDbgGui Pointer to the debugger gui object.
* @param a_rPatStr The selection pattern.
* @param a_pParent The parent object. NULL is fine.
*/
VBoxDbgStatsModelVM(VBoxDbgGui *a_pDbgGui, QString &a_rPatStr, QObject *a_pParent);
/** Destructor */
virtual ~VBoxDbgStatsModelVM();
virtual bool updateStatsByPattern(const QString &a_rPatStr);
virtual void resetStatsByPattern(const QString &a_rPatStr);
protected:
/**
* Enumeration callback used by createNewTree.
*/
static DECLCALLBACK(int) createNewTreeCallback(const char *pszName, STAMTYPE enmType, void *pvSample, STAMUNIT enmUnit,
STAMVISIBILITY enmVisibility, const char *pszDesc, void *pvUser);
/**
* Constructs a new statistics tree by query data from the VM.
*
* @returns Pointer to the root of the tree we've constructed. This will be NULL
* if the STAM API throws an error or we run out of memory.
* @param a_rPatStr The selection pattern.
*/
PDBGGUISTATSNODE createNewTree(QString &a_rPatStr);
};
/*******************************************************************************
* Internal Functions *
*******************************************************************************/
/**
* Formats a number into a 64-byte buffer.
*/
static char *formatNumber(char *psz, uint64_t u64)
{
static const char s_szDigits[] = "0123456789";
psz += 63;
*psz-- = '\0';
unsigned cDigits = 0;
for (;;)
{
const unsigned iDigit = u64 % 10;
u64 /= 10;
*psz = s_szDigits[iDigit];
if (!u64)
break;
psz--;
if (!(++cDigits % 3))
*psz-- = ',';
}
return psz;
}
/**
* Formats a number into a 64-byte buffer.
* (18 446 744 073 709 551 615)
*/
static char *formatNumberSigned(char *psz, int64_t i64)
{
static const char s_szDigits[] = "0123456789";
psz += 63;
*psz-- = '\0';
const bool fNegative = i64 < 0;
uint64_t u64 = fNegative ? -i64 : i64;
unsigned cDigits = 0;
for (;;)
{
const unsigned iDigit = u64 % 10;
u64 /= 10;
*psz = s_szDigits[iDigit];
if (!u64)
break;
psz--;
if (!(++cDigits % 3))
*psz-- = ',';
}
if (fNegative)
*--psz = '-';
return psz;
}
/**
* Formats a unsigned hexadecimal number into a into a 64-byte buffer.
*/
static char *formatHexNumber(char *psz, uint64_t u64, unsigned cZeros)
{
static const char s_szDigits[] = "0123456789abcdef";
psz += 63;
*psz-- = '\0';
unsigned cDigits = 0;
for (;;)
{
const unsigned iDigit = u64 % 16;
u64 /= 16;
*psz = s_szDigits[iDigit];
++cDigits;
if (!u64 && cDigits >= cZeros)
break;
psz--;
if (!(cDigits % 8))
*psz-- = '\'';
}
return psz;
}
#if 0/* unused */
/**
* Formats a sort key number.
*/
static void formatSortKey(char *psz, uint64_t u64)
{
static const char s_szDigits[] = "0123456789abcdef";
/* signed */
*psz++ = '+';
/* 16 hex digits */
psz[16] = '\0';
unsigned i = 16;
while (i-- > 0)
{
if (u64)
{
const unsigned iDigit = u64 % 16;
u64 /= 16;
psz[i] = s_szDigits[iDigit];
}
else
psz[i] = '0';
}
}
#endif
#if 0/* unused */
/**
* Formats a sort key number.
*/
static void formatSortKeySigned(char *psz, int64_t i64)
{
static const char s_szDigits[] = "0123456789abcdef";
/* signed */
uint64_t u64;
if (i64 >= 0)
{
*psz++ = '+';
u64 = i64;
}
else
{
*psz++ = '-';
u64 = -i64;
}
/* 16 hex digits */
psz[16] = '\0';
unsigned i = 16;
while (i-- > 0)
{
if (u64)
{
const unsigned iDigit = u64 % 16;
u64 /= 16;
psz[i] = s_szDigits[iDigit];
}
else
psz[i] = '0';
}
}
#endif
/*
*
* V B o x D b g S t a t s M o d e l
* V B o x D b g S t a t s M o d e l
* V B o x D b g S t a t s M o d e l
*
*
*/
VBoxDbgStatsModel::VBoxDbgStatsModel(QObject *a_pParent)
: QAbstractItemModel(a_pParent),
m_pRoot(NULL), m_iUpdateChild(UINT32_MAX), m_pUpdateParent(NULL), m_cchUpdateParent(0)
{
}
VBoxDbgStatsModel::~VBoxDbgStatsModel()
{
destroyTree(m_pRoot);
m_pRoot = NULL;
}
/*static*/ void
VBoxDbgStatsModel::destroyTree(PDBGGUISTATSNODE a_pRoot)
{
if (!a_pRoot)
return;
Assert(!a_pRoot->pParent);
Assert(!a_pRoot->iSelf);
destroyNode(a_pRoot);
}
/* static*/ void
VBoxDbgStatsModel::destroyNode(PDBGGUISTATSNODE a_pNode)
{
/* destroy all our children */
uint32_t i = a_pNode->cChildren;
while (i-- > 0)
{
destroyNode(a_pNode->papChildren[i]);
a_pNode->papChildren[i] = NULL;
}
/* free the resources we're using */
a_pNode->pParent = NULL;
RTMemFree(a_pNode->papChildren);
a_pNode->papChildren = NULL;
if (a_pNode->enmType == STAMTYPE_CALLBACK)
{
delete a_pNode->Data.pStr;
a_pNode->Data.pStr = NULL;
}
a_pNode->cChildren = 0;
a_pNode->iSelf = UINT32_MAX;
a_pNode->enmUnit = STAMUNIT_INVALID;
a_pNode->enmType = STAMTYPE_INVALID;
RTMemFree(a_pNode->pszName);
a_pNode->pszName = NULL;
if (a_pNode->pDescStr)
{
delete a_pNode->pDescStr;
a_pNode->pDescStr = NULL;
}
#ifdef VBOX_STRICT
/* poison it. */
a_pNode->pParent++;
a_pNode->Data.pStr++;
a_pNode->pDescStr++;
a_pNode->papChildren++;
a_pNode->cChildren = 8442;
#endif
/* Finally ourselves */
a_pNode->enmState = kDbgGuiStatsNodeState_kInvalid;
RTMemFree(a_pNode);
}
/*static*/ PDBGGUISTATSNODE
VBoxDbgStatsModel::createRootNode(void)
{
PDBGGUISTATSNODE pRoot = (PDBGGUISTATSNODE)RTMemAllocZ(sizeof(DBGGUISTATSNODE));
if (!pRoot)
return NULL;
pRoot->iSelf = 0;
pRoot->enmType = STAMTYPE_INVALID;
pRoot->enmUnit = STAMUNIT_INVALID;
pRoot->pszName = (char *)RTMemDup("/", sizeof("/"));
pRoot->cchName = 1;
pRoot->enmState = kDbgGuiStatsNodeState_kRoot;
return pRoot;
}
/*static*/ PDBGGUISTATSNODE
VBoxDbgStatsModel::createAndInsertNode(PDBGGUISTATSNODE pParent, const char *pszName, size_t cchName, uint32_t iPosition)
{
/*
* Create it.
*/
PDBGGUISTATSNODE pNode = (PDBGGUISTATSNODE)RTMemAllocZ(sizeof(DBGGUISTATSNODE));
if (!pNode)
return NULL;
pNode->iSelf = UINT32_MAX;
pNode->enmType = STAMTYPE_INVALID;
pNode->enmUnit = STAMUNIT_INVALID;
pNode->pszName = (char *)RTMemDupEx(pszName, cchName, 1);
pNode->cchName = cchName;
pNode->enmState = kDbgGuiStatsNodeState_kVisible;
/*
* Do we need to expand the array?
*/
if (!(pParent->cChildren & 31))
{
void *pvNew = RTMemRealloc(pParent->papChildren, sizeof(*pParent->papChildren) * (pParent->cChildren + 32));
if (!pvNew)
{
destroyNode(pNode);
return NULL;
}
pParent->papChildren = (PDBGGUISTATSNODE *)pvNew;
}
/*
* Insert it.
*/
pNode->pParent = pParent;
if (iPosition >= pParent->cChildren)
/* Last. */
iPosition = pParent->cChildren;
else
{
/* Shift all the items after ours. */
uint32_t iShift = pParent->cChildren;
while (iShift-- > iPosition)
{
PDBGGUISTATSNODE pChild = pParent->papChildren[iShift];
pParent->papChildren[iShift + 1] = pChild;
pChild->iSelf = iShift + 1;
}
}
/* Insert ours */
pNode->iSelf = iPosition;
pParent->papChildren[iPosition] = pNode;
pParent->cChildren++;
return pNode;
}
PDBGGUISTATSNODE
VBoxDbgStatsModel::createAndInsert(PDBGGUISTATSNODE pParent, const char *pszName, size_t cchName, uint32_t iPosition)
{
PDBGGUISTATSNODE pNode;
if (m_fUpdateInsertRemove)
pNode = createAndInsertNode(pParent, pszName, cchName, iPosition);
else
{
beginInsertRows(createIndex(pParent->iSelf, 0, pParent), 0, 0);
pNode = createAndInsertNode(pParent, pszName, cchName, iPosition);
endInsertRows();
}
return pNode;
}
/*static*/ PDBGGUISTATSNODE
VBoxDbgStatsModel::removeNode(PDBGGUISTATSNODE pNode)
{
PDBGGUISTATSNODE pParent = pNode->pParent;
if (pParent)
{
uint32_t iPosition = pNode->iSelf;
Assert(pParent->papChildren[iPosition] == pNode);
uint32_t const cChildren = --pParent->cChildren;
for (; iPosition < cChildren; iPosition++)
{
PDBGGUISTATSNODE pChild = pParent->papChildren[iPosition + 1];
pParent->papChildren[iPosition] = pChild;
pChild->iSelf = iPosition;
}
#ifdef VBOX_STRICT /* poison */
pParent->papChildren[iPosition] = (PDBGGUISTATSNODE)0x42;
#endif
}
return pNode;
}
/*static*/ void
VBoxDbgStatsModel::removeAndDestroyNode(PDBGGUISTATSNODE pNode)
{
removeNode(pNode);
destroyNode(pNode);
}
void
VBoxDbgStatsModel::removeAndDestroy(PDBGGUISTATSNODE pNode)
{
if (m_fUpdateInsertRemove)
removeAndDestroyNode(pNode);
else
{
/*
* Removing is fun since the docs are imprecise as to how persistent
* indexes are updated (or aren't). So, let try a few different ideas
* and see which works.
*/
#if 1
/* destroy the children first with the appropriate begin/endRemoveRows signals. */
DBGGUISTATSSTACK Stack;
Stack.a[0].pNode = pNode;
Stack.a[0].iChild = -1;
Stack.iTop = 0;
while (Stack.iTop >= 0)
{
/* get top element */
PDBGGUISTATSNODE pNode = Stack.a[Stack.iTop].pNode;
uint32_t iChild = ++Stack.a[Stack.iTop].iChild;
if (iChild < pNode->cChildren)
{
/* push */
Stack.iTop++;
Assert(Stack.iTop < (int32_t)RT_ELEMENTS(Stack.a));
Stack.a[Stack.iTop].pNode = pNode->papChildren[iChild];
Stack.a[Stack.iTop].iChild = 0;
}
else
{
/* pop and destroy all the children. */
Stack.iTop--;
uint32_t i = pNode->cChildren;
if (i)
{
beginRemoveRows(createIndex(pNode->iSelf, 0, pNode), 0, i - 1);
while (i-- > 0)
destroyNode(pNode->papChildren[i]);
pNode->cChildren = 0;
endRemoveRows();
}
}
}
Assert(!pNode->cChildren);
/* finally the node it self. */
PDBGGUISTATSNODE pParent = pNode->pParent;
beginRemoveRows(createIndex(pParent->iSelf, 0, pParent), pNode->iSelf, pNode->iSelf);
removeAndDestroyNode(pNode);
endRemoveRows();
#elif 0
/* This ain't working, leaves invalid indexes behind. */
PDBGGUISTATSNODE pParent = pNode->pParent;
beginRemoveRows(createIndex(pParent->iSelf, 0, pParent), pNode->iSelf, pNode->iSelf);
removeAndDestroyNode(pNode);
endRemoveRows();
#else
/* Force reset() of the model after the update. */
m_fUpdateInsertRemove = true;
removeAndDestroyNode(pNode);
#endif
}
}
/*static*/ void
VBoxDbgStatsModel::resetNode(PDBGGUISTATSNODE pNode)
{
/* free and reinit the data. */
if (pNode->enmType == STAMTYPE_CALLBACK)
{
delete pNode->Data.pStr;
pNode->Data.pStr = NULL;
}
pNode->enmType = STAMTYPE_INVALID;
/* free the description. */
if (pNode->pDescStr)
{
delete pNode->pDescStr;
pNode->pDescStr = NULL;
}
}
/*static*/ int
VBoxDbgStatsModel::initNode(PDBGGUISTATSNODE pNode, STAMTYPE enmType, void *pvSample, STAMUNIT enmUnit, const char *pszDesc)
{
/*
* Copy the data.
*/
pNode->enmUnit = enmUnit;
Assert(pNode->enmType == STAMTYPE_INVALID);
pNode->enmType = enmType;
if (pszDesc)
pNode->pDescStr = new QString(pszDesc); /* ignore allocation failure (well, at least up to the point we can ignore it) */
switch (enmType)
{
case STAMTYPE_COUNTER:
pNode->Data.Counter = *(PSTAMCOUNTER)pvSample;
break;
case STAMTYPE_PROFILE:
case STAMTYPE_PROFILE_ADV:
pNode->Data.Profile = *(PSTAMPROFILE)pvSample;
break;
case STAMTYPE_RATIO_U32:
case STAMTYPE_RATIO_U32_RESET:
pNode->Data.RatioU32 = *(PSTAMRATIOU32)pvSample;
break;
case STAMTYPE_CALLBACK:
{
const char *pszString = (const char *)pvSample;
pNode->Data.pStr = new QString(pszString);
break;
}
case STAMTYPE_U8:
case STAMTYPE_U8_RESET:
case STAMTYPE_X8:
case STAMTYPE_X8_RESET:
pNode->Data.u8 = *(uint8_t *)pvSample;
break;
case STAMTYPE_U16:
case STAMTYPE_U16_RESET:
case STAMTYPE_X16:
case STAMTYPE_X16_RESET:
pNode->Data.u16 = *(uint16_t *)pvSample;
break;
case STAMTYPE_U32:
case STAMTYPE_U32_RESET:
case STAMTYPE_X32:
case STAMTYPE_X32_RESET:
pNode->Data.u32 = *(uint32_t *)pvSample;
break;
case STAMTYPE_U64:
case STAMTYPE_U64_RESET:
case STAMTYPE_X64:
case STAMTYPE_X64_RESET:
pNode->Data.u64 = *(uint64_t *)pvSample;
break;
case STAMTYPE_BOOL:
case STAMTYPE_BOOL_RESET:
pNode->Data.f = *(bool *)pvSample;
break;
default:
AssertMsgFailed(("%d\n", enmType));
break;
}
return VINF_SUCCESS;
}
/*static*/ void
VBoxDbgStatsModel::updateNode(PDBGGUISTATSNODE pNode, STAMTYPE enmType, void *pvSample, STAMUNIT enmUnit, const char *pszDesc)
{
/*
* Reset and init the node if the type changed.
*/
if (enmType != pNode->enmType)
{
if (pNode->enmType != STAMTYPE_INVALID)
resetNode(pNode);
initNode(pNode, enmType, pvSample, enmUnit, pszDesc);
pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
}
else
{
/*
* ASSUME that only the sample value will change and that the unit, visibility
* and description remains the same.
*/
int64_t iDelta;
switch (enmType)
{
case STAMTYPE_COUNTER:
{
uint64_t cPrev = pNode->Data.Counter.c;
pNode->Data.Counter = *(PSTAMCOUNTER)pvSample;
iDelta = pNode->Data.Counter.c - cPrev;
if (iDelta || pNode->i64Delta)
{
pNode->i64Delta = iDelta;
pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
}
break;
}
case STAMTYPE_PROFILE:
case STAMTYPE_PROFILE_ADV:
{
uint64_t cPrevPeriods = pNode->Data.Profile.cPeriods;
pNode->Data.Profile = *(PSTAMPROFILE)pvSample;
iDelta = pNode->Data.Profile.cPeriods - cPrevPeriods;
if (iDelta || pNode->i64Delta)
{
pNode->i64Delta = iDelta;
pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
}
break;
}
case STAMTYPE_RATIO_U32:
case STAMTYPE_RATIO_U32_RESET:
{
STAMRATIOU32 Prev = pNode->Data.RatioU32;
pNode->Data.RatioU32 = *(PSTAMRATIOU32)pvSample;
int32_t iDeltaA = pNode->Data.RatioU32.u32A - Prev.u32A;
int32_t iDeltaB = pNode->Data.RatioU32.u32B - Prev.u32B;
if (iDeltaA == 0 && iDeltaB == 0)
{
if (pNode->i64Delta)
{
pNode->i64Delta = 0;
pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
}
}
else
{
if (iDeltaA >= 0)
pNode->i64Delta = iDeltaA + (iDeltaB >= 0 ? iDeltaB : -iDeltaB);
else
pNode->i64Delta = iDeltaA + (iDeltaB < 0 ? iDeltaB : -iDeltaB);
pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
}
break;
}
case STAMTYPE_CALLBACK:
{
const char *pszString = (const char *)pvSample;
if (!pNode->Data.pStr)
{
pNode->Data.pStr = new QString(pszString);
pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
}
else if (*pNode->Data.pStr == pszString)
{
delete pNode->Data.pStr;
pNode->Data.pStr = new QString(pszString);
pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
}
break;
}
case STAMTYPE_U8:
case STAMTYPE_U8_RESET:
case STAMTYPE_X8:
case STAMTYPE_X8_RESET:
{
uint8_t uPrev = pNode->Data.u8;
pNode->Data.u8 = *(uint8_t *)pvSample;
iDelta = (int32_t)pNode->Data.u8 - (int32_t)uPrev;
if (iDelta || pNode->i64Delta)
{
pNode->i64Delta = iDelta;
pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
}
break;
}
case STAMTYPE_U16:
case STAMTYPE_U16_RESET:
case STAMTYPE_X16:
case STAMTYPE_X16_RESET:
{
uint16_t uPrev = pNode->Data.u16;
pNode->Data.u16 = *(uint16_t *)pvSample;
iDelta = (int32_t)pNode->Data.u16 - (int32_t)uPrev;
if (iDelta || pNode->i64Delta)
{
pNode->i64Delta = iDelta;
pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
}
break;
}
case STAMTYPE_U32:
case STAMTYPE_U32_RESET:
case STAMTYPE_X32:
case STAMTYPE_X32_RESET:
{
uint32_t uPrev = pNode->Data.u32;
pNode->Data.u32 = *(uint32_t *)pvSample;
iDelta = (int64_t)pNode->Data.u32 - (int64_t)uPrev;
if (iDelta || pNode->i64Delta)
{
pNode->i64Delta = iDelta;
pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
}
break;
}
case STAMTYPE_U64:
case STAMTYPE_U64_RESET:
case STAMTYPE_X64:
case STAMTYPE_X64_RESET:
{
uint64_t uPrev = pNode->Data.u64;
pNode->Data.u64 = *(uint64_t *)pvSample;
iDelta = pNode->Data.u64 - uPrev;
if (iDelta || pNode->i64Delta)
{
pNode->i64Delta = iDelta;
pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
}
break;
}
case STAMTYPE_BOOL:
case STAMTYPE_BOOL_RESET:
{
bool fPrev = pNode->Data.f;
pNode->Data.f = *(bool *)pvSample;
iDelta = pNode->Data.f - fPrev;
if (iDelta || pNode->i64Delta)
{
pNode->i64Delta = iDelta;
pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
}
break;
}
default:
AssertMsgFailed(("%d\n", enmType));
break;
}
}
}
/*static*/ ssize_t
VBoxDbgStatsModel::getNodePath(PCDBGGUISTATSNODE pNode, char *psz, ssize_t cch)
{
ssize_t off;
if (!pNode->pParent)
{
/* root - don't add it's slash! */
AssertReturn(cch >= 1, -1);
off = 0;
*psz = '\0';
}
else
{
cch -= pNode->cchName + 1;
AssertReturn(cch > 0, -1);
off = getNodePath(pNode->pParent, psz, cch);
if (off >= 0)
{
psz[off++] = '/';
memcpy(&psz[off], pNode->pszName, pNode->cchName + 1);
off += pNode->cchName;
}
}
return off;
}
/*static*/ bool
VBoxDbgStatsModel::isNodeAncestorOf(PCDBGGUISTATSNODE pAncestor, PCDBGGUISTATSNODE pDescendant)
{
while (pDescendant)
{
pDescendant = pDescendant->pParent;
if (pDescendant == pAncestor)
return true;
}
return false;
}
/*static*/ PDBGGUISTATSNODE
VBoxDbgStatsModel::nextNode(PDBGGUISTATSNODE pNode)
{
if (!pNode)
return NULL;
/* descend to children. */
if (pNode->cChildren)
return pNode->papChildren[0];
PDBGGUISTATSNODE pParent = pNode->pParent;
if (!pParent)
return NULL;
/* next sibling. */
if (pNode->iSelf + 1 < pNode->pParent->cChildren)
return pParent->papChildren[pNode->iSelf + 1];
/* ascend and advanced to a parent's sibiling. */
for (;;)
{
uint32_t iSelf = pParent->iSelf;
pParent = pParent->pParent;
if (!pParent)
return NULL;
if (iSelf + 1 < pParent->cChildren)
return pParent->papChildren[iSelf + 1];
}
}
/*static*/ PDBGGUISTATSNODE
VBoxDbgStatsModel::nextDataNode(PDBGGUISTATSNODE pNode)
{
do
pNode = nextNode(pNode);
while ( pNode
&& pNode->enmType == STAMTYPE_INVALID);
return pNode;
}
/*static*/ PDBGGUISTATSNODE
VBoxDbgStatsModel::prevNode(PDBGGUISTATSNODE pNode)
{
if (!pNode)
return NULL;
PDBGGUISTATSNODE pParent = pNode->pParent;
if (!pParent)
return NULL;
/* previous sibling's latest descendant (better expression anyone?). */
if (pNode->iSelf > 0)
{
pNode = pParent->papChildren[pNode->iSelf - 1];
while (pNode->cChildren)
pNode = pNode->papChildren[pNode->cChildren - 1];
return pNode;
}
/* ascend to the parent. */
return pParent;
}
/*static*/ PDBGGUISTATSNODE
VBoxDbgStatsModel::prevDataNode(PDBGGUISTATSNODE pNode)
{
do
pNode = prevNode(pNode);
while ( pNode
&& pNode->enmType == STAMTYPE_INVALID);
return pNode;
}
#if 0
/*static*/ PDBGGUISTATSNODE
VBoxDbgStatsModel::createNewTree(IMachineDebugger *a_pIMachineDebugger)
{
/** @todo */
return NULL;
}
#endif
#if 0
/*static*/ PDBGGUISTATSNODE
VBoxDbgStatsModel::createNewTree(const char *pszFilename)
{
/** @todo */
return NULL;
}
#endif
#if 0
/*static*/ PDBGGUISTATSNODE
VBoxDbgStatsModel::createDiffTree(PDBGGUISTATSNODE pTree1, PDBGGUISTATSNODE pTree2)
{
/** @todo */
return NULL;
}
#endif
PDBGGUISTATSNODE
VBoxDbgStatsModel::updateCallbackHandleOutOfOrder(const char *pszName)
{
/*
* We might be inserting a new node between pPrev and pNode
* or we might be removing one or more nodes. Either case is
* handled in the same rough way.
*
* Might consider optimizing insertion at some later point since this
* is a normal occurrence (dynamic statistics in PATM, IOM, MM, ++).
*/
Assert(pszName[0] == '/');
Assert(m_szUpdateParent[m_cchUpdateParent - 1] == '/');
/*
* Start with the current parent node and look for a common ancestor
* hoping that this is faster than going from the root (saves lookup).
*/
PDBGGUISTATSNODE pNode = m_pUpdateParent->papChildren[m_iUpdateChild];
PDBGGUISTATSNODE const pPrev = prevDataNode(pNode);
pNode = pNode->pParent;
while (pNode != m_pRoot)
{
if (!strncmp(pszName, m_szUpdateParent, m_cchUpdateParent))
break;
Assert(m_cchUpdateParent > pNode->cchName);
m_cchUpdateParent -= pNode->cchName + 1;
m_szUpdateParent[m_cchUpdateParent] = '\0';
pNode = pNode->pParent;
}
Assert(m_szUpdateParent[m_cchUpdateParent - 1] == '/');
/*
* Descend until we've found/created the node pszName indicates,
* modifying m_szUpdateParent as we go along.
*/
while (pszName[m_cchUpdateParent - 1] == '/')
{
/* Find the end of this component. */
const char *const pszSubName = &pszName[m_cchUpdateParent];
const char *pszEnd = strchr(pszSubName, '/');
if (!pszEnd)
pszEnd = strchr(pszSubName, '\0');
size_t cchSubName = pszEnd - pszSubName;
/* Add the name to the path. */
memcpy(&m_szUpdateParent[m_cchUpdateParent], pszSubName, cchSubName);
m_cchUpdateParent += cchSubName;
m_szUpdateParent[m_cchUpdateParent++] = '/';
m_szUpdateParent[m_cchUpdateParent] = '\0';
Assert(m_cchUpdateParent < sizeof(m_szUpdateParent));
if (!pNode->cChildren)
{
/* first child */
pNode = createAndInsert(pNode, pszSubName, cchSubName, 0);
AssertReturn(pNode, NULL);
}
else
{
/* binary search. */
int32_t iStart = 0;
int32_t iLast = pNode->cChildren - 1;
for (;;)
{
int32_t i = iStart + (iLast + 1 - iStart) / 2;
int iDiff;
size_t const cchCompare = RT_MIN(pNode->papChildren[i]->cchName, cchSubName);
iDiff = memcmp(pszSubName, pNode->papChildren[i]->pszName, cchCompare);
if (!iDiff)
iDiff = cchSubName == cchCompare ? 0 : cchSubName > cchCompare ? 1 : -1;
if (iDiff > 0)
{
iStart = i + 1;
if (iStart > iLast)
{
pNode = createAndInsert(pNode, pszSubName, cchSubName, iStart);
AssertReturn(pNode, NULL);
break;
}
}
else if (iDiff < 0)
{
iLast = i - 1;
if (iLast < iStart)
{
pNode = createAndInsert(pNode, pszSubName, cchSubName, i);
AssertReturn(pNode, NULL);
break;
}
}
else
{
pNode = pNode->papChildren[i];
break;
}
}
}
}
Assert( !memcmp(pszName, m_szUpdateParent, m_cchUpdateParent - 2)
&& pszName[m_cchUpdateParent - 1] == '\0');
/*
* Remove all the nodes between pNode and pPrev but keep all
* of pNode's ancestors (or it'll get orphaned).
*/
PDBGGUISTATSNODE pCur = prevNode(pNode);
while (pCur != pPrev)
{
PDBGGUISTATSNODE pAdv = prevNode(pCur); Assert(pAdv || !pPrev);
if (!isNodeAncestorOf(pCur, pNode))
{
Assert(pCur != m_pRoot);
removeAndDestroy(pCur);
}
pCur = pAdv;
}
/*
* Remove the data from all ancestors of pNode that it doesn't
* share them pPrev.
*/
if (pPrev)
{
pCur = pNode->pParent;
while (!isNodeAncestorOf(pCur, pPrev))
{
resetNode(pNode);
pCur = pCur->pParent;
}
}
/*
* Finally, adjust the globals (szUpdateParent is one level too deep).
*/
Assert(m_cchUpdateParent > pNode->cchName + 1);
m_cchUpdateParent -= pNode->cchName + 1;
m_szUpdateParent[m_cchUpdateParent] = '\0';
m_pUpdateParent = pNode->pParent;
m_iUpdateChild = pNode->iSelf;
return pNode;
}
PDBGGUISTATSNODE
VBoxDbgStatsModel::updateCallbackHandleTail(const char *pszName)
{
/*
* Insert it at the end of the tree.
*
* Do the same as we're doing down in createNewTreeCallback, walk from the
* root and create whatever we need.
*/
AssertReturn(*pszName == '/' && pszName[1] != '/', NULL);
PDBGGUISTATSNODE pNode = m_pRoot;
const char *pszCur = pszName + 1;
while (*pszCur)
{
/* Find the end of this component. */
const char *pszNext = strchr(pszCur, '/');
if (!pszNext)
pszNext = strchr(pszCur, '\0');
size_t cchCur = pszNext - pszCur;
/* Create it if it doesn't exist (it will be last if it exists). */
if ( !pNode->cChildren
|| strncmp(pNode->papChildren[pNode->cChildren - 1]->pszName, pszCur, cchCur)
|| pNode->papChildren[pNode->cChildren - 1]->pszName[cchCur])
{
pNode = createAndInsert(pNode, pszCur, pszNext - pszCur, pNode->cChildren);
AssertReturn(pNode, NULL);
}
else
pNode = pNode->papChildren[pNode->cChildren - 1];
/* Advance */
pszCur = *pszNext ? pszNext + 1 : pszNext;
}
return pNode;
}
void
VBoxDbgStatsModel::updateCallbackAdvance(PDBGGUISTATSNODE pNode)
{
/*
* Advance to the next node with data.
*
* ASSUMES a leaf *must* have data and again we're ASSUMING the sorting
* on slash separated sub-strings.
*/
if (m_iUpdateChild != UINT32_MAX)
{
#ifdef VBOX_STRICT
PDBGGUISTATSNODE const pCorrectNext = nextDataNode(pNode);
#endif
PDBGGUISTATSNODE pParent = pNode->pParent;
if (pNode->cChildren)
{
/* descend to the first child. */
Assert(m_cchUpdateParent + pNode->cchName + 2 < sizeof(m_szUpdateParent));
memcpy(&m_szUpdateParent[m_cchUpdateParent], pNode->pszName, pNode->cchName);
m_cchUpdateParent += pNode->cchName;
m_szUpdateParent[m_cchUpdateParent++] = '/';
m_szUpdateParent[m_cchUpdateParent] = '\0';
pNode = pNode->papChildren[0];
}
else if (pNode->iSelf + 1 < pParent->cChildren)
{
/* next sibling or one if its descendants. */
Assert(m_pUpdateParent == pParent);
pNode = pParent->papChildren[pNode->iSelf + 1];
}
else
{
/* move up and down- / on-wards */
for (;;)
{
/* ascend */
pNode = pParent;
pParent = pParent->pParent;
if (!pParent)
{
Assert(pNode == m_pRoot);
m_iUpdateChild = UINT32_MAX;
m_szUpdateParent[0] = '\0';
m_cchUpdateParent = 0;
m_pUpdateParent = NULL;
break;
}
Assert(m_cchUpdateParent > pNode->cchName + 1);
m_cchUpdateParent -= pNode->cchName + 1;
/* try advance */
if (pNode->iSelf + 1 < pParent->cChildren)
{
pNode = pParent->papChildren[pNode->iSelf + 1];
m_szUpdateParent[m_cchUpdateParent] = '\0';
break;
}
}
}
/* descend to a node containing data and finalize the globals. (ASSUMES leaf has data.) */
if (m_iUpdateChild != UINT32_MAX)
{
while ( pNode->enmType == STAMTYPE_INVALID
&& pNode->cChildren > 0)
{
Assert(pNode->enmState == kDbgGuiStatsNodeState_kVisible);
Assert(m_cchUpdateParent + pNode->cchName + 2 < sizeof(m_szUpdateParent));
memcpy(&m_szUpdateParent[m_cchUpdateParent], pNode->pszName, pNode->cchName);
m_cchUpdateParent += pNode->cchName;
m_szUpdateParent[m_cchUpdateParent++] = '/';
m_szUpdateParent[m_cchUpdateParent] = '\0';
pNode = pNode->papChildren[0];
}
Assert(pNode->enmType != STAMTYPE_INVALID);
m_iUpdateChild = pNode->iSelf;
m_pUpdateParent = pNode->pParent;
Assert(pNode == pCorrectNext);
}
}
/* else: we're at the end */
}
/*static*/ DECLCALLBACK(int)
VBoxDbgStatsModel::updateCallback(const char *pszName, STAMTYPE enmType, void *pvSample, STAMUNIT enmUnit,
STAMVISIBILITY enmVisibility, const char *pszDesc, void *pvUser)
{
VBoxDbgStatsModelVM *pThis = (VBoxDbgStatsModelVM *)pvUser;
Log3(("updateCallback: %s\n", pszName));
/*
* Skip the ones which shouldn't be visible in the GUI.
*/
if (enmVisibility == STAMVISIBILITY_NOT_GUI)
return 0;
/*
* The default assumption is that nothing has changed.
* For now we'll reset the model when ever something changes.
*/
PDBGGUISTATSNODE pNode;
if (pThis->m_iUpdateChild != UINT32_MAX)
{
pNode = pThis->m_pUpdateParent->papChildren[pThis->m_iUpdateChild];
if ( !strncmp(pszName, pThis->m_szUpdateParent, pThis->m_cchUpdateParent)
&& !strcmp(pszName + pThis->m_cchUpdateParent, pNode->pszName))
/* got it! */;
else
{
/* insert/remove */
pNode = pThis->updateCallbackHandleOutOfOrder(pszName);
if (!pNode)
return VERR_NO_MEMORY;
}
}
else
{
/* append */
pNode = pThis->updateCallbackHandleTail(pszName);
if (!pNode)
return VERR_NO_MEMORY;
}
/*
* Perform the update and advance to the next one.
*/
updateNode(pNode, enmType, pvSample, enmUnit, pszDesc);
pThis->updateCallbackAdvance(pNode);
return VINF_SUCCESS;
}
bool
VBoxDbgStatsModel::updatePrepare(void)
{
/*
* Find the first child with data and set it up as the 'next'
* node to be updated.
*/
Assert(m_pRoot);
Assert(m_pRoot->enmType == STAMTYPE_INVALID);
PDBGGUISTATSNODE pFirst = nextDataNode(m_pRoot);
if (pFirst)
{
m_iUpdateChild = pFirst->iSelf;
m_pUpdateParent = pFirst->pParent; Assert(m_pUpdateParent);
m_cchUpdateParent = getNodePath(m_pUpdateParent, m_szUpdateParent, sizeof(m_szUpdateParent) - 1);
AssertReturn(m_cchUpdateParent >= 1, false);
m_szUpdateParent[m_cchUpdateParent++] = '/';
m_szUpdateParent[m_cchUpdateParent] = '\0';
}
else
{
m_iUpdateChild = UINT32_MAX;
m_pUpdateParent = NULL;
m_szUpdateParent[0] = '\0';
m_cchUpdateParent = 0;
}
/*
* Set the flag and signal possible layout change.
*/
m_fUpdateInsertRemove = false;
/* emit layoutAboutToBeChanged(); - debug this, it gets stuck... */
return true;
}
bool
VBoxDbgStatsModel::updateDone(bool a_fSuccess)
{
/*
* Remove any nodes following the last in the update (unless the update failed).
*/
if ( a_fSuccess
&& m_iUpdateChild != UINT32_MAX)
{
PDBGGUISTATSNODE const pLast = prevDataNode(m_pUpdateParent->papChildren[m_iUpdateChild]);
if (!pLast)
{
/* nuking the whole tree. */
setRootNode(createRootNode());
m_fUpdateInsertRemove = true;
}
else
{
PDBGGUISTATSNODE pNode;
while ((pNode = nextNode(pLast)))
{
Assert(pNode != m_pRoot);
removeAndDestroy(pNode);
}
}
}
/*
* We're done making layout changes (if I understood it correctly), so,
* signal this and then see what to do next. If we did too many removals
* we'll just reset the whole shebang.
*/
if (m_fUpdateInsertRemove)
{
/* emit layoutChanged(); - hrmpf, doesn't work reliably... */
reset();
}
else
{
/*
* Send dataChanged events.
*
* We do this here instead of from the updateCallback because it reduces
* the clutter in that method and allow us to emit bulk signals in an
* easier way because we can traverse the tree in a different fashion.
*/
DBGGUISTATSSTACK Stack;
Stack.a[0].pNode = m_pRoot;
Stack.a[0].iChild = -1;
Stack.iTop = 0;
while (Stack.iTop >= 0)
{
/* get top element */
PDBGGUISTATSNODE pNode = Stack.a[Stack.iTop].pNode;
uint32_t iChild = ++Stack.a[Stack.iTop].iChild;
if (iChild < pNode->cChildren)
{
/* push */
Stack.iTop++;
Assert(Stack.iTop < (int32_t)RT_ELEMENTS(Stack.a));
Stack.a[Stack.iTop].pNode = pNode->papChildren[iChild];
Stack.a[Stack.iTop].iChild = 0;
}
else
{
/* pop */
Stack.iTop--;
/* do the actual work. */
iChild = 0;
while (iChild < pNode->cChildren)
{
/* skip to the first needing updating. */
while ( iChild < pNode->cChildren
&& pNode->papChildren[iChild]->enmState != kDbgGuiStatsNodeState_kRefresh)
iChild++;
if (iChild >= pNode->cChildren)
break;
QModelIndex TopLeft = createIndex(iChild, 0, pNode->papChildren[iChild]);
pNode->papChildren[iChild]->enmState = kDbgGuiStatsNodeState_kVisible;
/* any subsequent nodes that also needs refreshing? */
if ( ++iChild < pNode->cChildren
&& pNode->papChildren[iChild]->enmState == kDbgGuiStatsNodeState_kRefresh)
{
do pNode->papChildren[iChild]->enmState = kDbgGuiStatsNodeState_kVisible;
while ( ++iChild < pNode->cChildren
&& pNode->papChildren[iChild]->enmState == kDbgGuiStatsNodeState_kRefresh);
QModelIndex BottomRight = createIndex(iChild - 1, DBGGUI_STATS_COLUMNS - 1, pNode->papChildren[iChild - 1]);
/* emit the refresh signal */
emit dataChanged(TopLeft, BottomRight);
}
else
{
/* emit the refresh signal */
emit dataChanged(TopLeft, TopLeft);
}
}
}
}
/* emit layoutChanged(); - hrmpf, doesn't work reliably... */
}
return m_fUpdateInsertRemove;
}
bool
VBoxDbgStatsModel::updateStatsByPattern(const QString &a_rPatStr)
{
/* stub */
NOREF(a_rPatStr);
return false;
}
void
VBoxDbgStatsModel::updateStatsByIndex(QModelIndex const &a_rIndex)
{
/** @todo implement this based on updateStatsByPattern. */
NOREF(a_rIndex);
}
void
VBoxDbgStatsModel::resetStatsByPattern(QString const &a_rPatStr)
{
/* stub */
NOREF(a_rPatStr);
}
void
VBoxDbgStatsModel::resetStatsByIndex(QModelIndex const &a_rIndex, bool fSubTree /*= true*/)
{
PCDBGGUISTATSNODE pNode = nodeFromIndex(a_rIndex);
if (pNode == m_pRoot || !a_rIndex.isValid())
{
if (fSubTree)
{
/* everything from the root down. */
resetStatsByPattern(QString());
}
}
else if (pNode)
{
/* the node pattern. */
char szPat[1024+1024+4];
ssize_t cch = getNodePath(pNode, szPat, 1024);
AssertReturnVoid(cch >= 0);
/* the sub-tree pattern. */
if (fSubTree && pNode->cChildren)
{
char *psz = &szPat[cch];
*psz++ = '|';
memcpy(psz, szPat, cch);
psz += cch;
*psz++ = '/';
*psz++ = '*';
*psz++ = '\0';
}
resetStatsByPattern(szPat);
}
}
QModelIndex
VBoxDbgStatsModel::getRootIndex(void) const
{
if (!m_pRoot)
return QModelIndex();
return createIndex(0, 0, m_pRoot);
}
void
VBoxDbgStatsModel::setRootNode(PDBGGUISTATSNODE a_pRoot)
{
PDBGGUISTATSNODE pOldTree = m_pRoot;
m_pRoot = a_pRoot;
destroyTree(pOldTree);
reset();
}
Qt::ItemFlags
VBoxDbgStatsModel::flags(const QModelIndex &a_rIndex) const
{
Qt::ItemFlags fFlags = QAbstractItemModel::flags(a_rIndex);
return fFlags;
}
int
VBoxDbgStatsModel::columnCount(const QModelIndex &a_rParent) const
{
NOREF(a_rParent);
return DBGGUI_STATS_COLUMNS;
}
int
VBoxDbgStatsModel::rowCount(const QModelIndex &a_rParent) const
{
PDBGGUISTATSNODE pParent = nodeFromIndex(a_rParent);
return pParent ? pParent->cChildren : 1 /* root */;
}
bool
VBoxDbgStatsModel::hasChildren(const QModelIndex &a_rParent) const
{
PDBGGUISTATSNODE pParent = nodeFromIndex(a_rParent);
return pParent ? pParent->cChildren > 0 : true /* root */;
}
QModelIndex
VBoxDbgStatsModel::index(int iRow, int iColumn, const QModelIndex &a_rParent) const
{
PDBGGUISTATSNODE pParent = nodeFromIndex(a_rParent);
if (!pParent)
{
if ( a_rParent.isValid()
|| iRow
|| (unsigned)iColumn < DBGGUI_STATS_COLUMNS)
{
Assert(!a_rParent.isValid());
Assert(!iRow);
Assert((unsigned)iColumn < DBGGUI_STATS_COLUMNS);
return QModelIndex();
}
/* root */
return createIndex(0, iColumn, m_pRoot);
}
if ((unsigned)iRow >= pParent->cChildren)
{
Log(("index: iRow=%d >= cChildren=%u (iColumn=%d)\n", iRow, (unsigned)pParent->cChildren, iColumn));
return QModelIndex(); /* bug? */
}
if ((unsigned)iColumn >= DBGGUI_STATS_COLUMNS)
{
Log(("index: iColumn=%d (iRow=%d)\n", iColumn, iRow));
return QModelIndex(); /* bug? */
}
PDBGGUISTATSNODE pChild = pParent->papChildren[iRow];
return createIndex(iRow, iColumn, pChild);
}
QModelIndex
VBoxDbgStatsModel::parent(const QModelIndex &a_rChild) const
{
PDBGGUISTATSNODE pChild = nodeFromIndex(a_rChild);
if (!pChild)
{
Log(("parent: invalid child\n"));
return QModelIndex(); /* bug */
}
PDBGGUISTATSNODE pParent = pChild->pParent;
if (!pParent)
return QModelIndex(); /* ultimate root */
return createIndex(pParent->iSelf, 0, pParent);
}
QVariant
VBoxDbgStatsModel::headerData(int a_iSection, Qt::Orientation a_eOrientation, int a_eRole) const
{
if ( a_eOrientation == Qt::Horizontal
&& a_eRole == Qt::DisplayRole)
switch (a_iSection)
{
case 0: return tr("Name");
case 1: return tr("Unit");
case 2: return tr("Value/Times");
case 3: return tr("Min");
case 4: return tr("Average");
case 5: return tr("Max");
case 6: return tr("Total");
case 7: return tr("dInt");
case 8: return tr("Description");
default:
AssertCompile(DBGGUI_STATS_COLUMNS == 9);
return QVariant(); /* bug */
}
else if ( a_eOrientation == Qt::Horizontal
&& a_eRole == Qt::TextAlignmentRole)
switch (a_iSection)
{
case 0:
case 1:
return QVariant();
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
return (int)(Qt::AlignRight | Qt::AlignVCenter);
case 8:
return QVariant();
default:
AssertCompile(DBGGUI_STATS_COLUMNS == 9);
return QVariant(); /* bug */
}
return QVariant();
}
/*static*/ QString
VBoxDbgStatsModel::strUnit(PCDBGGUISTATSNODE pNode)
{
if (pNode->enmUnit == STAMUNIT_INVALID)
return "";
return STAMR3GetUnit(pNode->enmUnit);
}
/*static*/ QString
VBoxDbgStatsModel::strValueTimes(PCDBGGUISTATSNODE pNode)
{
char sz[128];
switch (pNode->enmType)
{
case STAMTYPE_COUNTER:
return formatNumber(sz, pNode->Data.Counter.c);
case STAMTYPE_PROFILE:
case STAMTYPE_PROFILE_ADV:
if (!pNode->Data.Profile.cPeriods)
return "0";
return formatNumber(sz, pNode->Data.Profile.cPeriods);
case STAMTYPE_RATIO_U32:
case STAMTYPE_RATIO_U32_RESET:
{
formatNumber(sz, pNode->Data.RatioU32.u32A);
char *psz = strchr(sz, '\0');
*psz++ = ':';
formatNumber(psz, pNode->Data.RatioU32.u32B);
return psz;
}
case STAMTYPE_CALLBACK:
return *pNode->Data.pStr;
case STAMTYPE_U8:
case STAMTYPE_U8_RESET:
return formatNumber(sz, pNode->Data.u8);
case STAMTYPE_X8:
case STAMTYPE_X8_RESET:
return formatHexNumber(sz, pNode->Data.u8, 2);
case STAMTYPE_U16:
case STAMTYPE_U16_RESET:
return formatNumber(sz, pNode->Data.u16);
case STAMTYPE_X16:
case STAMTYPE_X16_RESET:
return formatHexNumber(sz, pNode->Data.u16, 4);
case STAMTYPE_U32:
case STAMTYPE_U32_RESET:
return formatNumber(sz, pNode->Data.u32);
case STAMTYPE_X32:
case STAMTYPE_X32_RESET:
return formatHexNumber(sz, pNode->Data.u32, 8);
case STAMTYPE_U64:
case STAMTYPE_U64_RESET:
return formatNumber(sz, pNode->Data.u64);
case STAMTYPE_X64:
case STAMTYPE_X64_RESET:
return formatHexNumber(sz, pNode->Data.u64, 16);
case STAMTYPE_BOOL:
case STAMTYPE_BOOL_RESET:
return pNode->Data.f ? "true" : "false";
default:
AssertMsgFailed(("%d\n", pNode->enmType));
case STAMTYPE_INVALID:
return "";
}
}
/*static*/ QString
VBoxDbgStatsModel::strMinValue(PCDBGGUISTATSNODE pNode)
{
char sz[128];
switch (pNode->enmType)
{
case STAMTYPE_PROFILE:
case STAMTYPE_PROFILE_ADV:
if (!pNode->Data.Profile.cPeriods)
return "0";
return formatNumber(sz, pNode->Data.Profile.cTicksMin);
default:
return "";
}
}
/*static*/ QString
VBoxDbgStatsModel::strAvgValue(PCDBGGUISTATSNODE pNode)
{
char sz[128];
switch (pNode->enmType)
{
case STAMTYPE_PROFILE:
case STAMTYPE_PROFILE_ADV:
if (!pNode->Data.Profile.cPeriods)
return "0";
return formatNumber(sz, pNode->Data.Profile.cTicks / pNode->Data.Profile.cPeriods);
default:
return "";
}
}
/*static*/ QString
VBoxDbgStatsModel::strMaxValue(PCDBGGUISTATSNODE pNode)
{
char sz[128];
switch (pNode->enmType)
{
case STAMTYPE_PROFILE:
case STAMTYPE_PROFILE_ADV:
if (!pNode->Data.Profile.cPeriods)
return "0";
return formatNumber(sz, pNode->Data.Profile.cTicksMax);
default:
return "";
}
}
/*static*/ QString
VBoxDbgStatsModel::strTotalValue(PCDBGGUISTATSNODE pNode)
{
char sz[128];
switch (pNode->enmType)
{
case STAMTYPE_PROFILE:
case STAMTYPE_PROFILE_ADV:
if (!pNode->Data.Profile.cPeriods)
return "0";
return formatNumber(sz, pNode->Data.Profile.cTicks);
default:
return "";
}
}
/*static*/ QString
VBoxDbgStatsModel::strDeltaValue(PCDBGGUISTATSNODE pNode)
{
char sz[128];
switch (pNode->enmType)
{
case STAMTYPE_PROFILE:
case STAMTYPE_PROFILE_ADV:
if (!pNode->Data.Profile.cPeriods)
return "0";
/* fall thru */
case STAMTYPE_COUNTER:
case STAMTYPE_RATIO_U32:
case STAMTYPE_RATIO_U32_RESET:
case STAMTYPE_U8:
case STAMTYPE_U8_RESET:
case STAMTYPE_X8:
case STAMTYPE_X8_RESET:
case STAMTYPE_U16:
case STAMTYPE_U16_RESET:
case STAMTYPE_X16:
case STAMTYPE_X16_RESET:
case STAMTYPE_U32:
case STAMTYPE_U32_RESET:
case STAMTYPE_X32:
case STAMTYPE_X32_RESET:
case STAMTYPE_U64:
case STAMTYPE_U64_RESET:
case STAMTYPE_X64:
case STAMTYPE_X64_RESET:
case STAMTYPE_BOOL:
case STAMTYPE_BOOL_RESET:
return formatNumberSigned(sz, pNode->i64Delta);
default:
return "";
}
}
QVariant
VBoxDbgStatsModel::data(const QModelIndex &a_rIndex, int a_eRole) const
{
unsigned iCol = a_rIndex.column();
if (iCol >= DBGGUI_STATS_COLUMNS)
return QVariant();
if (a_eRole == Qt::DisplayRole)
{
PDBGGUISTATSNODE pNode = nodeFromIndex(a_rIndex);
if (!pNode)
return QVariant();
switch (iCol)
{
case 0:
return QString(pNode->pszName);
case 1:
return strUnit(pNode);
case 2:
return strValueTimes(pNode);
case 3:
return strMinValue(pNode);
case 4:
return strAvgValue(pNode);
case 5:
return strMaxValue(pNode);
case 6:
return strTotalValue(pNode);
case 7:
return strDeltaValue(pNode);
case 8:
return pNode->pDescStr ? QString(*pNode->pDescStr) : QString("");
default:
AssertCompile(DBGGUI_STATS_COLUMNS == 9);
return QVariant();
}
}
else if (a_eRole == Qt::TextAlignmentRole)
switch (iCol)
{
case 0:
case 1:
return QVariant();
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
return (int)(Qt::AlignRight | Qt::AlignVCenter);
case 8:
return QVariant();
default:
AssertCompile(DBGGUI_STATS_COLUMNS == 9);
return QVariant(); /* bug */
}
return QVariant();
}
/*static*/ void
VBoxDbgStatsModel::stringifyNodeNoRecursion(PDBGGUISTATSNODE a_pNode, QString &a_rString)
{
/*
* Get the path, padding it to 32-chars and add it to the string.
*/
char szBuf[1024];
ssize_t off = getNodePath(a_pNode, szBuf, sizeof(szBuf) - 2);
AssertReturnVoid(off >= 0);
if (off < 32)
{
memset(&szBuf[off], ' ', 32 - off);
szBuf[32] = '\0';
off = 32;
}
szBuf[off++] = ' ';
szBuf[off] = '\0';
a_rString += szBuf;
/*
* The following is derived from stamR3PrintOne, except
* we print to szBuf, do no visibility checks and can skip
* the path bit.
*/
switch (a_pNode->enmType)
{
case STAMTYPE_COUNTER:
RTStrPrintf(szBuf, sizeof(szBuf), "%8llu %s", a_pNode->Data.Counter.c, STAMR3GetUnit(a_pNode->enmUnit));
break;
case STAMTYPE_PROFILE:
case STAMTYPE_PROFILE_ADV:
{
uint64_t u64 = a_pNode->Data.Profile.cPeriods ? a_pNode->Data.Profile.cPeriods : 1;
RTStrPrintf(szBuf, sizeof(szBuf),
"%8llu %s (%12llu ticks, %7llu times, max %9llu, min %7lld)",
a_pNode->Data.Profile.cTicks / u64, STAMR3GetUnit(a_pNode->enmUnit),
a_pNode->Data.Profile.cTicks, a_pNode->Data.Profile.cPeriods, a_pNode->Data.Profile.cTicksMax, a_pNode->Data.Profile.cTicksMin);
break;
}
case STAMTYPE_RATIO_U32:
case STAMTYPE_RATIO_U32_RESET:
RTStrPrintf(szBuf, sizeof(szBuf),
"%8u:%-8u %s",
a_pNode->Data.RatioU32.u32A, a_pNode->Data.RatioU32.u32B, STAMR3GetUnit(a_pNode->enmUnit));
break;
case STAMTYPE_CALLBACK:
if (a_pNode->Data.pStr)
a_rString += *a_pNode->Data.pStr;
RTStrPrintf(szBuf, sizeof(szBuf), " %s", STAMR3GetUnit(a_pNode->enmUnit));
break;
case STAMTYPE_U8:
case STAMTYPE_U8_RESET:
RTStrPrintf(szBuf, sizeof(szBuf), "%8u %s", a_pNode->Data.u8, STAMR3GetUnit(a_pNode->enmUnit));
break;
case STAMTYPE_X8:
case STAMTYPE_X8_RESET:
RTStrPrintf(szBuf, sizeof(szBuf), "%8x %s", a_pNode->Data.u8, STAMR3GetUnit(a_pNode->enmUnit));
break;
case STAMTYPE_U16:
case STAMTYPE_U16_RESET:
RTStrPrintf(szBuf, sizeof(szBuf), "%8u %s", a_pNode->Data.u16, STAMR3GetUnit(a_pNode->enmUnit));
break;
case STAMTYPE_X16:
case STAMTYPE_X16_RESET:
RTStrPrintf(szBuf, sizeof(szBuf), "%8x %s", a_pNode->Data.u16, STAMR3GetUnit(a_pNode->enmUnit));
break;
case STAMTYPE_U32:
case STAMTYPE_U32_RESET:
RTStrPrintf(szBuf, sizeof(szBuf), "%8u %s", a_pNode->Data.u32, STAMR3GetUnit(a_pNode->enmUnit));
break;
case STAMTYPE_X32:
case STAMTYPE_X32_RESET:
RTStrPrintf(szBuf, sizeof(szBuf), "%8x %s", a_pNode->Data.u32, STAMR3GetUnit(a_pNode->enmUnit));
break;
case STAMTYPE_U64:
case STAMTYPE_U64_RESET:
RTStrPrintf(szBuf, sizeof(szBuf), "%8llu %s", a_pNode->Data.u64, STAMR3GetUnit(a_pNode->enmUnit));
break;
case STAMTYPE_X64:
case STAMTYPE_X64_RESET:
RTStrPrintf(szBuf, sizeof(szBuf), "%8llx %s", a_pNode->Data.u64, STAMR3GetUnit(a_pNode->enmUnit));
break;
case STAMTYPE_BOOL:
case STAMTYPE_BOOL_RESET:
RTStrPrintf(szBuf, sizeof(szBuf), "%s %s", a_pNode->Data.f ? "true " : "false ", STAMR3GetUnit(a_pNode->enmUnit));
break;
default:
AssertMsgFailed(("enmType=%d\n", a_pNode->enmType));
return;
}
a_rString += szBuf;
}
/*static*/ void
VBoxDbgStatsModel::stringifyNode(PDBGGUISTATSNODE a_pNode, QString &a_rString)
{
/* this node (if it has data) */
if (a_pNode->enmType != STAMTYPE_INVALID)
{
if (!a_rString.isEmpty())
a_rString += "\n";
stringifyNodeNoRecursion(a_pNode, a_rString);
}
/* the children */
uint32_t const cChildren = a_pNode->cChildren;
for (uint32_t i = 0; i < cChildren; i++)
stringifyNode(a_pNode->papChildren[i], a_rString);
}
void
VBoxDbgStatsModel::stringifyTree(QModelIndex &a_rRoot, QString &a_rString) const
{
PDBGGUISTATSNODE pRoot = a_rRoot.isValid() ? nodeFromIndex(a_rRoot) : m_pRoot;
if (pRoot)
stringifyNode(pRoot, a_rString);
}
void
VBoxDbgStatsModel::copyTreeToClipboard(QModelIndex &a_rRoot) const
{
QString String;
stringifyTree(a_rRoot, String);
QClipboard *pClipboard = QApplication::clipboard();
if (pClipboard)
pClipboard->setText(String, QClipboard::Clipboard);
}
/*static*/ void
VBoxDbgStatsModel::logNode(PDBGGUISTATSNODE a_pNode, bool a_fReleaseLog)
{
/* this node (if it has data) */
if (a_pNode->enmType != STAMTYPE_INVALID)
{
QString SelfStr;
stringifyNodeNoRecursion(a_pNode, SelfStr);
QByteArray SelfByteArray = SelfStr.toUtf8();
if (a_fReleaseLog)
RTLogRelPrintf("%s\n", SelfByteArray.constData());
else
RTLogPrintf("%s\n", SelfByteArray.constData());
}
/* the children */
uint32_t const cChildren = a_pNode->cChildren;
for (uint32_t i = 0; i < cChildren; i++)
logNode(a_pNode->papChildren[i], a_fReleaseLog);
}
void
VBoxDbgStatsModel::logTree(QModelIndex &a_rRoot, bool a_fReleaseLog) const
{
PDBGGUISTATSNODE pRoot = a_rRoot.isValid() ? nodeFromIndex(a_rRoot) : m_pRoot;
if (pRoot)
logNode(pRoot, a_fReleaseLog);
}
/*
*
* V B o x D b g S t a t s M o d e l V M
* V B o x D b g S t a t s M o d e l V M
* V B o x D b g S t a t s M o d e l V M
*
*
*/
VBoxDbgStatsModelVM::VBoxDbgStatsModelVM(VBoxDbgGui *a_pDbgGui, QString &a_rPatStr, QObject *a_pParent)
: VBoxDbgStatsModel(a_pParent), VBoxDbgBase(a_pDbgGui)
{
/*
* Create a model containing the STAM entries matching the pattern.
* (The original idea was to get everything and rely on some hide/visible
* flag that it turned out didn't exist.)
*/
PDBGGUISTATSNODE pTree = createNewTree(a_rPatStr);
setRootNode(pTree);
}
VBoxDbgStatsModelVM::~VBoxDbgStatsModelVM()
{
/* nothing to do here. */
}
bool
VBoxDbgStatsModelVM::updateStatsByPattern(const QString &a_rPatStr)
{
/** @todo the way we update this stuff is independent of the source (XML, file, STAM), our only
* ASSUMPTION is that the input is strictly ordered by (fully slashed) name. So, all this stuff
* should really move up into the parent class. */
bool fRc = updatePrepare();
if (fRc)
{
int rc = stamEnum(a_rPatStr, updateCallback, this);
fRc = updateDone(RT_SUCCESS(rc));
}
return fRc;
}
void
VBoxDbgStatsModelVM::resetStatsByPattern(QString const &a_rPatStr)
{
stamReset(a_rPatStr);
}
/*static*/ DECLCALLBACK(int)
VBoxDbgStatsModelVM::createNewTreeCallback(const char *pszName, STAMTYPE enmType, void *pvSample, STAMUNIT enmUnit,
STAMVISIBILITY enmVisibility, const char *pszDesc, void *pvUser)
{
PDBGGUISTATSNODE pRoot = (PDBGGUISTATSNODE)pvUser;
Log3(("createNewTreeCallback: %s\n", pszName));
/*
* Skip the ones which shouldn't be visible in the GUI.
*/
if (enmVisibility == STAMVISIBILITY_NOT_GUI)
return 0;
/*
* Perform a mkdir -p like operation till we've walked / created the entire path down
* to the node specfied node. Remember the last node as that will be the one we will
* stuff the data into.
*/
AssertReturn(*pszName == '/' && pszName[1] != '/', VERR_INTERNAL_ERROR);
PDBGGUISTATSNODE pNode = pRoot;
const char *pszCur = pszName + 1;
while (*pszCur)
{
/* find the end of this component. */
const char *pszNext = strchr(pszCur, '/');
if (!pszNext)
pszNext = strchr(pszCur, '\0');
size_t cchCur = pszNext - pszCur;
/* Create it if it doesn't exist (it will be last if it exists). */
if ( !pNode->cChildren
|| strncmp(pNode->papChildren[pNode->cChildren - 1]->pszName, pszCur, cchCur)
|| pNode->papChildren[pNode->cChildren - 1]->pszName[cchCur])
{
pNode = createAndInsertNode(pNode, pszCur, pszNext - pszCur, UINT32_MAX);
if (!pNode)
return VERR_NO_MEMORY;
}
else
pNode = pNode->papChildren[pNode->cChildren - 1];
/* Advance */
pszCur = *pszNext ? pszNext + 1 : pszNext;
}
/*
* Save the data.
*/
return initNode(pNode, enmType, pvSample, enmUnit, pszDesc);
}
PDBGGUISTATSNODE
VBoxDbgStatsModelVM::createNewTree(QString &a_rPatStr)
{
PDBGGUISTATSNODE pRoot = createRootNode();
if (pRoot)
{
int rc = stamEnum(a_rPatStr, createNewTreeCallback, pRoot);
if (RT_SUCCESS(rc))
return pRoot;
/* failed, cleanup. */
destroyTree(pRoot);
}
return NULL;
}
/*
*
* V B o x D b g S t a t s V i e w
* V B o x D b g S t a t s V i e w
* V B o x D b g S t a t s V i e w
*
*
*/
VBoxDbgStatsView::VBoxDbgStatsView(VBoxDbgGui *a_pDbgGui, VBoxDbgStatsModel *a_pModel, VBoxDbgStats *a_pParent/* = NULL*/)
: QTreeView(a_pParent), VBoxDbgBase(a_pDbgGui), m_pModel(a_pModel), m_PatStr(), m_pParent(a_pParent),
m_pLeafMenu(NULL), m_pBranchMenu(NULL), m_pViewMenu(NULL), m_pCurMenu(NULL), m_CurIndex()
{
/*
* Set the model and view defaults.
*/
setRootIsDecorated(true);
setModel(m_pModel);
QModelIndex RootIdx = m_pModel->getRootIndex(); /* This should really be QModelIndex(), but Qt on darwin does wrong things then. */
setRootIndex(RootIdx);
setItemsExpandable(true);
setAlternatingRowColors(true);
setSelectionBehavior(SelectRows);
setSelectionMode(SingleSelection);
/// @todo sorting setSortingEnabled(true);
/*
* Create and setup the actions.
*/
m_pExpandAct = new QAction("Expand Tree", this);
m_pCollapseAct = new QAction("Collapse Tree", this);
m_pRefreshAct = new QAction("&Refresh", this);
m_pResetAct = new QAction("Rese&t", this);
m_pCopyAct = new QAction("&Copy", this);
m_pToLogAct = new QAction("To &Log", this);
m_pToRelLogAct = new QAction("T&o Release Log", this);
m_pAdjColumns = new QAction("&Adjust Columns", this);
m_pCopyAct->setShortcut(QKeySequence::Copy);
m_pExpandAct->setShortcut(QKeySequence("Ctrl+E"));
m_pCollapseAct->setShortcut(QKeySequence("Ctrl+D"));
m_pRefreshAct->setShortcut(QKeySequence("Ctrl+R"));
m_pResetAct->setShortcut(QKeySequence("Alt+R"));
m_pToLogAct->setShortcut(QKeySequence("Ctrl+Z"));
m_pToRelLogAct->setShortcut(QKeySequence("Alt+Z"));
m_pAdjColumns->setShortcut(QKeySequence("Ctrl+A"));
addAction(m_pCopyAct);
addAction(m_pExpandAct);
addAction(m_pCollapseAct);
addAction(m_pRefreshAct);
addAction(m_pResetAct);
addAction(m_pToLogAct);
addAction(m_pToRelLogAct);
addAction(m_pAdjColumns);
connect(m_pExpandAct, SIGNAL(triggered(bool)), this, SLOT(actExpand()));
connect(m_pCollapseAct, SIGNAL(triggered(bool)), this, SLOT(actCollapse()));
connect(m_pRefreshAct, SIGNAL(triggered(bool)), this, SLOT(actRefresh()));
connect(m_pResetAct, SIGNAL(triggered(bool)), this, SLOT(actReset()));
connect(m_pCopyAct, SIGNAL(triggered(bool)), this, SLOT(actCopy()));
connect(m_pToLogAct, SIGNAL(triggered(bool)), this, SLOT(actToLog()));
connect(m_pToRelLogAct, SIGNAL(triggered(bool)), this, SLOT(actToRelLog()));
connect(m_pAdjColumns, SIGNAL(triggered(bool)), this, SLOT(actAdjColumns()));
/*
* Create the menus and populate them.
*/
setContextMenuPolicy(Qt::DefaultContextMenu);
m_pLeafMenu = new QMenu();
m_pLeafMenu->addAction(m_pCopyAct);
m_pLeafMenu->addAction(m_pRefreshAct);
m_pLeafMenu->addAction(m_pResetAct);
m_pLeafMenu->addAction(m_pToLogAct);
m_pLeafMenu->addAction(m_pToRelLogAct);
m_pBranchMenu = new QMenu(this);
m_pBranchMenu->addAction(m_pCopyAct);
m_pBranchMenu->addAction(m_pRefreshAct);
m_pBranchMenu->addAction(m_pResetAct);
m_pBranchMenu->addAction(m_pToLogAct);
m_pBranchMenu->addAction(m_pToRelLogAct);
m_pBranchMenu->addSeparator();
m_pBranchMenu->addAction(m_pExpandAct);
m_pBranchMenu->addAction(m_pCollapseAct);
m_pViewMenu = new QMenu();
m_pViewMenu->addAction(m_pCopyAct);
m_pViewMenu->addAction(m_pRefreshAct);
m_pViewMenu->addAction(m_pResetAct);
m_pViewMenu->addAction(m_pToLogAct);
m_pViewMenu->addAction(m_pToRelLogAct);
m_pViewMenu->addSeparator();
m_pViewMenu->addAction(m_pExpandAct);
m_pViewMenu->addAction(m_pCollapseAct);
m_pViewMenu->addSeparator();
m_pViewMenu->addAction(m_pAdjColumns);
/* the header menu */
QHeaderView *pHdrView = header();
pHdrView->setContextMenuPolicy(Qt::CustomContextMenu);
connect(pHdrView, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(headerContextMenuRequested(const QPoint &)));
}
VBoxDbgStatsView::~VBoxDbgStatsView()
{
m_pParent = NULL;
m_pCurMenu = NULL;
m_CurIndex = QModelIndex();
#define DELETE_IT(m) if (m) { delete m; m = NULL; } else do {} while (0)
DELETE_IT(m_pModel);
DELETE_IT(m_pLeafMenu);
DELETE_IT(m_pBranchMenu);
DELETE_IT(m_pViewMenu);
DELETE_IT(m_pExpandAct);
DELETE_IT(m_pCollapseAct);
DELETE_IT(m_pRefreshAct);
DELETE_IT(m_pResetAct);
DELETE_IT(m_pCopyAct);
DELETE_IT(m_pToLogAct);
DELETE_IT(m_pToRelLogAct);
DELETE_IT(m_pAdjColumns);
#undef DELETE_IT
}
void
VBoxDbgStatsView::updateStats(const QString &rPatStr)
{
m_PatStr = rPatStr;
if (m_pModel->updateStatsByPattern(rPatStr))
setRootIndex(m_pModel->getRootIndex()); /* hack */
}
void
VBoxDbgStatsView::resizeColumnsToContent()
{
for (int i = 0; i <= 8; i++)
resizeColumnToContents(i);
}
void
VBoxDbgStatsView::setSubTreeExpanded(QModelIndex const &a_rIndex, bool a_fExpanded)
{
int cRows = m_pModel->rowCount(a_rIndex);
for (int i = 0; i < cRows; i++)
setSubTreeExpanded(a_rIndex.child(i, 0), a_fExpanded);
setExpanded(a_rIndex, a_fExpanded);
}
void
VBoxDbgStatsView::contextMenuEvent(QContextMenuEvent *a_pEvt)
{
/*
* Get the selected item.
* If it's a mouse event select the item under the cursor (if any).
*/
QModelIndex Idx;
if (a_pEvt->reason() == QContextMenuEvent::Mouse)
{
Idx = indexAt(a_pEvt->pos());
if (Idx.isValid())
setCurrentIndex(Idx);
}
else
{
QModelIndexList SelIdx = selectedIndexes();
if (!SelIdx.isEmpty())
Idx = SelIdx.at(0);
}
/*
* Popup the corresponding menu.
*/
QMenu *pMenu;
if (!Idx.isValid())
pMenu = m_pViewMenu;
else if (m_pModel->hasChildren(Idx))
pMenu = m_pBranchMenu;
else
pMenu = m_pLeafMenu;
if (pMenu)
{
m_pRefreshAct->setEnabled(!Idx.isValid() || Idx == m_pModel->getRootIndex());
m_CurIndex = Idx;
m_pCurMenu = pMenu;
pMenu->exec(a_pEvt->globalPos());
m_pCurMenu = NULL;
m_CurIndex = QModelIndex();
if (m_pRefreshAct)
m_pRefreshAct->setEnabled(true);
}
a_pEvt->accept();
}
void
VBoxDbgStatsView::headerContextMenuRequested(const QPoint &a_rPos)
{
/*
* Show the view menu.
*/
if (m_pViewMenu)
{
m_pRefreshAct->setEnabled(true);
m_CurIndex = m_pModel->getRootIndex();
m_pCurMenu = m_pViewMenu;
m_pViewMenu->exec(header()->mapToGlobal(a_rPos));
m_pCurMenu = NULL;
m_CurIndex = QModelIndex();
if (m_pRefreshAct)
m_pRefreshAct->setEnabled(true);
}
}
void
VBoxDbgStatsView::actExpand()
{
QModelIndex Idx = m_pCurMenu ? m_CurIndex : currentIndex();
if (Idx.isValid())
setSubTreeExpanded(Idx, true /* a_fExpanded */);
}
void
VBoxDbgStatsView::actCollapse()
{
QModelIndex Idx = m_pCurMenu ? m_CurIndex : currentIndex();
if (Idx.isValid())
setSubTreeExpanded(Idx, false /* a_fExpanded */);
}
void
VBoxDbgStatsView::actRefresh()
{
QModelIndex Idx = m_pCurMenu ? m_CurIndex : currentIndex();
if (!Idx.isValid() || Idx == m_pModel->getRootIndex())
{
if (m_pModel->updateStatsByPattern(m_PatStr))
setRootIndex(m_pModel->getRootIndex()); /* hack */
}
else
m_pModel->updateStatsByIndex(Idx);
}
void
VBoxDbgStatsView::actReset()
{
QModelIndex Idx = m_pCurMenu ? m_CurIndex : currentIndex();
if (!Idx.isValid() || Idx == m_pModel->getRootIndex())
m_pModel->resetStatsByPattern(m_PatStr);
else
m_pModel->resetStatsByIndex(Idx);
}
void
VBoxDbgStatsView::actCopy()
{
QModelIndex Idx = m_pCurMenu ? m_CurIndex : currentIndex();
m_pModel->copyTreeToClipboard(Idx);
}
void
VBoxDbgStatsView::actToLog()
{
QModelIndex Idx = m_pCurMenu ? m_CurIndex : currentIndex();
m_pModel->logTree(Idx, false /* a_fReleaseLog */);
}
void
VBoxDbgStatsView::actToRelLog()
{
QModelIndex Idx = m_pCurMenu ? m_CurIndex : currentIndex();
m_pModel->logTree(Idx, true /* a_fReleaseLog */);
}
void
VBoxDbgStatsView::actAdjColumns()
{
resizeColumnsToContent();
}
/*
*
* V B o x D b g S t a t s
* V B o x D b g S t a t s
* V B o x D b g S t a t s
*
*
*/
VBoxDbgStats::VBoxDbgStats(VBoxDbgGui *a_pDbgGui, const char *pszPat/* = NULL*/, unsigned uRefreshRate/* = 0*/, QWidget *pParent/* = NULL*/)
: VBoxDbgBaseWindow(a_pDbgGui, pParent), m_PatStr(pszPat), m_pPatCB(NULL), m_uRefreshRate(0), m_pTimer(NULL), m_pView(NULL)
{
setWindowTitle("VBoxDbg - Statistics");
/*
* On top, a horizontal box with the pattern field, buttons and refresh interval.
*/
QHBoxLayout *pHLayout = new QHBoxLayout;
QLabel *pLabel = new QLabel(" Pattern ");
pHLayout->addWidget(pLabel);
pLabel->setMaximumSize(pLabel->sizeHint());
pLabel->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
m_pPatCB = new QComboBox();
m_pPatCB->setAutoCompletion(false);
pHLayout->addWidget(m_pPatCB);
if (!m_PatStr.isEmpty())
m_pPatCB->addItem(m_PatStr);
m_pPatCB->setDuplicatesEnabled(false);
m_pPatCB->setEditable(true);
connect(m_pPatCB, SIGNAL(activated(const QString &)), this, SLOT(apply(const QString &)));
QPushButton *pPB = new QPushButton("&All");
pHLayout->addWidget(pPB);
pPB->setMaximumSize(pPB->sizeHint());
connect(pPB, SIGNAL(clicked()), this, SLOT(applyAll()));
pLabel = new QLabel(" Interval ");
pHLayout->addWidget(pLabel);
pLabel->setMaximumSize(pLabel->sizeHint());
pLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
QSpinBox *pSB = new QSpinBox();
pHLayout->addWidget(pSB);
pSB->setMinimum(0);
pSB->setMaximum(60);
pSB->setSingleStep(1);
pSB->setValue(uRefreshRate);
pSB->setSuffix(" s");
pSB->setWrapping(false);
pSB->setButtonSymbols(QSpinBox::PlusMinus);
pSB->setMaximumSize(pSB->sizeHint());
connect(pSB, SIGNAL(valueChanged(int)), this, SLOT(setRefresh(int)));
/*
* Create the tree view and setup the layout.
*/
VBoxDbgStatsModelVM *pModel = new VBoxDbgStatsModelVM(a_pDbgGui, m_PatStr, NULL);
m_pView = new VBoxDbgStatsView(a_pDbgGui, pModel, this);
QWidget *pHBox = new QWidget;
pHBox->setLayout(pHLayout);
QVBoxLayout *pVLayout = new QVBoxLayout;
pVLayout->addWidget(pHBox);
pVLayout->addWidget(m_pView);
setLayout(pVLayout);
/*
* Resize the columns.
* Seems this has to be done with all nodes expanded.
*/
m_pView->expandAll();
m_pView->resizeColumnsToContent();
m_pView->collapseAll();
/*
* Create a refresh timer and start it.
*/
m_pTimer = new QTimer(this);
connect(m_pTimer, SIGNAL(timeout()), this, SLOT(refresh()));
setRefresh(uRefreshRate);
/*
* And some shortcuts.
*/
m_pFocusToPat = new QAction("", this);
m_pFocusToPat->setShortcut(QKeySequence("Ctrl+L"));
addAction(m_pFocusToPat);
connect(m_pFocusToPat, SIGNAL(triggered(bool)), this, SLOT(actFocusToPat()));
}
VBoxDbgStats::~VBoxDbgStats()
{
if (m_pTimer)
{
delete m_pTimer;
m_pTimer = NULL;
}
if (m_pPatCB)
{
delete m_pPatCB;
m_pPatCB = NULL;
}
if (m_pView)
{
delete m_pView;
m_pView = NULL;
}
}
void
VBoxDbgStats::closeEvent(QCloseEvent *a_pCloseEvt)
{
a_pCloseEvt->accept();
delete this;
}
void
VBoxDbgStats::apply(const QString &Str)
{
m_PatStr = Str;
refresh();
}
void
VBoxDbgStats::applyAll()
{
apply("");
}
void
VBoxDbgStats::refresh()
{
m_pView->updateStats(m_PatStr);
}
void
VBoxDbgStats::setRefresh(int iRefresh)
{
if ((unsigned)iRefresh != m_uRefreshRate)
{
if (!m_uRefreshRate || iRefresh)
m_pTimer->start(iRefresh * 1000);
else
m_pTimer->stop();
m_uRefreshRate = iRefresh;
}
}
void
VBoxDbgStats::actFocusToPat()
{
if (!m_pPatCB->hasFocus())
m_pPatCB->setFocus(Qt::ShortcutFocusReason);
}