VirtualBox

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

Last change on this file since 101098 was 101098, checked in by vboxsync, 21 months ago

VBoxDbg: Some stats formatting tweaking.

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

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