VirtualBox

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

Last change on this file since 78296 was 77412, checked in by vboxsync, 5 years ago

VBoxDbg: Moved the title management to the VBoxDbgBaseWindow class, combining the approaches in the console and statistics windows.

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

© 2023 Oracle
ContactPrivacy policyTerms of Use