VirtualBox

source: vbox/trunk/src/VBox/Debugger/VBoxDbgStatsQt.cpp@ 103428

最後變更 在這個檔案從103428是 103403,由 vboxsync 提交於 13 月 前

VBoxDbg: Added sorting to the statistics as well as filtering out unused rows by default.

  • 屬性 svn:eol-style 設為 native
  • 屬性 svn:keywords 設為 Author Date Id Revision
檔案大小: 111.9 KB
 
1/* $Id: VBoxDbgStatsQt.cpp 103403 2024-02-17 01:51:52Z vboxsync $ */
2/** @file
3 * VBox Debugger GUI - Statistics.
4 */
5
6/*
7 * Copyright (C) 2006-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.alldomusa.eu.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28
29/*********************************************************************************************************************************
30* Header Files *
31*********************************************************************************************************************************/
32#define LOG_GROUP LOG_GROUP_DBGG
33#include "VBoxDbgStatsQt.h"
34
35#include <QLocale>
36#include <QPushButton>
37#include <QSpinBox>
38#include <QLabel>
39#include <QCheckBox>
40#include <QClipboard>
41#include <QApplication>
42#include <QHBoxLayout>
43#include <QVBoxLayout>
44#include <QKeySequence>
45#include <QAction>
46#include <QContextMenuEvent>
47#include <QHeaderView>
48#include <QSortFilterProxyModel>
49
50#include <iprt/errcore.h>
51#include <VBox/log.h>
52#include <iprt/string.h>
53#include <iprt/mem.h>
54#include <iprt/assert.h>
55
56#include "VBoxDbgGui.h"
57
58
59/*********************************************************************************************************************************
60* Defined Constants And Macros *
61*********************************************************************************************************************************/
62/** The number of column. */
63#define DBGGUI_STATS_COLUMNS 9
64
65/** Enables the sorting and filtering. */
66#define VBOXDBG_WITH_SORTED_AND_FILTERED_STATS
67
68
69/*********************************************************************************************************************************
70* Structures and Typedefs *
71*********************************************************************************************************************************/
72/**
73 * The state of a statistics sample node.
74 *
75 * This is used for two pass refresh (1. get data, 2. update the view) and
76 * for saving the result of a diff.
77 */
78typedef enum DBGGUISTATSNODESTATE
79{
80 /** The typical invalid zeroth entry. */
81 kDbgGuiStatsNodeState_kInvalid = 0,
82 /** The node is the root node. */
83 kDbgGuiStatsNodeState_kRoot,
84 /** The node is visible. */
85 kDbgGuiStatsNodeState_kVisible,
86 /** The node should be refreshed. */
87 kDbgGuiStatsNodeState_kRefresh,
88#if 0 /// @todo not implemented
89 /** diff: The node equals. */
90 kDbgGuiStatsNodeState_kDiffEqual,
91 /** diff: The node in set 1 is less than the one in set 2. */
92 kDbgGuiStatsNodeState_kDiffSmaller,
93 /** diff: The node in set 1 is greater than the one in set 2. */
94 kDbgGuiStatsNodeState_kDiffGreater,
95 /** diff: The node is only in set 1. */
96 kDbgGuiStatsNodeState_kDiffOnlyIn1,
97 /** diff: The node is only in set 2. */
98 kDbgGuiStatsNodeState_kDiffOnlyIn2,
99#endif
100 /** The end of the valid state values. */
101 kDbgGuiStatsNodeState_kEnd
102} DBGGUISTATENODESTATE;
103
104
105/**
106 * A tree node representing a statistic sample.
107 *
108 * The nodes carry a reference to the parent and to its position among its
109 * siblings. Both of these need updating when the grand parent or parent adds a
110 * new child. This will hopefully not be too expensive but rather pay off when
111 * we need to create a parent index.
112 */
113typedef struct DBGGUISTATSNODE
114{
115 /** Pointer to the parent. */
116 PDBGGUISTATSNODE pParent;
117 /** Array of pointers to the child nodes. */
118 PDBGGUISTATSNODE *papChildren;
119 /** The number of children. */
120 uint32_t cChildren;
121 /** Our index among the parent's children. */
122 uint32_t iSelf;
123 /** The unit string. (not allocated) */
124 const char *pszUnit;
125 /** The data type.
126 * For filler nodes not containing data, this will be set to STAMTYPE_INVALID. */
127 STAMTYPE enmType;
128 /** The data at last update. */
129 union
130 {
131 /** STAMTYPE_COUNTER. */
132 STAMCOUNTER Counter;
133 /** STAMTYPE_PROFILE. */
134 STAMPROFILE Profile;
135 /** STAMTYPE_PROFILE_ADV. */
136 STAMPROFILEADV ProfileAdv;
137 /** STAMTYPE_RATIO_U32. */
138 STAMRATIOU32 RatioU32;
139 /** STAMTYPE_U8 & STAMTYPE_U8_RESET. */
140 uint8_t u8;
141 /** STAMTYPE_U16 & STAMTYPE_U16_RESET. */
142 uint16_t u16;
143 /** STAMTYPE_U32 & STAMTYPE_U32_RESET. */
144 uint32_t u32;
145 /** STAMTYPE_U64 & STAMTYPE_U64_RESET. */
146 uint64_t u64;
147 /** STAMTYPE_BOOL and STAMTYPE_BOOL_RESET. */
148 bool f;
149 /** STAMTYPE_CALLBACK. */
150 QString *pStr;
151 } Data;
152 /** The delta. */
153 int64_t i64Delta;
154 /** The name. */
155 char *pszName;
156 /** The length of the name. */
157 size_t cchName;
158 /** The description string. */
159 QString *pDescStr;
160 /** The node state. */
161 DBGGUISTATENODESTATE enmState;
162} DBGGUISTATSNODE;
163
164
165/**
166 * Recursion stack.
167 */
168typedef struct DBGGUISTATSSTACK
169{
170 /** The top stack entry. */
171 int32_t iTop;
172 /** The stack array. */
173 struct DBGGUISTATSSTACKENTRY
174 {
175 /** The node. */
176 PDBGGUISTATSNODE pNode;
177 /** The current child. */
178 int32_t iChild;
179 /** Name string offset (if used). */
180 uint16_t cchName;
181 } a[32];
182} DBGGUISTATSSTACK;
183
184
185
186
187/**
188 * The item model for the statistics tree view.
189 *
190 * This manages the DBGGUISTATSNODE trees.
191 */
192class VBoxDbgStatsModel : public QAbstractItemModel
193{
194protected:
195 /** The root of the sample tree. */
196 PDBGGUISTATSNODE m_pRoot;
197
198private:
199 /** Next update child. This is UINT32_MAX when invalid. */
200 uint32_t m_iUpdateChild;
201 /** Pointer to the node m_szUpdateParent represent and m_iUpdateChild refers to. */
202 PDBGGUISTATSNODE m_pUpdateParent;
203 /** The length of the path. */
204 size_t m_cchUpdateParent;
205 /** The path to the current update parent, including a trailing slash. */
206 char m_szUpdateParent[1024];
207 /** Inserted or/and removed nodes during the update. */
208 bool m_fUpdateInsertRemove;
209
210
211public:
212 /**
213 * Constructor.
214 *
215 * @param a_pParent The parent object. See QAbstractItemModel in the Qt
216 * docs for details.
217 */
218 VBoxDbgStatsModel(QObject *a_pParent);
219
220 /**
221 * Destructor.
222 *
223 * This will free all the data the model holds.
224 */
225 virtual ~VBoxDbgStatsModel();
226
227 /**
228 * Updates the data matching the specified pattern, normally for the whole tree
229 * but optionally a sub-tree if @a a_pSubTree is given.
230 *
231 * This will should invoke updatePrep, updateCallback and updateDone.
232 *
233 * It is vitally important that updateCallback is fed the data in the right
234 * order. The code make very definite ASSUMPTIONS about the ordering being
235 * strictly sorted and taking the slash into account when doing so.
236 *
237 * @returns true if we reset the model and it's necessary to set the root index.
238 * @param a_rPatStr The selection pattern.
239 * @param a_pSubTree The node / sub-tree to update if this is partial update.
240 * This is NULL for a full tree update.
241 *
242 * @remarks The default implementation is an empty stub.
243 */
244 virtual bool updateStatsByPattern(const QString &a_rPatStr, PDBGGUISTATSNODE a_pSubTree = NULL);
245
246 /**
247 * Similar to updateStatsByPattern, except that it only works on a sub-tree and
248 * will not remove anything that's outside that tree.
249 *
250 * The default implementation will call redirect to updateStatsByPattern().
251 *
252 * @param a_rIndex The sub-tree root. Invalid index means root.
253 */
254 virtual void updateStatsByIndex(QModelIndex const &a_rIndex);
255
256 /**
257 * Reset the stats matching the specified pattern.
258 *
259 * @param a_rPatStr The selection pattern.
260 *
261 * @remarks The default implementation is an empty stub.
262 */
263 virtual void resetStatsByPattern(QString const &a_rPatStr);
264
265 /**
266 * Reset the stats of a sub-tree.
267 *
268 * @param a_rIndex The sub-tree root. Invalid index means root.
269 * @param a_fSubTree Whether to reset the sub-tree as well. Default is true.
270 *
271 * @remarks The default implementation makes use of resetStatsByPattern
272 */
273 virtual void resetStatsByIndex(QModelIndex const &a_rIndex, bool a_fSubTree = true);
274
275 /**
276 * Iterator callback function.
277 * @returns true to continue, false to stop.
278 */
279 typedef bool FNITERATOR(PDBGGUISTATSNODE pNode, QModelIndex const &a_rIndex, const char *pszFullName, void *pvUser);
280
281 /**
282 * Callback iterator.
283 *
284 * @param a_rPatStr The selection pattern.
285 * @param a_pfnCallback Callback function.
286 * @param a_pvUser Callback argument.
287 * @param a_fMatchChildren How to handle children of matching nodes:
288 * - @c true: continue with the children,
289 * - @c false: skip children.
290 */
291 virtual void iterateStatsByPattern(QString const &a_rPatStr, FNITERATOR *a_pfnCallback, void *a_pvUser,
292 bool a_fMatchChildren = true);
293
294 /**
295 * Gets the model index of the root node.
296 *
297 * @returns root index.
298 */
299 QModelIndex getRootIndex(void) const;
300
301
302protected:
303 /**
304 * Set the root node.
305 *
306 * This will free all the current data before taking the ownership of the new
307 * root node and its children.
308 *
309 * @param a_pRoot The new root node.
310 */
311 void setRootNode(PDBGGUISTATSNODE a_pRoot);
312
313 /** Creates the root node. */
314 static PDBGGUISTATSNODE createRootNode(void);
315
316 /** Creates and insert a node under the given parent. */
317 static PDBGGUISTATSNODE createAndInsertNode(PDBGGUISTATSNODE pParent, const char *pszName, size_t cchName, uint32_t iPosition);
318
319 /** Creates and insert a node under the given parent with correct Qt
320 * signalling. */
321 PDBGGUISTATSNODE createAndInsert(PDBGGUISTATSNODE pParent, const char *pszName, size_t cchName, uint32_t iPosition);
322
323 /**
324 * Resets the node to a pristine state.
325 *
326 * @param pNode The node.
327 */
328 static void resetNode(PDBGGUISTATSNODE pNode);
329
330 /**
331 * Initializes a pristine node.
332 */
333 static int initNode(PDBGGUISTATSNODE pNode, STAMTYPE enmType, void *pvSample, const char *pszUnit, const char *pszDesc);
334
335 /**
336 * Updates (or reinitializes if you like) a node.
337 */
338 static void updateNode(PDBGGUISTATSNODE pNode, STAMTYPE enmType, void *pvSample, const char *pszUnit, const char *pszDesc);
339
340 /**
341 * Called by updateStatsByPattern(), makes the necessary preparations.
342 *
343 * @returns Success indicator.
344 * @param a_pSubTree The node / sub-tree to update if this is partial update.
345 * This is NULL for a full tree update.
346 */
347 bool updatePrepare(PDBGGUISTATSNODE a_pSubTree = NULL);
348
349 /**
350 * Called by updateStatsByPattern(), finalizes the update.
351 *
352 * @returns See updateStatsByPattern().
353 *
354 * @param a_fSuccess Whether the update was successful or not.
355 * @param a_pSubTree The node / sub-tree to update if this is partial update.
356 * This is NULL for a full tree update.
357 */
358 bool updateDone(bool a_fSuccess, PDBGGUISTATSNODE a_pSubTree = NULL);
359
360 /**
361 * updateCallback() worker taking care of in-tree inserts and removals.
362 *
363 * @returns The current node.
364 * @param pszName The name of the tree element to update.
365 */
366 PDBGGUISTATSNODE updateCallbackHandleOutOfOrder(const char *pszName);
367
368 /**
369 * updateCallback() worker taking care of tail insertions.
370 *
371 * @returns The current node.
372 * @param pszName The name of the tree element to update.
373 */
374 PDBGGUISTATSNODE updateCallbackHandleTail(const char *pszName);
375
376 /**
377 * updateCallback() worker that advances the update state to the next data node
378 * in anticipation of the next updateCallback call.
379 *
380 * @param pNode The current node.
381 */
382 void updateCallbackAdvance(PDBGGUISTATSNODE pNode);
383
384 /** Callback used by updateStatsByPattern() and updateStatsByIndex() to feed
385 * changes.
386 * @copydoc FNSTAMR3ENUM */
387 static DECLCALLBACK(int) updateCallback(const char *pszName, STAMTYPE enmType, void *pvSample, STAMUNIT enmUnit,
388 const char *pszUnit, STAMVISIBILITY enmVisibility, const char *pszDesc, void *pvUser);
389
390 /**
391 * Calculates the full path of a node.
392 *
393 * @returns Number of bytes returned, negative value on buffer overflow
394 *
395 * @param pNode The node.
396 * @param psz The output buffer.
397 * @param cch The size of the buffer.
398 */
399 static ssize_t getNodePath(PCDBGGUISTATSNODE pNode, char *psz, ssize_t cch);
400
401 /**
402 * Calculates the full path of a node, returning the string pointer.
403 *
404 * @returns @a psz. On failure, NULL.
405 *
406 * @param pNode The node.
407 * @param psz The output buffer.
408 * @param cch The size of the buffer.
409 */
410 static char *getNodePath2(PCDBGGUISTATSNODE pNode, char *psz, ssize_t cch);
411
412 /**
413 * Returns the pattern for the node, optionally including the entire sub-tree
414 * under it.
415 *
416 * @returns Pattern.
417 * @param pNode The node.
418 * @param fSubTree Whether to include the sub-tree in the pattern.
419 */
420 static QString getNodePattern(PCDBGGUISTATSNODE pNode, bool fSubTree = true);
421
422 /**
423 * Check if the first node is an ancestor to the second one.
424 *
425 * @returns true/false.
426 * @param pAncestor The first node, the alleged ancestor.
427 * @param pDescendant The second node, the alleged descendant.
428 */
429 static bool isNodeAncestorOf(PCDBGGUISTATSNODE pAncestor, PCDBGGUISTATSNODE pDescendant);
430
431 /**
432 * Advance to the next node in the tree.
433 *
434 * @returns Pointer to the next node, NULL if we've reached the end or
435 * was handed a NULL node.
436 * @param pNode The current node.
437 */
438 static PDBGGUISTATSNODE nextNode(PDBGGUISTATSNODE pNode);
439
440 /**
441 * Advance to the next node in the tree that contains data.
442 *
443 * @returns Pointer to the next data node, NULL if we've reached the end or
444 * was handed a NULL node.
445 * @param pNode The current node.
446 */
447 static PDBGGUISTATSNODE nextDataNode(PDBGGUISTATSNODE pNode);
448
449 /**
450 * Advance to the previous node in the tree.
451 *
452 * @returns Pointer to the previous node, NULL if we've reached the end or
453 * was handed a NULL node.
454 * @param pNode The current node.
455 */
456 static PDBGGUISTATSNODE prevNode(PDBGGUISTATSNODE pNode);
457
458 /**
459 * Advance to the previous node in the tree that contains data.
460 *
461 * @returns Pointer to the previous data node, NULL if we've reached the end or
462 * was handed a NULL node.
463 * @param pNode The current node.
464 */
465 static PDBGGUISTATSNODE prevDataNode(PDBGGUISTATSNODE pNode);
466
467 /**
468 * Removes a node from the tree.
469 *
470 * @returns pNode.
471 * @param pNode The node.
472 */
473 static PDBGGUISTATSNODE removeNode(PDBGGUISTATSNODE pNode);
474
475 /**
476 * Removes a node from the tree and destroys it and all its descendants.
477 *
478 * @param pNode The node.
479 */
480 static void removeAndDestroyNode(PDBGGUISTATSNODE pNode);
481
482 /** Removes a node from the tree and destroys it and all its descendants
483 * performing the required Qt signalling. */
484 void removeAndDestroy(PDBGGUISTATSNODE pNode);
485
486 /**
487 * Destroys a statistics tree.
488 *
489 * @param a_pRoot The root of the tree. NULL is fine.
490 */
491 static void destroyTree(PDBGGUISTATSNODE a_pRoot);
492
493 /**
494 * Stringifies exactly one node, no children.
495 *
496 * This is for logging and clipboard.
497 *
498 * @param a_pNode The node.
499 * @param a_rString The string to append the stringified node to.
500 */
501 static void stringifyNodeNoRecursion(PDBGGUISTATSNODE a_pNode, QString &a_rString);
502
503 /**
504 * Stringifies a node and its children.
505 *
506 * This is for logging and clipboard.
507 *
508 * @param a_pNode The node.
509 * @param a_rString The string to append the stringified node to.
510 */
511 static void stringifyNode(PDBGGUISTATSNODE a_pNode, QString &a_rString);
512
513public:
514 /**
515 * Converts the specified tree to string.
516 *
517 * This is for logging and clipboard.
518 *
519 * @param a_rRoot Where to start. Use QModelIndex() to start at the root.
520 * @param a_rString Where to return to return the string dump.
521 */
522 void stringifyTree(QModelIndex &a_rRoot, QString &a_rString) const;
523
524 /**
525 * Dumps the given (sub-)tree as XML.
526 *
527 * @param a_rRoot Where to start. Use QModelIndex() to start at the root.
528 * @param a_rString Where to return to return the XML dump.
529 */
530 void xmlifyTree(QModelIndex &a_rRoot, QString &a_rString) const;
531
532 /**
533 * Puts the stringified tree on the clipboard.
534 *
535 * @param a_rRoot Where to start. Use QModelIndex() to start at the root.
536 */
537 void copyTreeToClipboard(QModelIndex &a_rRoot) const;
538
539
540protected:
541 /** Worker for logTree. */
542 static void logNode(PDBGGUISTATSNODE a_pNode, bool a_fReleaseLog);
543
544public:
545 /** Logs a (sub-)tree.
546 *
547 * @param a_rRoot Where to start. Use QModelIndex() to start at the root.
548 * @param a_fReleaseLog Whether to use the release log (true) or the debug log (false).
549 */
550 void logTree(QModelIndex &a_rRoot, bool a_fReleaseLog) const;
551
552 /** Gets the unit. */
553 static QString strUnit(PCDBGGUISTATSNODE pNode);
554 /** Gets the value/times. */
555 static QString strValueTimes(PCDBGGUISTATSNODE pNode);
556 /** Gets the value/times. */
557 static uint64_t getValueTimesAsUInt(PCDBGGUISTATSNODE pNode);
558 /** Gets the minimum value. */
559 static QString strMinValue(PCDBGGUISTATSNODE pNode);
560 /** Gets the minimum value. */
561 static uint64_t getMinValueAsUInt(PCDBGGUISTATSNODE pNode);
562 /** Gets the average value. */
563 static QString strAvgValue(PCDBGGUISTATSNODE pNode);
564 /** Gets the average value. */
565 static uint64_t getAvgValueAsUInt(PCDBGGUISTATSNODE pNode);
566 /** Gets the maximum value. */
567 static QString strMaxValue(PCDBGGUISTATSNODE pNode);
568 /** Gets the maximum value. */
569 static uint64_t getMaxValueAsUInt(PCDBGGUISTATSNODE pNode);
570 /** Gets the total value. */
571 static QString strTotalValue(PCDBGGUISTATSNODE pNode);
572 /** Gets the total value. */
573 static uint64_t getTotalValueAsUInt(PCDBGGUISTATSNODE pNode);
574 /** Gets the delta value. */
575 static QString strDeltaValue(PCDBGGUISTATSNODE pNode);
576
577
578protected:
579 /**
580 * Destroys a node and all its children.
581 *
582 * @param a_pNode The node to destroy.
583 */
584 static void destroyNode(PDBGGUISTATSNODE a_pNode);
585
586 /**
587 * Converts an index to a node pointer.
588 *
589 * @returns Pointer to the node, NULL if invalid reference.
590 * @param a_rIndex Reference to the index
591 */
592 inline PDBGGUISTATSNODE nodeFromIndex(const QModelIndex &a_rIndex) const
593 {
594 if (RT_LIKELY(a_rIndex.isValid()))
595 return (PDBGGUISTATSNODE)a_rIndex.internalPointer();
596 return NULL;
597 }
598
599public:
600
601 /** @name Overridden QAbstractItemModel methods
602 * @{ */
603 virtual int columnCount(const QModelIndex &a_rParent) const RT_OVERRIDE;
604 virtual QVariant data(const QModelIndex &a_rIndex, int a_eRole) const RT_OVERRIDE;
605 virtual Qt::ItemFlags flags(const QModelIndex &a_rIndex) const RT_OVERRIDE;
606 virtual bool hasChildren(const QModelIndex &a_rParent) const RT_OVERRIDE;
607 virtual QVariant headerData(int a_iSection, Qt::Orientation a_ePrientation, int a_eRole) const RT_OVERRIDE;
608 virtual QModelIndex index(int a_iRow, int a_iColumn, const QModelIndex &a_rParent) const RT_OVERRIDE;
609 virtual QModelIndex parent(const QModelIndex &a_rChild) const RT_OVERRIDE;
610 virtual int rowCount(const QModelIndex &a_rParent) const RT_OVERRIDE;
611 ///virtual void sort(int a_iColumn, Qt::SortOrder a_eOrder) RT_OVERRIDE;
612 /** @} */
613};
614
615
616/**
617 * Model using the VM / STAM interface as data source.
618 */
619class VBoxDbgStatsModelVM : public VBoxDbgStatsModel, public VBoxDbgBase
620{
621public:
622 /**
623 * Constructor.
624 *
625 * @param a_pDbgGui Pointer to the debugger gui object.
626 * @param a_rPatStr The selection pattern.
627 * @param a_pParent The parent object. NULL is fine.
628 * @param a_pVMM The VMM function table.
629 */
630 VBoxDbgStatsModelVM(VBoxDbgGui *a_pDbgGui, QString &a_rPatStr, QObject *a_pParent, PCVMMR3VTABLE a_pVMM);
631
632 /** Destructor */
633 virtual ~VBoxDbgStatsModelVM();
634
635 virtual bool updateStatsByPattern(const QString &a_rPatStr, PDBGGUISTATSNODE a_pSubTree = NULL);
636 virtual void resetStatsByPattern(const QString &a_rPatStr);
637
638protected:
639 /**
640 * Enumeration callback used by createNewTree.
641 */
642 static DECLCALLBACK(int) createNewTreeCallback(const char *pszName, STAMTYPE enmType, void *pvSample, STAMUNIT enmUnit,
643 const char *pszUnit, STAMVISIBILITY enmVisibility, const char *pszDesc,
644 void *pvUser);
645
646 /**
647 * Constructs a new statistics tree by query data from the VM.
648 *
649 * @returns Pointer to the root of the tree we've constructed. This will be NULL
650 * if the STAM API throws an error or we run out of memory.
651 * @param a_rPatStr The selection pattern.
652 */
653 PDBGGUISTATSNODE createNewTree(QString &a_rPatStr);
654
655 /** The VMM function table. */
656 PCVMMR3VTABLE m_pVMM;
657};
658
659
660#ifdef VBOXDBG_WITH_SORTED_AND_FILTERED_STATS
661/**
662 * Model using the VM / STAM interface as data source.
663 */
664class VBoxDbgStatsSortFileProxyModel : public QSortFilterProxyModel
665{
666public:
667 /**
668 * Constructor.
669 *
670 * @param a_pParent The parent object.
671 */
672 VBoxDbgStatsSortFileProxyModel(QObject *a_pParent);
673
674 /** Destructor */
675 virtual ~VBoxDbgStatsSortFileProxyModel()
676 {}
677
678 /** Sets whether or not to show unused rows (all zeros). */
679 void setShowUnusedRows(bool a_fHide);
680
681protected:
682 /**
683 * Converts an index to a node pointer.
684 *
685 * @returns Pointer to the node, NULL if invalid reference.
686 * @param a_rIndex Reference to the index
687 */
688 inline PDBGGUISTATSNODE nodeFromIndex(const QModelIndex &a_rIndex) const
689 {
690 if (RT_LIKELY(a_rIndex.isValid()))
691 return (PDBGGUISTATSNODE)a_rIndex.internalPointer();
692 return NULL;
693 }
694
695 /** Does the row filtering. */
696 bool filterAcceptsRow(int a_iSrcRow, const QModelIndex &a_rSrcParent) const RT_OVERRIDE;
697 /** For implementing the sorting. */
698 bool lessThan(const QModelIndex &a_rSrcLeft, const QModelIndex &a_rSrcRight) const RT_OVERRIDE;
699
700 /** Whether to show unused rows (all zeros) or not. */
701 bool m_fShowUnusedRows;
702};
703#endif /* VBOXDBG_WITH_SORTED_AND_FILTERED_STATS */
704
705
706/*********************************************************************************************************************************
707* Internal Functions *
708*********************************************************************************************************************************/
709
710
711/**
712 * Formats a number into a 64-byte buffer.
713 */
714static char *formatNumber(char *psz, uint64_t u64)
715{
716 if (!u64)
717 {
718 psz[0] = '0';
719 psz[1] = '\0';
720 }
721 else
722 {
723 static const char s_szDigits[] = "0123456789";
724 psz += 63;
725 *psz-- = '\0';
726 unsigned cDigits = 0;
727 for (;;)
728 {
729 const unsigned iDigit = u64 % 10;
730 u64 /= 10;
731 *psz = s_szDigits[iDigit];
732 if (!u64)
733 break;
734 psz--;
735 if (!(++cDigits % 3))
736 *psz-- = ',';
737 }
738 }
739 return psz;
740}
741
742
743/**
744 * Formats a number into a 64-byte buffer.
745 * (18 446 744 073 709 551 615)
746 */
747static char *formatNumberSigned(char *psz, int64_t i64, bool fPositivePlus)
748{
749 static const char s_szDigits[] = "0123456789";
750 psz += 63;
751 *psz-- = '\0';
752 const bool fNegative = i64 < 0;
753 uint64_t u64 = fNegative ? -i64 : i64;
754 unsigned cDigits = 0;
755 for (;;)
756 {
757 const unsigned iDigit = u64 % 10;
758 u64 /= 10;
759 *psz = s_szDigits[iDigit];
760 if (!u64)
761 break;
762 psz--;
763 if (!(++cDigits % 3))
764 *psz-- = ',';
765 }
766 if (fNegative)
767 *--psz = '-';
768 else if (fPositivePlus)
769 *--psz = '+';
770 return psz;
771}
772
773
774/**
775 * Formats a unsigned hexadecimal number into a into a 64-byte buffer.
776 */
777static char *formatHexNumber(char *psz, uint64_t u64, unsigned cZeros)
778{
779 static const char s_szDigits[] = "0123456789abcdef";
780 psz += 63;
781 *psz-- = '\0';
782 unsigned cDigits = 0;
783 for (;;)
784 {
785 const unsigned iDigit = u64 % 16;
786 u64 /= 16;
787 *psz = s_szDigits[iDigit];
788 ++cDigits;
789 if (!u64 && cDigits >= cZeros)
790 break;
791 psz--;
792 if (!(cDigits % 8))
793 *psz-- = '\'';
794 }
795 return psz;
796}
797
798
799#if 0/* unused */
800/**
801 * Formats a sort key number.
802 */
803static void formatSortKey(char *psz, uint64_t u64)
804{
805 static const char s_szDigits[] = "0123456789abcdef";
806 /* signed */
807 *psz++ = '+';
808
809 /* 16 hex digits */
810 psz[16] = '\0';
811 unsigned i = 16;
812 while (i-- > 0)
813 {
814 if (u64)
815 {
816 const unsigned iDigit = u64 % 16;
817 u64 /= 16;
818 psz[i] = s_szDigits[iDigit];
819 }
820 else
821 psz[i] = '0';
822 }
823}
824#endif
825
826
827#if 0/* unused */
828/**
829 * Formats a sort key number.
830 */
831static void formatSortKeySigned(char *psz, int64_t i64)
832{
833 static const char s_szDigits[] = "0123456789abcdef";
834
835 /* signed */
836 uint64_t u64;
837 if (i64 >= 0)
838 {
839 *psz++ = '+';
840 u64 = i64;
841 }
842 else
843 {
844 *psz++ = '-';
845 u64 = -i64;
846 }
847
848 /* 16 hex digits */
849 psz[16] = '\0';
850 unsigned i = 16;
851 while (i-- > 0)
852 {
853 if (u64)
854 {
855 const unsigned iDigit = u64 % 16;
856 u64 /= 16;
857 psz[i] = s_szDigits[iDigit];
858 }
859 else
860 psz[i] = '0';
861 }
862}
863#endif
864
865
866
867/*
868 *
869 * V B o x D b g S t a t s M o d e l
870 * V B o x D b g S t a t s M o d e l
871 * V B o x D b g S t a t s M o d e l
872 *
873 *
874 */
875
876
877VBoxDbgStatsModel::VBoxDbgStatsModel(QObject *a_pParent)
878 : QAbstractItemModel(a_pParent),
879 m_pRoot(NULL), m_iUpdateChild(UINT32_MAX), m_pUpdateParent(NULL), m_cchUpdateParent(0)
880{
881}
882
883
884
885VBoxDbgStatsModel::~VBoxDbgStatsModel()
886{
887 destroyTree(m_pRoot);
888 m_pRoot = NULL;
889}
890
891
892/*static*/ void
893VBoxDbgStatsModel::destroyTree(PDBGGUISTATSNODE a_pRoot)
894{
895 if (!a_pRoot)
896 return;
897 Assert(!a_pRoot->pParent);
898 Assert(!a_pRoot->iSelf);
899
900 destroyNode(a_pRoot);
901}
902
903
904/* static*/ void
905VBoxDbgStatsModel::destroyNode(PDBGGUISTATSNODE a_pNode)
906{
907 /* destroy all our children */
908 uint32_t i = a_pNode->cChildren;
909 while (i-- > 0)
910 {
911 destroyNode(a_pNode->papChildren[i]);
912 a_pNode->papChildren[i] = NULL;
913 }
914
915 /* free the resources we're using */
916 a_pNode->pParent = NULL;
917
918 RTMemFree(a_pNode->papChildren);
919 a_pNode->papChildren = NULL;
920
921 if (a_pNode->enmType == STAMTYPE_CALLBACK)
922 {
923 delete a_pNode->Data.pStr;
924 a_pNode->Data.pStr = NULL;
925 }
926
927 a_pNode->cChildren = 0;
928 a_pNode->iSelf = UINT32_MAX;
929 a_pNode->pszUnit = "";
930 a_pNode->enmType = STAMTYPE_INVALID;
931
932 RTMemFree(a_pNode->pszName);
933 a_pNode->pszName = NULL;
934
935 if (a_pNode->pDescStr)
936 {
937 delete a_pNode->pDescStr;
938 a_pNode->pDescStr = NULL;
939 }
940
941#ifdef VBOX_STRICT
942 /* poison it. */
943 a_pNode->pParent++;
944 a_pNode->Data.pStr++;
945 a_pNode->pDescStr++;
946 a_pNode->papChildren++;
947 a_pNode->cChildren = 8442;
948#endif
949
950 /* Finally ourselves */
951 a_pNode->enmState = kDbgGuiStatsNodeState_kInvalid;
952 RTMemFree(a_pNode);
953}
954
955
956/*static*/ PDBGGUISTATSNODE
957VBoxDbgStatsModel::createRootNode(void)
958{
959 PDBGGUISTATSNODE pRoot = (PDBGGUISTATSNODE)RTMemAllocZ(sizeof(DBGGUISTATSNODE));
960 if (!pRoot)
961 return NULL;
962 pRoot->iSelf = 0;
963 pRoot->enmType = STAMTYPE_INVALID;
964 pRoot->pszUnit = "";
965 pRoot->pszName = (char *)RTMemDup("/", sizeof("/"));
966 pRoot->cchName = 1;
967 pRoot->enmState = kDbgGuiStatsNodeState_kRoot;
968
969 return pRoot;
970}
971
972
973/*static*/ PDBGGUISTATSNODE
974VBoxDbgStatsModel::createAndInsertNode(PDBGGUISTATSNODE pParent, const char *pszName, size_t cchName, uint32_t iPosition)
975{
976 /*
977 * Create it.
978 */
979 PDBGGUISTATSNODE pNode = (PDBGGUISTATSNODE)RTMemAllocZ(sizeof(DBGGUISTATSNODE));
980 if (!pNode)
981 return NULL;
982 pNode->iSelf = UINT32_MAX;
983 pNode->enmType = STAMTYPE_INVALID;
984 pNode->pszUnit = "";
985 pNode->pszName = (char *)RTMemDupEx(pszName, cchName, 1);
986 pNode->cchName = cchName;
987 pNode->enmState = kDbgGuiStatsNodeState_kVisible;
988
989 /*
990 * Do we need to expand the array?
991 */
992 if (!(pParent->cChildren & 31))
993 {
994 void *pvNew = RTMemRealloc(pParent->papChildren, sizeof(*pParent->papChildren) * (pParent->cChildren + 32));
995 if (!pvNew)
996 {
997 destroyNode(pNode);
998 return NULL;
999 }
1000 pParent->papChildren = (PDBGGUISTATSNODE *)pvNew;
1001 }
1002
1003 /*
1004 * Insert it.
1005 */
1006 pNode->pParent = pParent;
1007 if (iPosition >= pParent->cChildren)
1008 /* Last. */
1009 iPosition = pParent->cChildren;
1010 else
1011 {
1012 /* Shift all the items after ours. */
1013 uint32_t iShift = pParent->cChildren;
1014 while (iShift-- > iPosition)
1015 {
1016 PDBGGUISTATSNODE pChild = pParent->papChildren[iShift];
1017 pParent->papChildren[iShift + 1] = pChild;
1018 pChild->iSelf = iShift + 1;
1019 }
1020 }
1021
1022 /* Insert ours */
1023 pNode->iSelf = iPosition;
1024 pParent->papChildren[iPosition] = pNode;
1025 pParent->cChildren++;
1026
1027 return pNode;
1028}
1029
1030
1031PDBGGUISTATSNODE
1032VBoxDbgStatsModel::createAndInsert(PDBGGUISTATSNODE pParent, const char *pszName, size_t cchName, uint32_t iPosition)
1033{
1034 PDBGGUISTATSNODE pNode;
1035 if (m_fUpdateInsertRemove)
1036 pNode = createAndInsertNode(pParent, pszName, cchName, iPosition);
1037 else
1038 {
1039 beginInsertRows(createIndex(pParent->iSelf, 0, pParent), iPosition, iPosition);
1040 pNode = createAndInsertNode(pParent, pszName, cchName, iPosition);
1041 endInsertRows();
1042 }
1043 return pNode;
1044}
1045
1046/*static*/ PDBGGUISTATSNODE
1047VBoxDbgStatsModel::removeNode(PDBGGUISTATSNODE pNode)
1048{
1049 PDBGGUISTATSNODE pParent = pNode->pParent;
1050 if (pParent)
1051 {
1052 uint32_t iPosition = pNode->iSelf;
1053 Assert(pParent->papChildren[iPosition] == pNode);
1054 uint32_t const cChildren = --pParent->cChildren;
1055 for (; iPosition < cChildren; iPosition++)
1056 {
1057 PDBGGUISTATSNODE pChild = pParent->papChildren[iPosition + 1];
1058 pParent->papChildren[iPosition] = pChild;
1059 pChild->iSelf = iPosition;
1060 }
1061#ifdef VBOX_STRICT /* poison */
1062 pParent->papChildren[iPosition] = (PDBGGUISTATSNODE)0x42;
1063#endif
1064 }
1065 return pNode;
1066}
1067
1068
1069/*static*/ void
1070VBoxDbgStatsModel::removeAndDestroyNode(PDBGGUISTATSNODE pNode)
1071{
1072 removeNode(pNode);
1073 destroyNode(pNode);
1074}
1075
1076
1077void
1078VBoxDbgStatsModel::removeAndDestroy(PDBGGUISTATSNODE pNode)
1079{
1080 if (m_fUpdateInsertRemove)
1081 removeAndDestroyNode(pNode);
1082 else
1083 {
1084 /*
1085 * Removing is fun since the docs are imprecise as to how persistent
1086 * indexes are updated (or aren't). So, let try a few different ideas
1087 * and see which works.
1088 */
1089#if 1
1090 /* destroy the children first with the appropriate begin/endRemoveRows signals. */
1091 DBGGUISTATSSTACK Stack;
1092 Stack.a[0].pNode = pNode;
1093 Stack.a[0].iChild = -1;
1094 Stack.iTop = 0;
1095 while (Stack.iTop >= 0)
1096 {
1097 /* get top element */
1098 PDBGGUISTATSNODE pCurNode = Stack.a[Stack.iTop].pNode;
1099 uint32_t iChild = ++Stack.a[Stack.iTop].iChild;
1100 if (iChild < pCurNode->cChildren)
1101 {
1102 /* push */
1103 Stack.iTop++;
1104 Assert(Stack.iTop < (int32_t)RT_ELEMENTS(Stack.a));
1105 Stack.a[Stack.iTop].pNode = pCurNode->papChildren[iChild];
1106 Stack.a[Stack.iTop].iChild = 0;
1107 }
1108 else
1109 {
1110 /* pop and destroy all the children. */
1111 Stack.iTop--;
1112 uint32_t i = pCurNode->cChildren;
1113 if (i)
1114 {
1115 beginRemoveRows(createIndex(pCurNode->iSelf, 0, pCurNode), 0, i - 1);
1116 while (i-- > 0)
1117 destroyNode(pCurNode->papChildren[i]);
1118 pCurNode->cChildren = 0;
1119 endRemoveRows();
1120 }
1121 }
1122 }
1123 Assert(!pNode->cChildren);
1124
1125 /* finally the node it self. */
1126 PDBGGUISTATSNODE pParent = pNode->pParent;
1127 beginRemoveRows(createIndex(pParent->iSelf, 0, pParent), pNode->iSelf, pNode->iSelf);
1128 removeAndDestroyNode(pNode);
1129 endRemoveRows();
1130
1131#elif 0
1132 /* This ain't working, leaves invalid indexes behind. */
1133 PDBGGUISTATSNODE pParent = pNode->pParent;
1134 beginRemoveRows(createIndex(pParent->iSelf, 0, pParent), pNode->iSelf, pNode->iSelf);
1135 removeAndDestroyNode(pNode);
1136 endRemoveRows();
1137#else
1138 /* Force reset() of the model after the update. */
1139 m_fUpdateInsertRemove = true;
1140 removeAndDestroyNode(pNode);
1141#endif
1142 }
1143}
1144
1145
1146/*static*/ void
1147VBoxDbgStatsModel::resetNode(PDBGGUISTATSNODE pNode)
1148{
1149 /* free and reinit the data. */
1150 if (pNode->enmType == STAMTYPE_CALLBACK)
1151 {
1152 delete pNode->Data.pStr;
1153 pNode->Data.pStr = NULL;
1154 }
1155 pNode->enmType = STAMTYPE_INVALID;
1156
1157 /* free the description. */
1158 if (pNode->pDescStr)
1159 {
1160 delete pNode->pDescStr;
1161 pNode->pDescStr = NULL;
1162 }
1163}
1164
1165
1166/*static*/ int
1167VBoxDbgStatsModel::initNode(PDBGGUISTATSNODE pNode, STAMTYPE enmType, void *pvSample,
1168 const char *pszUnit, const char *pszDesc)
1169{
1170 /*
1171 * Copy the data.
1172 */
1173 pNode->pszUnit = pszUnit;
1174 Assert(pNode->enmType == STAMTYPE_INVALID);
1175 pNode->enmType = enmType;
1176 if (pszDesc)
1177 pNode->pDescStr = new QString(pszDesc); /* ignore allocation failure (well, at least up to the point we can ignore it) */
1178
1179 switch (enmType)
1180 {
1181 case STAMTYPE_COUNTER:
1182 pNode->Data.Counter = *(PSTAMCOUNTER)pvSample;
1183 break;
1184
1185 case STAMTYPE_PROFILE:
1186 case STAMTYPE_PROFILE_ADV:
1187 pNode->Data.Profile = *(PSTAMPROFILE)pvSample;
1188 break;
1189
1190 case STAMTYPE_RATIO_U32:
1191 case STAMTYPE_RATIO_U32_RESET:
1192 pNode->Data.RatioU32 = *(PSTAMRATIOU32)pvSample;
1193 break;
1194
1195 case STAMTYPE_CALLBACK:
1196 {
1197 const char *pszString = (const char *)pvSample;
1198 pNode->Data.pStr = new QString(pszString);
1199 break;
1200 }
1201
1202 case STAMTYPE_U8:
1203 case STAMTYPE_U8_RESET:
1204 case STAMTYPE_X8:
1205 case STAMTYPE_X8_RESET:
1206 pNode->Data.u8 = *(uint8_t *)pvSample;
1207 break;
1208
1209 case STAMTYPE_U16:
1210 case STAMTYPE_U16_RESET:
1211 case STAMTYPE_X16:
1212 case STAMTYPE_X16_RESET:
1213 pNode->Data.u16 = *(uint16_t *)pvSample;
1214 break;
1215
1216 case STAMTYPE_U32:
1217 case STAMTYPE_U32_RESET:
1218 case STAMTYPE_X32:
1219 case STAMTYPE_X32_RESET:
1220 pNode->Data.u32 = *(uint32_t *)pvSample;
1221 break;
1222
1223 case STAMTYPE_U64:
1224 case STAMTYPE_U64_RESET:
1225 case STAMTYPE_X64:
1226 case STAMTYPE_X64_RESET:
1227 pNode->Data.u64 = *(uint64_t *)pvSample;
1228 break;
1229
1230 case STAMTYPE_BOOL:
1231 case STAMTYPE_BOOL_RESET:
1232 pNode->Data.f = *(bool *)pvSample;
1233 break;
1234
1235 default:
1236 AssertMsgFailed(("%d\n", enmType));
1237 break;
1238 }
1239
1240 return VINF_SUCCESS;
1241}
1242
1243
1244
1245
1246/*static*/ void
1247VBoxDbgStatsModel::updateNode(PDBGGUISTATSNODE pNode, STAMTYPE enmType, void *pvSample, const char *pszUnit, const char *pszDesc)
1248{
1249 /*
1250 * Reset and init the node if the type changed.
1251 */
1252 if (enmType != pNode->enmType)
1253 {
1254 if (pNode->enmType != STAMTYPE_INVALID)
1255 resetNode(pNode);
1256 initNode(pNode, enmType, pvSample, pszUnit, pszDesc);
1257 pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
1258 }
1259 else
1260 {
1261 /*
1262 * ASSUME that only the sample value will change and that the unit, visibility
1263 * and description remains the same.
1264 */
1265
1266 int64_t iDelta;
1267 switch (enmType)
1268 {
1269 case STAMTYPE_COUNTER:
1270 {
1271 uint64_t cPrev = pNode->Data.Counter.c;
1272 pNode->Data.Counter = *(PSTAMCOUNTER)pvSample;
1273 iDelta = pNode->Data.Counter.c - cPrev;
1274 if (iDelta || pNode->i64Delta)
1275 {
1276 pNode->i64Delta = iDelta;
1277 pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
1278 }
1279 break;
1280 }
1281
1282 case STAMTYPE_PROFILE:
1283 case STAMTYPE_PROFILE_ADV:
1284 {
1285 uint64_t cPrevPeriods = pNode->Data.Profile.cPeriods;
1286 pNode->Data.Profile = *(PSTAMPROFILE)pvSample;
1287 iDelta = pNode->Data.Profile.cPeriods - cPrevPeriods;
1288 if (iDelta || pNode->i64Delta)
1289 {
1290 pNode->i64Delta = iDelta;
1291 pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
1292 }
1293 break;
1294 }
1295
1296 case STAMTYPE_RATIO_U32:
1297 case STAMTYPE_RATIO_U32_RESET:
1298 {
1299 STAMRATIOU32 Prev = pNode->Data.RatioU32;
1300 pNode->Data.RatioU32 = *(PSTAMRATIOU32)pvSample;
1301 int32_t iDeltaA = pNode->Data.RatioU32.u32A - Prev.u32A;
1302 int32_t iDeltaB = pNode->Data.RatioU32.u32B - Prev.u32B;
1303 if (iDeltaA == 0 && iDeltaB == 0)
1304 {
1305 if (pNode->i64Delta)
1306 {
1307 pNode->i64Delta = 0;
1308 pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
1309 }
1310 }
1311 else
1312 {
1313 if (iDeltaA >= 0)
1314 pNode->i64Delta = iDeltaA + (iDeltaB >= 0 ? iDeltaB : -iDeltaB);
1315 else
1316 pNode->i64Delta = iDeltaA + (iDeltaB < 0 ? iDeltaB : -iDeltaB);
1317 pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
1318 }
1319 break;
1320 }
1321
1322 case STAMTYPE_CALLBACK:
1323 {
1324 const char *pszString = (const char *)pvSample;
1325 if (!pNode->Data.pStr)
1326 {
1327 pNode->Data.pStr = new QString(pszString);
1328 pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
1329 }
1330 else if (*pNode->Data.pStr == pszString)
1331 {
1332 delete pNode->Data.pStr;
1333 pNode->Data.pStr = new QString(pszString);
1334 pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
1335 }
1336 break;
1337 }
1338
1339 case STAMTYPE_U8:
1340 case STAMTYPE_U8_RESET:
1341 case STAMTYPE_X8:
1342 case STAMTYPE_X8_RESET:
1343 {
1344 uint8_t uPrev = pNode->Data.u8;
1345 pNode->Data.u8 = *(uint8_t *)pvSample;
1346 iDelta = (int32_t)pNode->Data.u8 - (int32_t)uPrev;
1347 if (iDelta || pNode->i64Delta)
1348 {
1349 pNode->i64Delta = iDelta;
1350 pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
1351 }
1352 break;
1353 }
1354
1355 case STAMTYPE_U16:
1356 case STAMTYPE_U16_RESET:
1357 case STAMTYPE_X16:
1358 case STAMTYPE_X16_RESET:
1359 {
1360 uint16_t uPrev = pNode->Data.u16;
1361 pNode->Data.u16 = *(uint16_t *)pvSample;
1362 iDelta = (int32_t)pNode->Data.u16 - (int32_t)uPrev;
1363 if (iDelta || pNode->i64Delta)
1364 {
1365 pNode->i64Delta = iDelta;
1366 pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
1367 }
1368 break;
1369 }
1370
1371 case STAMTYPE_U32:
1372 case STAMTYPE_U32_RESET:
1373 case STAMTYPE_X32:
1374 case STAMTYPE_X32_RESET:
1375 {
1376 uint32_t uPrev = pNode->Data.u32;
1377 pNode->Data.u32 = *(uint32_t *)pvSample;
1378 iDelta = (int64_t)pNode->Data.u32 - (int64_t)uPrev;
1379 if (iDelta || pNode->i64Delta)
1380 {
1381 pNode->i64Delta = iDelta;
1382 pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
1383 }
1384 break;
1385 }
1386
1387 case STAMTYPE_U64:
1388 case STAMTYPE_U64_RESET:
1389 case STAMTYPE_X64:
1390 case STAMTYPE_X64_RESET:
1391 {
1392 uint64_t uPrev = pNode->Data.u64;
1393 pNode->Data.u64 = *(uint64_t *)pvSample;
1394 iDelta = pNode->Data.u64 - uPrev;
1395 if (iDelta || pNode->i64Delta)
1396 {
1397 pNode->i64Delta = iDelta;
1398 pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
1399 }
1400 break;
1401 }
1402
1403 case STAMTYPE_BOOL:
1404 case STAMTYPE_BOOL_RESET:
1405 {
1406 bool fPrev = pNode->Data.f;
1407 pNode->Data.f = *(bool *)pvSample;
1408 iDelta = pNode->Data.f - fPrev;
1409 if (iDelta || pNode->i64Delta)
1410 {
1411 pNode->i64Delta = iDelta;
1412 pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
1413 }
1414 break;
1415 }
1416
1417 default:
1418 AssertMsgFailed(("%d\n", enmType));
1419 break;
1420 }
1421 }
1422}
1423
1424
1425/*static*/ ssize_t
1426VBoxDbgStatsModel::getNodePath(PCDBGGUISTATSNODE pNode, char *psz, ssize_t cch)
1427{
1428 ssize_t off;
1429 if (!pNode->pParent)
1430 {
1431 /* root - don't add it's slash! */
1432 AssertReturn(cch >= 1, -1);
1433 off = 0;
1434 *psz = '\0';
1435 }
1436 else
1437 {
1438 cch -= pNode->cchName + 1;
1439 AssertReturn(cch > 0, -1);
1440 off = getNodePath(pNode->pParent, psz, cch);
1441 if (off >= 0)
1442 {
1443 psz[off++] = '/';
1444 memcpy(&psz[off], pNode->pszName, pNode->cchName + 1);
1445 off += pNode->cchName;
1446 }
1447 }
1448 return off;
1449}
1450
1451
1452/*static*/ char *
1453VBoxDbgStatsModel::getNodePath2(PCDBGGUISTATSNODE pNode, char *psz, ssize_t cch)
1454{
1455 if (VBoxDbgStatsModel::getNodePath(pNode, psz, cch) < 0)
1456 return NULL;
1457 return psz;
1458}
1459
1460
1461/*static*/ QString
1462VBoxDbgStatsModel::getNodePattern(PCDBGGUISTATSNODE pNode, bool fSubTree /*= true*/)
1463{
1464 /* the node pattern. */
1465 char szPat[1024+1024+4];
1466 ssize_t cch = getNodePath(pNode, szPat, 1024);
1467 AssertReturn(cch >= 0, QString("//////////////////////////////////////////////////////"));
1468
1469 /* the sub-tree pattern. */
1470 if (fSubTree && pNode->cChildren)
1471 {
1472 char *psz = &szPat[cch];
1473 *psz++ = '|';
1474 memcpy(psz, szPat, cch);
1475 psz += cch;
1476 *psz++ = '/';
1477 *psz++ = '*';
1478 *psz++ = '\0';
1479 }
1480 return szPat;
1481}
1482
1483
1484/*static*/ bool
1485VBoxDbgStatsModel::isNodeAncestorOf(PCDBGGUISTATSNODE pAncestor, PCDBGGUISTATSNODE pDescendant)
1486{
1487 while (pDescendant)
1488 {
1489 pDescendant = pDescendant->pParent;
1490 if (pDescendant == pAncestor)
1491 return true;
1492 }
1493 return false;
1494}
1495
1496
1497/*static*/ PDBGGUISTATSNODE
1498VBoxDbgStatsModel::nextNode(PDBGGUISTATSNODE pNode)
1499{
1500 if (!pNode)
1501 return NULL;
1502
1503 /* descend to children. */
1504 if (pNode->cChildren)
1505 return pNode->papChildren[0];
1506
1507 PDBGGUISTATSNODE pParent = pNode->pParent;
1508 if (!pParent)
1509 return NULL;
1510
1511 /* next sibling. */
1512 if (pNode->iSelf + 1 < pNode->pParent->cChildren)
1513 return pParent->papChildren[pNode->iSelf + 1];
1514
1515 /* ascend and advanced to a parent's sibiling. */
1516 for (;;)
1517 {
1518 uint32_t iSelf = pParent->iSelf;
1519 pParent = pParent->pParent;
1520 if (!pParent)
1521 return NULL;
1522 if (iSelf + 1 < pParent->cChildren)
1523 return pParent->papChildren[iSelf + 1];
1524 }
1525}
1526
1527
1528/*static*/ PDBGGUISTATSNODE
1529VBoxDbgStatsModel::nextDataNode(PDBGGUISTATSNODE pNode)
1530{
1531 do
1532 pNode = nextNode(pNode);
1533 while ( pNode
1534 && pNode->enmType == STAMTYPE_INVALID);
1535 return pNode;
1536}
1537
1538
1539/*static*/ PDBGGUISTATSNODE
1540VBoxDbgStatsModel::prevNode(PDBGGUISTATSNODE pNode)
1541{
1542 if (!pNode)
1543 return NULL;
1544 PDBGGUISTATSNODE pParent = pNode->pParent;
1545 if (!pParent)
1546 return NULL;
1547
1548 /* previous sibling's latest descendant (better expression anyone?). */
1549 if (pNode->iSelf > 0)
1550 {
1551 pNode = pParent->papChildren[pNode->iSelf - 1];
1552 while (pNode->cChildren)
1553 pNode = pNode->papChildren[pNode->cChildren - 1];
1554 return pNode;
1555 }
1556
1557 /* ascend to the parent. */
1558 return pParent;
1559}
1560
1561
1562/*static*/ PDBGGUISTATSNODE
1563VBoxDbgStatsModel::prevDataNode(PDBGGUISTATSNODE pNode)
1564{
1565 do
1566 pNode = prevNode(pNode);
1567 while ( pNode
1568 && pNode->enmType == STAMTYPE_INVALID);
1569 return pNode;
1570}
1571
1572
1573#if 0
1574/*static*/ PDBGGUISTATSNODE
1575VBoxDbgStatsModel::createNewTree(IMachineDebugger *a_pIMachineDebugger)
1576{
1577 /** @todo */
1578 return NULL;
1579}
1580#endif
1581
1582
1583#if 0
1584/*static*/ PDBGGUISTATSNODE
1585VBoxDbgStatsModel::createNewTree(const char *pszFilename)
1586{
1587 /** @todo */
1588 return NULL;
1589}
1590#endif
1591
1592
1593#if 0
1594/*static*/ PDBGGUISTATSNODE
1595VBoxDbgStatsModel::createDiffTree(PDBGGUISTATSNODE pTree1, PDBGGUISTATSNODE pTree2)
1596{
1597 /** @todo */
1598 return NULL;
1599}
1600#endif
1601
1602
1603PDBGGUISTATSNODE
1604VBoxDbgStatsModel::updateCallbackHandleOutOfOrder(const char *pszName)
1605{
1606#if defined(VBOX_STRICT) || defined(LOG_ENABLED)
1607 char szStrict[1024];
1608#endif
1609
1610 /*
1611 * We might be inserting a new node between pPrev and pNode
1612 * or we might be removing one or more nodes. Either case is
1613 * handled in the same rough way.
1614 *
1615 * Might consider optimizing insertion at some later point since this
1616 * is a normal occurrence (dynamic statistics in PATM, IOM, MM, ++).
1617 */
1618 Assert(pszName[0] == '/');
1619 Assert(m_szUpdateParent[m_cchUpdateParent - 1] == '/');
1620
1621 /*
1622 * Start with the current parent node and look for a common ancestor
1623 * hoping that this is faster than going from the root (saves lookup).
1624 */
1625 PDBGGUISTATSNODE pNode = m_pUpdateParent->papChildren[m_iUpdateChild];
1626 PDBGGUISTATSNODE const pPrev = prevDataNode(pNode);
1627 AssertMsg(strcmp(pszName, getNodePath2(pNode, szStrict, sizeof(szStrict))), ("%s\n", szStrict));
1628 AssertMsg(!pPrev || strcmp(pszName, getNodePath2(pPrev, szStrict, sizeof(szStrict))), ("%s\n", szStrict));
1629 Log(("updateCallbackHandleOutOfOrder: pszName='%s' m_szUpdateParent='%s' m_cchUpdateParent=%u pNode='%s'\n",
1630 pszName, m_szUpdateParent, m_cchUpdateParent, getNodePath2(pNode, szStrict, sizeof(szStrict))));
1631
1632 pNode = pNode->pParent;
1633 while (pNode != m_pRoot)
1634 {
1635 if (!strncmp(pszName, m_szUpdateParent, m_cchUpdateParent))
1636 break;
1637 Assert(m_cchUpdateParent > pNode->cchName);
1638 m_cchUpdateParent -= pNode->cchName + 1;
1639 m_szUpdateParent[m_cchUpdateParent] = '\0';
1640 Log2(("updateCallbackHandleOutOfOrder: m_szUpdateParent='%s' m_cchUpdateParent=%u, removed '/%s' (%u)\n", m_szUpdateParent, m_cchUpdateParent, pNode->pszName, __LINE__));
1641 pNode = pNode->pParent;
1642 }
1643 Assert(m_szUpdateParent[m_cchUpdateParent - 1] == '/');
1644
1645 /*
1646 * Descend until we've found/created the node pszName indicates,
1647 * modifying m_szUpdateParent as we go along.
1648 */
1649 while (pszName[m_cchUpdateParent - 1] == '/')
1650 {
1651 /* Find the end of this component. */
1652 const char * const pszSubName = &pszName[m_cchUpdateParent];
1653 const char *pszEnd = strchr(pszSubName, '/');
1654 if (!pszEnd)
1655 pszEnd = strchr(pszSubName, '\0');
1656 size_t cchSubName = pszEnd - pszSubName;
1657
1658 /* Add the name to the path. */
1659 memcpy(&m_szUpdateParent[m_cchUpdateParent], pszSubName, cchSubName);
1660 m_cchUpdateParent += cchSubName;
1661 m_szUpdateParent[m_cchUpdateParent++] = '/';
1662 m_szUpdateParent[m_cchUpdateParent] = '\0';
1663 Assert(m_cchUpdateParent < sizeof(m_szUpdateParent));
1664 Log2(("updateCallbackHandleOutOfOrder: m_szUpdateParent='%s' m_cchUpdateParent=%u (%u)\n", m_szUpdateParent, m_cchUpdateParent, __LINE__));
1665
1666 if (!pNode->cChildren)
1667 {
1668 /* first child */
1669 pNode = createAndInsert(pNode, pszSubName, cchSubName, 0);
1670 AssertReturn(pNode, NULL);
1671 }
1672 else
1673 {
1674 /* binary search. */
1675 int32_t iStart = 0;
1676 int32_t iLast = pNode->cChildren - 1;
1677 for (;;)
1678 {
1679 int32_t i = iStart + (iLast + 1 - iStart) / 2;
1680 int iDiff;
1681 size_t const cchCompare = RT_MIN(pNode->papChildren[i]->cchName, cchSubName);
1682 iDiff = memcmp(pszSubName, pNode->papChildren[i]->pszName, cchCompare);
1683 if (!iDiff)
1684 {
1685 iDiff = cchSubName == cchCompare ? 0 : cchSubName > cchCompare ? 1 : -1;
1686 /* For cases when exisiting node name is same as new node name with additional characters. */
1687 if (!iDiff)
1688 iDiff = cchSubName == pNode->papChildren[i]->cchName ? 0 : cchSubName > pNode->papChildren[i]->cchName ? 1 : -1;
1689 }
1690 if (iDiff > 0)
1691 {
1692 iStart = i + 1;
1693 if (iStart > iLast)
1694 {
1695 pNode = createAndInsert(pNode, pszSubName, cchSubName, iStart);
1696 AssertReturn(pNode, NULL);
1697 break;
1698 }
1699 }
1700 else if (iDiff < 0)
1701 {
1702 iLast = i - 1;
1703 if (iLast < iStart)
1704 {
1705 pNode = createAndInsert(pNode, pszSubName, cchSubName, i);
1706 AssertReturn(pNode, NULL);
1707 break;
1708 }
1709 }
1710 else
1711 {
1712 pNode = pNode->papChildren[i];
1713 break;
1714 }
1715 }
1716 }
1717 }
1718 Assert( !memcmp(pszName, m_szUpdateParent, m_cchUpdateParent - 2)
1719 && pszName[m_cchUpdateParent - 1] == '\0');
1720
1721 /*
1722 * Remove all the nodes between pNode and pPrev but keep all
1723 * of pNode's ancestors (or it'll get orphaned).
1724 */
1725 PDBGGUISTATSNODE pCur = prevNode(pNode);
1726 while (pCur != pPrev)
1727 {
1728 PDBGGUISTATSNODE pAdv = prevNode(pCur); Assert(pAdv || !pPrev);
1729 if (!isNodeAncestorOf(pCur, pNode))
1730 {
1731 Assert(pCur != m_pRoot);
1732 removeAndDestroy(pCur);
1733 }
1734 pCur = pAdv;
1735 }
1736
1737 /*
1738 * Remove the data from all ancestors of pNode that it doesn't
1739 * share them pPrev.
1740 */
1741 if (pPrev)
1742 {
1743 pCur = pNode->pParent;
1744 while (!isNodeAncestorOf(pCur, pPrev))
1745 {
1746 resetNode(pNode);
1747 pCur = pCur->pParent;
1748 }
1749 }
1750
1751 /*
1752 * Finally, adjust the globals (szUpdateParent is one level too deep).
1753 */
1754 Assert(m_cchUpdateParent > pNode->cchName + 1);
1755 m_cchUpdateParent -= pNode->cchName + 1;
1756 m_szUpdateParent[m_cchUpdateParent] = '\0';
1757 m_pUpdateParent = pNode->pParent;
1758 m_iUpdateChild = pNode->iSelf;
1759 Log2(("updateCallbackHandleOutOfOrder: m_szUpdateParent='%s' m_cchUpdateParent=%u (%u)\n", m_szUpdateParent, m_cchUpdateParent, __LINE__));
1760
1761 return pNode;
1762}
1763
1764
1765PDBGGUISTATSNODE
1766VBoxDbgStatsModel::updateCallbackHandleTail(const char *pszName)
1767{
1768 /*
1769 * Insert it at the end of the tree.
1770 *
1771 * Do the same as we're doing down in createNewTreeCallback, walk from the
1772 * root and create whatever we need.
1773 */
1774 AssertReturn(*pszName == '/' && pszName[1] != '/', NULL);
1775 PDBGGUISTATSNODE pNode = m_pRoot;
1776 const char *pszCur = pszName + 1;
1777 while (*pszCur)
1778 {
1779 /* Find the end of this component. */
1780 const char *pszNext = strchr(pszCur, '/');
1781 if (!pszNext)
1782 pszNext = strchr(pszCur, '\0');
1783 size_t cchCur = pszNext - pszCur;
1784
1785 /* Create it if it doesn't exist (it will be last if it exists). */
1786 if ( !pNode->cChildren
1787 || strncmp(pNode->papChildren[pNode->cChildren - 1]->pszName, pszCur, cchCur)
1788 || pNode->papChildren[pNode->cChildren - 1]->pszName[cchCur])
1789 {
1790 pNode = createAndInsert(pNode, pszCur, pszNext - pszCur, pNode->cChildren);
1791 AssertReturn(pNode, NULL);
1792 }
1793 else
1794 pNode = pNode->papChildren[pNode->cChildren - 1];
1795
1796 /* Advance */
1797 pszCur = *pszNext ? pszNext + 1 : pszNext;
1798 }
1799
1800 return pNode;
1801}
1802
1803
1804void
1805VBoxDbgStatsModel::updateCallbackAdvance(PDBGGUISTATSNODE pNode)
1806{
1807 /*
1808 * Advance to the next node with data.
1809 *
1810 * ASSUMES a leaf *must* have data and again we're ASSUMING the sorting
1811 * on slash separated sub-strings.
1812 */
1813 if (m_iUpdateChild != UINT32_MAX)
1814 {
1815#ifdef VBOX_STRICT
1816 PDBGGUISTATSNODE const pCorrectNext = nextDataNode(pNode);
1817#endif
1818 PDBGGUISTATSNODE pParent = pNode->pParent;
1819 if (pNode->cChildren)
1820 {
1821 /* descend to the first child. */
1822 Assert(m_cchUpdateParent + pNode->cchName + 2 < sizeof(m_szUpdateParent));
1823 memcpy(&m_szUpdateParent[m_cchUpdateParent], pNode->pszName, pNode->cchName);
1824 m_cchUpdateParent += pNode->cchName;
1825 m_szUpdateParent[m_cchUpdateParent++] = '/';
1826 m_szUpdateParent[m_cchUpdateParent] = '\0';
1827
1828 pNode = pNode->papChildren[0];
1829 }
1830 else if (pNode->iSelf + 1 < pParent->cChildren)
1831 {
1832 /* next sibling or one if its descendants. */
1833 Assert(m_pUpdateParent == pParent);
1834 pNode = pParent->papChildren[pNode->iSelf + 1];
1835 }
1836 else
1837 {
1838 /* move up and down- / on-wards */
1839 for (;;)
1840 {
1841 /* ascend */
1842 pNode = pParent;
1843 pParent = pParent->pParent;
1844 if (!pParent)
1845 {
1846 Assert(pNode == m_pRoot);
1847 m_iUpdateChild = UINT32_MAX;
1848 m_szUpdateParent[0] = '\0';
1849 m_cchUpdateParent = 0;
1850 m_pUpdateParent = NULL;
1851 break;
1852 }
1853 Assert(m_cchUpdateParent > pNode->cchName + 1);
1854 m_cchUpdateParent -= pNode->cchName + 1;
1855
1856 /* try advance */
1857 if (pNode->iSelf + 1 < pParent->cChildren)
1858 {
1859 pNode = pParent->papChildren[pNode->iSelf + 1];
1860 m_szUpdateParent[m_cchUpdateParent] = '\0';
1861 break;
1862 }
1863 }
1864 }
1865
1866 /* descend to a node containing data and finalize the globals. (ASSUMES leaf has data.) */
1867 if (m_iUpdateChild != UINT32_MAX)
1868 {
1869 while ( pNode->enmType == STAMTYPE_INVALID
1870 && pNode->cChildren > 0)
1871 {
1872 Assert(pNode->enmState == kDbgGuiStatsNodeState_kVisible);
1873
1874 Assert(m_cchUpdateParent + pNode->cchName + 2 < sizeof(m_szUpdateParent));
1875 memcpy(&m_szUpdateParent[m_cchUpdateParent], pNode->pszName, pNode->cchName);
1876 m_cchUpdateParent += pNode->cchName;
1877 m_szUpdateParent[m_cchUpdateParent++] = '/';
1878 m_szUpdateParent[m_cchUpdateParent] = '\0';
1879
1880 pNode = pNode->papChildren[0];
1881 }
1882 Assert(pNode->enmType != STAMTYPE_INVALID);
1883 m_iUpdateChild = pNode->iSelf;
1884 m_pUpdateParent = pNode->pParent;
1885 Assert(pNode == pCorrectNext);
1886 }
1887 }
1888 /* else: we're at the end */
1889}
1890
1891
1892/*static*/ DECLCALLBACK(int)
1893VBoxDbgStatsModel::updateCallback(const char *pszName, STAMTYPE enmType, void *pvSample, STAMUNIT enmUnit,
1894 const char *pszUnit, STAMVISIBILITY enmVisibility, const char *pszDesc, void *pvUser)
1895{
1896 VBoxDbgStatsModelVM *pThis = (VBoxDbgStatsModelVM *)pvUser;
1897 Log3(("updateCallback: %s\n", pszName));
1898 RT_NOREF(enmUnit);
1899
1900 /*
1901 * Skip the ones which shouldn't be visible in the GUI.
1902 */
1903 if (enmVisibility == STAMVISIBILITY_NOT_GUI)
1904 return 0;
1905
1906 /*
1907 * The default assumption is that nothing has changed.
1908 * For now we'll reset the model when ever something changes.
1909 */
1910 PDBGGUISTATSNODE pNode;
1911 if (pThis->m_iUpdateChild != UINT32_MAX)
1912 {
1913 pNode = pThis->m_pUpdateParent->papChildren[pThis->m_iUpdateChild];
1914 if ( !strncmp(pszName, pThis->m_szUpdateParent, pThis->m_cchUpdateParent)
1915 && !strcmp(pszName + pThis->m_cchUpdateParent, pNode->pszName))
1916 /* got it! */;
1917 else
1918 {
1919 /* insert/remove */
1920 pNode = pThis->updateCallbackHandleOutOfOrder(pszName);
1921 if (!pNode)
1922 return VERR_NO_MEMORY;
1923 }
1924 }
1925 else
1926 {
1927 /* append */
1928 pNode = pThis->updateCallbackHandleTail(pszName);
1929 if (!pNode)
1930 return VERR_NO_MEMORY;
1931 }
1932
1933 /*
1934 * Perform the update and advance to the next one.
1935 */
1936 updateNode(pNode, enmType, pvSample, pszUnit, pszDesc);
1937 pThis->updateCallbackAdvance(pNode);
1938
1939 return VINF_SUCCESS;
1940}
1941
1942
1943bool
1944VBoxDbgStatsModel::updatePrepare(PDBGGUISTATSNODE a_pSubTree /*= NULL*/)
1945{
1946 /*
1947 * Find the first child with data and set it up as the 'next'
1948 * node to be updated.
1949 */
1950 PDBGGUISTATSNODE pFirst;
1951 Assert(m_pRoot);
1952 Assert(m_pRoot->enmType == STAMTYPE_INVALID);
1953 if (!a_pSubTree)
1954 pFirst = nextDataNode(m_pRoot);
1955 else
1956 pFirst = a_pSubTree->enmType != STAMTYPE_INVALID ? a_pSubTree : nextDataNode(a_pSubTree);
1957 if (pFirst)
1958 {
1959 m_iUpdateChild = pFirst->iSelf;
1960 m_pUpdateParent = pFirst->pParent; Assert(m_pUpdateParent);
1961 m_cchUpdateParent = getNodePath(m_pUpdateParent, m_szUpdateParent, sizeof(m_szUpdateParent) - 1);
1962 AssertReturn(m_cchUpdateParent >= 1, false);
1963 m_szUpdateParent[m_cchUpdateParent++] = '/';
1964 m_szUpdateParent[m_cchUpdateParent] = '\0';
1965 }
1966 else
1967 {
1968 m_iUpdateChild = UINT32_MAX;
1969 m_pUpdateParent = NULL;
1970 m_szUpdateParent[0] = '\0';
1971 m_cchUpdateParent = 0;
1972 }
1973
1974 /*
1975 * Set the flag and signal possible layout change.
1976 */
1977 m_fUpdateInsertRemove = false;
1978 /* emit layoutAboutToBeChanged(); - debug this, it gets stuck... */
1979 return true;
1980}
1981
1982
1983bool
1984VBoxDbgStatsModel::updateDone(bool a_fSuccess, PDBGGUISTATSNODE a_pSubTree /*= NULL*/)
1985{
1986 /*
1987 * Remove any nodes following the last in the update (unless the update failed).
1988 */
1989 if ( a_fSuccess
1990 && m_iUpdateChild != UINT32_MAX
1991 && a_pSubTree == NULL)
1992 {
1993 PDBGGUISTATSNODE const pLast = prevDataNode(m_pUpdateParent->papChildren[m_iUpdateChild]);
1994 if (!pLast)
1995 {
1996 /* nuking the whole tree. */
1997 setRootNode(createRootNode());
1998 m_fUpdateInsertRemove = true;
1999 }
2000 else
2001 {
2002 PDBGGUISTATSNODE pNode;
2003 while ((pNode = nextNode(pLast)))
2004 {
2005 Assert(pNode != m_pRoot);
2006 removeAndDestroy(pNode);
2007 }
2008 }
2009 }
2010
2011 /*
2012 * We're done making layout changes (if I understood it correctly), so,
2013 * signal this and then see what to do next. If we did too many removals
2014 * we'll just reset the whole shebang.
2015 */
2016 if (m_fUpdateInsertRemove)
2017 {
2018#if 0 /* hrmpf, layoutChanged() didn't work reliably at some point so doing this as well... */
2019 beginResetModel();
2020 endResetModel();
2021#else
2022 emit layoutChanged();
2023#endif
2024 }
2025 else
2026 {
2027 /*
2028 * Send dataChanged events.
2029 *
2030 * We do this here instead of from the updateCallback because it reduces
2031 * the clutter in that method and allow us to emit bulk signals in an
2032 * easier way because we can traverse the tree in a different fashion.
2033 */
2034 DBGGUISTATSSTACK Stack;
2035 Stack.a[0].pNode = !a_pSubTree ? m_pRoot : a_pSubTree;
2036 Stack.a[0].iChild = -1;
2037 Stack.iTop = 0;
2038
2039 while (Stack.iTop >= 0)
2040 {
2041 /* get top element */
2042 PDBGGUISTATSNODE pNode = Stack.a[Stack.iTop].pNode;
2043 uint32_t iChild = ++Stack.a[Stack.iTop].iChild;
2044 if (iChild < pNode->cChildren)
2045 {
2046 /* push */
2047 Stack.iTop++;
2048 Assert(Stack.iTop < (int32_t)RT_ELEMENTS(Stack.a));
2049 Stack.a[Stack.iTop].pNode = pNode->papChildren[iChild];
2050 Stack.a[Stack.iTop].iChild = -1;
2051 }
2052 else
2053 {
2054 /* pop */
2055 Stack.iTop--;
2056
2057 /* do the actual work. */
2058 iChild = 0;
2059 while (iChild < pNode->cChildren)
2060 {
2061 /* skip to the first needing updating. */
2062 while ( iChild < pNode->cChildren
2063 && pNode->papChildren[iChild]->enmState != kDbgGuiStatsNodeState_kRefresh)
2064 iChild++;
2065 if (iChild >= pNode->cChildren)
2066 break;
2067 PDBGGUISTATSNODE pChild = pNode->papChildren[iChild];
2068 QModelIndex const TopLeft = createIndex(iChild, 2, pChild);
2069 pChild->enmState = kDbgGuiStatsNodeState_kVisible;
2070
2071 /* Any subsequent nodes that also needs refreshing? */
2072 int const iRightCol = pChild->enmType != STAMTYPE_PROFILE && pChild->enmType != STAMTYPE_PROFILE_ADV ? 4 : 7;
2073 if (iRightCol == 4)
2074 while ( iChild + 1 < pNode->cChildren
2075 && (pChild = pNode->papChildren[iChild + 1])->enmState == kDbgGuiStatsNodeState_kRefresh
2076 && pChild->enmType != STAMTYPE_PROFILE
2077 && pChild->enmType != STAMTYPE_PROFILE_ADV)
2078 iChild++;
2079 else
2080 while ( iChild + 1 < pNode->cChildren
2081 && (pChild = pNode->papChildren[iChild + 1])->enmState == kDbgGuiStatsNodeState_kRefresh
2082 && ( pChild->enmType == STAMTYPE_PROFILE
2083 || pChild->enmType == STAMTYPE_PROFILE_ADV))
2084 iChild++;
2085
2086 /* emit the refresh signal */
2087 QModelIndex const BottomRight = createIndex(iChild, iRightCol, pNode->papChildren[iChild]);
2088 emit dataChanged(TopLeft, BottomRight);
2089 iChild++;
2090 }
2091 }
2092 }
2093
2094 /*
2095 * If a_pSubTree is not an intermediate node, invalidate it explicitly.
2096 */
2097 if (a_pSubTree && a_pSubTree->enmType != STAMTYPE_INVALID)
2098 {
2099 int const iRightCol = a_pSubTree->enmType != STAMTYPE_PROFILE && a_pSubTree->enmType != STAMTYPE_PROFILE_ADV
2100 ? 4 : 7;
2101 QModelIndex const BottomRight = createIndex(a_pSubTree->iSelf, iRightCol, a_pSubTree);
2102 QModelIndex const TopLeft = createIndex(a_pSubTree->iSelf, 2, a_pSubTree);
2103 emit dataChanged(TopLeft, BottomRight);
2104 }
2105 }
2106
2107 return m_fUpdateInsertRemove;
2108}
2109
2110
2111bool
2112VBoxDbgStatsModel::updateStatsByPattern(const QString &a_rPatStr, PDBGGUISTATSNODE a_pSubTree /*= NULL*/)
2113{
2114 /* stub */
2115 RT_NOREF(a_rPatStr, a_pSubTree);
2116 return false;
2117}
2118
2119
2120void
2121VBoxDbgStatsModel::updateStatsByIndex(QModelIndex const &a_rIndex)
2122{
2123 PDBGGUISTATSNODE pNode = nodeFromIndex(a_rIndex);
2124 if (pNode == m_pRoot || !a_rIndex.isValid())
2125 updateStatsByPattern(QString());
2126 else if (pNode)
2127 /** @todo this doesn't quite work if pNode is excluded by the m_PatStr. */
2128 updateStatsByPattern(getNodePattern(pNode, true /*fSubTree*/), pNode);
2129}
2130
2131
2132void
2133VBoxDbgStatsModel::resetStatsByPattern(QString const &a_rPatStr)
2134{
2135 /* stub */
2136 NOREF(a_rPatStr);
2137}
2138
2139
2140void
2141VBoxDbgStatsModel::resetStatsByIndex(QModelIndex const &a_rIndex, bool fSubTree /*= true*/)
2142{
2143 PCDBGGUISTATSNODE pNode = nodeFromIndex(a_rIndex);
2144 if (pNode == m_pRoot || !a_rIndex.isValid())
2145 {
2146 /* The root can't be reset, so only take action if fSubTree is set. */
2147 if (fSubTree)
2148 resetStatsByPattern(QString());
2149 }
2150 else if (pNode)
2151 resetStatsByPattern(getNodePattern(pNode, fSubTree));
2152}
2153
2154
2155void
2156VBoxDbgStatsModel::iterateStatsByPattern(QString const &a_rPatStr, VBoxDbgStatsModel::FNITERATOR *a_pfnCallback, void *a_pvUser,
2157 bool a_fMatchChildren /*= true*/)
2158{
2159 const QByteArray &PatBytes = a_rPatStr.toUtf8();
2160 const char * const pszPattern = PatBytes.constData();
2161 size_t const cchPattern = strlen(pszPattern);
2162
2163 DBGGUISTATSSTACK Stack;
2164 Stack.a[0].pNode = m_pRoot;
2165 Stack.a[0].iChild = 0;
2166 Stack.a[0].cchName = 0;
2167 Stack.iTop = 0;
2168
2169 char szName[1024];
2170 szName[0] = '\0';
2171
2172 while (Stack.iTop >= 0)
2173 {
2174 /* get top element */
2175 PDBGGUISTATSNODE const pNode = Stack.a[Stack.iTop].pNode;
2176 uint16_t cchName = Stack.a[Stack.iTop].cchName;
2177 uint32_t const iChild = Stack.a[Stack.iTop].iChild++;
2178 if (iChild < pNode->cChildren)
2179 {
2180 PDBGGUISTATSNODE pChild = pNode->papChildren[iChild];
2181
2182 /* Build the name and match the pattern. */
2183 Assert(cchName + 1 + pChild->cchName < sizeof(szName));
2184 szName[cchName++] = '/';
2185 memcpy(&szName[cchName], pChild->pszName, pChild->cchName);
2186 cchName += (uint16_t)pChild->cchName;
2187 szName[cchName] = '\0';
2188
2189 if (RTStrSimplePatternMultiMatch(pszPattern, cchPattern, szName, cchName, NULL))
2190 {
2191 /* Do callback. */
2192 QModelIndex const Index = createIndex(iChild, 0, pChild);
2193 if (!a_pfnCallback(pChild, Index, szName, a_pvUser))
2194 return;
2195 if (!a_fMatchChildren)
2196 continue;
2197 }
2198
2199 /* push */
2200 Stack.iTop++;
2201 Assert(Stack.iTop < (int32_t)RT_ELEMENTS(Stack.a));
2202 Stack.a[Stack.iTop].pNode = pChild;
2203 Stack.a[Stack.iTop].iChild = 0;
2204 Stack.a[Stack.iTop].cchName = cchName;
2205 }
2206 else
2207 {
2208 /* pop */
2209 Stack.iTop--;
2210 }
2211 }
2212}
2213
2214
2215QModelIndex
2216VBoxDbgStatsModel::getRootIndex(void) const
2217{
2218 if (!m_pRoot)
2219 return QModelIndex();
2220 return createIndex(0, 0, m_pRoot);
2221}
2222
2223
2224void
2225VBoxDbgStatsModel::setRootNode(PDBGGUISTATSNODE a_pRoot)
2226{
2227 PDBGGUISTATSNODE pOldTree = m_pRoot;
2228 m_pRoot = a_pRoot;
2229 destroyTree(pOldTree);
2230 beginResetModel();
2231 endResetModel();
2232}
2233
2234
2235Qt::ItemFlags
2236VBoxDbgStatsModel::flags(const QModelIndex &a_rIndex) const
2237{
2238 Qt::ItemFlags fFlags = QAbstractItemModel::flags(a_rIndex);
2239 return fFlags;
2240}
2241
2242
2243int
2244VBoxDbgStatsModel::columnCount(const QModelIndex &a_rParent) const
2245{
2246 NOREF(a_rParent);
2247 return DBGGUI_STATS_COLUMNS;
2248}
2249
2250
2251int
2252VBoxDbgStatsModel::rowCount(const QModelIndex &a_rParent) const
2253{
2254 PDBGGUISTATSNODE pParent = nodeFromIndex(a_rParent);
2255 return pParent ? pParent->cChildren : 1 /* root */;
2256}
2257
2258
2259bool
2260VBoxDbgStatsModel::hasChildren(const QModelIndex &a_rParent) const
2261{
2262 PDBGGUISTATSNODE pParent = nodeFromIndex(a_rParent);
2263 return pParent ? pParent->cChildren > 0 : true /* root */;
2264}
2265
2266
2267QModelIndex
2268VBoxDbgStatsModel::index(int iRow, int iColumn, const QModelIndex &a_rParent) const
2269{
2270 PDBGGUISTATSNODE pParent = nodeFromIndex(a_rParent);
2271 if (pParent)
2272 {
2273 AssertMsgReturn((unsigned)iRow < pParent->cChildren,
2274 ("iRow=%d >= cChildren=%u (iColumn=%d)\n", iRow, (unsigned)pParent->cChildren, iColumn),
2275 QModelIndex());
2276 AssertMsgReturn((unsigned)iColumn < DBGGUI_STATS_COLUMNS, ("iColumn=%d (iRow=%d)\n", iColumn, iRow), QModelIndex());
2277
2278 PDBGGUISTATSNODE pChild = pParent->papChildren[iRow];
2279 return createIndex(iRow, iColumn, pChild);
2280 }
2281
2282 /* root? */
2283 AssertReturn(a_rParent.isValid() || (iRow == 0 && iColumn >= 0), QModelIndex());
2284 AssertMsgReturn(iRow == 0 && (unsigned)iColumn < DBGGUI_STATS_COLUMNS, ("iRow=%d iColumn=%d", iRow, iColumn), QModelIndex());
2285 return createIndex(0, iColumn, m_pRoot);
2286}
2287
2288
2289QModelIndex
2290VBoxDbgStatsModel::parent(const QModelIndex &a_rChild) const
2291{
2292 PDBGGUISTATSNODE pChild = nodeFromIndex(a_rChild);
2293 if (!pChild)
2294 {
2295 Log(("parent: invalid child\n"));
2296 return QModelIndex(); /* bug */
2297 }
2298 PDBGGUISTATSNODE pParent = pChild->pParent;
2299 if (!pParent)
2300 return QModelIndex(); /* ultimate root */
2301
2302 return createIndex(pParent->iSelf, 0, pParent);
2303}
2304
2305
2306QVariant
2307VBoxDbgStatsModel::headerData(int a_iSection, Qt::Orientation a_eOrientation, int a_eRole) const
2308{
2309 if ( a_eOrientation == Qt::Horizontal
2310 && a_eRole == Qt::DisplayRole)
2311 switch (a_iSection)
2312 {
2313 case 0: return tr("Name");
2314 case 1: return tr("Unit");
2315 case 2: return tr("Value/Times");
2316 case 3: return tr("dInt");
2317 case 4: return tr("Min");
2318 case 5: return tr("Average");
2319 case 6: return tr("Max");
2320 case 7: return tr("Total");
2321 case 8: return tr("Description");
2322 default:
2323 AssertCompile(DBGGUI_STATS_COLUMNS == 9);
2324 return QVariant(); /* bug */
2325 }
2326 else if ( a_eOrientation == Qt::Horizontal
2327 && a_eRole == Qt::TextAlignmentRole)
2328 switch (a_iSection)
2329 {
2330 case 0:
2331 case 1:
2332 return QVariant();
2333 case 2:
2334 case 3:
2335 case 4:
2336 case 5:
2337 case 6:
2338 case 7:
2339 return (int)(Qt::AlignRight | Qt::AlignVCenter);
2340 case 8:
2341 return QVariant();
2342 default:
2343 AssertCompile(DBGGUI_STATS_COLUMNS == 9);
2344 return QVariant(); /* bug */
2345 }
2346
2347 return QVariant();
2348}
2349
2350
2351/*static*/ QString
2352VBoxDbgStatsModel::strUnit(PCDBGGUISTATSNODE pNode)
2353{
2354 return pNode->pszUnit;
2355}
2356
2357
2358/*static*/ QString
2359VBoxDbgStatsModel::strValueTimes(PCDBGGUISTATSNODE pNode)
2360{
2361 char sz[128];
2362
2363 switch (pNode->enmType)
2364 {
2365 case STAMTYPE_COUNTER:
2366 return formatNumber(sz, pNode->Data.Counter.c);
2367
2368 case STAMTYPE_PROFILE:
2369 case STAMTYPE_PROFILE_ADV:
2370 return formatNumber(sz, pNode->Data.Profile.cPeriods);
2371
2372 case STAMTYPE_RATIO_U32:
2373 case STAMTYPE_RATIO_U32_RESET:
2374 {
2375 char szTmp[64];
2376 char *psz = formatNumber(szTmp, pNode->Data.RatioU32.u32A);
2377 size_t off = strlen(psz);
2378 memcpy(sz, psz, off);
2379 sz[off++] = ':';
2380 strcpy(&sz[off], formatNumber(szTmp, pNode->Data.RatioU32.u32B));
2381 return sz;
2382 }
2383
2384 case STAMTYPE_CALLBACK:
2385 return *pNode->Data.pStr;
2386
2387 case STAMTYPE_U8:
2388 case STAMTYPE_U8_RESET:
2389 return formatNumber(sz, pNode->Data.u8);
2390
2391 case STAMTYPE_X8:
2392 case STAMTYPE_X8_RESET:
2393 return formatHexNumber(sz, pNode->Data.u8, 2);
2394
2395 case STAMTYPE_U16:
2396 case STAMTYPE_U16_RESET:
2397 return formatNumber(sz, pNode->Data.u16);
2398
2399 case STAMTYPE_X16:
2400 case STAMTYPE_X16_RESET:
2401 return formatHexNumber(sz, pNode->Data.u16, 4);
2402
2403 case STAMTYPE_U32:
2404 case STAMTYPE_U32_RESET:
2405 return formatNumber(sz, pNode->Data.u32);
2406
2407 case STAMTYPE_X32:
2408 case STAMTYPE_X32_RESET:
2409 return formatHexNumber(sz, pNode->Data.u32, 8);
2410
2411 case STAMTYPE_U64:
2412 case STAMTYPE_U64_RESET:
2413 return formatNumber(sz, pNode->Data.u64);
2414
2415 case STAMTYPE_X64:
2416 case STAMTYPE_X64_RESET:
2417 return formatHexNumber(sz, pNode->Data.u64, 16);
2418
2419 case STAMTYPE_BOOL:
2420 case STAMTYPE_BOOL_RESET:
2421 return pNode->Data.f ? "true" : "false";
2422
2423 default:
2424 AssertMsgFailed(("%d\n", pNode->enmType));
2425 RT_FALL_THRU();
2426 case STAMTYPE_INVALID:
2427 return "";
2428 }
2429}
2430
2431
2432/*static*/ uint64_t
2433VBoxDbgStatsModel::getValueTimesAsUInt(PCDBGGUISTATSNODE pNode)
2434{
2435 switch (pNode->enmType)
2436 {
2437 case STAMTYPE_COUNTER:
2438 return pNode->Data.Counter.c;
2439
2440 case STAMTYPE_PROFILE:
2441 case STAMTYPE_PROFILE_ADV:
2442 return pNode->Data.Profile.cPeriods;
2443
2444 case STAMTYPE_RATIO_U32:
2445 case STAMTYPE_RATIO_U32_RESET:
2446 return RT_MAKE_U64(pNode->Data.RatioU32.u32A, pNode->Data.RatioU32.u32B);
2447
2448 case STAMTYPE_CALLBACK:
2449 return UINT64_MAX;
2450
2451 case STAMTYPE_U8:
2452 case STAMTYPE_U8_RESET:
2453 case STAMTYPE_X8:
2454 case STAMTYPE_X8_RESET:
2455 return pNode->Data.u8;
2456
2457 case STAMTYPE_U16:
2458 case STAMTYPE_U16_RESET:
2459 case STAMTYPE_X16:
2460 case STAMTYPE_X16_RESET:
2461 return pNode->Data.u16;
2462
2463 case STAMTYPE_U32:
2464 case STAMTYPE_U32_RESET:
2465 case STAMTYPE_X32:
2466 case STAMTYPE_X32_RESET:
2467 return pNode->Data.u32;
2468
2469 case STAMTYPE_U64:
2470 case STAMTYPE_U64_RESET:
2471 case STAMTYPE_X64:
2472 case STAMTYPE_X64_RESET:
2473 return pNode->Data.u64;
2474
2475 case STAMTYPE_BOOL:
2476 case STAMTYPE_BOOL_RESET:
2477 return pNode->Data.f;
2478
2479 default:
2480 AssertMsgFailed(("%d\n", pNode->enmType));
2481 RT_FALL_THRU();
2482 case STAMTYPE_INVALID:
2483 return UINT64_MAX;
2484 }
2485}
2486
2487
2488/*static*/ QString
2489VBoxDbgStatsModel::strMinValue(PCDBGGUISTATSNODE pNode)
2490{
2491 char sz[128];
2492
2493 switch (pNode->enmType)
2494 {
2495 case STAMTYPE_PROFILE:
2496 case STAMTYPE_PROFILE_ADV:
2497 if (pNode->Data.Profile.cPeriods)
2498 return formatNumber(sz, pNode->Data.Profile.cTicksMin);
2499 return "0"; /* cTicksMin is set to UINT64_MAX */
2500 default:
2501 return "";
2502 }
2503}
2504
2505
2506/*static*/ uint64_t
2507VBoxDbgStatsModel::getMinValueAsUInt(PCDBGGUISTATSNODE pNode)
2508{
2509 switch (pNode->enmType)
2510 {
2511 case STAMTYPE_PROFILE:
2512 case STAMTYPE_PROFILE_ADV:
2513 if (pNode->Data.Profile.cPeriods)
2514 return pNode->Data.Profile.cTicksMin;
2515 return 0; /* cTicksMin is set to UINT64_MAX */
2516 default:
2517 return UINT64_MAX;
2518 }
2519}
2520
2521
2522/*static*/ QString
2523VBoxDbgStatsModel::strAvgValue(PCDBGGUISTATSNODE pNode)
2524{
2525 char sz[128];
2526
2527 switch (pNode->enmType)
2528 {
2529 case STAMTYPE_PROFILE:
2530 case STAMTYPE_PROFILE_ADV:
2531 if (pNode->Data.Profile.cPeriods)
2532 return formatNumber(sz, pNode->Data.Profile.cTicks / pNode->Data.Profile.cPeriods);
2533 return "0";
2534 default:
2535 return "";
2536 }
2537}
2538
2539
2540/*static*/ uint64_t
2541VBoxDbgStatsModel::getAvgValueAsUInt(PCDBGGUISTATSNODE pNode)
2542{
2543 switch (pNode->enmType)
2544 {
2545 case STAMTYPE_PROFILE:
2546 case STAMTYPE_PROFILE_ADV:
2547 if (pNode->Data.Profile.cPeriods)
2548 return pNode->Data.Profile.cTicks / pNode->Data.Profile.cPeriods;
2549 return 0;
2550 default:
2551 return UINT64_MAX;
2552 }
2553}
2554
2555
2556
2557/*static*/ QString
2558VBoxDbgStatsModel::strMaxValue(PCDBGGUISTATSNODE pNode)
2559{
2560 char sz[128];
2561
2562 switch (pNode->enmType)
2563 {
2564 case STAMTYPE_PROFILE:
2565 case STAMTYPE_PROFILE_ADV:
2566 return formatNumber(sz, pNode->Data.Profile.cTicksMax);
2567 default:
2568 return "";
2569 }
2570}
2571
2572
2573/*static*/ uint64_t
2574VBoxDbgStatsModel::getMaxValueAsUInt(PCDBGGUISTATSNODE pNode)
2575{
2576 switch (pNode->enmType)
2577 {
2578 case STAMTYPE_PROFILE:
2579 case STAMTYPE_PROFILE_ADV:
2580 return pNode->Data.Profile.cTicksMax;
2581 default:
2582 return UINT64_MAX;
2583 }
2584}
2585
2586
2587/*static*/ QString
2588VBoxDbgStatsModel::strTotalValue(PCDBGGUISTATSNODE pNode)
2589{
2590 char sz[128];
2591
2592 switch (pNode->enmType)
2593 {
2594 case STAMTYPE_PROFILE:
2595 case STAMTYPE_PROFILE_ADV:
2596 return formatNumber(sz, pNode->Data.Profile.cTicks);
2597 default:
2598 return "";
2599 }
2600}
2601
2602
2603/*static*/ uint64_t
2604VBoxDbgStatsModel::getTotalValueAsUInt(PCDBGGUISTATSNODE pNode)
2605{
2606 switch (pNode->enmType)
2607 {
2608 case STAMTYPE_PROFILE:
2609 case STAMTYPE_PROFILE_ADV:
2610 return pNode->Data.Profile.cTicks;
2611 default:
2612 return UINT64_MAX;
2613 }
2614}
2615
2616
2617/*static*/ QString
2618VBoxDbgStatsModel::strDeltaValue(PCDBGGUISTATSNODE pNode)
2619{
2620 switch (pNode->enmType)
2621 {
2622 case STAMTYPE_PROFILE:
2623 case STAMTYPE_PROFILE_ADV:
2624 case STAMTYPE_COUNTER:
2625 case STAMTYPE_RATIO_U32:
2626 case STAMTYPE_RATIO_U32_RESET:
2627 case STAMTYPE_U8:
2628 case STAMTYPE_U8_RESET:
2629 case STAMTYPE_X8:
2630 case STAMTYPE_X8_RESET:
2631 case STAMTYPE_U16:
2632 case STAMTYPE_U16_RESET:
2633 case STAMTYPE_X16:
2634 case STAMTYPE_X16_RESET:
2635 case STAMTYPE_U32:
2636 case STAMTYPE_U32_RESET:
2637 case STAMTYPE_X32:
2638 case STAMTYPE_X32_RESET:
2639 case STAMTYPE_U64:
2640 case STAMTYPE_U64_RESET:
2641 case STAMTYPE_X64:
2642 case STAMTYPE_X64_RESET:
2643 case STAMTYPE_BOOL:
2644 case STAMTYPE_BOOL_RESET:
2645 if (pNode->i64Delta)
2646 {
2647 char sz[128];
2648 return formatNumberSigned(sz, pNode->i64Delta, true /*fPositivePlus*/);
2649 }
2650 return "0";
2651 case STAMTYPE_INTERNAL_SUM:
2652 case STAMTYPE_INTERNAL_PCT_OF_SUM:
2653 case STAMTYPE_END:
2654 AssertFailed(); RT_FALL_THRU();
2655 case STAMTYPE_CALLBACK:
2656 case STAMTYPE_INVALID:
2657 break;
2658 }
2659 return "";
2660}
2661
2662
2663QVariant
2664VBoxDbgStatsModel::data(const QModelIndex &a_rIndex, int a_eRole) const
2665{
2666 unsigned iCol = a_rIndex.column();
2667 AssertMsgReturn(iCol < DBGGUI_STATS_COLUMNS, ("%d\n", iCol), QVariant());
2668 Log4(("Model::data(%p(%d,%d), %d)\n", nodeFromIndex(a_rIndex), iCol, a_rIndex.row(), a_eRole));
2669
2670 if (a_eRole == Qt::DisplayRole)
2671 {
2672 PDBGGUISTATSNODE pNode = nodeFromIndex(a_rIndex);
2673 AssertReturn(pNode, QVariant());
2674
2675 switch (iCol)
2676 {
2677 case 0:
2678 return QString(pNode->pszName);
2679 case 1:
2680 return strUnit(pNode);
2681 case 2:
2682 return strValueTimes(pNode);
2683 case 3:
2684 return strDeltaValue(pNode);
2685 case 4:
2686 return strMinValue(pNode);
2687 case 5:
2688 return strAvgValue(pNode);
2689 case 6:
2690 return strMaxValue(pNode);
2691 case 7:
2692 return strTotalValue(pNode);
2693 case 8:
2694 return pNode->pDescStr ? QString(*pNode->pDescStr) : QString("");
2695 default:
2696 AssertCompile(DBGGUI_STATS_COLUMNS == 9);
2697 return QVariant();
2698 }
2699 }
2700 else if (a_eRole == Qt::TextAlignmentRole)
2701 switch (iCol)
2702 {
2703 case 0:
2704 case 1:
2705 return QVariant();
2706 case 2:
2707 case 3:
2708 case 4:
2709 case 5:
2710 case 6:
2711 case 7:
2712 return (int)(Qt::AlignRight | Qt::AlignVCenter);
2713 case 8:
2714 return QVariant();
2715 default:
2716 AssertCompile(DBGGUI_STATS_COLUMNS == 9);
2717 return QVariant(); /* bug */
2718 }
2719 return QVariant();
2720}
2721
2722
2723/*static*/ void
2724VBoxDbgStatsModel::stringifyNodeNoRecursion(PDBGGUISTATSNODE a_pNode, QString &a_rString)
2725{
2726 /*
2727 * Get the path, padding it to 32-chars and add it to the string.
2728 */
2729 char szBuf[1024];
2730 ssize_t off = getNodePath(a_pNode, szBuf, sizeof(szBuf) - 2);
2731 AssertReturnVoid(off >= 0);
2732 if (off < 32)
2733 {
2734 memset(&szBuf[off], ' ', 32 - off);
2735 szBuf[32] = '\0';
2736 off = 32;
2737 }
2738 szBuf[off++] = ' ';
2739 szBuf[off] = '\0';
2740 a_rString += szBuf;
2741
2742 /*
2743 * The following is derived from stamR3PrintOne, except
2744 * we print to szBuf, do no visibility checks and can skip
2745 * the path bit.
2746 */
2747 switch (a_pNode->enmType)
2748 {
2749 case STAMTYPE_COUNTER:
2750 RTStrPrintf(szBuf, sizeof(szBuf), "%8llu %s", a_pNode->Data.Counter.c, a_pNode->pszUnit);
2751 break;
2752
2753 case STAMTYPE_PROFILE:
2754 case STAMTYPE_PROFILE_ADV:
2755 {
2756 uint64_t u64 = a_pNode->Data.Profile.cPeriods ? a_pNode->Data.Profile.cPeriods : 1;
2757 RTStrPrintf(szBuf, sizeof(szBuf),
2758 "%8llu %s (%12llu ticks, %7llu times, max %9llu, min %7lld)",
2759 a_pNode->Data.Profile.cTicks / u64, a_pNode->pszUnit,
2760 a_pNode->Data.Profile.cTicks, a_pNode->Data.Profile.cPeriods, a_pNode->Data.Profile.cTicksMax, a_pNode->Data.Profile.cTicksMin);
2761 break;
2762 }
2763
2764 case STAMTYPE_RATIO_U32:
2765 case STAMTYPE_RATIO_U32_RESET:
2766 RTStrPrintf(szBuf, sizeof(szBuf),
2767 "%8u:%-8u %s",
2768 a_pNode->Data.RatioU32.u32A, a_pNode->Data.RatioU32.u32B, a_pNode->pszUnit);
2769 break;
2770
2771 case STAMTYPE_CALLBACK:
2772 if (a_pNode->Data.pStr)
2773 a_rString += *a_pNode->Data.pStr;
2774 RTStrPrintf(szBuf, sizeof(szBuf), " %s", a_pNode->pszUnit);
2775 break;
2776
2777 case STAMTYPE_U8:
2778 case STAMTYPE_U8_RESET:
2779 RTStrPrintf(szBuf, sizeof(szBuf), "%8u %s", a_pNode->Data.u8, a_pNode->pszUnit);
2780 break;
2781
2782 case STAMTYPE_X8:
2783 case STAMTYPE_X8_RESET:
2784 RTStrPrintf(szBuf, sizeof(szBuf), "%8x %s", a_pNode->Data.u8, a_pNode->pszUnit);
2785 break;
2786
2787 case STAMTYPE_U16:
2788 case STAMTYPE_U16_RESET:
2789 RTStrPrintf(szBuf, sizeof(szBuf), "%8u %s", a_pNode->Data.u16, a_pNode->pszUnit);
2790 break;
2791
2792 case STAMTYPE_X16:
2793 case STAMTYPE_X16_RESET:
2794 RTStrPrintf(szBuf, sizeof(szBuf), "%8x %s", a_pNode->Data.u16, a_pNode->pszUnit);
2795 break;
2796
2797 case STAMTYPE_U32:
2798 case STAMTYPE_U32_RESET:
2799 RTStrPrintf(szBuf, sizeof(szBuf), "%8u %s", a_pNode->Data.u32, a_pNode->pszUnit);
2800 break;
2801
2802 case STAMTYPE_X32:
2803 case STAMTYPE_X32_RESET:
2804 RTStrPrintf(szBuf, sizeof(szBuf), "%8x %s", a_pNode->Data.u32, a_pNode->pszUnit);
2805 break;
2806
2807 case STAMTYPE_U64:
2808 case STAMTYPE_U64_RESET:
2809 RTStrPrintf(szBuf, sizeof(szBuf), "%8llu %s", a_pNode->Data.u64, a_pNode->pszUnit);
2810 break;
2811
2812 case STAMTYPE_X64:
2813 case STAMTYPE_X64_RESET:
2814 RTStrPrintf(szBuf, sizeof(szBuf), "%8llx %s", a_pNode->Data.u64, a_pNode->pszUnit);
2815 break;
2816
2817 case STAMTYPE_BOOL:
2818 case STAMTYPE_BOOL_RESET:
2819 RTStrPrintf(szBuf, sizeof(szBuf), "%s %s", a_pNode->Data.f ? "true " : "false ", a_pNode->pszUnit);
2820 break;
2821
2822 default:
2823 AssertMsgFailed(("enmType=%d\n", a_pNode->enmType));
2824 return;
2825 }
2826
2827 a_rString += szBuf;
2828}
2829
2830
2831/*static*/ void
2832VBoxDbgStatsModel::stringifyNode(PDBGGUISTATSNODE a_pNode, QString &a_rString)
2833{
2834 /* this node (if it has data) */
2835 if (a_pNode->enmType != STAMTYPE_INVALID)
2836 {
2837 if (!a_rString.isEmpty())
2838 a_rString += "\n";
2839 stringifyNodeNoRecursion(a_pNode, a_rString);
2840 }
2841
2842 /* the children */
2843 uint32_t const cChildren = a_pNode->cChildren;
2844 for (uint32_t i = 0; i < cChildren; i++)
2845 stringifyNode(a_pNode->papChildren[i], a_rString);
2846}
2847
2848
2849void
2850VBoxDbgStatsModel::stringifyTree(QModelIndex &a_rRoot, QString &a_rString) const
2851{
2852 PDBGGUISTATSNODE pRoot = a_rRoot.isValid() ? nodeFromIndex(a_rRoot) : m_pRoot;
2853 if (pRoot)
2854 stringifyNode(pRoot, a_rString);
2855}
2856
2857
2858void
2859VBoxDbgStatsModel::copyTreeToClipboard(QModelIndex &a_rRoot) const
2860{
2861 QString String;
2862 stringifyTree(a_rRoot, String);
2863
2864 QClipboard *pClipboard = QApplication::clipboard();
2865 if (pClipboard)
2866 pClipboard->setText(String, QClipboard::Clipboard);
2867}
2868
2869
2870/*static*/ void
2871VBoxDbgStatsModel::logNode(PDBGGUISTATSNODE a_pNode, bool a_fReleaseLog)
2872{
2873 /* this node (if it has data) */
2874 if (a_pNode->enmType != STAMTYPE_INVALID)
2875 {
2876 QString SelfStr;
2877 stringifyNodeNoRecursion(a_pNode, SelfStr);
2878 QByteArray SelfByteArray = SelfStr.toUtf8();
2879 if (a_fReleaseLog)
2880 RTLogRelPrintf("%s\n", SelfByteArray.constData());
2881 else
2882 RTLogPrintf("%s\n", SelfByteArray.constData());
2883 }
2884
2885 /* the children */
2886 uint32_t const cChildren = a_pNode->cChildren;
2887 for (uint32_t i = 0; i < cChildren; i++)
2888 logNode(a_pNode->papChildren[i], a_fReleaseLog);
2889}
2890
2891
2892void
2893VBoxDbgStatsModel::logTree(QModelIndex &a_rRoot, bool a_fReleaseLog) const
2894{
2895 PDBGGUISTATSNODE pRoot = a_rRoot.isValid() ? nodeFromIndex(a_rRoot) : m_pRoot;
2896 if (pRoot)
2897 logNode(pRoot, a_fReleaseLog);
2898}
2899
2900
2901
2902
2903
2904
2905
2906/*
2907 *
2908 * V B o x D b g S t a t s M o d e l V M
2909 * V B o x D b g S t a t s M o d e l V M
2910 * V B o x D b g S t a t s M o d e l V M
2911 *
2912 *
2913 */
2914
2915
2916VBoxDbgStatsModelVM::VBoxDbgStatsModelVM(VBoxDbgGui *a_pDbgGui, QString &a_rPatStr, QObject *a_pParent, PCVMMR3VTABLE a_pVMM)
2917 : VBoxDbgStatsModel(a_pParent), VBoxDbgBase(a_pDbgGui), m_pVMM(a_pVMM)
2918{
2919 /*
2920 * Create a model containing the STAM entries matching the pattern.
2921 * (The original idea was to get everything and rely on some hide/visible
2922 * flag that it turned out didn't exist.)
2923 */
2924 PDBGGUISTATSNODE pTree = createNewTree(a_rPatStr);
2925 setRootNode(pTree);
2926}
2927
2928
2929VBoxDbgStatsModelVM::~VBoxDbgStatsModelVM()
2930{
2931 /* nothing to do here. */
2932}
2933
2934
2935bool
2936VBoxDbgStatsModelVM::updateStatsByPattern(const QString &a_rPatStr, PDBGGUISTATSNODE a_pSubTree /*= NULL*/)
2937{
2938 /** @todo the way we update this stuff is independent of the source (XML, file, STAM), our only
2939 * ASSUMPTION is that the input is strictly ordered by (fully slashed) name. So, all this stuff
2940 * should really move up into the parent class. */
2941 bool fRc = updatePrepare(a_pSubTree);
2942 if (fRc)
2943 {
2944 int rc = stamEnum(a_rPatStr, updateCallback, this);
2945 fRc = updateDone(RT_SUCCESS(rc), a_pSubTree);
2946 }
2947 return fRc;
2948}
2949
2950
2951void
2952VBoxDbgStatsModelVM::resetStatsByPattern(QString const &a_rPatStr)
2953{
2954 stamReset(a_rPatStr);
2955}
2956
2957
2958/*static*/ DECLCALLBACK(int)
2959VBoxDbgStatsModelVM::createNewTreeCallback(const char *pszName, STAMTYPE enmType, void *pvSample, STAMUNIT enmUnit,
2960 const char *pszUnit, STAMVISIBILITY enmVisibility, const char *pszDesc, void *pvUser)
2961{
2962 PDBGGUISTATSNODE pRoot = (PDBGGUISTATSNODE)pvUser;
2963 Log3(("createNewTreeCallback: %s\n", pszName));
2964 RT_NOREF(enmUnit);
2965
2966 /*
2967 * Skip the ones which shouldn't be visible in the GUI.
2968 */
2969 if (enmVisibility == STAMVISIBILITY_NOT_GUI)
2970 return 0;
2971
2972 /*
2973 * Perform a mkdir -p like operation till we've walked / created the entire path down
2974 * to the node specfied node. Remember the last node as that will be the one we will
2975 * stuff the data into.
2976 */
2977 AssertReturn(*pszName == '/' && pszName[1] != '/', VERR_INTERNAL_ERROR);
2978 PDBGGUISTATSNODE pNode = pRoot;
2979 const char *pszCur = pszName + 1;
2980 while (*pszCur)
2981 {
2982 /* find the end of this component. */
2983 const char *pszNext = strchr(pszCur, '/');
2984 if (!pszNext)
2985 pszNext = strchr(pszCur, '\0');
2986 size_t cchCur = pszNext - pszCur;
2987
2988 /* Create it if it doesn't exist (it will be last if it exists). */
2989 if ( !pNode->cChildren
2990 || strncmp(pNode->papChildren[pNode->cChildren - 1]->pszName, pszCur, cchCur)
2991 || pNode->papChildren[pNode->cChildren - 1]->pszName[cchCur])
2992 {
2993 pNode = createAndInsertNode(pNode, pszCur, pszNext - pszCur, UINT32_MAX);
2994 if (!pNode)
2995 return VERR_NO_MEMORY;
2996 }
2997 else
2998 pNode = pNode->papChildren[pNode->cChildren - 1];
2999
3000 /* Advance */
3001 pszCur = *pszNext ? pszNext + 1 : pszNext;
3002 }
3003
3004 /*
3005 * Save the data.
3006 */
3007 return initNode(pNode, enmType, pvSample, pszUnit, pszDesc);
3008}
3009
3010
3011PDBGGUISTATSNODE
3012VBoxDbgStatsModelVM::createNewTree(QString &a_rPatStr)
3013{
3014 PDBGGUISTATSNODE pRoot = createRootNode();
3015 if (pRoot)
3016 {
3017 int rc = stamEnum(a_rPatStr, createNewTreeCallback, pRoot);
3018 if (RT_SUCCESS(rc))
3019 return pRoot;
3020
3021 /* failed, cleanup. */
3022 destroyTree(pRoot);
3023 }
3024
3025 return NULL;
3026}
3027
3028
3029
3030
3031
3032
3033
3034
3035/*
3036 *
3037 * V B o x D b g S t a t s S o r t F i l e P r o x y M o d e l
3038 * V B o x D b g S t a t s S o r t F i l e P r o x y M o d e l
3039 * V B o x D b g S t a t s S o r t F i l e P r o x y M o d e l
3040 *
3041 *
3042 */
3043
3044#ifdef VBOXDBG_WITH_SORTED_AND_FILTERED_STATS
3045
3046VBoxDbgStatsSortFileProxyModel::VBoxDbgStatsSortFileProxyModel(QObject *a_pParent)
3047 : QSortFilterProxyModel(a_pParent)
3048 , m_fShowUnusedRows(false)
3049{
3050
3051}
3052
3053
3054bool
3055VBoxDbgStatsSortFileProxyModel::filterAcceptsRow(int a_iSrcRow, const QModelIndex &a_rSrcParent) const
3056{
3057 if (!m_fShowUnusedRows)
3058 {
3059 PDBGGUISTATSNODE const pParent = nodeFromIndex(a_rSrcParent);
3060 if (pParent)
3061 {
3062 if ((unsigned)a_iSrcRow < pParent->cChildren)
3063 {
3064 PDBGGUISTATSNODE const pNode = pParent->papChildren[a_iSrcRow];
3065 if (pNode) /* paranoia */
3066 {
3067 /* Only relevant for leaf nodes. */
3068 if (pNode->cChildren == 0)
3069 {
3070 if (pNode->enmState != kDbgGuiStatsNodeState_kInvalid)
3071 {
3072 if (pNode->i64Delta == 0)
3073 {
3074 /* Is the cached statistics value zero? */
3075 switch (pNode->enmType)
3076 {
3077 case STAMTYPE_COUNTER:
3078 if (pNode->Data.Counter.c != 0)
3079 break;
3080 return false;
3081
3082 case STAMTYPE_PROFILE:
3083 case STAMTYPE_PROFILE_ADV:
3084 if (pNode->Data.Profile.cPeriods)
3085 break;
3086 return false;
3087
3088 case STAMTYPE_RATIO_U32:
3089 case STAMTYPE_RATIO_U32_RESET:
3090 if (pNode->Data.RatioU32.u32A || pNode->Data.RatioU32.u32B)
3091 break;
3092 return false;
3093
3094 case STAMTYPE_CALLBACK:
3095 if (pNode->Data.pStr && !pNode->Data.pStr->isEmpty())
3096 break;
3097 return false;
3098
3099 case STAMTYPE_U8:
3100 case STAMTYPE_U8_RESET:
3101 case STAMTYPE_X8:
3102 case STAMTYPE_X8_RESET:
3103 if (pNode->Data.u8)
3104 break;
3105 return false;
3106
3107 case STAMTYPE_U16:
3108 case STAMTYPE_U16_RESET:
3109 case STAMTYPE_X16:
3110 case STAMTYPE_X16_RESET:
3111 if (pNode->Data.u16)
3112 break;
3113 return false;
3114
3115 case STAMTYPE_U32:
3116 case STAMTYPE_U32_RESET:
3117 case STAMTYPE_X32:
3118 case STAMTYPE_X32_RESET:
3119 if (pNode->Data.u32)
3120 break;
3121 return false;
3122
3123 case STAMTYPE_U64:
3124 case STAMTYPE_U64_RESET:
3125 case STAMTYPE_X64:
3126 case STAMTYPE_X64_RESET:
3127 if (pNode->Data.u64)
3128 break;
3129 return false;
3130
3131 case STAMTYPE_BOOL:
3132 case STAMTYPE_BOOL_RESET:
3133 /* not possible to detect */
3134 return false;
3135
3136 case STAMTYPE_INVALID:
3137 break;
3138
3139 default:
3140 AssertMsgFailedBreak(("enmType=%d\n", pNode->enmType));
3141 }
3142 }
3143 }
3144 else
3145 return false;
3146 }
3147 }
3148 }
3149 }
3150 }
3151 return true;
3152}
3153
3154
3155bool
3156VBoxDbgStatsSortFileProxyModel::lessThan(const QModelIndex &a_rSrcLeft, const QModelIndex &a_rSrcRight) const
3157{
3158 PCDBGGUISTATSNODE const pLeft = nodeFromIndex(a_rSrcLeft);
3159 PCDBGGUISTATSNODE const pRight = nodeFromIndex(a_rSrcRight);
3160 if (pLeft == pRight)
3161 return false;
3162 if (pLeft && pRight)
3163 {
3164 if (pLeft->pParent == pRight->pParent)
3165 {
3166 switch (a_rSrcLeft.column())
3167 {
3168 case 0:
3169 return RTStrCmp(pLeft->pszName, pRight->pszName) < 0;
3170 case 1:
3171 return RTStrCmp(pLeft->pszUnit, pRight->pszUnit) < 0;
3172 case 2:
3173 return VBoxDbgStatsModel::getValueTimesAsUInt(pLeft) < VBoxDbgStatsModel::getValueTimesAsUInt(pRight);
3174 case 3:
3175 return pLeft->i64Delta < pRight->i64Delta;
3176 case 4:
3177 return VBoxDbgStatsModel::getMinValueAsUInt(pLeft) < VBoxDbgStatsModel::getMinValueAsUInt(pRight);
3178 case 5:
3179 return VBoxDbgStatsModel::getAvgValueAsUInt(pLeft) < VBoxDbgStatsModel::getAvgValueAsUInt(pRight);
3180 case 6:
3181 return VBoxDbgStatsModel::getMaxValueAsUInt(pLeft) < VBoxDbgStatsModel::getMaxValueAsUInt(pRight);
3182 case 7:
3183 return VBoxDbgStatsModel::getTotalValueAsUInt(pLeft) < VBoxDbgStatsModel::getTotalValueAsUInt(pRight);
3184 case 8:
3185 if (pLeft->pDescStr == pRight->pDescStr)
3186 return false;
3187 if (!pLeft->pDescStr)
3188 return true;
3189 if (!pRight->pDescStr)
3190 return false;
3191 return *pLeft->pDescStr < *pRight->pDescStr;
3192 default:
3193 AssertCompile(DBGGUI_STATS_COLUMNS == 9);
3194 return true;
3195 }
3196 }
3197 return false;
3198 }
3199 return !pLeft;
3200}
3201
3202
3203void
3204VBoxDbgStatsSortFileProxyModel::setShowUnusedRows(bool a_fHide)
3205{
3206 if (a_fHide != m_fShowUnusedRows)
3207 {
3208 m_fShowUnusedRows = a_fHide;
3209 invalidateRowsFilter();
3210 }
3211}
3212
3213
3214#endif /* VBOXDBG_WITH_SORTED_AND_FILTERED_STATS */
3215
3216
3217
3218/*
3219 *
3220 * V B o x D b g S t a t s V i e w
3221 * V B o x D b g S t a t s V i e w
3222 * V B o x D b g S t a t s V i e w
3223 *
3224 *
3225 */
3226
3227
3228VBoxDbgStatsView::VBoxDbgStatsView(VBoxDbgGui *a_pDbgGui, VBoxDbgStatsModel *a_pVBoxModel,
3229 VBoxDbgStatsSortFileProxyModel *a_pProxyModel, VBoxDbgStats *a_pParent/* = NULL*/)
3230 : QTreeView(a_pParent)
3231 , VBoxDbgBase(a_pDbgGui)
3232 , m_pVBoxModel(a_pVBoxModel)
3233 , m_pProxyModel(a_pProxyModel)
3234 , m_pModel(NULL)
3235 , m_PatStr()
3236 , m_pParent(a_pParent)
3237 , m_pLeafMenu(NULL)
3238 , m_pBranchMenu(NULL)
3239 , m_pViewMenu(NULL)
3240 , m_pCurMenu(NULL)
3241 , m_CurIndex()
3242{
3243 /*
3244 * Set the model and view defaults.
3245 */
3246 setRootIsDecorated(true);
3247 if (a_pProxyModel)
3248 {
3249 m_pModel = a_pProxyModel;
3250 a_pProxyModel->setSourceModel(a_pVBoxModel);
3251 }
3252 else
3253 m_pModel = a_pVBoxModel;
3254 setModel(m_pModel);
3255 QModelIndex RootIdx = myGetRootIndex(); /* This should really be QModelIndex(), but Qt on darwin does wrong things then. */
3256 setRootIndex(RootIdx);
3257 setItemsExpandable(true);
3258 setAlternatingRowColors(true);
3259 setSelectionBehavior(SelectRows);
3260 setSelectionMode(SingleSelection);
3261 if (a_pProxyModel)
3262 setSortingEnabled(true);
3263
3264 /*
3265 * Create and setup the actions.
3266 */
3267 m_pExpandAct = new QAction("Expand Tree", this);
3268 m_pCollapseAct = new QAction("Collapse Tree", this);
3269 m_pRefreshAct = new QAction("&Refresh", this);
3270 m_pResetAct = new QAction("Rese&t", this);
3271 m_pCopyAct = new QAction("&Copy", this);
3272 m_pToLogAct = new QAction("To &Log", this);
3273 m_pToRelLogAct = new QAction("T&o Release Log", this);
3274 m_pAdjColumns = new QAction("&Adjust Columns", this);
3275
3276 m_pCopyAct->setShortcut(QKeySequence::Copy);
3277 m_pExpandAct->setShortcut(QKeySequence("Ctrl+E"));
3278 m_pCollapseAct->setShortcut(QKeySequence("Ctrl+D"));
3279 m_pRefreshAct->setShortcut(QKeySequence("Ctrl+R"));
3280 m_pResetAct->setShortcut(QKeySequence("Alt+R"));
3281 m_pToLogAct->setShortcut(QKeySequence("Ctrl+Z"));
3282 m_pToRelLogAct->setShortcut(QKeySequence("Alt+Z"));
3283 m_pAdjColumns->setShortcut(QKeySequence("Ctrl+A"));
3284
3285 addAction(m_pCopyAct);
3286 addAction(m_pExpandAct);
3287 addAction(m_pCollapseAct);
3288 addAction(m_pRefreshAct);
3289 addAction(m_pResetAct);
3290 addAction(m_pToLogAct);
3291 addAction(m_pToRelLogAct);
3292 addAction(m_pAdjColumns);
3293
3294 connect(m_pExpandAct, SIGNAL(triggered(bool)), this, SLOT(actExpand()));
3295 connect(m_pCollapseAct, SIGNAL(triggered(bool)), this, SLOT(actCollapse()));
3296 connect(m_pRefreshAct, SIGNAL(triggered(bool)), this, SLOT(actRefresh()));
3297 connect(m_pResetAct, SIGNAL(triggered(bool)), this, SLOT(actReset()));
3298 connect(m_pCopyAct, SIGNAL(triggered(bool)), this, SLOT(actCopy()));
3299 connect(m_pToLogAct, SIGNAL(triggered(bool)), this, SLOT(actToLog()));
3300 connect(m_pToRelLogAct, SIGNAL(triggered(bool)), this, SLOT(actToRelLog()));
3301 connect(m_pAdjColumns, SIGNAL(triggered(bool)), this, SLOT(actAdjColumns()));
3302
3303
3304 /*
3305 * Create the menus and populate them.
3306 */
3307 setContextMenuPolicy(Qt::DefaultContextMenu);
3308
3309 m_pLeafMenu = new QMenu();
3310 m_pLeafMenu->addAction(m_pCopyAct);
3311 m_pLeafMenu->addAction(m_pRefreshAct);
3312 m_pLeafMenu->addAction(m_pResetAct);
3313 m_pLeafMenu->addAction(m_pToLogAct);
3314 m_pLeafMenu->addAction(m_pToRelLogAct);
3315
3316 m_pBranchMenu = new QMenu(this);
3317 m_pBranchMenu->addAction(m_pCopyAct);
3318 m_pBranchMenu->addAction(m_pRefreshAct);
3319 m_pBranchMenu->addAction(m_pResetAct);
3320 m_pBranchMenu->addAction(m_pToLogAct);
3321 m_pBranchMenu->addAction(m_pToRelLogAct);
3322 m_pBranchMenu->addSeparator();
3323 m_pBranchMenu->addAction(m_pExpandAct);
3324 m_pBranchMenu->addAction(m_pCollapseAct);
3325
3326 m_pViewMenu = new QMenu();
3327 m_pViewMenu->addAction(m_pCopyAct);
3328 m_pViewMenu->addAction(m_pRefreshAct);
3329 m_pViewMenu->addAction(m_pResetAct);
3330 m_pViewMenu->addAction(m_pToLogAct);
3331 m_pViewMenu->addAction(m_pToRelLogAct);
3332 m_pViewMenu->addSeparator();
3333 m_pViewMenu->addAction(m_pExpandAct);
3334 m_pViewMenu->addAction(m_pCollapseAct);
3335 m_pViewMenu->addSeparator();
3336 m_pViewMenu->addAction(m_pAdjColumns);
3337
3338 /* the header menu */
3339 QHeaderView *pHdrView = header();
3340 pHdrView->setContextMenuPolicy(Qt::CustomContextMenu);
3341 connect(pHdrView, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(headerContextMenuRequested(const QPoint &)));
3342}
3343
3344
3345VBoxDbgStatsView::~VBoxDbgStatsView()
3346{
3347 m_pParent = NULL;
3348 m_pCurMenu = NULL;
3349 m_CurIndex = QModelIndex();
3350
3351#define DELETE_IT(m) if (m) { delete m; m = NULL; } else do {} while (0)
3352 DELETE_IT(m_pProxyModel);
3353 DELETE_IT(m_pVBoxModel);
3354
3355 DELETE_IT(m_pLeafMenu);
3356 DELETE_IT(m_pBranchMenu);
3357 DELETE_IT(m_pViewMenu);
3358
3359 DELETE_IT(m_pExpandAct);
3360 DELETE_IT(m_pCollapseAct);
3361 DELETE_IT(m_pRefreshAct);
3362 DELETE_IT(m_pResetAct);
3363 DELETE_IT(m_pCopyAct);
3364 DELETE_IT(m_pToLogAct);
3365 DELETE_IT(m_pToRelLogAct);
3366 DELETE_IT(m_pAdjColumns);
3367#undef DELETE_IT
3368}
3369
3370
3371void
3372VBoxDbgStatsView::updateStats(const QString &rPatStr)
3373{
3374 m_PatStr = rPatStr;
3375 if (m_pVBoxModel->updateStatsByPattern(rPatStr))
3376 setRootIndex(myGetRootIndex()); /* hack */
3377}
3378
3379
3380void
3381VBoxDbgStatsView::setShowUnusedRows(bool a_fHide)
3382{
3383#ifdef VBOXDBG_WITH_SORTED_AND_FILTERED_STATS
3384 if (m_pProxyModel)
3385 m_pProxyModel->setShowUnusedRows(a_fHide);
3386#else
3387 RT_NOREF_PV(a_fHide);
3388#endif
3389}
3390
3391
3392void
3393VBoxDbgStatsView::resizeColumnsToContent()
3394{
3395 for (int i = 0; i <= 8; i++)
3396 {
3397 resizeColumnToContents(i);
3398 /* Some extra room for distinguishing numbers better in Value, Min, Avg, Max, Total, dInt columns. */
3399 if (i >= 2 && i <= 7)
3400 setColumnWidth(i, columnWidth(i) + 10);
3401 }
3402}
3403
3404
3405/*static*/ bool
3406VBoxDbgStatsView::expandMatchingCallback(PDBGGUISTATSNODE pNode, QModelIndex const &a_rIndex,
3407 const char *pszFullName, void *pvUser)
3408{
3409 VBoxDbgStatsView *pThis = (VBoxDbgStatsView *)pvUser;
3410
3411 QModelIndex ParentIndex; /* this isn't 100% optimal */
3412 if (pThis->m_pProxyModel)
3413 {
3414 QModelIndex const ProxyIndex = pThis->m_pProxyModel->mapFromSource(a_rIndex);
3415
3416 ParentIndex = pThis->m_pModel->parent(ProxyIndex);
3417 }
3418 else
3419 {
3420 pThis->setExpanded(a_rIndex, true);
3421
3422 ParentIndex = pThis->m_pModel->parent(a_rIndex);
3423 }
3424 while (ParentIndex.isValid() && !pThis->isExpanded(ParentIndex))
3425 {
3426 pThis->setExpanded(ParentIndex, true);
3427 ParentIndex = pThis->m_pModel->parent(ParentIndex);
3428 }
3429
3430 RT_NOREF(pNode, pszFullName);
3431 return true;
3432}
3433
3434
3435void
3436VBoxDbgStatsView::expandMatching(const QString &rPatStr)
3437{
3438 m_pVBoxModel->iterateStatsByPattern(rPatStr, expandMatchingCallback, this);
3439}
3440
3441
3442void
3443VBoxDbgStatsView::setSubTreeExpanded(QModelIndex const &a_rIndex, bool a_fExpanded)
3444{
3445 int cRows = m_pModel->rowCount(a_rIndex);
3446 if (a_rIndex.model())
3447 for (int i = 0; i < cRows; i++)
3448 setSubTreeExpanded(a_rIndex.model()->index(i, 0, a_rIndex), a_fExpanded);
3449 setExpanded(a_rIndex, a_fExpanded);
3450}
3451
3452
3453void
3454VBoxDbgStatsView::contextMenuEvent(QContextMenuEvent *a_pEvt)
3455{
3456 /*
3457 * Get the selected item.
3458 * If it's a mouse event select the item under the cursor (if any).
3459 */
3460 QModelIndex Idx;
3461 if (a_pEvt->reason() == QContextMenuEvent::Mouse)
3462 {
3463 Idx = indexAt(a_pEvt->pos());
3464 if (Idx.isValid())
3465 setCurrentIndex(Idx);
3466 }
3467 else
3468 {
3469 QModelIndexList SelIdx = selectedIndexes();
3470 if (!SelIdx.isEmpty())
3471 Idx = SelIdx.at(0);
3472 }
3473
3474 /*
3475 * Popup the corresponding menu.
3476 */
3477 QMenu *pMenu;
3478 if (!Idx.isValid())
3479 pMenu = m_pViewMenu;
3480 else if (m_pModel->hasChildren(Idx))
3481 pMenu = m_pBranchMenu;
3482 else
3483 pMenu = m_pLeafMenu;
3484 if (pMenu)
3485 {
3486 m_pRefreshAct->setEnabled(!Idx.isValid() || Idx == myGetRootIndex());
3487 m_CurIndex = Idx;
3488 m_pCurMenu = pMenu;
3489
3490 pMenu->exec(a_pEvt->globalPos());
3491
3492 m_pCurMenu = NULL;
3493 m_CurIndex = QModelIndex();
3494 if (m_pRefreshAct)
3495 m_pRefreshAct->setEnabled(true);
3496 }
3497 a_pEvt->accept();
3498}
3499
3500
3501QModelIndex
3502VBoxDbgStatsView::myGetRootIndex(void) const
3503{
3504 if (!m_pProxyModel)
3505 return m_pVBoxModel->getRootIndex();
3506 return m_pProxyModel->mapFromSource(m_pVBoxModel->getRootIndex());
3507}
3508
3509
3510void
3511VBoxDbgStatsView::headerContextMenuRequested(const QPoint &a_rPos)
3512{
3513 /*
3514 * Show the view menu.
3515 */
3516 if (m_pViewMenu)
3517 {
3518 m_pRefreshAct->setEnabled(true);
3519 m_CurIndex = myGetRootIndex();
3520 m_pCurMenu = m_pViewMenu;
3521
3522 m_pViewMenu->exec(header()->mapToGlobal(a_rPos));
3523
3524 m_pCurMenu = NULL;
3525 m_CurIndex = QModelIndex();
3526 if (m_pRefreshAct)
3527 m_pRefreshAct->setEnabled(true);
3528 }
3529}
3530
3531
3532void
3533VBoxDbgStatsView::actExpand()
3534{
3535 QModelIndex Idx = m_pCurMenu ? m_CurIndex : currentIndex();
3536 if (Idx.isValid())
3537 setSubTreeExpanded(Idx, true /* a_fExpanded */);
3538}
3539
3540
3541void
3542VBoxDbgStatsView::actCollapse()
3543{
3544 QModelIndex Idx = m_pCurMenu ? m_CurIndex : currentIndex();
3545 if (Idx.isValid())
3546 setSubTreeExpanded(Idx, false /* a_fExpanded */);
3547}
3548
3549
3550void
3551VBoxDbgStatsView::actRefresh()
3552{
3553 QModelIndex Idx = m_pCurMenu ? m_CurIndex : currentIndex();
3554 if (!Idx.isValid() || Idx == myGetRootIndex())
3555 {
3556 if (m_pVBoxModel->updateStatsByPattern(m_PatStr))
3557 setRootIndex(myGetRootIndex()); /* hack */
3558 }
3559 else
3560 {
3561 if (m_pProxyModel)
3562 Idx = m_pProxyModel->mapToSource(Idx);
3563 m_pVBoxModel->updateStatsByIndex(Idx);
3564 }
3565}
3566
3567
3568void
3569VBoxDbgStatsView::actReset()
3570{
3571 QModelIndex Idx = m_pCurMenu ? m_CurIndex : currentIndex();
3572 if (!Idx.isValid() || Idx == myGetRootIndex())
3573 m_pVBoxModel->resetStatsByPattern(m_PatStr);
3574 else
3575 {
3576 if (m_pProxyModel)
3577 Idx = m_pProxyModel->mapToSource(Idx);
3578 m_pVBoxModel->resetStatsByIndex(Idx);
3579 }
3580}
3581
3582
3583void
3584VBoxDbgStatsView::actCopy()
3585{
3586 QModelIndex Idx = m_pCurMenu ? m_CurIndex : currentIndex();
3587 if (m_pProxyModel)
3588 Idx = m_pProxyModel->mapToSource(Idx);
3589 m_pVBoxModel->copyTreeToClipboard(Idx);
3590}
3591
3592
3593void
3594VBoxDbgStatsView::actToLog()
3595{
3596 QModelIndex Idx = m_pCurMenu ? m_CurIndex : currentIndex();
3597 if (m_pProxyModel)
3598 Idx = m_pProxyModel->mapToSource(Idx);
3599 m_pVBoxModel->logTree(Idx, false /* a_fReleaseLog */);
3600}
3601
3602
3603void
3604VBoxDbgStatsView::actToRelLog()
3605{
3606 QModelIndex Idx = m_pCurMenu ? m_CurIndex : currentIndex();
3607 if (m_pProxyModel)
3608 Idx = m_pProxyModel->mapToSource(Idx);
3609 m_pVBoxModel->logTree(Idx, true /* a_fReleaseLog */);
3610}
3611
3612
3613void
3614VBoxDbgStatsView::actAdjColumns()
3615{
3616 resizeColumnsToContent();
3617}
3618
3619
3620
3621
3622
3623
3624/*
3625 *
3626 * V B o x D b g S t a t s
3627 * V B o x D b g S t a t s
3628 * V B o x D b g S t a t s
3629 *
3630 *
3631 */
3632
3633
3634VBoxDbgStats::VBoxDbgStats(VBoxDbgGui *a_pDbgGui, const char *pszFilter /*= NULL*/, const char *pszExpand /*= NULL*/,
3635 unsigned uRefreshRate/* = 0*/, QWidget *pParent/* = NULL*/)
3636 : VBoxDbgBaseWindow(a_pDbgGui, pParent, "Statistics")
3637 , m_PatStr(pszFilter), m_pPatCB(NULL), m_uRefreshRate(0), m_pTimer(NULL), m_pView(NULL)
3638{
3639 /* Delete dialog on close: */
3640 setAttribute(Qt::WA_DeleteOnClose);
3641
3642 /*
3643 * On top, a horizontal box with the pattern field, buttons and refresh interval.
3644 */
3645 QHBoxLayout *pHLayout = new QHBoxLayout;
3646
3647 QLabel *pLabel = new QLabel(" Pattern ");
3648 pHLayout->addWidget(pLabel);
3649 pLabel->setMaximumSize(pLabel->sizeHint());
3650 pLabel->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
3651
3652 m_pPatCB = new QComboBox();
3653 pHLayout->addWidget(m_pPatCB);
3654 if (!m_PatStr.isEmpty())
3655 m_pPatCB->addItem(m_PatStr);
3656 m_pPatCB->setDuplicatesEnabled(false);
3657 m_pPatCB->setEditable(true);
3658 m_pPatCB->setCompleter(0);
3659 connect(m_pPatCB, SIGNAL(textActivated(const QString &)), this, SLOT(apply(const QString &)));
3660
3661 QPushButton *pPB = new QPushButton("&All");
3662 pHLayout->addWidget(pPB);
3663 pPB->setMaximumSize(pPB->sizeHint());
3664 connect(pPB, SIGNAL(clicked()), this, SLOT(applyAll()));
3665
3666 pLabel = new QLabel(" Interval ");
3667 pHLayout->addWidget(pLabel);
3668 pLabel->setMaximumSize(pLabel->sizeHint());
3669 pLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
3670
3671 QSpinBox *pSB = new QSpinBox();
3672 pHLayout->addWidget(pSB);
3673 pSB->setMinimum(0);
3674 pSB->setMaximum(60);
3675 pSB->setSingleStep(1);
3676 pSB->setValue(uRefreshRate);
3677 pSB->setSuffix(" s");
3678 pSB->setWrapping(false);
3679 pSB->setButtonSymbols(QSpinBox::PlusMinus);
3680 pSB->setMaximumSize(pSB->sizeHint());
3681 connect(pSB, SIGNAL(valueChanged(int)), this, SLOT(setRefresh(int)));
3682
3683#ifdef VBOXDBG_WITH_SORTED_AND_FILTERED_STATS
3684 QCheckBox *pCheckBox = new QCheckBox("Show unused");
3685 pHLayout->addWidget(pCheckBox);
3686 pCheckBox->setMaximumSize(pCheckBox->sizeHint());
3687 connect(pCheckBox, SIGNAL(stateChanged(int)), this, SLOT(sltShowUnusedRowsChanged(int)));
3688#endif
3689
3690 /*
3691 * Create the tree view and setup the layout.
3692 */
3693 VBoxDbgStatsModelVM *pModel = new VBoxDbgStatsModelVM(a_pDbgGui, m_PatStr, NULL, a_pDbgGui->getVMMFunctionTable());
3694#ifdef VBOXDBG_WITH_SORTED_AND_FILTERED_STATS
3695 VBoxDbgStatsSortFileProxyModel *pProxyModel = new VBoxDbgStatsSortFileProxyModel(this);
3696 m_pView = new VBoxDbgStatsView(a_pDbgGui, pModel, pProxyModel, this);
3697#else
3698 m_pView = new VBoxDbgStatsView(a_pDbgGui, pModel, NULL, this);
3699#endif
3700
3701 QWidget *pHBox = new QWidget;
3702 pHBox->setLayout(pHLayout);
3703
3704 QVBoxLayout *pVLayout = new QVBoxLayout;
3705 pVLayout->addWidget(pHBox);
3706 pVLayout->addWidget(m_pView);
3707 setLayout(pVLayout);
3708
3709 /*
3710 * Resize the columns.
3711 * Seems this has to be done with all nodes expanded.
3712 */
3713 m_pView->expandAll();
3714 m_pView->resizeColumnsToContent();
3715 m_pView->collapseAll();
3716
3717 if (pszExpand && *pszExpand)
3718 m_pView->expandMatching(QString(pszExpand));
3719
3720 /*
3721 * Create a refresh timer and start it.
3722 */
3723 m_pTimer = new QTimer(this);
3724 connect(m_pTimer, SIGNAL(timeout()), this, SLOT(refresh()));
3725 setRefresh(uRefreshRate);
3726
3727 /*
3728 * And some shortcuts.
3729 */
3730 m_pFocusToPat = new QAction("", this);
3731 m_pFocusToPat->setShortcut(QKeySequence("Ctrl+L"));
3732 addAction(m_pFocusToPat);
3733 connect(m_pFocusToPat, SIGNAL(triggered(bool)), this, SLOT(actFocusToPat()));
3734}
3735
3736
3737VBoxDbgStats::~VBoxDbgStats()
3738{
3739 if (m_pTimer)
3740 {
3741 delete m_pTimer;
3742 m_pTimer = NULL;
3743 }
3744
3745 if (m_pPatCB)
3746 {
3747 delete m_pPatCB;
3748 m_pPatCB = NULL;
3749 }
3750
3751 if (m_pView)
3752 {
3753 delete m_pView;
3754 m_pView = NULL;
3755 }
3756}
3757
3758
3759void
3760VBoxDbgStats::closeEvent(QCloseEvent *a_pCloseEvt)
3761{
3762 a_pCloseEvt->accept();
3763}
3764
3765
3766void
3767VBoxDbgStats::apply(const QString &Str)
3768{
3769 m_PatStr = Str;
3770 refresh();
3771}
3772
3773
3774void
3775VBoxDbgStats::applyAll()
3776{
3777 apply("");
3778}
3779
3780
3781
3782void
3783VBoxDbgStats::refresh()
3784{
3785 m_pView->updateStats(m_PatStr);
3786}
3787
3788
3789void
3790VBoxDbgStats::setRefresh(int iRefresh)
3791{
3792 if ((unsigned)iRefresh != m_uRefreshRate)
3793 {
3794 if (!m_uRefreshRate || iRefresh)
3795 m_pTimer->start(iRefresh * 1000);
3796 else
3797 m_pTimer->stop();
3798 m_uRefreshRate = iRefresh;
3799 }
3800}
3801
3802
3803void
3804VBoxDbgStats::actFocusToPat()
3805{
3806 if (!m_pPatCB->hasFocus())
3807 m_pPatCB->setFocus(Qt::ShortcutFocusReason);
3808}
3809
3810
3811void
3812VBoxDbgStats::sltShowUnusedRowsChanged(int a_iState)
3813{
3814#ifdef VBOXDBG_WITH_SORTED_AND_FILTERED_STATS
3815 m_pView->setShowUnusedRows(a_iState != Qt::Unchecked);
3816#else
3817 RT_NOREF_PV(a_iState);
3818#endif
3819}
注意: 瀏覽 TracBrowser 來幫助您使用儲存庫瀏覽器

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette