VirtualBox

source: vbox/trunk/src/VBox/Debugger/VBoxDbgStatsQt4.cpp@ 50653

Last change on this file since 50653 was 48564, checked in by vboxsync, 11 years ago

Debugger/VBoxDbgStatsQt4: Auto-resize certain columns with slight marginal room for better readability.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 89.7 KB
Line 
1/* $Id: VBoxDbgStatsQt4.cpp 48564 2013-09-19 21:09:58Z vboxsync $ */
2/** @file
3 * VBox Debugger GUI - Statistics.
4 */
5
6/*
7 * Copyright (C) 2006-2012 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 "VBoxDbgStatsQt4.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 <VBox/err.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(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 iDiff = cchSubName == cchCompare ? 0 : cchSubName > cchCompare ? 1 : -1;
1536 if (iDiff > 0)
1537 {
1538 iStart = i + 1;
1539 if (iStart > iLast)
1540 {
1541 pNode = createAndInsert(pNode, pszSubName, cchSubName, iStart);
1542 AssertReturn(pNode, NULL);
1543 break;
1544 }
1545 }
1546 else if (iDiff < 0)
1547 {
1548 iLast = i - 1;
1549 if (iLast < iStart)
1550 {
1551 pNode = createAndInsert(pNode, pszSubName, cchSubName, i);
1552 AssertReturn(pNode, NULL);
1553 break;
1554 }
1555 }
1556 else
1557 {
1558 pNode = pNode->papChildren[i];
1559 break;
1560 }
1561 }
1562 }
1563 }
1564 Assert( !memcmp(pszName, m_szUpdateParent, m_cchUpdateParent - 2)
1565 && pszName[m_cchUpdateParent - 1] == '\0');
1566
1567 /*
1568 * Remove all the nodes between pNode and pPrev but keep all
1569 * of pNode's ancestors (or it'll get orphaned).
1570 */
1571 PDBGGUISTATSNODE pCur = prevNode(pNode);
1572 while (pCur != pPrev)
1573 {
1574 PDBGGUISTATSNODE pAdv = prevNode(pCur); Assert(pAdv || !pPrev);
1575 if (!isNodeAncestorOf(pCur, pNode))
1576 {
1577 Assert(pCur != m_pRoot);
1578 removeAndDestroy(pCur);
1579 }
1580 pCur = pAdv;
1581 }
1582
1583 /*
1584 * Remove the data from all ancestors of pNode that it doesn't
1585 * share them pPrev.
1586 */
1587 if (pPrev)
1588 {
1589 pCur = pNode->pParent;
1590 while (!isNodeAncestorOf(pCur, pPrev))
1591 {
1592 resetNode(pNode);
1593 pCur = pCur->pParent;
1594 }
1595 }
1596
1597 /*
1598 * Finally, adjust the globals (szUpdateParent is one level too deep).
1599 */
1600 Assert(m_cchUpdateParent > pNode->cchName + 1);
1601 m_cchUpdateParent -= pNode->cchName + 1;
1602 m_szUpdateParent[m_cchUpdateParent] = '\0';
1603 m_pUpdateParent = pNode->pParent;
1604 m_iUpdateChild = pNode->iSelf;
1605 Log2(("updateCallbackHandleOutOfOrder: m_szUpdateParent='%s' m_cchUpdateParent=%u (%u)\n", m_szUpdateParent, m_cchUpdateParent, __LINE__));
1606
1607 return pNode;
1608}
1609
1610
1611PDBGGUISTATSNODE
1612VBoxDbgStatsModel::updateCallbackHandleTail(const char *pszName)
1613{
1614 /*
1615 * Insert it at the end of the tree.
1616 *
1617 * Do the same as we're doing down in createNewTreeCallback, walk from the
1618 * root and create whatever we need.
1619 */
1620 AssertReturn(*pszName == '/' && pszName[1] != '/', NULL);
1621 PDBGGUISTATSNODE pNode = m_pRoot;
1622 const char *pszCur = pszName + 1;
1623 while (*pszCur)
1624 {
1625 /* Find the end of this component. */
1626 const char *pszNext = strchr(pszCur, '/');
1627 if (!pszNext)
1628 pszNext = strchr(pszCur, '\0');
1629 size_t cchCur = pszNext - pszCur;
1630
1631 /* Create it if it doesn't exist (it will be last if it exists). */
1632 if ( !pNode->cChildren
1633 || strncmp(pNode->papChildren[pNode->cChildren - 1]->pszName, pszCur, cchCur)
1634 || pNode->papChildren[pNode->cChildren - 1]->pszName[cchCur])
1635 {
1636 pNode = createAndInsert(pNode, pszCur, pszNext - pszCur, pNode->cChildren);
1637 AssertReturn(pNode, NULL);
1638 }
1639 else
1640 pNode = pNode->papChildren[pNode->cChildren - 1];
1641
1642 /* Advance */
1643 pszCur = *pszNext ? pszNext + 1 : pszNext;
1644 }
1645
1646 return pNode;
1647}
1648
1649
1650void
1651VBoxDbgStatsModel::updateCallbackAdvance(PDBGGUISTATSNODE pNode)
1652{
1653 /*
1654 * Advance to the next node with data.
1655 *
1656 * ASSUMES a leaf *must* have data and again we're ASSUMING the sorting
1657 * on slash separated sub-strings.
1658 */
1659 if (m_iUpdateChild != UINT32_MAX)
1660 {
1661#ifdef VBOX_STRICT
1662 PDBGGUISTATSNODE const pCorrectNext = nextDataNode(pNode);
1663#endif
1664 PDBGGUISTATSNODE pParent = pNode->pParent;
1665 if (pNode->cChildren)
1666 {
1667 /* descend to the first child. */
1668 Assert(m_cchUpdateParent + pNode->cchName + 2 < sizeof(m_szUpdateParent));
1669 memcpy(&m_szUpdateParent[m_cchUpdateParent], pNode->pszName, pNode->cchName);
1670 m_cchUpdateParent += pNode->cchName;
1671 m_szUpdateParent[m_cchUpdateParent++] = '/';
1672 m_szUpdateParent[m_cchUpdateParent] = '\0';
1673
1674 pNode = pNode->papChildren[0];
1675 }
1676 else if (pNode->iSelf + 1 < pParent->cChildren)
1677 {
1678 /* next sibling or one if its descendants. */
1679 Assert(m_pUpdateParent == pParent);
1680 pNode = pParent->papChildren[pNode->iSelf + 1];
1681 }
1682 else
1683 {
1684 /* move up and down- / on-wards */
1685 for (;;)
1686 {
1687 /* ascend */
1688 pNode = pParent;
1689 pParent = pParent->pParent;
1690 if (!pParent)
1691 {
1692 Assert(pNode == m_pRoot);
1693 m_iUpdateChild = UINT32_MAX;
1694 m_szUpdateParent[0] = '\0';
1695 m_cchUpdateParent = 0;
1696 m_pUpdateParent = NULL;
1697 break;
1698 }
1699 Assert(m_cchUpdateParent > pNode->cchName + 1);
1700 m_cchUpdateParent -= pNode->cchName + 1;
1701
1702 /* try advance */
1703 if (pNode->iSelf + 1 < pParent->cChildren)
1704 {
1705 pNode = pParent->papChildren[pNode->iSelf + 1];
1706 m_szUpdateParent[m_cchUpdateParent] = '\0';
1707 break;
1708 }
1709 }
1710 }
1711
1712 /* descend to a node containing data and finalize the globals. (ASSUMES leaf has data.) */
1713 if (m_iUpdateChild != UINT32_MAX)
1714 {
1715 while ( pNode->enmType == STAMTYPE_INVALID
1716 && pNode->cChildren > 0)
1717 {
1718 Assert(pNode->enmState == kDbgGuiStatsNodeState_kVisible);
1719
1720 Assert(m_cchUpdateParent + pNode->cchName + 2 < sizeof(m_szUpdateParent));
1721 memcpy(&m_szUpdateParent[m_cchUpdateParent], pNode->pszName, pNode->cchName);
1722 m_cchUpdateParent += pNode->cchName;
1723 m_szUpdateParent[m_cchUpdateParent++] = '/';
1724 m_szUpdateParent[m_cchUpdateParent] = '\0';
1725
1726 pNode = pNode->papChildren[0];
1727 }
1728 Assert(pNode->enmType != STAMTYPE_INVALID);
1729 m_iUpdateChild = pNode->iSelf;
1730 m_pUpdateParent = pNode->pParent;
1731 Assert(pNode == pCorrectNext);
1732 }
1733 }
1734 /* else: we're at the end */
1735}
1736
1737
1738/*static*/ DECLCALLBACK(int)
1739VBoxDbgStatsModel::updateCallback(const char *pszName, STAMTYPE enmType, void *pvSample, STAMUNIT enmUnit,
1740 STAMVISIBILITY enmVisibility, const char *pszDesc, void *pvUser)
1741{
1742 VBoxDbgStatsModelVM *pThis = (VBoxDbgStatsModelVM *)pvUser;
1743 Log3(("updateCallback: %s\n", pszName));
1744
1745 /*
1746 * Skip the ones which shouldn't be visible in the GUI.
1747 */
1748 if (enmVisibility == STAMVISIBILITY_NOT_GUI)
1749 return 0;
1750
1751 /*
1752 * The default assumption is that nothing has changed.
1753 * For now we'll reset the model when ever something changes.
1754 */
1755 PDBGGUISTATSNODE pNode;
1756 if (pThis->m_iUpdateChild != UINT32_MAX)
1757 {
1758 pNode = pThis->m_pUpdateParent->papChildren[pThis->m_iUpdateChild];
1759 if ( !strncmp(pszName, pThis->m_szUpdateParent, pThis->m_cchUpdateParent)
1760 && !strcmp(pszName + pThis->m_cchUpdateParent, pNode->pszName))
1761 /* got it! */;
1762 else
1763 {
1764 /* insert/remove */
1765 pNode = pThis->updateCallbackHandleOutOfOrder(pszName);
1766 if (!pNode)
1767 return VERR_NO_MEMORY;
1768 }
1769 }
1770 else
1771 {
1772 /* append */
1773 pNode = pThis->updateCallbackHandleTail(pszName);
1774 if (!pNode)
1775 return VERR_NO_MEMORY;
1776 }
1777
1778 /*
1779 * Perform the update and advance to the next one.
1780 */
1781 updateNode(pNode, enmType, pvSample, enmUnit, pszDesc);
1782 pThis->updateCallbackAdvance(pNode);
1783
1784 return VINF_SUCCESS;
1785}
1786
1787
1788bool
1789VBoxDbgStatsModel::updatePrepare(void)
1790{
1791 /*
1792 * Find the first child with data and set it up as the 'next'
1793 * node to be updated.
1794 */
1795 Assert(m_pRoot);
1796 Assert(m_pRoot->enmType == STAMTYPE_INVALID);
1797 PDBGGUISTATSNODE pFirst = nextDataNode(m_pRoot);
1798 if (pFirst)
1799 {
1800 m_iUpdateChild = pFirst->iSelf;
1801 m_pUpdateParent = pFirst->pParent; Assert(m_pUpdateParent);
1802 m_cchUpdateParent = getNodePath(m_pUpdateParent, m_szUpdateParent, sizeof(m_szUpdateParent) - 1);
1803 AssertReturn(m_cchUpdateParent >= 1, false);
1804 m_szUpdateParent[m_cchUpdateParent++] = '/';
1805 m_szUpdateParent[m_cchUpdateParent] = '\0';
1806 }
1807 else
1808 {
1809 m_iUpdateChild = UINT32_MAX;
1810 m_pUpdateParent = NULL;
1811 m_szUpdateParent[0] = '\0';
1812 m_cchUpdateParent = 0;
1813 }
1814
1815 /*
1816 * Set the flag and signal possible layout change.
1817 */
1818 m_fUpdateInsertRemove = false;
1819 /* emit layoutAboutToBeChanged(); - debug this, it gets stuck... */
1820 return true;
1821}
1822
1823
1824bool
1825VBoxDbgStatsModel::updateDone(bool a_fSuccess)
1826{
1827 /*
1828 * Remove any nodes following the last in the update (unless the update failed).
1829 */
1830 if ( a_fSuccess
1831 && m_iUpdateChild != UINT32_MAX)
1832 {
1833 PDBGGUISTATSNODE const pLast = prevDataNode(m_pUpdateParent->papChildren[m_iUpdateChild]);
1834 if (!pLast)
1835 {
1836 /* nuking the whole tree. */
1837 setRootNode(createRootNode());
1838 m_fUpdateInsertRemove = true;
1839 }
1840 else
1841 {
1842 PDBGGUISTATSNODE pNode;
1843 while ((pNode = nextNode(pLast)))
1844 {
1845 Assert(pNode != m_pRoot);
1846 removeAndDestroy(pNode);
1847 }
1848 }
1849 }
1850
1851 /*
1852 * We're done making layout changes (if I understood it correctly), so,
1853 * signal this and then see what to do next. If we did too many removals
1854 * we'll just reset the whole shebang.
1855 */
1856 if (m_fUpdateInsertRemove)
1857 {
1858 /* emit layoutChanged(); - hrmpf, doesn't work reliably... */
1859 reset();
1860 }
1861 else
1862 {
1863 /*
1864 * Send dataChanged events.
1865 *
1866 * We do this here instead of from the updateCallback because it reduces
1867 * the clutter in that method and allow us to emit bulk signals in an
1868 * easier way because we can traverse the tree in a different fashion.
1869 */
1870 DBGGUISTATSSTACK Stack;
1871 Stack.a[0].pNode = m_pRoot;
1872 Stack.a[0].iChild = -1;
1873 Stack.iTop = 0;
1874
1875 while (Stack.iTop >= 0)
1876 {
1877 /* get top element */
1878 PDBGGUISTATSNODE pNode = Stack.a[Stack.iTop].pNode;
1879 uint32_t iChild = ++Stack.a[Stack.iTop].iChild;
1880 if (iChild < pNode->cChildren)
1881 {
1882 /* push */
1883 Stack.iTop++;
1884 Assert(Stack.iTop < (int32_t)RT_ELEMENTS(Stack.a));
1885 Stack.a[Stack.iTop].pNode = pNode->papChildren[iChild];
1886 Stack.a[Stack.iTop].iChild = 0;
1887 }
1888 else
1889 {
1890 /* pop */
1891 Stack.iTop--;
1892
1893 /* do the actual work. */
1894 iChild = 0;
1895 while (iChild < pNode->cChildren)
1896 {
1897 /* skip to the first needing updating. */
1898 while ( iChild < pNode->cChildren
1899 && pNode->papChildren[iChild]->enmState != kDbgGuiStatsNodeState_kRefresh)
1900 iChild++;
1901 if (iChild >= pNode->cChildren)
1902 break;
1903 QModelIndex TopLeft = createIndex(iChild, 0, pNode->papChildren[iChild]);
1904 pNode->papChildren[iChild]->enmState = kDbgGuiStatsNodeState_kVisible;
1905
1906 /* any subsequent nodes that also needs refreshing? */
1907 if ( ++iChild < pNode->cChildren
1908 && pNode->papChildren[iChild]->enmState == kDbgGuiStatsNodeState_kRefresh)
1909 {
1910 do pNode->papChildren[iChild]->enmState = kDbgGuiStatsNodeState_kVisible;
1911 while ( ++iChild < pNode->cChildren
1912 && pNode->papChildren[iChild]->enmState == kDbgGuiStatsNodeState_kRefresh);
1913 QModelIndex BottomRight = createIndex(iChild - 1, DBGGUI_STATS_COLUMNS - 1, pNode->papChildren[iChild - 1]);
1914
1915 /* emit the refresh signal */
1916 emit dataChanged(TopLeft, BottomRight);
1917 }
1918 else
1919 {
1920 /* emit the refresh signal */
1921 emit dataChanged(TopLeft, TopLeft);
1922 }
1923 }
1924 }
1925 }
1926 /* emit layoutChanged(); - hrmpf, doesn't work reliably... */
1927 }
1928
1929 return m_fUpdateInsertRemove;
1930}
1931
1932
1933bool
1934VBoxDbgStatsModel::updateStatsByPattern(const QString &a_rPatStr)
1935{
1936 /* stub */
1937 NOREF(a_rPatStr);
1938 return false;
1939}
1940
1941
1942void
1943VBoxDbgStatsModel::updateStatsByIndex(QModelIndex const &a_rIndex)
1944{
1945 /** @todo implement this based on updateStatsByPattern. */
1946 NOREF(a_rIndex);
1947}
1948
1949
1950void
1951VBoxDbgStatsModel::resetStatsByPattern(QString const &a_rPatStr)
1952{
1953 /* stub */
1954 NOREF(a_rPatStr);
1955}
1956
1957
1958void
1959VBoxDbgStatsModel::resetStatsByIndex(QModelIndex const &a_rIndex, bool fSubTree /*= true*/)
1960{
1961 PCDBGGUISTATSNODE pNode = nodeFromIndex(a_rIndex);
1962 if (pNode == m_pRoot || !a_rIndex.isValid())
1963 {
1964 if (fSubTree)
1965 {
1966 /* everything from the root down. */
1967 resetStatsByPattern(QString());
1968 }
1969 }
1970 else if (pNode)
1971 {
1972 /* the node pattern. */
1973 char szPat[1024+1024+4];
1974 ssize_t cch = getNodePath(pNode, szPat, 1024);
1975 AssertReturnVoid(cch >= 0);
1976
1977 /* the sub-tree pattern. */
1978 if (fSubTree && pNode->cChildren)
1979 {
1980 char *psz = &szPat[cch];
1981 *psz++ = '|';
1982 memcpy(psz, szPat, cch);
1983 psz += cch;
1984 *psz++ = '/';
1985 *psz++ = '*';
1986 *psz++ = '\0';
1987 }
1988
1989 resetStatsByPattern(szPat);
1990 }
1991}
1992
1993
1994QModelIndex
1995VBoxDbgStatsModel::getRootIndex(void) const
1996{
1997 if (!m_pRoot)
1998 return QModelIndex();
1999 return createIndex(0, 0, m_pRoot);
2000}
2001
2002
2003void
2004VBoxDbgStatsModel::setRootNode(PDBGGUISTATSNODE a_pRoot)
2005{
2006 PDBGGUISTATSNODE pOldTree = m_pRoot;
2007 m_pRoot = a_pRoot;
2008 destroyTree(pOldTree);
2009 reset();
2010}
2011
2012
2013Qt::ItemFlags
2014VBoxDbgStatsModel::flags(const QModelIndex &a_rIndex) const
2015{
2016 Qt::ItemFlags fFlags = QAbstractItemModel::flags(a_rIndex);
2017 return fFlags;
2018}
2019
2020
2021int
2022VBoxDbgStatsModel::columnCount(const QModelIndex &a_rParent) const
2023{
2024 NOREF(a_rParent);
2025 return DBGGUI_STATS_COLUMNS;
2026}
2027
2028
2029int
2030VBoxDbgStatsModel::rowCount(const QModelIndex &a_rParent) const
2031{
2032 PDBGGUISTATSNODE pParent = nodeFromIndex(a_rParent);
2033 return pParent ? pParent->cChildren : 1 /* root */;
2034}
2035
2036
2037bool
2038VBoxDbgStatsModel::hasChildren(const QModelIndex &a_rParent) const
2039{
2040 PDBGGUISTATSNODE pParent = nodeFromIndex(a_rParent);
2041 return pParent ? pParent->cChildren > 0 : true /* root */;
2042}
2043
2044
2045QModelIndex
2046VBoxDbgStatsModel::index(int iRow, int iColumn, const QModelIndex &a_rParent) const
2047{
2048 PDBGGUISTATSNODE pParent = nodeFromIndex(a_rParent);
2049 if (!pParent)
2050 {
2051 if ( a_rParent.isValid()
2052 || iRow
2053 || (unsigned)iColumn < DBGGUI_STATS_COLUMNS)
2054 {
2055 Assert(!a_rParent.isValid());
2056 Assert(!iRow);
2057 Assert((unsigned)iColumn < DBGGUI_STATS_COLUMNS);
2058 return QModelIndex();
2059 }
2060
2061 /* root */
2062 return createIndex(0, iColumn, m_pRoot);
2063 }
2064 if ((unsigned)iRow >= pParent->cChildren)
2065 {
2066 Log(("index: iRow=%d >= cChildren=%u (iColumn=%d)\n", iRow, (unsigned)pParent->cChildren, iColumn));
2067 return QModelIndex(); /* bug? */
2068 }
2069 if ((unsigned)iColumn >= DBGGUI_STATS_COLUMNS)
2070 {
2071 Log(("index: iColumn=%d (iRow=%d)\n", iColumn, iRow));
2072 return QModelIndex(); /* bug? */
2073 }
2074 PDBGGUISTATSNODE pChild = pParent->papChildren[iRow];
2075 return createIndex(iRow, iColumn, pChild);
2076}
2077
2078
2079QModelIndex
2080VBoxDbgStatsModel::parent(const QModelIndex &a_rChild) const
2081{
2082 PDBGGUISTATSNODE pChild = nodeFromIndex(a_rChild);
2083 if (!pChild)
2084 {
2085 Log(("parent: invalid child\n"));
2086 return QModelIndex(); /* bug */
2087 }
2088 PDBGGUISTATSNODE pParent = pChild->pParent;
2089 if (!pParent)
2090 return QModelIndex(); /* ultimate root */
2091
2092 return createIndex(pParent->iSelf, 0, pParent);
2093}
2094
2095
2096QVariant
2097VBoxDbgStatsModel::headerData(int a_iSection, Qt::Orientation a_eOrientation, int a_eRole) const
2098{
2099 if ( a_eOrientation == Qt::Horizontal
2100 && a_eRole == Qt::DisplayRole)
2101 switch (a_iSection)
2102 {
2103 case 0: return tr("Name");
2104 case 1: return tr("Unit");
2105 case 2: return tr("Value/Times");
2106 case 3: return tr("Min");
2107 case 4: return tr("Average");
2108 case 5: return tr("Max");
2109 case 6: return tr("Total");
2110 case 7: return tr("dInt");
2111 case 8: return tr("Description");
2112 default:
2113 AssertCompile(DBGGUI_STATS_COLUMNS == 9);
2114 return QVariant(); /* bug */
2115 }
2116 else if ( a_eOrientation == Qt::Horizontal
2117 && a_eRole == Qt::TextAlignmentRole)
2118 switch (a_iSection)
2119 {
2120 case 0:
2121 case 1:
2122 return QVariant();
2123 case 2:
2124 case 3:
2125 case 4:
2126 case 5:
2127 case 6:
2128 case 7:
2129 return (int)(Qt::AlignRight | Qt::AlignVCenter);
2130 case 8:
2131 return QVariant();
2132 default:
2133 AssertCompile(DBGGUI_STATS_COLUMNS == 9);
2134 return QVariant(); /* bug */
2135 }
2136
2137 return QVariant();
2138}
2139
2140
2141/*static*/ QString
2142VBoxDbgStatsModel::strUnit(PCDBGGUISTATSNODE pNode)
2143{
2144 if (pNode->enmUnit == STAMUNIT_INVALID)
2145 return "";
2146 return STAMR3GetUnit(pNode->enmUnit);
2147}
2148
2149
2150/*static*/ QString
2151VBoxDbgStatsModel::strValueTimes(PCDBGGUISTATSNODE pNode)
2152{
2153 char sz[128];
2154
2155 switch (pNode->enmType)
2156 {
2157 case STAMTYPE_COUNTER:
2158 return formatNumber(sz, pNode->Data.Counter.c);
2159
2160 case STAMTYPE_PROFILE:
2161 case STAMTYPE_PROFILE_ADV:
2162 if (!pNode->Data.Profile.cPeriods)
2163 return "0";
2164 return formatNumber(sz, pNode->Data.Profile.cPeriods);
2165
2166 case STAMTYPE_RATIO_U32:
2167 case STAMTYPE_RATIO_U32_RESET:
2168 {
2169 formatNumber(sz, pNode->Data.RatioU32.u32A);
2170 char *psz = strchr(sz, '\0');
2171 *psz++ = ':';
2172 formatNumber(psz, pNode->Data.RatioU32.u32B);
2173 return psz;
2174 }
2175
2176 case STAMTYPE_CALLBACK:
2177 return *pNode->Data.pStr;
2178
2179 case STAMTYPE_U8:
2180 case STAMTYPE_U8_RESET:
2181 return formatNumber(sz, pNode->Data.u8);
2182
2183 case STAMTYPE_X8:
2184 case STAMTYPE_X8_RESET:
2185 return formatHexNumber(sz, pNode->Data.u8, 2);
2186
2187 case STAMTYPE_U16:
2188 case STAMTYPE_U16_RESET:
2189 return formatNumber(sz, pNode->Data.u16);
2190
2191 case STAMTYPE_X16:
2192 case STAMTYPE_X16_RESET:
2193 return formatHexNumber(sz, pNode->Data.u16, 4);
2194
2195 case STAMTYPE_U32:
2196 case STAMTYPE_U32_RESET:
2197 return formatNumber(sz, pNode->Data.u32);
2198
2199 case STAMTYPE_X32:
2200 case STAMTYPE_X32_RESET:
2201 return formatHexNumber(sz, pNode->Data.u32, 8);
2202
2203 case STAMTYPE_U64:
2204 case STAMTYPE_U64_RESET:
2205 return formatNumber(sz, pNode->Data.u64);
2206
2207 case STAMTYPE_X64:
2208 case STAMTYPE_X64_RESET:
2209 return formatHexNumber(sz, pNode->Data.u64, 16);
2210
2211 case STAMTYPE_BOOL:
2212 case STAMTYPE_BOOL_RESET:
2213 return pNode->Data.f ? "true" : "false";
2214
2215 default:
2216 AssertMsgFailed(("%d\n", pNode->enmType));
2217 case STAMTYPE_INVALID:
2218 return "";
2219 }
2220}
2221
2222
2223/*static*/ QString
2224VBoxDbgStatsModel::strMinValue(PCDBGGUISTATSNODE pNode)
2225{
2226 char sz[128];
2227
2228 switch (pNode->enmType)
2229 {
2230 case STAMTYPE_PROFILE:
2231 case STAMTYPE_PROFILE_ADV:
2232 if (!pNode->Data.Profile.cPeriods)
2233 return "0";
2234 return formatNumber(sz, pNode->Data.Profile.cTicksMin);
2235 default:
2236 return "";
2237 }
2238}
2239
2240
2241/*static*/ QString
2242VBoxDbgStatsModel::strAvgValue(PCDBGGUISTATSNODE pNode)
2243{
2244 char sz[128];
2245
2246 switch (pNode->enmType)
2247 {
2248 case STAMTYPE_PROFILE:
2249 case STAMTYPE_PROFILE_ADV:
2250 if (!pNode->Data.Profile.cPeriods)
2251 return "0";
2252 return formatNumber(sz, pNode->Data.Profile.cTicks / pNode->Data.Profile.cPeriods);
2253 default:
2254 return "";
2255 }
2256}
2257
2258
2259/*static*/ QString
2260VBoxDbgStatsModel::strMaxValue(PCDBGGUISTATSNODE pNode)
2261{
2262 char sz[128];
2263
2264 switch (pNode->enmType)
2265 {
2266 case STAMTYPE_PROFILE:
2267 case STAMTYPE_PROFILE_ADV:
2268 if (!pNode->Data.Profile.cPeriods)
2269 return "0";
2270 return formatNumber(sz, pNode->Data.Profile.cTicksMax);
2271 default:
2272 return "";
2273 }
2274}
2275
2276
2277/*static*/ QString
2278VBoxDbgStatsModel::strTotalValue(PCDBGGUISTATSNODE pNode)
2279{
2280 char sz[128];
2281
2282 switch (pNode->enmType)
2283 {
2284 case STAMTYPE_PROFILE:
2285 case STAMTYPE_PROFILE_ADV:
2286 if (!pNode->Data.Profile.cPeriods)
2287 return "0";
2288 return formatNumber(sz, pNode->Data.Profile.cTicks);
2289 default:
2290 return "";
2291 }
2292}
2293
2294
2295/*static*/ QString
2296VBoxDbgStatsModel::strDeltaValue(PCDBGGUISTATSNODE pNode)
2297{
2298 char sz[128];
2299
2300 switch (pNode->enmType)
2301 {
2302 case STAMTYPE_PROFILE:
2303 case STAMTYPE_PROFILE_ADV:
2304 if (!pNode->Data.Profile.cPeriods)
2305 return "0";
2306 /* fall thru */
2307 case STAMTYPE_COUNTER:
2308 case STAMTYPE_RATIO_U32:
2309 case STAMTYPE_RATIO_U32_RESET:
2310 case STAMTYPE_U8:
2311 case STAMTYPE_U8_RESET:
2312 case STAMTYPE_X8:
2313 case STAMTYPE_X8_RESET:
2314 case STAMTYPE_U16:
2315 case STAMTYPE_U16_RESET:
2316 case STAMTYPE_X16:
2317 case STAMTYPE_X16_RESET:
2318 case STAMTYPE_U32:
2319 case STAMTYPE_U32_RESET:
2320 case STAMTYPE_X32:
2321 case STAMTYPE_X32_RESET:
2322 case STAMTYPE_U64:
2323 case STAMTYPE_U64_RESET:
2324 case STAMTYPE_X64:
2325 case STAMTYPE_X64_RESET:
2326 case STAMTYPE_BOOL:
2327 case STAMTYPE_BOOL_RESET:
2328 return formatNumberSigned(sz, pNode->i64Delta);
2329 default:
2330 return "";
2331 }
2332}
2333
2334
2335QVariant
2336VBoxDbgStatsModel::data(const QModelIndex &a_rIndex, int a_eRole) const
2337{
2338 unsigned iCol = a_rIndex.column();
2339 if (iCol >= DBGGUI_STATS_COLUMNS)
2340 return QVariant();
2341
2342 if (a_eRole == Qt::DisplayRole)
2343 {
2344 PDBGGUISTATSNODE pNode = nodeFromIndex(a_rIndex);
2345 if (!pNode)
2346 return QVariant();
2347
2348 switch (iCol)
2349 {
2350 case 0:
2351 return QString(pNode->pszName);
2352 case 1:
2353 return strUnit(pNode);
2354 case 2:
2355 return strValueTimes(pNode);
2356 case 3:
2357 return strMinValue(pNode);
2358 case 4:
2359 return strAvgValue(pNode);
2360 case 5:
2361 return strMaxValue(pNode);
2362 case 6:
2363 return strTotalValue(pNode);
2364 case 7:
2365 return strDeltaValue(pNode);
2366 case 8:
2367 return pNode->pDescStr ? QString(*pNode->pDescStr) : QString("");
2368 default:
2369 AssertCompile(DBGGUI_STATS_COLUMNS == 9);
2370 return QVariant();
2371 }
2372 }
2373 else if (a_eRole == Qt::TextAlignmentRole)
2374 switch (iCol)
2375 {
2376 case 0:
2377 case 1:
2378 return QVariant();
2379 case 2:
2380 case 3:
2381 case 4:
2382 case 5:
2383 case 6:
2384 case 7:
2385 return (int)(Qt::AlignRight | Qt::AlignVCenter);
2386 case 8:
2387 return QVariant();
2388 default:
2389 AssertCompile(DBGGUI_STATS_COLUMNS == 9);
2390 return QVariant(); /* bug */
2391 }
2392 return QVariant();
2393}
2394
2395
2396/*static*/ void
2397VBoxDbgStatsModel::stringifyNodeNoRecursion(PDBGGUISTATSNODE a_pNode, QString &a_rString)
2398{
2399 /*
2400 * Get the path, padding it to 32-chars and add it to the string.
2401 */
2402 char szBuf[1024];
2403 ssize_t off = getNodePath(a_pNode, szBuf, sizeof(szBuf) - 2);
2404 AssertReturnVoid(off >= 0);
2405 if (off < 32)
2406 {
2407 memset(&szBuf[off], ' ', 32 - off);
2408 szBuf[32] = '\0';
2409 off = 32;
2410 }
2411 szBuf[off++] = ' ';
2412 szBuf[off] = '\0';
2413 a_rString += szBuf;
2414
2415 /*
2416 * The following is derived from stamR3PrintOne, except
2417 * we print to szBuf, do no visibility checks and can skip
2418 * the path bit.
2419 */
2420 switch (a_pNode->enmType)
2421 {
2422 case STAMTYPE_COUNTER:
2423 RTStrPrintf(szBuf, sizeof(szBuf), "%8llu %s", a_pNode->Data.Counter.c, STAMR3GetUnit(a_pNode->enmUnit));
2424 break;
2425
2426 case STAMTYPE_PROFILE:
2427 case STAMTYPE_PROFILE_ADV:
2428 {
2429 uint64_t u64 = a_pNode->Data.Profile.cPeriods ? a_pNode->Data.Profile.cPeriods : 1;
2430 RTStrPrintf(szBuf, sizeof(szBuf),
2431 "%8llu %s (%12llu ticks, %7llu times, max %9llu, min %7lld)",
2432 a_pNode->Data.Profile.cTicks / u64, STAMR3GetUnit(a_pNode->enmUnit),
2433 a_pNode->Data.Profile.cTicks, a_pNode->Data.Profile.cPeriods, a_pNode->Data.Profile.cTicksMax, a_pNode->Data.Profile.cTicksMin);
2434 break;
2435 }
2436
2437 case STAMTYPE_RATIO_U32:
2438 case STAMTYPE_RATIO_U32_RESET:
2439 RTStrPrintf(szBuf, sizeof(szBuf),
2440 "%8u:%-8u %s",
2441 a_pNode->Data.RatioU32.u32A, a_pNode->Data.RatioU32.u32B, STAMR3GetUnit(a_pNode->enmUnit));
2442 break;
2443
2444 case STAMTYPE_CALLBACK:
2445 if (a_pNode->Data.pStr)
2446 a_rString += *a_pNode->Data.pStr;
2447 RTStrPrintf(szBuf, sizeof(szBuf), " %s", STAMR3GetUnit(a_pNode->enmUnit));
2448 break;
2449
2450 case STAMTYPE_U8:
2451 case STAMTYPE_U8_RESET:
2452 RTStrPrintf(szBuf, sizeof(szBuf), "%8u %s", a_pNode->Data.u8, STAMR3GetUnit(a_pNode->enmUnit));
2453 break;
2454
2455 case STAMTYPE_X8:
2456 case STAMTYPE_X8_RESET:
2457 RTStrPrintf(szBuf, sizeof(szBuf), "%8x %s", a_pNode->Data.u8, STAMR3GetUnit(a_pNode->enmUnit));
2458 break;
2459
2460 case STAMTYPE_U16:
2461 case STAMTYPE_U16_RESET:
2462 RTStrPrintf(szBuf, sizeof(szBuf), "%8u %s", a_pNode->Data.u16, STAMR3GetUnit(a_pNode->enmUnit));
2463 break;
2464
2465 case STAMTYPE_X16:
2466 case STAMTYPE_X16_RESET:
2467 RTStrPrintf(szBuf, sizeof(szBuf), "%8x %s", a_pNode->Data.u16, STAMR3GetUnit(a_pNode->enmUnit));
2468 break;
2469
2470 case STAMTYPE_U32:
2471 case STAMTYPE_U32_RESET:
2472 RTStrPrintf(szBuf, sizeof(szBuf), "%8u %s", a_pNode->Data.u32, STAMR3GetUnit(a_pNode->enmUnit));
2473 break;
2474
2475 case STAMTYPE_X32:
2476 case STAMTYPE_X32_RESET:
2477 RTStrPrintf(szBuf, sizeof(szBuf), "%8x %s", a_pNode->Data.u32, STAMR3GetUnit(a_pNode->enmUnit));
2478 break;
2479
2480 case STAMTYPE_U64:
2481 case STAMTYPE_U64_RESET:
2482 RTStrPrintf(szBuf, sizeof(szBuf), "%8llu %s", a_pNode->Data.u64, STAMR3GetUnit(a_pNode->enmUnit));
2483 break;
2484
2485 case STAMTYPE_X64:
2486 case STAMTYPE_X64_RESET:
2487 RTStrPrintf(szBuf, sizeof(szBuf), "%8llx %s", a_pNode->Data.u64, STAMR3GetUnit(a_pNode->enmUnit));
2488 break;
2489
2490 case STAMTYPE_BOOL:
2491 case STAMTYPE_BOOL_RESET:
2492 RTStrPrintf(szBuf, sizeof(szBuf), "%s %s", a_pNode->Data.f ? "true " : "false ", STAMR3GetUnit(a_pNode->enmUnit));
2493 break;
2494
2495 default:
2496 AssertMsgFailed(("enmType=%d\n", a_pNode->enmType));
2497 return;
2498 }
2499
2500 a_rString += szBuf;
2501}
2502
2503
2504/*static*/ void
2505VBoxDbgStatsModel::stringifyNode(PDBGGUISTATSNODE a_pNode, QString &a_rString)
2506{
2507 /* this node (if it has data) */
2508 if (a_pNode->enmType != STAMTYPE_INVALID)
2509 {
2510 if (!a_rString.isEmpty())
2511 a_rString += "\n";
2512 stringifyNodeNoRecursion(a_pNode, a_rString);
2513 }
2514
2515 /* the children */
2516 uint32_t const cChildren = a_pNode->cChildren;
2517 for (uint32_t i = 0; i < cChildren; i++)
2518 stringifyNode(a_pNode->papChildren[i], a_rString);
2519}
2520
2521
2522void
2523VBoxDbgStatsModel::stringifyTree(QModelIndex &a_rRoot, QString &a_rString) const
2524{
2525 PDBGGUISTATSNODE pRoot = a_rRoot.isValid() ? nodeFromIndex(a_rRoot) : m_pRoot;
2526 if (pRoot)
2527 stringifyNode(pRoot, a_rString);
2528}
2529
2530
2531void
2532VBoxDbgStatsModel::copyTreeToClipboard(QModelIndex &a_rRoot) const
2533{
2534 QString String;
2535 stringifyTree(a_rRoot, String);
2536
2537 QClipboard *pClipboard = QApplication::clipboard();
2538 if (pClipboard)
2539 pClipboard->setText(String, QClipboard::Clipboard);
2540}
2541
2542
2543/*static*/ void
2544VBoxDbgStatsModel::logNode(PDBGGUISTATSNODE a_pNode, bool a_fReleaseLog)
2545{
2546 /* this node (if it has data) */
2547 if (a_pNode->enmType != STAMTYPE_INVALID)
2548 {
2549 QString SelfStr;
2550 stringifyNodeNoRecursion(a_pNode, SelfStr);
2551 QByteArray SelfByteArray = SelfStr.toUtf8();
2552 if (a_fReleaseLog)
2553 RTLogRelPrintf("%s\n", SelfByteArray.constData());
2554 else
2555 RTLogPrintf("%s\n", SelfByteArray.constData());
2556 }
2557
2558 /* the children */
2559 uint32_t const cChildren = a_pNode->cChildren;
2560 for (uint32_t i = 0; i < cChildren; i++)
2561 logNode(a_pNode->papChildren[i], a_fReleaseLog);
2562}
2563
2564
2565void
2566VBoxDbgStatsModel::logTree(QModelIndex &a_rRoot, bool a_fReleaseLog) const
2567{
2568 PDBGGUISTATSNODE pRoot = a_rRoot.isValid() ? nodeFromIndex(a_rRoot) : m_pRoot;
2569 if (pRoot)
2570 logNode(pRoot, a_fReleaseLog);
2571}
2572
2573
2574
2575
2576
2577
2578
2579/*
2580 *
2581 * V B o x D b g S t a t s M o d e l V M
2582 * V B o x D b g S t a t s M o d e l V M
2583 * V B o x D b g S t a t s M o d e l V M
2584 *
2585 *
2586 */
2587
2588
2589VBoxDbgStatsModelVM::VBoxDbgStatsModelVM(VBoxDbgGui *a_pDbgGui, QString &a_rPatStr, QObject *a_pParent)
2590 : VBoxDbgStatsModel(a_pParent), VBoxDbgBase(a_pDbgGui)
2591{
2592 /*
2593 * Create a model containing the STAM entries matching the pattern.
2594 * (The original idea was to get everything and rely on some hide/visible
2595 * flag that it turned out didn't exist.)
2596 */
2597 PDBGGUISTATSNODE pTree = createNewTree(a_rPatStr);
2598 setRootNode(pTree);
2599}
2600
2601
2602VBoxDbgStatsModelVM::~VBoxDbgStatsModelVM()
2603{
2604 /* nothing to do here. */
2605}
2606
2607
2608bool
2609VBoxDbgStatsModelVM::updateStatsByPattern(const QString &a_rPatStr)
2610{
2611 /** @todo the way we update this stuff is independent of the source (XML, file, STAM), our only
2612 * ASSUMPTION is that the input is strictly ordered by (fully slashed) name. So, all this stuff
2613 * should really move up into the parent class. */
2614 bool fRc = updatePrepare();
2615 if (fRc)
2616 {
2617 int rc = stamEnum(a_rPatStr, updateCallback, this);
2618 fRc = updateDone(RT_SUCCESS(rc));
2619 }
2620 return fRc;
2621}
2622
2623
2624void
2625VBoxDbgStatsModelVM::resetStatsByPattern(QString const &a_rPatStr)
2626{
2627 stamReset(a_rPatStr);
2628}
2629
2630
2631/*static*/ DECLCALLBACK(int)
2632VBoxDbgStatsModelVM::createNewTreeCallback(const char *pszName, STAMTYPE enmType, void *pvSample, STAMUNIT enmUnit,
2633 STAMVISIBILITY enmVisibility, const char *pszDesc, void *pvUser)
2634{
2635 PDBGGUISTATSNODE pRoot = (PDBGGUISTATSNODE)pvUser;
2636 Log3(("createNewTreeCallback: %s\n", pszName));
2637
2638 /*
2639 * Skip the ones which shouldn't be visible in the GUI.
2640 */
2641 if (enmVisibility == STAMVISIBILITY_NOT_GUI)
2642 return 0;
2643
2644 /*
2645 * Perform a mkdir -p like operation till we've walked / created the entire path down
2646 * to the node specfied node. Remember the last node as that will be the one we will
2647 * stuff the data into.
2648 */
2649 AssertReturn(*pszName == '/' && pszName[1] != '/', VERR_INTERNAL_ERROR);
2650 PDBGGUISTATSNODE pNode = pRoot;
2651 const char *pszCur = pszName + 1;
2652 while (*pszCur)
2653 {
2654 /* find the end of this component. */
2655 const char *pszNext = strchr(pszCur, '/');
2656 if (!pszNext)
2657 pszNext = strchr(pszCur, '\0');
2658 size_t cchCur = pszNext - pszCur;
2659
2660 /* Create it if it doesn't exist (it will be last if it exists). */
2661 if ( !pNode->cChildren
2662 || strncmp(pNode->papChildren[pNode->cChildren - 1]->pszName, pszCur, cchCur)
2663 || pNode->papChildren[pNode->cChildren - 1]->pszName[cchCur])
2664 {
2665 pNode = createAndInsertNode(pNode, pszCur, pszNext - pszCur, UINT32_MAX);
2666 if (!pNode)
2667 return VERR_NO_MEMORY;
2668 }
2669 else
2670 pNode = pNode->papChildren[pNode->cChildren - 1];
2671
2672 /* Advance */
2673 pszCur = *pszNext ? pszNext + 1 : pszNext;
2674 }
2675
2676 /*
2677 * Save the data.
2678 */
2679 return initNode(pNode, enmType, pvSample, enmUnit, pszDesc);
2680}
2681
2682
2683PDBGGUISTATSNODE
2684VBoxDbgStatsModelVM::createNewTree(QString &a_rPatStr)
2685{
2686 PDBGGUISTATSNODE pRoot = createRootNode();
2687 if (pRoot)
2688 {
2689 int rc = stamEnum(a_rPatStr, createNewTreeCallback, pRoot);
2690 if (RT_SUCCESS(rc))
2691 return pRoot;
2692
2693 /* failed, cleanup. */
2694 destroyTree(pRoot);
2695 }
2696
2697 return NULL;
2698}
2699
2700
2701
2702
2703
2704
2705
2706
2707/*
2708 *
2709 * V B o x D b g S t a t s V i e w
2710 * V B o x D b g S t a t s V i e w
2711 * V B o x D b g S t a t s V i e w
2712 *
2713 *
2714 */
2715
2716
2717VBoxDbgStatsView::VBoxDbgStatsView(VBoxDbgGui *a_pDbgGui, VBoxDbgStatsModel *a_pModel, VBoxDbgStats *a_pParent/* = NULL*/)
2718 : QTreeView(a_pParent), VBoxDbgBase(a_pDbgGui), m_pModel(a_pModel), m_PatStr(), m_pParent(a_pParent),
2719 m_pLeafMenu(NULL), m_pBranchMenu(NULL), m_pViewMenu(NULL), m_pCurMenu(NULL), m_CurIndex()
2720
2721{
2722 /*
2723 * Set the model and view defaults.
2724 */
2725 setRootIsDecorated(true);
2726 setModel(m_pModel);
2727 QModelIndex RootIdx = m_pModel->getRootIndex(); /* This should really be QModelIndex(), but Qt on darwin does wrong things then. */
2728 setRootIndex(RootIdx);
2729 setItemsExpandable(true);
2730 setAlternatingRowColors(true);
2731 setSelectionBehavior(SelectRows);
2732 setSelectionMode(SingleSelection);
2733 /// @todo sorting setSortingEnabled(true);
2734
2735 /*
2736 * Create and setup the actions.
2737 */
2738 m_pExpandAct = new QAction("Expand Tree", this);
2739 m_pCollapseAct = new QAction("Collapse Tree", this);
2740 m_pRefreshAct = new QAction("&Refresh", this);
2741 m_pResetAct = new QAction("Rese&t", this);
2742 m_pCopyAct = new QAction("&Copy", this);
2743 m_pToLogAct = new QAction("To &Log", this);
2744 m_pToRelLogAct = new QAction("T&o Release Log", this);
2745 m_pAdjColumns = new QAction("&Adjust Columns", this);
2746
2747 m_pCopyAct->setShortcut(QKeySequence::Copy);
2748 m_pExpandAct->setShortcut(QKeySequence("Ctrl+E"));
2749 m_pCollapseAct->setShortcut(QKeySequence("Ctrl+D"));
2750 m_pRefreshAct->setShortcut(QKeySequence("Ctrl+R"));
2751 m_pResetAct->setShortcut(QKeySequence("Alt+R"));
2752 m_pToLogAct->setShortcut(QKeySequence("Ctrl+Z"));
2753 m_pToRelLogAct->setShortcut(QKeySequence("Alt+Z"));
2754 m_pAdjColumns->setShortcut(QKeySequence("Ctrl+A"));
2755
2756 addAction(m_pCopyAct);
2757 addAction(m_pExpandAct);
2758 addAction(m_pCollapseAct);
2759 addAction(m_pRefreshAct);
2760 addAction(m_pResetAct);
2761 addAction(m_pToLogAct);
2762 addAction(m_pToRelLogAct);
2763 addAction(m_pAdjColumns);
2764
2765 connect(m_pExpandAct, SIGNAL(triggered(bool)), this, SLOT(actExpand()));
2766 connect(m_pCollapseAct, SIGNAL(triggered(bool)), this, SLOT(actCollapse()));
2767 connect(m_pRefreshAct, SIGNAL(triggered(bool)), this, SLOT(actRefresh()));
2768 connect(m_pResetAct, SIGNAL(triggered(bool)), this, SLOT(actReset()));
2769 connect(m_pCopyAct, SIGNAL(triggered(bool)), this, SLOT(actCopy()));
2770 connect(m_pToLogAct, SIGNAL(triggered(bool)), this, SLOT(actToLog()));
2771 connect(m_pToRelLogAct, SIGNAL(triggered(bool)), this, SLOT(actToRelLog()));
2772 connect(m_pAdjColumns, SIGNAL(triggered(bool)), this, SLOT(actAdjColumns()));
2773
2774
2775 /*
2776 * Create the menus and populate them.
2777 */
2778 setContextMenuPolicy(Qt::DefaultContextMenu);
2779
2780 m_pLeafMenu = new QMenu();
2781 m_pLeafMenu->addAction(m_pCopyAct);
2782 m_pLeafMenu->addAction(m_pRefreshAct);
2783 m_pLeafMenu->addAction(m_pResetAct);
2784 m_pLeafMenu->addAction(m_pToLogAct);
2785 m_pLeafMenu->addAction(m_pToRelLogAct);
2786
2787 m_pBranchMenu = new QMenu(this);
2788 m_pBranchMenu->addAction(m_pCopyAct);
2789 m_pBranchMenu->addAction(m_pRefreshAct);
2790 m_pBranchMenu->addAction(m_pResetAct);
2791 m_pBranchMenu->addAction(m_pToLogAct);
2792 m_pBranchMenu->addAction(m_pToRelLogAct);
2793 m_pBranchMenu->addSeparator();
2794 m_pBranchMenu->addAction(m_pExpandAct);
2795 m_pBranchMenu->addAction(m_pCollapseAct);
2796
2797 m_pViewMenu = new QMenu();
2798 m_pViewMenu->addAction(m_pCopyAct);
2799 m_pViewMenu->addAction(m_pRefreshAct);
2800 m_pViewMenu->addAction(m_pResetAct);
2801 m_pViewMenu->addAction(m_pToLogAct);
2802 m_pViewMenu->addAction(m_pToRelLogAct);
2803 m_pViewMenu->addSeparator();
2804 m_pViewMenu->addAction(m_pExpandAct);
2805 m_pViewMenu->addAction(m_pCollapseAct);
2806 m_pViewMenu->addSeparator();
2807 m_pViewMenu->addAction(m_pAdjColumns);
2808
2809 /* the header menu */
2810 QHeaderView *pHdrView = header();
2811 pHdrView->setContextMenuPolicy(Qt::CustomContextMenu);
2812 connect(pHdrView, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(headerContextMenuRequested(const QPoint &)));
2813}
2814
2815
2816VBoxDbgStatsView::~VBoxDbgStatsView()
2817{
2818 m_pParent = NULL;
2819 m_pCurMenu = NULL;
2820 m_CurIndex = QModelIndex();
2821
2822#define DELETE_IT(m) if (m) { delete m; m = NULL; } else do {} while (0)
2823 DELETE_IT(m_pModel);
2824
2825 DELETE_IT(m_pLeafMenu);
2826 DELETE_IT(m_pBranchMenu);
2827 DELETE_IT(m_pViewMenu);
2828
2829 DELETE_IT(m_pExpandAct);
2830 DELETE_IT(m_pCollapseAct);
2831 DELETE_IT(m_pRefreshAct);
2832 DELETE_IT(m_pResetAct);
2833 DELETE_IT(m_pCopyAct);
2834 DELETE_IT(m_pToLogAct);
2835 DELETE_IT(m_pToRelLogAct);
2836 DELETE_IT(m_pAdjColumns);
2837#undef DELETE_IT
2838}
2839
2840
2841void
2842VBoxDbgStatsView::updateStats(const QString &rPatStr)
2843{
2844 m_PatStr = rPatStr;
2845 if (m_pModel->updateStatsByPattern(rPatStr))
2846 setRootIndex(m_pModel->getRootIndex()); /* hack */
2847}
2848
2849
2850void
2851VBoxDbgStatsView::resizeColumnsToContent()
2852{
2853 for (int i = 0; i <= 8; i++)
2854 {
2855 resizeColumnToContents(i);
2856 /* Some extra room for distinguishing numbers better in Value, Min, Avg, Max, Total, dInt columns. */
2857 if (i >= 2 && i <= 7)
2858 setColumnWidth(i, columnWidth(i) + 10);
2859 }
2860}
2861
2862
2863void
2864VBoxDbgStatsView::setSubTreeExpanded(QModelIndex const &a_rIndex, bool a_fExpanded)
2865{
2866 int cRows = m_pModel->rowCount(a_rIndex);
2867 for (int i = 0; i < cRows; i++)
2868 setSubTreeExpanded(a_rIndex.child(i, 0), a_fExpanded);
2869 setExpanded(a_rIndex, a_fExpanded);
2870}
2871
2872
2873void
2874VBoxDbgStatsView::contextMenuEvent(QContextMenuEvent *a_pEvt)
2875{
2876 /*
2877 * Get the selected item.
2878 * If it's a mouse event select the item under the cursor (if any).
2879 */
2880 QModelIndex Idx;
2881 if (a_pEvt->reason() == QContextMenuEvent::Mouse)
2882 {
2883 Idx = indexAt(a_pEvt->pos());
2884 if (Idx.isValid())
2885 setCurrentIndex(Idx);
2886 }
2887 else
2888 {
2889 QModelIndexList SelIdx = selectedIndexes();
2890 if (!SelIdx.isEmpty())
2891 Idx = SelIdx.at(0);
2892 }
2893
2894 /*
2895 * Popup the corresponding menu.
2896 */
2897 QMenu *pMenu;
2898 if (!Idx.isValid())
2899 pMenu = m_pViewMenu;
2900 else if (m_pModel->hasChildren(Idx))
2901 pMenu = m_pBranchMenu;
2902 else
2903 pMenu = m_pLeafMenu;
2904 if (pMenu)
2905 {
2906 m_pRefreshAct->setEnabled(!Idx.isValid() || Idx == m_pModel->getRootIndex());
2907 m_CurIndex = Idx;
2908 m_pCurMenu = pMenu;
2909
2910 pMenu->exec(a_pEvt->globalPos());
2911
2912 m_pCurMenu = NULL;
2913 m_CurIndex = QModelIndex();
2914 if (m_pRefreshAct)
2915 m_pRefreshAct->setEnabled(true);
2916 }
2917 a_pEvt->accept();
2918}
2919
2920
2921void
2922VBoxDbgStatsView::headerContextMenuRequested(const QPoint &a_rPos)
2923{
2924 /*
2925 * Show the view menu.
2926 */
2927 if (m_pViewMenu)
2928 {
2929 m_pRefreshAct->setEnabled(true);
2930 m_CurIndex = m_pModel->getRootIndex();
2931 m_pCurMenu = m_pViewMenu;
2932
2933 m_pViewMenu->exec(header()->mapToGlobal(a_rPos));
2934
2935 m_pCurMenu = NULL;
2936 m_CurIndex = QModelIndex();
2937 if (m_pRefreshAct)
2938 m_pRefreshAct->setEnabled(true);
2939 }
2940}
2941
2942
2943void
2944VBoxDbgStatsView::actExpand()
2945{
2946 QModelIndex Idx = m_pCurMenu ? m_CurIndex : currentIndex();
2947 if (Idx.isValid())
2948 setSubTreeExpanded(Idx, true /* a_fExpanded */);
2949}
2950
2951
2952void
2953VBoxDbgStatsView::actCollapse()
2954{
2955 QModelIndex Idx = m_pCurMenu ? m_CurIndex : currentIndex();
2956 if (Idx.isValid())
2957 setSubTreeExpanded(Idx, false /* a_fExpanded */);
2958}
2959
2960
2961void
2962VBoxDbgStatsView::actRefresh()
2963{
2964 QModelIndex Idx = m_pCurMenu ? m_CurIndex : currentIndex();
2965 if (!Idx.isValid() || Idx == m_pModel->getRootIndex())
2966 {
2967 if (m_pModel->updateStatsByPattern(m_PatStr))
2968 setRootIndex(m_pModel->getRootIndex()); /* hack */
2969 }
2970 else
2971 m_pModel->updateStatsByIndex(Idx);
2972}
2973
2974
2975void
2976VBoxDbgStatsView::actReset()
2977{
2978 QModelIndex Idx = m_pCurMenu ? m_CurIndex : currentIndex();
2979 if (!Idx.isValid() || Idx == m_pModel->getRootIndex())
2980 m_pModel->resetStatsByPattern(m_PatStr);
2981 else
2982 m_pModel->resetStatsByIndex(Idx);
2983}
2984
2985
2986void
2987VBoxDbgStatsView::actCopy()
2988{
2989 QModelIndex Idx = m_pCurMenu ? m_CurIndex : currentIndex();
2990 m_pModel->copyTreeToClipboard(Idx);
2991}
2992
2993
2994void
2995VBoxDbgStatsView::actToLog()
2996{
2997 QModelIndex Idx = m_pCurMenu ? m_CurIndex : currentIndex();
2998 m_pModel->logTree(Idx, false /* a_fReleaseLog */);
2999}
3000
3001
3002void
3003VBoxDbgStatsView::actToRelLog()
3004{
3005 QModelIndex Idx = m_pCurMenu ? m_CurIndex : currentIndex();
3006 m_pModel->logTree(Idx, true /* a_fReleaseLog */);
3007}
3008
3009
3010void
3011VBoxDbgStatsView::actAdjColumns()
3012{
3013 resizeColumnsToContent();
3014}
3015
3016
3017
3018
3019
3020
3021/*
3022 *
3023 * V B o x D b g S t a t s
3024 * V B o x D b g S t a t s
3025 * V B o x D b g S t a t s
3026 *
3027 *
3028 */
3029
3030
3031VBoxDbgStats::VBoxDbgStats(VBoxDbgGui *a_pDbgGui, const char *pszPat/* = NULL*/, unsigned uRefreshRate/* = 0*/, QWidget *pParent/* = NULL*/)
3032 : VBoxDbgBaseWindow(a_pDbgGui, pParent), m_PatStr(pszPat), m_pPatCB(NULL), m_uRefreshRate(0), m_pTimer(NULL), m_pView(NULL)
3033{
3034 setWindowTitle("VBoxDbg - Statistics");
3035
3036 /*
3037 * On top, a horizontal box with the pattern field, buttons and refresh interval.
3038 */
3039 QHBoxLayout *pHLayout = new QHBoxLayout;
3040
3041 QLabel *pLabel = new QLabel(" Pattern ");
3042 pHLayout->addWidget(pLabel);
3043 pLabel->setMaximumSize(pLabel->sizeHint());
3044 pLabel->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
3045
3046 m_pPatCB = new QComboBox();
3047 m_pPatCB->setAutoCompletion(false);
3048 pHLayout->addWidget(m_pPatCB);
3049 if (!m_PatStr.isEmpty())
3050 m_pPatCB->addItem(m_PatStr);
3051 m_pPatCB->setDuplicatesEnabled(false);
3052 m_pPatCB->setEditable(true);
3053 connect(m_pPatCB, SIGNAL(activated(const QString &)), this, SLOT(apply(const QString &)));
3054
3055 QPushButton *pPB = new QPushButton("&All");
3056 pHLayout->addWidget(pPB);
3057 pPB->setMaximumSize(pPB->sizeHint());
3058 connect(pPB, SIGNAL(clicked()), this, SLOT(applyAll()));
3059
3060 pLabel = new QLabel(" Interval ");
3061 pHLayout->addWidget(pLabel);
3062 pLabel->setMaximumSize(pLabel->sizeHint());
3063 pLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
3064
3065 QSpinBox *pSB = new QSpinBox();
3066 pHLayout->addWidget(pSB);
3067 pSB->setMinimum(0);
3068 pSB->setMaximum(60);
3069 pSB->setSingleStep(1);
3070 pSB->setValue(uRefreshRate);
3071 pSB->setSuffix(" s");
3072 pSB->setWrapping(false);
3073 pSB->setButtonSymbols(QSpinBox::PlusMinus);
3074 pSB->setMaximumSize(pSB->sizeHint());
3075 connect(pSB, SIGNAL(valueChanged(int)), this, SLOT(setRefresh(int)));
3076
3077 /*
3078 * Create the tree view and setup the layout.
3079 */
3080 VBoxDbgStatsModelVM *pModel = new VBoxDbgStatsModelVM(a_pDbgGui, m_PatStr, NULL);
3081 m_pView = new VBoxDbgStatsView(a_pDbgGui, pModel, this);
3082
3083 QWidget *pHBox = new QWidget;
3084 pHBox->setLayout(pHLayout);
3085
3086 QVBoxLayout *pVLayout = new QVBoxLayout;
3087 pVLayout->addWidget(pHBox);
3088 pVLayout->addWidget(m_pView);
3089 setLayout(pVLayout);
3090
3091 /*
3092 * Resize the columns.
3093 * Seems this has to be done with all nodes expanded.
3094 */
3095 m_pView->expandAll();
3096 m_pView->resizeColumnsToContent();
3097 m_pView->collapseAll();
3098
3099 /*
3100 * Create a refresh timer and start it.
3101 */
3102 m_pTimer = new QTimer(this);
3103 connect(m_pTimer, SIGNAL(timeout()), this, SLOT(refresh()));
3104 setRefresh(uRefreshRate);
3105
3106 /*
3107 * And some shortcuts.
3108 */
3109 m_pFocusToPat = new QAction("", this);
3110 m_pFocusToPat->setShortcut(QKeySequence("Ctrl+L"));
3111 addAction(m_pFocusToPat);
3112 connect(m_pFocusToPat, SIGNAL(triggered(bool)), this, SLOT(actFocusToPat()));
3113}
3114
3115
3116VBoxDbgStats::~VBoxDbgStats()
3117{
3118 if (m_pTimer)
3119 {
3120 delete m_pTimer;
3121 m_pTimer = NULL;
3122 }
3123
3124 if (m_pPatCB)
3125 {
3126 delete m_pPatCB;
3127 m_pPatCB = NULL;
3128 }
3129
3130 if (m_pView)
3131 {
3132 delete m_pView;
3133 m_pView = NULL;
3134 }
3135}
3136
3137
3138void
3139VBoxDbgStats::closeEvent(QCloseEvent *a_pCloseEvt)
3140{
3141 a_pCloseEvt->accept();
3142 delete this;
3143}
3144
3145
3146void
3147VBoxDbgStats::apply(const QString &Str)
3148{
3149 m_PatStr = Str;
3150 refresh();
3151}
3152
3153
3154void
3155VBoxDbgStats::applyAll()
3156{
3157 apply("");
3158}
3159
3160
3161
3162void
3163VBoxDbgStats::refresh()
3164{
3165 m_pView->updateStats(m_PatStr);
3166}
3167
3168
3169void
3170VBoxDbgStats::setRefresh(int iRefresh)
3171{
3172 if ((unsigned)iRefresh != m_uRefreshRate)
3173 {
3174 if (!m_uRefreshRate || iRefresh)
3175 m_pTimer->start(iRefresh * 1000);
3176 else
3177 m_pTimer->stop();
3178 m_uRefreshRate = iRefresh;
3179 }
3180}
3181
3182
3183void
3184VBoxDbgStats::actFocusToPat()
3185{
3186 if (!m_pPatCB->hasFocus())
3187 m_pPatCB->setFocus(Qt::ShortcutFocusReason);
3188}
3189
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use