VirtualBox

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

Last change on this file since 100108 was 99739, checked in by vboxsync, 13 months ago

*: doxygen corrections (mostly about removing @returns from functions returning void).

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

© 2023 Oracle
ContactPrivacy policyTerms of Use