VirtualBox

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

Last change on this file since 99040 was 98103, checked in by vboxsync, 23 months ago

Copyright year updates by scm.

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

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