VirtualBox

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

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

VBoxDbg: Made the statistics selection pattern work again (hooked up to signal removed in Qt 6) and moved the unsetting of the completer as either setDuplicatesEnabled or setEditable seems to implicitly set a case-insensitive completer.

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

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