summaryrefslogtreecommitdiffstats
path: root/src/VBox/Debugger/VBoxDbgStatsQt.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
commitf215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch)
tree6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/Debugger/VBoxDbgStatsQt.cpp
parentInitial commit. (diff)
downloadvirtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz
virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.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.cpp3329
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);
+}
+