diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
commit | f215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch) | |
tree | 6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/Debugger/VBoxDbgStatsQt.cpp | |
parent | Initial commit. (diff) | |
download | virtualbox-upstream/7.0.14-dfsg.tar.xz virtualbox-upstream/7.0.14-dfsg.zip |
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Debugger/VBoxDbgStatsQt.cpp')
-rw-r--r-- | src/VBox/Debugger/VBoxDbgStatsQt.cpp | 3329 |
1 files changed, 3329 insertions, 0 deletions
diff --git a/src/VBox/Debugger/VBoxDbgStatsQt.cpp b/src/VBox/Debugger/VBoxDbgStatsQt.cpp new file mode 100644 index 00000000..5c08b9e2 --- /dev/null +++ b/src/VBox/Debugger/VBoxDbgStatsQt.cpp @@ -0,0 +1,3329 @@ +/* $Id: VBoxDbgStatsQt.cpp $ */ +/** @file + * VBox Debugger GUI - Statistics. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DBGG +#include "VBoxDbgStatsQt.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 <iprt/errcore.h> +#include <VBox/log.h> +#include <iprt/string.h> +#include <iprt/mem.h> +#include <iprt/assert.h> + +#include "VBoxDbgGui.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 string. (not allocated) */ + const char *pszUnit; + /** 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; + /** Name string offset (if used). */ + uint16_t cchName; + } 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); + + /** + * Iterator callback function. + * @returns true to continue, false to stop. + */ + typedef bool FNITERATOR(PDBGGUISTATSNODE pNode, QModelIndex const &a_rIndex, const char *pszFullName, void *pvUser); + + /** + * Callback iterator. + * + * @param a_rPatStr The selection pattern. + * @param a_pfnCallback Callback function. + * @param a_pvUser Callback argument. + * @param a_fMatchChildren How to handle children of matching nodes: + * - @c true: continue with the children, + * - @c false: skip children. + */ + virtual void iterateStatsByPattern(QString const &a_rPatStr, FNITERATOR *a_pfnCallback, void *a_pvUser, + bool a_fMatchChildren = 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, const char *pszUnit, const char *pszDesc); + + /** + * Updates (or reinitializes if you like) a node. + */ + static void updateNode(PDBGGUISTATSNODE pNode, STAMTYPE enmType, void *pvSample, const char *pszUnit, 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. + * + * @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, + const char *pszUnit, 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); + + /** + * Calculates the full path of a node, returning the string pointer. + * + * @returns @a psz. On failure, NULL. + * + * @param pNode The node. + * @param psz The output buffer. + * @param cch The size of the buffer. + */ + static char *getNodePath2(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. + * @param a_pVMM The VMM function table. + */ + VBoxDbgStatsModelVM(VBoxDbgGui *a_pDbgGui, QString &a_rPatStr, QObject *a_pParent, PCVMMR3VTABLE a_pVMM); + + /** 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, + const char *pszUnit, 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); + + /** The VMM function table. */ + PCVMMR3VTABLE m_pVMM; +}; + + +/********************************************************************************************************************************* +* 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->pszUnit = ""; + 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->pszUnit = ""; + 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->pszUnit = ""; + 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 pCurNode = Stack.a[Stack.iTop].pNode; + uint32_t iChild = ++Stack.a[Stack.iTop].iChild; + if (iChild < pCurNode->cChildren) + { + /* push */ + Stack.iTop++; + Assert(Stack.iTop < (int32_t)RT_ELEMENTS(Stack.a)); + Stack.a[Stack.iTop].pNode = pCurNode->papChildren[iChild]; + Stack.a[Stack.iTop].iChild = 0; + } + else + { + /* pop and destroy all the children. */ + Stack.iTop--; + uint32_t i = pCurNode->cChildren; + if (i) + { + beginRemoveRows(createIndex(pCurNode->iSelf, 0, pCurNode), 0, i - 1); + while (i-- > 0) + destroyNode(pCurNode->papChildren[i]); + pCurNode->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, + const char *pszUnit, const char *pszDesc) +{ + /* + * Copy the data. + */ + pNode->pszUnit = pszUnit; + 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, const char *pszUnit, 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, pszUnit, 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*/ char * +VBoxDbgStatsModel::getNodePath2(PCDBGGUISTATSNODE pNode, char *psz, ssize_t cch) +{ + if (VBoxDbgStatsModel::getNodePath(pNode, psz, cch) < 0) + return NULL; + return psz; +} + + + +/*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) +{ +#if defined(VBOX_STRICT) || defined(LOG_ENABLED) + char szStrict[1024]; +#endif + + /* + * 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); + AssertMsg(strcmp(pszName, getNodePath2(pNode, szStrict, sizeof(szStrict))), ("%s\n", szStrict)); + AssertMsg(!pPrev || strcmp(pszName, getNodePath2(pPrev, szStrict, sizeof(szStrict))), ("%s\n", szStrict)); + Log(("updateCallbackHandleOutOfOrder: pszName='%s' m_szUpdateParent='%s' m_cchUpdateParent=%u pNode='%s'\n", + pszName, m_szUpdateParent, m_cchUpdateParent, getNodePath2(pNode, szStrict, sizeof(szStrict)))); + + 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'; + Log2(("updateCallbackHandleOutOfOrder: m_szUpdateParent='%s' m_cchUpdateParent=%u, removed '/%s' (%u)\n", m_szUpdateParent, m_cchUpdateParent, pNode->pszName, __LINE__)); + 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)); + Log2(("updateCallbackHandleOutOfOrder: m_szUpdateParent='%s' m_cchUpdateParent=%u (%u)\n", m_szUpdateParent, m_cchUpdateParent, __LINE__)); + + 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; + /* For cases when exisiting node name is same as new node name with additional characters. */ + if (!iDiff) + iDiff = cchSubName == pNode->papChildren[i]->cchName ? 0 : cchSubName > pNode->papChildren[i]->cchName ? 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; + Log2(("updateCallbackHandleOutOfOrder: m_szUpdateParent='%s' m_cchUpdateParent=%u (%u)\n", m_szUpdateParent, m_cchUpdateParent, __LINE__)); + + 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, + const char *pszUnit, STAMVISIBILITY enmVisibility, const char *pszDesc, void *pvUser) +{ + VBoxDbgStatsModelVM *pThis = (VBoxDbgStatsModelVM *)pvUser; + Log3(("updateCallback: %s\n", pszName)); + RT_NOREF(enmUnit); + + /* + * 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, pszUnit, 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... */ + beginResetModel(); + endResetModel(); + } + 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); + } +} + + +void +VBoxDbgStatsModel::iterateStatsByPattern(QString const &a_rPatStr, VBoxDbgStatsModel::FNITERATOR *a_pfnCallback, void *a_pvUser, + bool a_fMatchChildren /*= true*/) +{ + const QByteArray &PatBytes = a_rPatStr.toUtf8(); + const char * const pszPattern = PatBytes.constData(); + size_t const cchPattern = strlen(pszPattern); + + DBGGUISTATSSTACK Stack; + Stack.a[0].pNode = m_pRoot; + Stack.a[0].iChild = 0; + Stack.a[0].cchName = 0; + Stack.iTop = 0; + + char szName[1024]; + szName[0] = '\0'; + + while (Stack.iTop >= 0) + { + /* get top element */ + PDBGGUISTATSNODE const pNode = Stack.a[Stack.iTop].pNode; + uint16_t cchName = Stack.a[Stack.iTop].cchName; + uint32_t const iChild = Stack.a[Stack.iTop].iChild++; + if (iChild < pNode->cChildren) + { + PDBGGUISTATSNODE pChild = pNode->papChildren[iChild]; + + /* Build the name and match the pattern. */ + Assert(cchName + 1 + pChild->cchName < sizeof(szName)); + szName[cchName++] = '/'; + memcpy(&szName[cchName], pChild->pszName, pChild->cchName); + cchName += (uint16_t)pChild->cchName; + szName[cchName] = '\0'; + + if (RTStrSimplePatternMultiMatch(pszPattern, cchPattern, szName, cchName, NULL)) + { + /* Do callback. */ + QModelIndex const Index = createIndex(iChild, 0, pChild); + if (!a_pfnCallback(pChild, Index, szName, a_pvUser)) + return; + if (!a_fMatchChildren) + continue; + } + + /* push */ + Stack.iTop++; + Assert(Stack.iTop < (int32_t)RT_ELEMENTS(Stack.a)); + Stack.a[Stack.iTop].pNode = pChild; + Stack.a[Stack.iTop].iChild = 0; + Stack.a[Stack.iTop].cchName = cchName; + } + else + { + /* pop */ + Stack.iTop--; + } + } +} + + +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); + beginResetModel(); + endResetModel(); +} + + +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) +{ + return pNode->pszUnit; +} + + +/*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: + { + char szTmp[64]; + char *psz = formatNumber(szTmp, pNode->Data.RatioU32.u32A); + size_t off = strlen(psz); + memcpy(sz, psz, off); + sz[off++] = ':'; + strcpy(&sz[off], formatNumber(szTmp, pNode->Data.RatioU32.u32B)); + return sz; + } + + 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)); + RT_FALL_THRU(); + 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"; + RT_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, a_pNode->pszUnit); + 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, a_pNode->pszUnit, + 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, a_pNode->pszUnit); + break; + + case STAMTYPE_CALLBACK: + if (a_pNode->Data.pStr) + a_rString += *a_pNode->Data.pStr; + RTStrPrintf(szBuf, sizeof(szBuf), " %s", a_pNode->pszUnit); + break; + + case STAMTYPE_U8: + case STAMTYPE_U8_RESET: + RTStrPrintf(szBuf, sizeof(szBuf), "%8u %s", a_pNode->Data.u8, a_pNode->pszUnit); + break; + + case STAMTYPE_X8: + case STAMTYPE_X8_RESET: + RTStrPrintf(szBuf, sizeof(szBuf), "%8x %s", a_pNode->Data.u8, a_pNode->pszUnit); + break; + + case STAMTYPE_U16: + case STAMTYPE_U16_RESET: + RTStrPrintf(szBuf, sizeof(szBuf), "%8u %s", a_pNode->Data.u16, a_pNode->pszUnit); + break; + + case STAMTYPE_X16: + case STAMTYPE_X16_RESET: + RTStrPrintf(szBuf, sizeof(szBuf), "%8x %s", a_pNode->Data.u16, a_pNode->pszUnit); + break; + + case STAMTYPE_U32: + case STAMTYPE_U32_RESET: + RTStrPrintf(szBuf, sizeof(szBuf), "%8u %s", a_pNode->Data.u32, a_pNode->pszUnit); + break; + + case STAMTYPE_X32: + case STAMTYPE_X32_RESET: + RTStrPrintf(szBuf, sizeof(szBuf), "%8x %s", a_pNode->Data.u32, a_pNode->pszUnit); + break; + + case STAMTYPE_U64: + case STAMTYPE_U64_RESET: + RTStrPrintf(szBuf, sizeof(szBuf), "%8llu %s", a_pNode->Data.u64, a_pNode->pszUnit); + break; + + case STAMTYPE_X64: + case STAMTYPE_X64_RESET: + RTStrPrintf(szBuf, sizeof(szBuf), "%8llx %s", a_pNode->Data.u64, a_pNode->pszUnit); + break; + + case STAMTYPE_BOOL: + case STAMTYPE_BOOL_RESET: + RTStrPrintf(szBuf, sizeof(szBuf), "%s %s", a_pNode->Data.f ? "true " : "false ", a_pNode->pszUnit); + 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, PCVMMR3VTABLE a_pVMM) + : VBoxDbgStatsModel(a_pParent), VBoxDbgBase(a_pDbgGui), m_pVMM(a_pVMM) +{ + /* + * 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, + const char *pszUnit, STAMVISIBILITY enmVisibility, const char *pszDesc, void *pvUser) +{ + PDBGGUISTATSNODE pRoot = (PDBGGUISTATSNODE)pvUser; + Log3(("createNewTreeCallback: %s\n", pszName)); + RT_NOREF(enmUnit); + + /* + * 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, pszUnit, 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); + /* Some extra room for distinguishing numbers better in Value, Min, Avg, Max, Total, dInt columns. */ + if (i >= 2 && i <= 7) + setColumnWidth(i, columnWidth(i) + 10); + } +} + + +/*static*/ bool +VBoxDbgStatsView::expandMatchingCallback(PDBGGUISTATSNODE pNode, QModelIndex const &a_rIndex, + const char *pszFullName, void *pvUser) +{ + VBoxDbgStatsView *pThis = (VBoxDbgStatsView *)pvUser; + + pThis->setExpanded(a_rIndex, true); + + QModelIndex ParentIndex = pThis->m_pModel->parent(a_rIndex); + while (ParentIndex.isValid() && !pThis->isExpanded(ParentIndex)) + { + pThis->setExpanded(ParentIndex, true); + ParentIndex = pThis->m_pModel->parent(ParentIndex); + } + + RT_NOREF(pNode, pszFullName); + return true; +} + + +void +VBoxDbgStatsView::expandMatching(const QString &rPatStr) +{ + m_pModel->iterateStatsByPattern(rPatStr, expandMatchingCallback, this); +} + + +void +VBoxDbgStatsView::setSubTreeExpanded(QModelIndex const &a_rIndex, bool a_fExpanded) +{ + int cRows = m_pModel->rowCount(a_rIndex); + if (a_rIndex.model()) + for (int i = 0; i < cRows; i++) + setSubTreeExpanded(a_rIndex.model()->index(i, 0, a_rIndex), 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 *pszFilter /*= NULL*/, const char *pszExpand /*= NULL*/, + unsigned uRefreshRate/* = 0*/, QWidget *pParent/* = NULL*/) + : VBoxDbgBaseWindow(a_pDbgGui, pParent, "Statistics") + , m_PatStr(pszFilter), m_pPatCB(NULL), m_uRefreshRate(0), m_pTimer(NULL), m_pView(NULL) +{ + /* Delete dialog on close: */ + setAttribute(Qt::WA_DeleteOnClose); + + /* + * 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->setCompleter(0); + 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, a_pDbgGui->getVMMFunctionTable()); + 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(); + + if (pszExpand && *pszExpand) + m_pView->expandMatching(QString(pszExpand)); + + /* + * 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(); +} + + +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); +} + |