VirtualBox

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

Last change on this file since 92973 was 92867, checked in by vboxsync, 3 years ago

VBoxDbg: Fixed bug in iterateStatsByPattern making it skip the first child of all but the root.

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

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