VirtualBox

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

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

Debugger,Main: Doxygen fixes. bugref:10074

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