VirtualBox

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

Last change on this file since 103461 was 103461, checked in by vboxsync, 15 months ago

VBoxDbg: Added some simple sub-tree filtering to the statistics viewer. [fix] bugref:10376

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 125.9 KB
Line 
1/* $Id: VBoxDbgStatsQt.cpp 103461 2024-02-19 23:24:33Z vboxsync $ */
2/** @file
3 * VBox Debugger GUI - Statistics.
4 */
5
6/*
7 * Copyright (C) 2006-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.215389.xyz.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28
29/*********************************************************************************************************************************
30* Header Files *
31*********************************************************************************************************************************/
32#define LOG_GROUP LOG_GROUP_DBGG
33#include "VBoxDbgStatsQt.h"
34
35#include <QAction>
36#include <QApplication>
37#include <QCheckBox>
38#include <QClipboard>
39#include <QContextMenuEvent>
40#include <QDialog>
41#include <QDialogButtonBox>
42#include <QGroupBox>
43#include <QGridLayout>
44#include <QHBoxLayout>
45#include <QHeaderView>
46#include <QKeySequence>
47#include <QLabel>
48#include <QLineEdit>
49#include <QLocale>
50#include <QMessageBox>
51#include <QPushButton>
52#include <QSortFilterProxyModel>
53#include <QSpinBox>
54#include <QVBoxLayout>
55
56#include <iprt/errcore.h>
57#include <VBox/log.h>
58#include <iprt/string.h>
59#include <iprt/mem.h>
60#include <iprt/assert.h>
61
62#include "VBoxDbgGui.h"
63
64
65/*********************************************************************************************************************************
66* Defined Constants And Macros *
67*********************************************************************************************************************************/
68/** The number of column. */
69#define DBGGUI_STATS_COLUMNS 9
70
71/** Enables the sorting and filtering. */
72#define VBOXDBG_WITH_SORTED_AND_FILTERED_STATS
73
74
75/*********************************************************************************************************************************
76* Structures and Typedefs *
77*********************************************************************************************************************************/
78/**
79 * The state of a statistics sample node.
80 *
81 * This is used for two pass refresh (1. get data, 2. update the view) and
82 * for saving the result of a diff.
83 */
84typedef enum DBGGUISTATSNODESTATE
85{
86 /** The typical invalid zeroth entry. */
87 kDbgGuiStatsNodeState_kInvalid = 0,
88 /** The node is the root node. */
89 kDbgGuiStatsNodeState_kRoot,
90 /** The node is visible. */
91 kDbgGuiStatsNodeState_kVisible,
92 /** The node should be refreshed. */
93 kDbgGuiStatsNodeState_kRefresh,
94#if 0 /// @todo not implemented
95 /** diff: The node equals. */
96 kDbgGuiStatsNodeState_kDiffEqual,
97 /** diff: The node in set 1 is less than the one in set 2. */
98 kDbgGuiStatsNodeState_kDiffSmaller,
99 /** diff: The node in set 1 is greater than the one in set 2. */
100 kDbgGuiStatsNodeState_kDiffGreater,
101 /** diff: The node is only in set 1. */
102 kDbgGuiStatsNodeState_kDiffOnlyIn1,
103 /** diff: The node is only in set 2. */
104 kDbgGuiStatsNodeState_kDiffOnlyIn2,
105#endif
106 /** The end of the valid state values. */
107 kDbgGuiStatsNodeState_kEnd
108} DBGGUISTATENODESTATE;
109
110
111/**
112 * Filtering data.
113 */
114typedef struct VBoxGuiStatsFilterData
115{
116 /** Number of instances. */
117 static uint32_t volatile s_cInstances;
118 uint64_t uMinValue;
119 uint64_t uMaxValue;
120 QRegularExpression *pRegexName;
121
122 VBoxGuiStatsFilterData()
123 : uMinValue(0)
124 , uMaxValue(UINT64_MAX)
125 , pRegexName(NULL)
126 {
127 s_cInstances += 1;
128 }
129
130 ~VBoxGuiStatsFilterData()
131 {
132 if (pRegexName)
133 {
134 delete pRegexName;
135 pRegexName = NULL;
136 }
137 s_cInstances -= 1;
138 }
139
140 bool isAllDefaults(void) const
141 {
142 return (uMinValue == 0 || uMinValue == UINT64_MAX)
143 && (uMaxValue == 0 || uMaxValue == UINT64_MAX)
144 && pRegexName == NULL;
145 }
146} VBoxGuiStatsFilterData;
147
148
149/**
150 * A tree node representing a statistic sample.
151 *
152 * The nodes carry a reference to the parent and to its position among its
153 * siblings. Both of these need updating when the grand parent or parent adds a
154 * new child. This will hopefully not be too expensive but rather pay off when
155 * we need to create a parent index.
156 */
157typedef struct DBGGUISTATSNODE
158{
159 /** Pointer to the parent. */
160 PDBGGUISTATSNODE pParent;
161 /** Array of pointers to the child nodes. */
162 PDBGGUISTATSNODE *papChildren;
163 /** The number of children. */
164 uint32_t cChildren;
165 /** Our index among the parent's children. */
166 uint32_t iSelf;
167 /** Sub-tree filtering config (typically NULL). */
168 VBoxGuiStatsFilterData *pFilter;
169 /** The unit string. (not allocated) */
170 const char *pszUnit;
171 /** The delta. */
172 int64_t i64Delta;
173 /** The name. */
174 char *pszName;
175 /** The length of the name. */
176 size_t cchName;
177 /** The description string. */
178 QString *pDescStr;
179 /** The node state. */
180 DBGGUISTATENODESTATE enmState;
181 /** The data type.
182 * For filler nodes not containing data, this will be set to STAMTYPE_INVALID. */
183 STAMTYPE enmType;
184 /** The data at last update. */
185 union
186 {
187 /** STAMTYPE_COUNTER. */
188 STAMCOUNTER Counter;
189 /** STAMTYPE_PROFILE. */
190 STAMPROFILE Profile;
191 /** STAMTYPE_PROFILE_ADV. */
192 STAMPROFILEADV ProfileAdv;
193 /** STAMTYPE_RATIO_U32. */
194 STAMRATIOU32 RatioU32;
195 /** STAMTYPE_U8 & STAMTYPE_U8_RESET. */
196 uint8_t u8;
197 /** STAMTYPE_U16 & STAMTYPE_U16_RESET. */
198 uint16_t u16;
199 /** STAMTYPE_U32 & STAMTYPE_U32_RESET. */
200 uint32_t u32;
201 /** STAMTYPE_U64 & STAMTYPE_U64_RESET. */
202 uint64_t u64;
203 /** STAMTYPE_BOOL and STAMTYPE_BOOL_RESET. */
204 bool f;
205 /** STAMTYPE_CALLBACK. */
206 QString *pStr;
207 } Data;
208} DBGGUISTATSNODE;
209
210
211/**
212 * Recursion stack.
213 */
214typedef struct DBGGUISTATSSTACK
215{
216 /** The top stack entry. */
217 int32_t iTop;
218 /** The stack array. */
219 struct DBGGUISTATSSTACKENTRY
220 {
221 /** The node. */
222 PDBGGUISTATSNODE pNode;
223 /** The current child. */
224 int32_t iChild;
225 /** Name string offset (if used). */
226 uint16_t cchName;
227 } a[32];
228} DBGGUISTATSSTACK;
229
230
231
232
233/**
234 * The item model for the statistics tree view.
235 *
236 * This manages the DBGGUISTATSNODE trees.
237 */
238class VBoxDbgStatsModel : public QAbstractItemModel
239{
240protected:
241 /** The root of the sample tree. */
242 PDBGGUISTATSNODE m_pRoot;
243
244private:
245 /** Next update child. This is UINT32_MAX when invalid. */
246 uint32_t m_iUpdateChild;
247 /** Pointer to the node m_szUpdateParent represent and m_iUpdateChild refers to. */
248 PDBGGUISTATSNODE m_pUpdateParent;
249 /** The length of the path. */
250 size_t m_cchUpdateParent;
251 /** The path to the current update parent, including a trailing slash. */
252 char m_szUpdateParent[1024];
253 /** Inserted or/and removed nodes during the update. */
254 bool m_fUpdateInsertRemove;
255
256
257public:
258 /**
259 * Constructor.
260 *
261 * @param a_pParent The parent object. See QAbstractItemModel in the Qt
262 * docs for details.
263 */
264 VBoxDbgStatsModel(QObject *a_pParent);
265
266 /**
267 * Destructor.
268 *
269 * This will free all the data the model holds.
270 */
271 virtual ~VBoxDbgStatsModel();
272
273 /**
274 * Updates the data matching the specified pattern, normally for the whole tree
275 * but optionally a sub-tree if @a a_pSubTree is given.
276 *
277 * This will should invoke updatePrep, updateCallback and updateDone.
278 *
279 * It is vitally important that updateCallback is fed the data in the right
280 * order. The code make very definite ASSUMPTIONS about the ordering being
281 * strictly sorted and taking the slash into account when doing so.
282 *
283 * @returns true if we reset the model and it's necessary to set the root index.
284 * @param a_rPatStr The selection pattern.
285 * @param a_pSubTree The node / sub-tree to update if this is partial update.
286 * This is NULL for a full tree update.
287 *
288 * @remarks The default implementation is an empty stub.
289 */
290 virtual bool updateStatsByPattern(const QString &a_rPatStr, PDBGGUISTATSNODE a_pSubTree = NULL);
291
292 /**
293 * Similar to updateStatsByPattern, except that it only works on a sub-tree and
294 * will not remove anything that's outside that tree.
295 *
296 * The default implementation will call redirect to updateStatsByPattern().
297 *
298 * @param a_rIndex The sub-tree root. Invalid index means root.
299 */
300 virtual void updateStatsByIndex(QModelIndex const &a_rIndex);
301
302 /**
303 * Reset the stats matching the specified pattern.
304 *
305 * @param a_rPatStr The selection pattern.
306 *
307 * @remarks The default implementation is an empty stub.
308 */
309 virtual void resetStatsByPattern(QString const &a_rPatStr);
310
311 /**
312 * Reset the stats of a sub-tree.
313 *
314 * @param a_rIndex The sub-tree root. Invalid index means root.
315 * @param a_fSubTree Whether to reset the sub-tree as well. Default is true.
316 *
317 * @remarks The default implementation makes use of resetStatsByPattern
318 */
319 virtual void resetStatsByIndex(QModelIndex const &a_rIndex, bool a_fSubTree = true);
320
321 /**
322 * Iterator callback function.
323 * @returns true to continue, false to stop.
324 */
325 typedef bool FNITERATOR(PDBGGUISTATSNODE pNode, QModelIndex const &a_rIndex, const char *pszFullName, void *pvUser);
326
327 /**
328 * Callback iterator.
329 *
330 * @param a_rPatStr The selection pattern.
331 * @param a_pfnCallback Callback function.
332 * @param a_pvUser Callback argument.
333 * @param a_fMatchChildren How to handle children of matching nodes:
334 * - @c true: continue with the children,
335 * - @c false: skip children.
336 */
337 virtual void iterateStatsByPattern(QString const &a_rPatStr, FNITERATOR *a_pfnCallback, void *a_pvUser,
338 bool a_fMatchChildren = true);
339
340 /**
341 * Gets the model index of the root node.
342 *
343 * @returns root index.
344 */
345 QModelIndex getRootIndex(void) const;
346
347
348protected:
349 /**
350 * Set the root node.
351 *
352 * This will free all the current data before taking the ownership of the new
353 * root node and its children.
354 *
355 * @param a_pRoot The new root node.
356 */
357 void setRootNode(PDBGGUISTATSNODE a_pRoot);
358
359 /** Creates the root node. */
360 static PDBGGUISTATSNODE createRootNode(void);
361
362 /** Creates and insert a node under the given parent. */
363 static PDBGGUISTATSNODE createAndInsertNode(PDBGGUISTATSNODE pParent, const char *pszName, size_t cchName, uint32_t iPosition);
364
365 /** Creates and insert a node under the given parent with correct Qt
366 * signalling. */
367 PDBGGUISTATSNODE createAndInsert(PDBGGUISTATSNODE pParent, const char *pszName, size_t cchName, uint32_t iPosition);
368
369 /**
370 * Resets the node to a pristine state.
371 *
372 * @param pNode The node.
373 */
374 static void resetNode(PDBGGUISTATSNODE pNode);
375
376 /**
377 * Initializes a pristine node.
378 */
379 static int initNode(PDBGGUISTATSNODE pNode, STAMTYPE enmType, void *pvSample, const char *pszUnit, const char *pszDesc);
380
381 /**
382 * Updates (or reinitializes if you like) a node.
383 */
384 static void updateNode(PDBGGUISTATSNODE pNode, STAMTYPE enmType, void *pvSample, const char *pszUnit, const char *pszDesc);
385
386 /**
387 * Called by updateStatsByPattern(), makes the necessary preparations.
388 *
389 * @returns Success indicator.
390 * @param a_pSubTree The node / sub-tree to update if this is partial update.
391 * This is NULL for a full tree update.
392 */
393 bool updatePrepare(PDBGGUISTATSNODE a_pSubTree = NULL);
394
395 /**
396 * Called by updateStatsByPattern(), finalizes the update.
397 *
398 * @returns See updateStatsByPattern().
399 *
400 * @param a_fSuccess Whether the update was successful or not.
401 * @param a_pSubTree The node / sub-tree to update if this is partial update.
402 * This is NULL for a full tree update.
403 */
404 bool updateDone(bool a_fSuccess, PDBGGUISTATSNODE a_pSubTree = NULL);
405
406 /**
407 * updateCallback() worker taking care of in-tree inserts and removals.
408 *
409 * @returns The current node.
410 * @param pszName The name of the tree element to update.
411 */
412 PDBGGUISTATSNODE updateCallbackHandleOutOfOrder(const char *pszName);
413
414 /**
415 * updateCallback() worker taking care of tail insertions.
416 *
417 * @returns The current node.
418 * @param pszName The name of the tree element to update.
419 */
420 PDBGGUISTATSNODE updateCallbackHandleTail(const char *pszName);
421
422 /**
423 * updateCallback() worker that advances the update state to the next data node
424 * in anticipation of the next updateCallback call.
425 *
426 * @param pNode The current node.
427 */
428 void updateCallbackAdvance(PDBGGUISTATSNODE pNode);
429
430 /** Callback used by updateStatsByPattern() and updateStatsByIndex() to feed
431 * changes.
432 * @copydoc FNSTAMR3ENUM */
433 static DECLCALLBACK(int) updateCallback(const char *pszName, STAMTYPE enmType, void *pvSample, STAMUNIT enmUnit,
434 const char *pszUnit, STAMVISIBILITY enmVisibility, const char *pszDesc, void *pvUser);
435
436public:
437 /**
438 * Calculates the full path of a node.
439 *
440 * @returns Number of bytes returned, negative value on buffer overflow
441 *
442 * @param pNode The node.
443 * @param psz The output buffer.
444 * @param cch The size of the buffer.
445 */
446 static ssize_t getNodePath(PCDBGGUISTATSNODE pNode, char *psz, ssize_t cch);
447
448protected:
449 /**
450 * Calculates the full path of a node, returning the string pointer.
451 *
452 * @returns @a psz. On failure, NULL.
453 *
454 * @param pNode The node.
455 * @param psz The output buffer.
456 * @param cch The size of the buffer.
457 */
458 static char *getNodePath2(PCDBGGUISTATSNODE pNode, char *psz, ssize_t cch);
459
460 /**
461 * Returns the pattern for the node, optionally including the entire sub-tree
462 * under it.
463 *
464 * @returns Pattern.
465 * @param pNode The node.
466 * @param fSubTree Whether to include the sub-tree in the pattern.
467 */
468 static QString getNodePattern(PCDBGGUISTATSNODE pNode, bool fSubTree = true);
469
470 /**
471 * Check if the first node is an ancestor to the second one.
472 *
473 * @returns true/false.
474 * @param pAncestor The first node, the alleged ancestor.
475 * @param pDescendant The second node, the alleged descendant.
476 */
477 static bool isNodeAncestorOf(PCDBGGUISTATSNODE pAncestor, PCDBGGUISTATSNODE pDescendant);
478
479 /**
480 * Advance to the next node in the tree.
481 *
482 * @returns Pointer to the next node, NULL if we've reached the end or
483 * was handed a NULL node.
484 * @param pNode The current node.
485 */
486 static PDBGGUISTATSNODE nextNode(PDBGGUISTATSNODE pNode);
487
488 /**
489 * Advance to the next node in the tree that contains data.
490 *
491 * @returns Pointer to the next data node, NULL if we've reached the end or
492 * was handed a NULL node.
493 * @param pNode The current node.
494 */
495 static PDBGGUISTATSNODE nextDataNode(PDBGGUISTATSNODE pNode);
496
497 /**
498 * Advance to the previous node in the tree.
499 *
500 * @returns Pointer to the previous node, NULL if we've reached the end or
501 * was handed a NULL node.
502 * @param pNode The current node.
503 */
504 static PDBGGUISTATSNODE prevNode(PDBGGUISTATSNODE pNode);
505
506 /**
507 * Advance to the previous node in the tree that contains data.
508 *
509 * @returns Pointer to the previous data node, NULL if we've reached the end or
510 * was handed a NULL node.
511 * @param pNode The current node.
512 */
513 static PDBGGUISTATSNODE prevDataNode(PDBGGUISTATSNODE pNode);
514
515 /**
516 * Removes a node from the tree.
517 *
518 * @returns pNode.
519 * @param pNode The node.
520 */
521 static PDBGGUISTATSNODE removeNode(PDBGGUISTATSNODE pNode);
522
523 /**
524 * Removes a node from the tree and destroys it and all its descendants.
525 *
526 * @param pNode The node.
527 */
528 static void removeAndDestroyNode(PDBGGUISTATSNODE pNode);
529
530 /** Removes a node from the tree and destroys it and all its descendants
531 * performing the required Qt signalling. */
532 void removeAndDestroy(PDBGGUISTATSNODE pNode);
533
534 /**
535 * Destroys a statistics tree.
536 *
537 * @param a_pRoot The root of the tree. NULL is fine.
538 */
539 static void destroyTree(PDBGGUISTATSNODE a_pRoot);
540
541 /**
542 * Stringifies exactly one node, no children.
543 *
544 * This is for logging and clipboard.
545 *
546 * @param a_pNode The node.
547 * @param a_rString The string to append the stringified node to.
548 */
549 static void stringifyNodeNoRecursion(PDBGGUISTATSNODE a_pNode, QString &a_rString);
550
551 /**
552 * Stringifies a node and its children.
553 *
554 * This is for logging and clipboard.
555 *
556 * @param a_pNode The node.
557 * @param a_rString The string to append the stringified node to.
558 */
559 static void stringifyNode(PDBGGUISTATSNODE a_pNode, QString &a_rString);
560
561public:
562 /**
563 * Converts the specified tree to string.
564 *
565 * This is for logging and clipboard.
566 *
567 * @param a_rRoot Where to start. Use QModelIndex() to start at the root.
568 * @param a_rString Where to return to return the string dump.
569 */
570 void stringifyTree(QModelIndex &a_rRoot, QString &a_rString) const;
571
572 /**
573 * Dumps the given (sub-)tree as XML.
574 *
575 * @param a_rRoot Where to start. Use QModelIndex() to start at the root.
576 * @param a_rString Where to return to return the XML dump.
577 */
578 void xmlifyTree(QModelIndex &a_rRoot, QString &a_rString) const;
579
580 /**
581 * Puts the stringified tree on the clipboard.
582 *
583 * @param a_rRoot Where to start. Use QModelIndex() to start at the root.
584 */
585 void copyTreeToClipboard(QModelIndex &a_rRoot) const;
586
587
588protected:
589 /** Worker for logTree. */
590 static void logNode(PDBGGUISTATSNODE a_pNode, bool a_fReleaseLog);
591
592public:
593 /** Logs a (sub-)tree.
594 *
595 * @param a_rRoot Where to start. Use QModelIndex() to start at the root.
596 * @param a_fReleaseLog Whether to use the release log (true) or the debug log (false).
597 */
598 void logTree(QModelIndex &a_rRoot, bool a_fReleaseLog) const;
599
600 /** Gets the unit. */
601 static QString strUnit(PCDBGGUISTATSNODE pNode);
602 /** Gets the value/times. */
603 static QString strValueTimes(PCDBGGUISTATSNODE pNode);
604 /** Gets the value/times. */
605 static uint64_t getValueTimesAsUInt(PCDBGGUISTATSNODE pNode);
606 /** Gets the value/avg. */
607 static uint64_t getValueOrAvgAsUInt(PCDBGGUISTATSNODE pNode);
608 /** Gets the minimum value. */
609 static QString strMinValue(PCDBGGUISTATSNODE pNode);
610 /** Gets the minimum value. */
611 static uint64_t getMinValueAsUInt(PCDBGGUISTATSNODE pNode);
612 /** Gets the average value. */
613 static QString strAvgValue(PCDBGGUISTATSNODE pNode);
614 /** Gets the average value. */
615 static uint64_t getAvgValueAsUInt(PCDBGGUISTATSNODE pNode);
616 /** Gets the maximum value. */
617 static QString strMaxValue(PCDBGGUISTATSNODE pNode);
618 /** Gets the maximum value. */
619 static uint64_t getMaxValueAsUInt(PCDBGGUISTATSNODE pNode);
620 /** Gets the total value. */
621 static QString strTotalValue(PCDBGGUISTATSNODE pNode);
622 /** Gets the total value. */
623 static uint64_t getTotalValueAsUInt(PCDBGGUISTATSNODE pNode);
624 /** Gets the delta value. */
625 static QString strDeltaValue(PCDBGGUISTATSNODE pNode);
626
627
628protected:
629 /**
630 * Destroys a node and all its children.
631 *
632 * @param a_pNode The node to destroy.
633 */
634 static void destroyNode(PDBGGUISTATSNODE a_pNode);
635
636public:
637 /**
638 * Converts an index to a node pointer.
639 *
640 * @returns Pointer to the node, NULL if invalid reference.
641 * @param a_rIndex Reference to the index
642 */
643 inline PDBGGUISTATSNODE nodeFromIndex(const QModelIndex &a_rIndex) const
644 {
645 if (RT_LIKELY(a_rIndex.isValid()))
646 return (PDBGGUISTATSNODE)a_rIndex.internalPointer();
647 return NULL;
648 }
649
650public:
651
652 /** @name Overridden QAbstractItemModel methods
653 * @{ */
654 virtual int columnCount(const QModelIndex &a_rParent) const RT_OVERRIDE;
655 virtual QVariant data(const QModelIndex &a_rIndex, int a_eRole) const RT_OVERRIDE;
656 virtual Qt::ItemFlags flags(const QModelIndex &a_rIndex) const RT_OVERRIDE;
657 virtual bool hasChildren(const QModelIndex &a_rParent) const RT_OVERRIDE;
658 virtual QVariant headerData(int a_iSection, Qt::Orientation a_ePrientation, int a_eRole) const RT_OVERRIDE;
659 virtual QModelIndex index(int a_iRow, int a_iColumn, const QModelIndex &a_rParent) const RT_OVERRIDE;
660 virtual QModelIndex parent(const QModelIndex &a_rChild) const RT_OVERRIDE;
661 virtual int rowCount(const QModelIndex &a_rParent) const RT_OVERRIDE;
662 ///virtual void sort(int a_iColumn, Qt::SortOrder a_eOrder) RT_OVERRIDE;
663 /** @} */
664};
665
666
667/**
668 * Model using the VM / STAM interface as data source.
669 */
670class VBoxDbgStatsModelVM : public VBoxDbgStatsModel, public VBoxDbgBase
671{
672public:
673 /**
674 * Constructor.
675 *
676 * @param a_pDbgGui Pointer to the debugger gui object.
677 * @param a_rPatStr The selection pattern.
678 * @param a_pParent The parent object. NULL is fine.
679 * @param a_pVMM The VMM function table.
680 */
681 VBoxDbgStatsModelVM(VBoxDbgGui *a_pDbgGui, QString &a_rPatStr, QObject *a_pParent, PCVMMR3VTABLE a_pVMM);
682
683 /** Destructor */
684 virtual ~VBoxDbgStatsModelVM();
685
686 virtual bool updateStatsByPattern(const QString &a_rPatStr, PDBGGUISTATSNODE a_pSubTree = NULL);
687 virtual void resetStatsByPattern(const QString &a_rPatStr);
688
689protected:
690 /**
691 * Enumeration callback used by createNewTree.
692 */
693 static DECLCALLBACK(int) createNewTreeCallback(const char *pszName, STAMTYPE enmType, void *pvSample, STAMUNIT enmUnit,
694 const char *pszUnit, STAMVISIBILITY enmVisibility, const char *pszDesc,
695 void *pvUser);
696
697 /**
698 * Constructs a new statistics tree by query data from the VM.
699 *
700 * @returns Pointer to the root of the tree we've constructed. This will be NULL
701 * if the STAM API throws an error or we run out of memory.
702 * @param a_rPatStr The selection pattern.
703 */
704 PDBGGUISTATSNODE createNewTree(QString &a_rPatStr);
705
706 /** The VMM function table. */
707 PCVMMR3VTABLE m_pVMM;
708};
709
710#ifdef VBOXDBG_WITH_SORTED_AND_FILTERED_STATS
711
712/**
713 * Model using the VM / STAM interface as data source.
714 */
715class VBoxDbgStatsSortFileProxyModel : public QSortFilterProxyModel
716{
717public:
718 /**
719 * Constructor.
720 *
721 * @param a_pszAdvFilter Configuration string from the command line or such.
722 * @param a_pParent The parent object.
723 */
724 VBoxDbgStatsSortFileProxyModel(const char *a_pszAdvFilter, QObject *a_pParent);
725
726 /** Destructor */
727 virtual ~VBoxDbgStatsSortFileProxyModel()
728 {}
729
730 /** Gets the unused-rows visibility status. */
731 bool isShowingUnusedRows() const { return m_fShowUnusedRows; }
732
733 /** Sets whether or not to show unused rows (all zeros). */
734 void setShowUnusedRows(bool a_fHide);
735
736 /**
737 * Notification that a filter has been added, removed or modified.
738 */
739 void notifyFilterChanges();
740
741protected:
742 /**
743 * Converts an index to a node pointer.
744 *
745 * @returns Pointer to the node, NULL if invalid reference.
746 * @param a_rIndex Reference to the index
747 */
748 inline PDBGGUISTATSNODE nodeFromIndex(const QModelIndex &a_rIndex) const
749 {
750 if (RT_LIKELY(a_rIndex.isValid()))
751 return (PDBGGUISTATSNODE)a_rIndex.internalPointer();
752 return NULL;
753 }
754
755 /** Does the row filtering. */
756 bool filterAcceptsRow(int a_iSrcRow, const QModelIndex &a_rSrcParent) const RT_OVERRIDE;
757 /** For implementing the sorting. */
758 bool lessThan(const QModelIndex &a_rSrcLeft, const QModelIndex &a_rSrcRight) const RT_OVERRIDE;
759
760 /** Whether to show unused rows (all zeros) or not. */
761 bool m_fShowUnusedRows;
762};
763
764
765/**
766 * Dialog for sub-tree filtering config.
767 */
768class VBoxDbgStatsFilterDialog : public QDialog
769{
770public:
771 /**
772 * Constructor.
773 *
774 * @param a_pNode The node to configure filtering for.
775 */
776 VBoxDbgStatsFilterDialog(QWidget *a_pParent, PCDBGGUISTATSNODE a_pNode);
777
778 /** Destructor. */
779 virtual ~VBoxDbgStatsFilterDialog();
780
781 /**
782 * Returns a copy of the filter data or NULL if all defaults.
783 */
784 VBoxGuiStatsFilterData *dupFilterData(void) const;
785
786protected slots:
787
788 /** Validates and (maybe) accepts the dialog data. */
789 void validateAndAccept(void);
790
791protected:
792 /**
793 * Validates and converts the content of an uint64_t entry field.s
794 *
795 * @returns The converted value (or default)
796 * @param a_pField The entry field widget.
797 * @param a_uDefault The default return value.
798 * @param a_pszField The field name (for error messages).
799 * @param a_pLstErrors The error list to append validation errors to.
800 */
801 static uint64_t validateUInt64Field(QLineEdit const *a_pField, uint64_t a_uDefault,
802 const char *a_pszField, QStringList *a_pLstErrors);
803
804
805private:
806 /** The filter data. */
807 VBoxGuiStatsFilterData m_Data;
808
809 /** The minium value/average entry field. */
810 QLineEdit *m_pValueAvgMin;
811 /** The maxium value/average entry field. */
812 QLineEdit *m_pValueAvgMax;
813 /** The name filtering regexp entry field. */
814 QLineEdit *m_pNameRegExp;
815
816 /** Regular expression for validating the uint64_t entry fields. */
817 static QRegularExpression const s_UInt64ValidatorRegExp;
818
819 /**
820 * Creates an entry field for a uint64_t value.
821 */
822 static QLineEdit *createUInt64LineEdit(uint64_t uValue);
823};
824
825#endif /* VBOXDBG_WITH_SORTED_AND_FILTERED_STATS */
826
827
828/*********************************************************************************************************************************
829* Global Variables *
830*********************************************************************************************************************************/
831/*static*/ uint32_t volatile VBoxGuiStatsFilterData::s_cInstances = 0;
832
833
834/*********************************************************************************************************************************
835* Internal Functions *
836*********************************************************************************************************************************/
837
838
839/**
840 * Formats a number into a 64-byte buffer.
841 */
842static char *formatNumber(char *psz, uint64_t u64)
843{
844 if (!u64)
845 {
846 psz[0] = '0';
847 psz[1] = '\0';
848 }
849 else
850 {
851 static const char s_szDigits[] = "0123456789";
852 psz += 63;
853 *psz-- = '\0';
854 unsigned cDigits = 0;
855 for (;;)
856 {
857 const unsigned iDigit = u64 % 10;
858 u64 /= 10;
859 *psz = s_szDigits[iDigit];
860 if (!u64)
861 break;
862 psz--;
863 if (!(++cDigits % 3))
864 *psz-- = ',';
865 }
866 }
867 return psz;
868}
869
870
871/**
872 * Formats a number into a 64-byte buffer.
873 * (18 446 744 073 709 551 615)
874 */
875static char *formatNumberSigned(char *psz, int64_t i64, bool fPositivePlus)
876{
877 static const char s_szDigits[] = "0123456789";
878 psz += 63;
879 *psz-- = '\0';
880 const bool fNegative = i64 < 0;
881 uint64_t u64 = fNegative ? -i64 : i64;
882 unsigned cDigits = 0;
883 for (;;)
884 {
885 const unsigned iDigit = u64 % 10;
886 u64 /= 10;
887 *psz = s_szDigits[iDigit];
888 if (!u64)
889 break;
890 psz--;
891 if (!(++cDigits % 3))
892 *psz-- = ',';
893 }
894 if (fNegative)
895 *--psz = '-';
896 else if (fPositivePlus)
897 *--psz = '+';
898 return psz;
899}
900
901
902/**
903 * Formats a unsigned hexadecimal number into a into a 64-byte buffer.
904 */
905static char *formatHexNumber(char *psz, uint64_t u64, unsigned cZeros)
906{
907 static const char s_szDigits[] = "0123456789abcdef";
908 psz += 63;
909 *psz-- = '\0';
910 unsigned cDigits = 0;
911 for (;;)
912 {
913 const unsigned iDigit = u64 % 16;
914 u64 /= 16;
915 *psz = s_szDigits[iDigit];
916 ++cDigits;
917 if (!u64 && cDigits >= cZeros)
918 break;
919 psz--;
920 if (!(cDigits % 8))
921 *psz-- = '\'';
922 }
923 return psz;
924}
925
926
927#if 0/* unused */
928/**
929 * Formats a sort key number.
930 */
931static void formatSortKey(char *psz, uint64_t u64)
932{
933 static const char s_szDigits[] = "0123456789abcdef";
934 /* signed */
935 *psz++ = '+';
936
937 /* 16 hex digits */
938 psz[16] = '\0';
939 unsigned i = 16;
940 while (i-- > 0)
941 {
942 if (u64)
943 {
944 const unsigned iDigit = u64 % 16;
945 u64 /= 16;
946 psz[i] = s_szDigits[iDigit];
947 }
948 else
949 psz[i] = '0';
950 }
951}
952#endif
953
954
955#if 0/* unused */
956/**
957 * Formats a sort key number.
958 */
959static void formatSortKeySigned(char *psz, int64_t i64)
960{
961 static const char s_szDigits[] = "0123456789abcdef";
962
963 /* signed */
964 uint64_t u64;
965 if (i64 >= 0)
966 {
967 *psz++ = '+';
968 u64 = i64;
969 }
970 else
971 {
972 *psz++ = '-';
973 u64 = -i64;
974 }
975
976 /* 16 hex digits */
977 psz[16] = '\0';
978 unsigned i = 16;
979 while (i-- > 0)
980 {
981 if (u64)
982 {
983 const unsigned iDigit = u64 % 16;
984 u64 /= 16;
985 psz[i] = s_szDigits[iDigit];
986 }
987 else
988 psz[i] = '0';
989 }
990}
991#endif
992
993
994
995/*
996 *
997 * V B o x D b g S t a t s M o d e l
998 * V B o x D b g S t a t s M o d e l
999 * V B o x D b g S t a t s M o d e l
1000 *
1001 *
1002 */
1003
1004
1005VBoxDbgStatsModel::VBoxDbgStatsModel(QObject *a_pParent)
1006 : QAbstractItemModel(a_pParent),
1007 m_pRoot(NULL), m_iUpdateChild(UINT32_MAX), m_pUpdateParent(NULL), m_cchUpdateParent(0)
1008{
1009}
1010
1011
1012
1013VBoxDbgStatsModel::~VBoxDbgStatsModel()
1014{
1015 destroyTree(m_pRoot);
1016 m_pRoot = NULL;
1017}
1018
1019
1020/*static*/ void
1021VBoxDbgStatsModel::destroyTree(PDBGGUISTATSNODE a_pRoot)
1022{
1023 if (!a_pRoot)
1024 return;
1025 Assert(!a_pRoot->pParent);
1026 Assert(!a_pRoot->iSelf);
1027
1028 destroyNode(a_pRoot);
1029}
1030
1031
1032/* static*/ void
1033VBoxDbgStatsModel::destroyNode(PDBGGUISTATSNODE a_pNode)
1034{
1035 /* destroy all our children */
1036 uint32_t i = a_pNode->cChildren;
1037 while (i-- > 0)
1038 {
1039 destroyNode(a_pNode->papChildren[i]);
1040 a_pNode->papChildren[i] = NULL;
1041 }
1042
1043 /* free the resources we're using */
1044 a_pNode->pParent = NULL;
1045
1046 RTMemFree(a_pNode->papChildren);
1047 a_pNode->papChildren = NULL;
1048
1049 if (a_pNode->enmType == STAMTYPE_CALLBACK)
1050 {
1051 delete a_pNode->Data.pStr;
1052 a_pNode->Data.pStr = NULL;
1053 }
1054
1055 a_pNode->cChildren = 0;
1056 a_pNode->iSelf = UINT32_MAX;
1057 a_pNode->pszUnit = "";
1058 a_pNode->enmType = STAMTYPE_INVALID;
1059
1060 RTMemFree(a_pNode->pszName);
1061 a_pNode->pszName = NULL;
1062
1063 if (a_pNode->pDescStr)
1064 {
1065 delete a_pNode->pDescStr;
1066 a_pNode->pDescStr = NULL;
1067 }
1068
1069 VBoxGuiStatsFilterData const *pFilter = a_pNode->pFilter;
1070 if (!pFilter)
1071 { /* likely */ }
1072 else
1073 {
1074 delete pFilter;
1075 a_pNode->pFilter = NULL;
1076 }
1077
1078#ifdef VBOX_STRICT
1079 /* poison it. */
1080 a_pNode->pParent++;
1081 a_pNode->Data.pStr++;
1082 a_pNode->pDescStr++;
1083 a_pNode->papChildren++;
1084 a_pNode->cChildren = 8442;
1085 a_pNode->pFilter++;
1086#endif
1087
1088 /* Finally ourselves */
1089 a_pNode->enmState = kDbgGuiStatsNodeState_kInvalid;
1090 RTMemFree(a_pNode);
1091}
1092
1093
1094/*static*/ PDBGGUISTATSNODE
1095VBoxDbgStatsModel::createRootNode(void)
1096{
1097 PDBGGUISTATSNODE pRoot = (PDBGGUISTATSNODE)RTMemAllocZ(sizeof(DBGGUISTATSNODE));
1098 if (!pRoot)
1099 return NULL;
1100 pRoot->iSelf = 0;
1101 pRoot->enmType = STAMTYPE_INVALID;
1102 pRoot->pszUnit = "";
1103 pRoot->pszName = (char *)RTMemDup("/", sizeof("/"));
1104 pRoot->cchName = 1;
1105 pRoot->enmState = kDbgGuiStatsNodeState_kRoot;
1106
1107 return pRoot;
1108}
1109
1110
1111/*static*/ PDBGGUISTATSNODE
1112VBoxDbgStatsModel::createAndInsertNode(PDBGGUISTATSNODE pParent, const char *pszName, size_t cchName, uint32_t iPosition)
1113{
1114 /*
1115 * Create it.
1116 */
1117 PDBGGUISTATSNODE pNode = (PDBGGUISTATSNODE)RTMemAllocZ(sizeof(DBGGUISTATSNODE));
1118 if (!pNode)
1119 return NULL;
1120 pNode->iSelf = UINT32_MAX;
1121 pNode->enmType = STAMTYPE_INVALID;
1122 pNode->pszUnit = "";
1123 pNode->pszName = (char *)RTMemDupEx(pszName, cchName, 1);
1124 pNode->cchName = cchName;
1125 pNode->enmState = kDbgGuiStatsNodeState_kVisible;
1126
1127 /*
1128 * Do we need to expand the array?
1129 */
1130 if (!(pParent->cChildren & 31))
1131 {
1132 void *pvNew = RTMemRealloc(pParent->papChildren, sizeof(*pParent->papChildren) * (pParent->cChildren + 32));
1133 if (!pvNew)
1134 {
1135 destroyNode(pNode);
1136 return NULL;
1137 }
1138 pParent->papChildren = (PDBGGUISTATSNODE *)pvNew;
1139 }
1140
1141 /*
1142 * Insert it.
1143 */
1144 pNode->pParent = pParent;
1145 if (iPosition >= pParent->cChildren)
1146 /* Last. */
1147 iPosition = pParent->cChildren;
1148 else
1149 {
1150 /* Shift all the items after ours. */
1151 uint32_t iShift = pParent->cChildren;
1152 while (iShift-- > iPosition)
1153 {
1154 PDBGGUISTATSNODE pChild = pParent->papChildren[iShift];
1155 pParent->papChildren[iShift + 1] = pChild;
1156 pChild->iSelf = iShift + 1;
1157 }
1158 }
1159
1160 /* Insert ours */
1161 pNode->iSelf = iPosition;
1162 pParent->papChildren[iPosition] = pNode;
1163 pParent->cChildren++;
1164
1165 return pNode;
1166}
1167
1168
1169PDBGGUISTATSNODE
1170VBoxDbgStatsModel::createAndInsert(PDBGGUISTATSNODE pParent, const char *pszName, size_t cchName, uint32_t iPosition)
1171{
1172 PDBGGUISTATSNODE pNode;
1173 if (m_fUpdateInsertRemove)
1174 pNode = createAndInsertNode(pParent, pszName, cchName, iPosition);
1175 else
1176 {
1177 beginInsertRows(createIndex(pParent->iSelf, 0, pParent), iPosition, iPosition);
1178 pNode = createAndInsertNode(pParent, pszName, cchName, iPosition);
1179 endInsertRows();
1180 }
1181 return pNode;
1182}
1183
1184/*static*/ PDBGGUISTATSNODE
1185VBoxDbgStatsModel::removeNode(PDBGGUISTATSNODE pNode)
1186{
1187 PDBGGUISTATSNODE pParent = pNode->pParent;
1188 if (pParent)
1189 {
1190 uint32_t iPosition = pNode->iSelf;
1191 Assert(pParent->papChildren[iPosition] == pNode);
1192 uint32_t const cChildren = --pParent->cChildren;
1193 for (; iPosition < cChildren; iPosition++)
1194 {
1195 PDBGGUISTATSNODE pChild = pParent->papChildren[iPosition + 1];
1196 pParent->papChildren[iPosition] = pChild;
1197 pChild->iSelf = iPosition;
1198 }
1199#ifdef VBOX_STRICT /* poison */
1200 pParent->papChildren[iPosition] = (PDBGGUISTATSNODE)0x42;
1201#endif
1202 }
1203 return pNode;
1204}
1205
1206
1207/*static*/ void
1208VBoxDbgStatsModel::removeAndDestroyNode(PDBGGUISTATSNODE pNode)
1209{
1210 removeNode(pNode);
1211 destroyNode(pNode);
1212}
1213
1214
1215void
1216VBoxDbgStatsModel::removeAndDestroy(PDBGGUISTATSNODE pNode)
1217{
1218 if (m_fUpdateInsertRemove)
1219 removeAndDestroyNode(pNode);
1220 else
1221 {
1222 /*
1223 * Removing is fun since the docs are imprecise as to how persistent
1224 * indexes are updated (or aren't). So, let try a few different ideas
1225 * and see which works.
1226 */
1227#if 1
1228 /* destroy the children first with the appropriate begin/endRemoveRows signals. */
1229 DBGGUISTATSSTACK Stack;
1230 Stack.a[0].pNode = pNode;
1231 Stack.a[0].iChild = -1;
1232 Stack.iTop = 0;
1233 while (Stack.iTop >= 0)
1234 {
1235 /* get top element */
1236 PDBGGUISTATSNODE pCurNode = Stack.a[Stack.iTop].pNode;
1237 uint32_t iChild = ++Stack.a[Stack.iTop].iChild;
1238 if (iChild < pCurNode->cChildren)
1239 {
1240 /* push */
1241 Stack.iTop++;
1242 Assert(Stack.iTop < (int32_t)RT_ELEMENTS(Stack.a));
1243 Stack.a[Stack.iTop].pNode = pCurNode->papChildren[iChild];
1244 Stack.a[Stack.iTop].iChild = 0;
1245 }
1246 else
1247 {
1248 /* pop and destroy all the children. */
1249 Stack.iTop--;
1250 uint32_t i = pCurNode->cChildren;
1251 if (i)
1252 {
1253 beginRemoveRows(createIndex(pCurNode->iSelf, 0, pCurNode), 0, i - 1);
1254 while (i-- > 0)
1255 destroyNode(pCurNode->papChildren[i]);
1256 pCurNode->cChildren = 0;
1257 endRemoveRows();
1258 }
1259 }
1260 }
1261 Assert(!pNode->cChildren);
1262
1263 /* finally the node it self. */
1264 PDBGGUISTATSNODE pParent = pNode->pParent;
1265 beginRemoveRows(createIndex(pParent->iSelf, 0, pParent), pNode->iSelf, pNode->iSelf);
1266 removeAndDestroyNode(pNode);
1267 endRemoveRows();
1268
1269#elif 0
1270 /* This ain't working, leaves invalid indexes behind. */
1271 PDBGGUISTATSNODE pParent = pNode->pParent;
1272 beginRemoveRows(createIndex(pParent->iSelf, 0, pParent), pNode->iSelf, pNode->iSelf);
1273 removeAndDestroyNode(pNode);
1274 endRemoveRows();
1275#else
1276 /* Force reset() of the model after the update. */
1277 m_fUpdateInsertRemove = true;
1278 removeAndDestroyNode(pNode);
1279#endif
1280 }
1281}
1282
1283
1284/*static*/ void
1285VBoxDbgStatsModel::resetNode(PDBGGUISTATSNODE pNode)
1286{
1287 /* free and reinit the data. */
1288 if (pNode->enmType == STAMTYPE_CALLBACK)
1289 {
1290 delete pNode->Data.pStr;
1291 pNode->Data.pStr = NULL;
1292 }
1293 pNode->enmType = STAMTYPE_INVALID;
1294
1295 /* free the description. */
1296 if (pNode->pDescStr)
1297 {
1298 delete pNode->pDescStr;
1299 pNode->pDescStr = NULL;
1300 }
1301}
1302
1303
1304/*static*/ int
1305VBoxDbgStatsModel::initNode(PDBGGUISTATSNODE pNode, STAMTYPE enmType, void *pvSample,
1306 const char *pszUnit, const char *pszDesc)
1307{
1308 /*
1309 * Copy the data.
1310 */
1311 pNode->pszUnit = pszUnit;
1312 Assert(pNode->enmType == STAMTYPE_INVALID);
1313 pNode->enmType = enmType;
1314 if (pszDesc)
1315 pNode->pDescStr = new QString(pszDesc); /* ignore allocation failure (well, at least up to the point we can ignore it) */
1316
1317 switch (enmType)
1318 {
1319 case STAMTYPE_COUNTER:
1320 pNode->Data.Counter = *(PSTAMCOUNTER)pvSample;
1321 break;
1322
1323 case STAMTYPE_PROFILE:
1324 case STAMTYPE_PROFILE_ADV:
1325 pNode->Data.Profile = *(PSTAMPROFILE)pvSample;
1326 break;
1327
1328 case STAMTYPE_RATIO_U32:
1329 case STAMTYPE_RATIO_U32_RESET:
1330 pNode->Data.RatioU32 = *(PSTAMRATIOU32)pvSample;
1331 break;
1332
1333 case STAMTYPE_CALLBACK:
1334 {
1335 const char *pszString = (const char *)pvSample;
1336 pNode->Data.pStr = new QString(pszString);
1337 break;
1338 }
1339
1340 case STAMTYPE_U8:
1341 case STAMTYPE_U8_RESET:
1342 case STAMTYPE_X8:
1343 case STAMTYPE_X8_RESET:
1344 pNode->Data.u8 = *(uint8_t *)pvSample;
1345 break;
1346
1347 case STAMTYPE_U16:
1348 case STAMTYPE_U16_RESET:
1349 case STAMTYPE_X16:
1350 case STAMTYPE_X16_RESET:
1351 pNode->Data.u16 = *(uint16_t *)pvSample;
1352 break;
1353
1354 case STAMTYPE_U32:
1355 case STAMTYPE_U32_RESET:
1356 case STAMTYPE_X32:
1357 case STAMTYPE_X32_RESET:
1358 pNode->Data.u32 = *(uint32_t *)pvSample;
1359 break;
1360
1361 case STAMTYPE_U64:
1362 case STAMTYPE_U64_RESET:
1363 case STAMTYPE_X64:
1364 case STAMTYPE_X64_RESET:
1365 pNode->Data.u64 = *(uint64_t *)pvSample;
1366 break;
1367
1368 case STAMTYPE_BOOL:
1369 case STAMTYPE_BOOL_RESET:
1370 pNode->Data.f = *(bool *)pvSample;
1371 break;
1372
1373 default:
1374 AssertMsgFailed(("%d\n", enmType));
1375 break;
1376 }
1377
1378 return VINF_SUCCESS;
1379}
1380
1381
1382
1383
1384/*static*/ void
1385VBoxDbgStatsModel::updateNode(PDBGGUISTATSNODE pNode, STAMTYPE enmType, void *pvSample, const char *pszUnit, const char *pszDesc)
1386{
1387 /*
1388 * Reset and init the node if the type changed.
1389 */
1390 if (enmType != pNode->enmType)
1391 {
1392 if (pNode->enmType != STAMTYPE_INVALID)
1393 resetNode(pNode);
1394 initNode(pNode, enmType, pvSample, pszUnit, pszDesc);
1395 pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
1396 }
1397 else
1398 {
1399 /*
1400 * ASSUME that only the sample value will change and that the unit, visibility
1401 * and description remains the same.
1402 */
1403
1404 int64_t iDelta;
1405 switch (enmType)
1406 {
1407 case STAMTYPE_COUNTER:
1408 {
1409 uint64_t cPrev = pNode->Data.Counter.c;
1410 pNode->Data.Counter = *(PSTAMCOUNTER)pvSample;
1411 iDelta = pNode->Data.Counter.c - cPrev;
1412 if (iDelta || pNode->i64Delta)
1413 {
1414 pNode->i64Delta = iDelta;
1415 pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
1416 }
1417 break;
1418 }
1419
1420 case STAMTYPE_PROFILE:
1421 case STAMTYPE_PROFILE_ADV:
1422 {
1423 uint64_t cPrevPeriods = pNode->Data.Profile.cPeriods;
1424 pNode->Data.Profile = *(PSTAMPROFILE)pvSample;
1425 iDelta = pNode->Data.Profile.cPeriods - cPrevPeriods;
1426 if (iDelta || pNode->i64Delta)
1427 {
1428 pNode->i64Delta = iDelta;
1429 pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
1430 }
1431 break;
1432 }
1433
1434 case STAMTYPE_RATIO_U32:
1435 case STAMTYPE_RATIO_U32_RESET:
1436 {
1437 STAMRATIOU32 Prev = pNode->Data.RatioU32;
1438 pNode->Data.RatioU32 = *(PSTAMRATIOU32)pvSample;
1439 int32_t iDeltaA = pNode->Data.RatioU32.u32A - Prev.u32A;
1440 int32_t iDeltaB = pNode->Data.RatioU32.u32B - Prev.u32B;
1441 if (iDeltaA == 0 && iDeltaB == 0)
1442 {
1443 if (pNode->i64Delta)
1444 {
1445 pNode->i64Delta = 0;
1446 pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
1447 }
1448 }
1449 else
1450 {
1451 if (iDeltaA >= 0)
1452 pNode->i64Delta = iDeltaA + (iDeltaB >= 0 ? iDeltaB : -iDeltaB);
1453 else
1454 pNode->i64Delta = iDeltaA + (iDeltaB < 0 ? iDeltaB : -iDeltaB);
1455 pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
1456 }
1457 break;
1458 }
1459
1460 case STAMTYPE_CALLBACK:
1461 {
1462 const char *pszString = (const char *)pvSample;
1463 if (!pNode->Data.pStr)
1464 {
1465 pNode->Data.pStr = new QString(pszString);
1466 pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
1467 }
1468 else if (*pNode->Data.pStr == pszString)
1469 {
1470 delete pNode->Data.pStr;
1471 pNode->Data.pStr = new QString(pszString);
1472 pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
1473 }
1474 break;
1475 }
1476
1477 case STAMTYPE_U8:
1478 case STAMTYPE_U8_RESET:
1479 case STAMTYPE_X8:
1480 case STAMTYPE_X8_RESET:
1481 {
1482 uint8_t uPrev = pNode->Data.u8;
1483 pNode->Data.u8 = *(uint8_t *)pvSample;
1484 iDelta = (int32_t)pNode->Data.u8 - (int32_t)uPrev;
1485 if (iDelta || pNode->i64Delta)
1486 {
1487 pNode->i64Delta = iDelta;
1488 pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
1489 }
1490 break;
1491 }
1492
1493 case STAMTYPE_U16:
1494 case STAMTYPE_U16_RESET:
1495 case STAMTYPE_X16:
1496 case STAMTYPE_X16_RESET:
1497 {
1498 uint16_t uPrev = pNode->Data.u16;
1499 pNode->Data.u16 = *(uint16_t *)pvSample;
1500 iDelta = (int32_t)pNode->Data.u16 - (int32_t)uPrev;
1501 if (iDelta || pNode->i64Delta)
1502 {
1503 pNode->i64Delta = iDelta;
1504 pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
1505 }
1506 break;
1507 }
1508
1509 case STAMTYPE_U32:
1510 case STAMTYPE_U32_RESET:
1511 case STAMTYPE_X32:
1512 case STAMTYPE_X32_RESET:
1513 {
1514 uint32_t uPrev = pNode->Data.u32;
1515 pNode->Data.u32 = *(uint32_t *)pvSample;
1516 iDelta = (int64_t)pNode->Data.u32 - (int64_t)uPrev;
1517 if (iDelta || pNode->i64Delta)
1518 {
1519 pNode->i64Delta = iDelta;
1520 pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
1521 }
1522 break;
1523 }
1524
1525 case STAMTYPE_U64:
1526 case STAMTYPE_U64_RESET:
1527 case STAMTYPE_X64:
1528 case STAMTYPE_X64_RESET:
1529 {
1530 uint64_t uPrev = pNode->Data.u64;
1531 pNode->Data.u64 = *(uint64_t *)pvSample;
1532 iDelta = pNode->Data.u64 - uPrev;
1533 if (iDelta || pNode->i64Delta)
1534 {
1535 pNode->i64Delta = iDelta;
1536 pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
1537 }
1538 break;
1539 }
1540
1541 case STAMTYPE_BOOL:
1542 case STAMTYPE_BOOL_RESET:
1543 {
1544 bool fPrev = pNode->Data.f;
1545 pNode->Data.f = *(bool *)pvSample;
1546 iDelta = pNode->Data.f - fPrev;
1547 if (iDelta || pNode->i64Delta)
1548 {
1549 pNode->i64Delta = iDelta;
1550 pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
1551 }
1552 break;
1553 }
1554
1555 default:
1556 AssertMsgFailed(("%d\n", enmType));
1557 break;
1558 }
1559 }
1560}
1561
1562
1563/*static*/ ssize_t
1564VBoxDbgStatsModel::getNodePath(PCDBGGUISTATSNODE pNode, char *psz, ssize_t cch)
1565{
1566 ssize_t off;
1567 if (!pNode->pParent)
1568 {
1569 /* root - don't add it's slash! */
1570 AssertReturn(cch >= 1, -1);
1571 off = 0;
1572 *psz = '\0';
1573 }
1574 else
1575 {
1576 cch -= pNode->cchName + 1;
1577 AssertReturn(cch > 0, -1);
1578 off = getNodePath(pNode->pParent, psz, cch);
1579 if (off >= 0)
1580 {
1581 psz[off++] = '/';
1582 memcpy(&psz[off], pNode->pszName, pNode->cchName + 1);
1583 off += pNode->cchName;
1584 }
1585 }
1586 return off;
1587}
1588
1589
1590/*static*/ char *
1591VBoxDbgStatsModel::getNodePath2(PCDBGGUISTATSNODE pNode, char *psz, ssize_t cch)
1592{
1593 if (VBoxDbgStatsModel::getNodePath(pNode, psz, cch) < 0)
1594 return NULL;
1595 return psz;
1596}
1597
1598
1599/*static*/ QString
1600VBoxDbgStatsModel::getNodePattern(PCDBGGUISTATSNODE pNode, bool fSubTree /*= true*/)
1601{
1602 /* the node pattern. */
1603 char szPat[1024+1024+4];
1604 ssize_t cch = getNodePath(pNode, szPat, 1024);
1605 AssertReturn(cch >= 0, QString("//////////////////////////////////////////////////////"));
1606
1607 /* the sub-tree pattern. */
1608 if (fSubTree && pNode->cChildren)
1609 {
1610 char *psz = &szPat[cch];
1611 *psz++ = '|';
1612 memcpy(psz, szPat, cch);
1613 psz += cch;
1614 *psz++ = '/';
1615 *psz++ = '*';
1616 *psz++ = '\0';
1617 }
1618 return szPat;
1619}
1620
1621
1622/*static*/ bool
1623VBoxDbgStatsModel::isNodeAncestorOf(PCDBGGUISTATSNODE pAncestor, PCDBGGUISTATSNODE pDescendant)
1624{
1625 while (pDescendant)
1626 {
1627 pDescendant = pDescendant->pParent;
1628 if (pDescendant == pAncestor)
1629 return true;
1630 }
1631 return false;
1632}
1633
1634
1635/*static*/ PDBGGUISTATSNODE
1636VBoxDbgStatsModel::nextNode(PDBGGUISTATSNODE pNode)
1637{
1638 if (!pNode)
1639 return NULL;
1640
1641 /* descend to children. */
1642 if (pNode->cChildren)
1643 return pNode->papChildren[0];
1644
1645 PDBGGUISTATSNODE pParent = pNode->pParent;
1646 if (!pParent)
1647 return NULL;
1648
1649 /* next sibling. */
1650 if (pNode->iSelf + 1 < pNode->pParent->cChildren)
1651 return pParent->papChildren[pNode->iSelf + 1];
1652
1653 /* ascend and advanced to a parent's sibiling. */
1654 for (;;)
1655 {
1656 uint32_t iSelf = pParent->iSelf;
1657 pParent = pParent->pParent;
1658 if (!pParent)
1659 return NULL;
1660 if (iSelf + 1 < pParent->cChildren)
1661 return pParent->papChildren[iSelf + 1];
1662 }
1663}
1664
1665
1666/*static*/ PDBGGUISTATSNODE
1667VBoxDbgStatsModel::nextDataNode(PDBGGUISTATSNODE pNode)
1668{
1669 do
1670 pNode = nextNode(pNode);
1671 while ( pNode
1672 && pNode->enmType == STAMTYPE_INVALID);
1673 return pNode;
1674}
1675
1676
1677/*static*/ PDBGGUISTATSNODE
1678VBoxDbgStatsModel::prevNode(PDBGGUISTATSNODE pNode)
1679{
1680 if (!pNode)
1681 return NULL;
1682 PDBGGUISTATSNODE pParent = pNode->pParent;
1683 if (!pParent)
1684 return NULL;
1685
1686 /* previous sibling's latest descendant (better expression anyone?). */
1687 if (pNode->iSelf > 0)
1688 {
1689 pNode = pParent->papChildren[pNode->iSelf - 1];
1690 while (pNode->cChildren)
1691 pNode = pNode->papChildren[pNode->cChildren - 1];
1692 return pNode;
1693 }
1694
1695 /* ascend to the parent. */
1696 return pParent;
1697}
1698
1699
1700/*static*/ PDBGGUISTATSNODE
1701VBoxDbgStatsModel::prevDataNode(PDBGGUISTATSNODE pNode)
1702{
1703 do
1704 pNode = prevNode(pNode);
1705 while ( pNode
1706 && pNode->enmType == STAMTYPE_INVALID);
1707 return pNode;
1708}
1709
1710
1711#if 0
1712/*static*/ PDBGGUISTATSNODE
1713VBoxDbgStatsModel::createNewTree(IMachineDebugger *a_pIMachineDebugger)
1714{
1715 /** @todo */
1716 return NULL;
1717}
1718#endif
1719
1720
1721#if 0
1722/*static*/ PDBGGUISTATSNODE
1723VBoxDbgStatsModel::createNewTree(const char *pszFilename)
1724{
1725 /** @todo */
1726 return NULL;
1727}
1728#endif
1729
1730
1731#if 0
1732/*static*/ PDBGGUISTATSNODE
1733VBoxDbgStatsModel::createDiffTree(PDBGGUISTATSNODE pTree1, PDBGGUISTATSNODE pTree2)
1734{
1735 /** @todo */
1736 return NULL;
1737}
1738#endif
1739
1740
1741PDBGGUISTATSNODE
1742VBoxDbgStatsModel::updateCallbackHandleOutOfOrder(const char *pszName)
1743{
1744#if defined(VBOX_STRICT) || defined(LOG_ENABLED)
1745 char szStrict[1024];
1746#endif
1747
1748 /*
1749 * We might be inserting a new node between pPrev and pNode
1750 * or we might be removing one or more nodes. Either case is
1751 * handled in the same rough way.
1752 *
1753 * Might consider optimizing insertion at some later point since this
1754 * is a normal occurrence (dynamic statistics in PATM, IOM, MM, ++).
1755 */
1756 Assert(pszName[0] == '/');
1757 Assert(m_szUpdateParent[m_cchUpdateParent - 1] == '/');
1758
1759 /*
1760 * Start with the current parent node and look for a common ancestor
1761 * hoping that this is faster than going from the root (saves lookup).
1762 */
1763 PDBGGUISTATSNODE pNode = m_pUpdateParent->papChildren[m_iUpdateChild];
1764 PDBGGUISTATSNODE const pPrev = prevDataNode(pNode);
1765 AssertMsg(strcmp(pszName, getNodePath2(pNode, szStrict, sizeof(szStrict))), ("%s\n", szStrict));
1766 AssertMsg(!pPrev || strcmp(pszName, getNodePath2(pPrev, szStrict, sizeof(szStrict))), ("%s\n", szStrict));
1767 Log(("updateCallbackHandleOutOfOrder: pszName='%s' m_szUpdateParent='%s' m_cchUpdateParent=%u pNode='%s'\n",
1768 pszName, m_szUpdateParent, m_cchUpdateParent, getNodePath2(pNode, szStrict, sizeof(szStrict))));
1769
1770 pNode = pNode->pParent;
1771 while (pNode != m_pRoot)
1772 {
1773 if (!strncmp(pszName, m_szUpdateParent, m_cchUpdateParent))
1774 break;
1775 Assert(m_cchUpdateParent > pNode->cchName);
1776 m_cchUpdateParent -= pNode->cchName + 1;
1777 m_szUpdateParent[m_cchUpdateParent] = '\0';
1778 Log2(("updateCallbackHandleOutOfOrder: m_szUpdateParent='%s' m_cchUpdateParent=%u, removed '/%s' (%u)\n", m_szUpdateParent, m_cchUpdateParent, pNode->pszName, __LINE__));
1779 pNode = pNode->pParent;
1780 }
1781 Assert(m_szUpdateParent[m_cchUpdateParent - 1] == '/');
1782
1783 /*
1784 * Descend until we've found/created the node pszName indicates,
1785 * modifying m_szUpdateParent as we go along.
1786 */
1787 while (pszName[m_cchUpdateParent - 1] == '/')
1788 {
1789 /* Find the end of this component. */
1790 const char * const pszSubName = &pszName[m_cchUpdateParent];
1791 const char *pszEnd = strchr(pszSubName, '/');
1792 if (!pszEnd)
1793 pszEnd = strchr(pszSubName, '\0');
1794 size_t cchSubName = pszEnd - pszSubName;
1795
1796 /* Add the name to the path. */
1797 memcpy(&m_szUpdateParent[m_cchUpdateParent], pszSubName, cchSubName);
1798 m_cchUpdateParent += cchSubName;
1799 m_szUpdateParent[m_cchUpdateParent++] = '/';
1800 m_szUpdateParent[m_cchUpdateParent] = '\0';
1801 Assert(m_cchUpdateParent < sizeof(m_szUpdateParent));
1802 Log2(("updateCallbackHandleOutOfOrder: m_szUpdateParent='%s' m_cchUpdateParent=%u (%u)\n", m_szUpdateParent, m_cchUpdateParent, __LINE__));
1803
1804 if (!pNode->cChildren)
1805 {
1806 /* first child */
1807 pNode = createAndInsert(pNode, pszSubName, cchSubName, 0);
1808 AssertReturn(pNode, NULL);
1809 }
1810 else
1811 {
1812 /* binary search. */
1813 int32_t iStart = 0;
1814 int32_t iLast = pNode->cChildren - 1;
1815 for (;;)
1816 {
1817 int32_t i = iStart + (iLast + 1 - iStart) / 2;
1818 int iDiff;
1819 size_t const cchCompare = RT_MIN(pNode->papChildren[i]->cchName, cchSubName);
1820 iDiff = memcmp(pszSubName, pNode->papChildren[i]->pszName, cchCompare);
1821 if (!iDiff)
1822 {
1823 iDiff = cchSubName == cchCompare ? 0 : cchSubName > cchCompare ? 1 : -1;
1824 /* For cases when exisiting node name is same as new node name with additional characters. */
1825 if (!iDiff)
1826 iDiff = cchSubName == pNode->papChildren[i]->cchName ? 0 : cchSubName > pNode->papChildren[i]->cchName ? 1 : -1;
1827 }
1828 if (iDiff > 0)
1829 {
1830 iStart = i + 1;
1831 if (iStart > iLast)
1832 {
1833 pNode = createAndInsert(pNode, pszSubName, cchSubName, iStart);
1834 AssertReturn(pNode, NULL);
1835 break;
1836 }
1837 }
1838 else if (iDiff < 0)
1839 {
1840 iLast = i - 1;
1841 if (iLast < iStart)
1842 {
1843 pNode = createAndInsert(pNode, pszSubName, cchSubName, i);
1844 AssertReturn(pNode, NULL);
1845 break;
1846 }
1847 }
1848 else
1849 {
1850 pNode = pNode->papChildren[i];
1851 break;
1852 }
1853 }
1854 }
1855 }
1856 Assert( !memcmp(pszName, m_szUpdateParent, m_cchUpdateParent - 2)
1857 && pszName[m_cchUpdateParent - 1] == '\0');
1858
1859 /*
1860 * Remove all the nodes between pNode and pPrev but keep all
1861 * of pNode's ancestors (or it'll get orphaned).
1862 */
1863 PDBGGUISTATSNODE pCur = prevNode(pNode);
1864 while (pCur != pPrev)
1865 {
1866 PDBGGUISTATSNODE pAdv = prevNode(pCur); Assert(pAdv || !pPrev);
1867 if (!isNodeAncestorOf(pCur, pNode))
1868 {
1869 Assert(pCur != m_pRoot);
1870 removeAndDestroy(pCur);
1871 }
1872 pCur = pAdv;
1873 }
1874
1875 /*
1876 * Remove the data from all ancestors of pNode that it doesn't
1877 * share them pPrev.
1878 */
1879 if (pPrev)
1880 {
1881 pCur = pNode->pParent;
1882 while (!isNodeAncestorOf(pCur, pPrev))
1883 {
1884 resetNode(pNode);
1885 pCur = pCur->pParent;
1886 }
1887 }
1888
1889 /*
1890 * Finally, adjust the globals (szUpdateParent is one level too deep).
1891 */
1892 Assert(m_cchUpdateParent > pNode->cchName + 1);
1893 m_cchUpdateParent -= pNode->cchName + 1;
1894 m_szUpdateParent[m_cchUpdateParent] = '\0';
1895 m_pUpdateParent = pNode->pParent;
1896 m_iUpdateChild = pNode->iSelf;
1897 Log2(("updateCallbackHandleOutOfOrder: m_szUpdateParent='%s' m_cchUpdateParent=%u (%u)\n", m_szUpdateParent, m_cchUpdateParent, __LINE__));
1898
1899 return pNode;
1900}
1901
1902
1903PDBGGUISTATSNODE
1904VBoxDbgStatsModel::updateCallbackHandleTail(const char *pszName)
1905{
1906 /*
1907 * Insert it at the end of the tree.
1908 *
1909 * Do the same as we're doing down in createNewTreeCallback, walk from the
1910 * root and create whatever we need.
1911 */
1912 AssertReturn(*pszName == '/' && pszName[1] != '/', NULL);
1913 PDBGGUISTATSNODE pNode = m_pRoot;
1914 const char *pszCur = pszName + 1;
1915 while (*pszCur)
1916 {
1917 /* Find the end of this component. */
1918 const char *pszNext = strchr(pszCur, '/');
1919 if (!pszNext)
1920 pszNext = strchr(pszCur, '\0');
1921 size_t cchCur = pszNext - pszCur;
1922
1923 /* Create it if it doesn't exist (it will be last if it exists). */
1924 if ( !pNode->cChildren
1925 || strncmp(pNode->papChildren[pNode->cChildren - 1]->pszName, pszCur, cchCur)
1926 || pNode->papChildren[pNode->cChildren - 1]->pszName[cchCur])
1927 {
1928 pNode = createAndInsert(pNode, pszCur, pszNext - pszCur, pNode->cChildren);
1929 AssertReturn(pNode, NULL);
1930 }
1931 else
1932 pNode = pNode->papChildren[pNode->cChildren - 1];
1933
1934 /* Advance */
1935 pszCur = *pszNext ? pszNext + 1 : pszNext;
1936 }
1937
1938 return pNode;
1939}
1940
1941
1942void
1943VBoxDbgStatsModel::updateCallbackAdvance(PDBGGUISTATSNODE pNode)
1944{
1945 /*
1946 * Advance to the next node with data.
1947 *
1948 * ASSUMES a leaf *must* have data and again we're ASSUMING the sorting
1949 * on slash separated sub-strings.
1950 */
1951 if (m_iUpdateChild != UINT32_MAX)
1952 {
1953#ifdef VBOX_STRICT
1954 PDBGGUISTATSNODE const pCorrectNext = nextDataNode(pNode);
1955#endif
1956 PDBGGUISTATSNODE pParent = pNode->pParent;
1957 if (pNode->cChildren)
1958 {
1959 /* descend to the first child. */
1960 Assert(m_cchUpdateParent + pNode->cchName + 2 < sizeof(m_szUpdateParent));
1961 memcpy(&m_szUpdateParent[m_cchUpdateParent], pNode->pszName, pNode->cchName);
1962 m_cchUpdateParent += pNode->cchName;
1963 m_szUpdateParent[m_cchUpdateParent++] = '/';
1964 m_szUpdateParent[m_cchUpdateParent] = '\0';
1965
1966 pNode = pNode->papChildren[0];
1967 }
1968 else if (pNode->iSelf + 1 < pParent->cChildren)
1969 {
1970 /* next sibling or one if its descendants. */
1971 Assert(m_pUpdateParent == pParent);
1972 pNode = pParent->papChildren[pNode->iSelf + 1];
1973 }
1974 else
1975 {
1976 /* move up and down- / on-wards */
1977 for (;;)
1978 {
1979 /* ascend */
1980 pNode = pParent;
1981 pParent = pParent->pParent;
1982 if (!pParent)
1983 {
1984 Assert(pNode == m_pRoot);
1985 m_iUpdateChild = UINT32_MAX;
1986 m_szUpdateParent[0] = '\0';
1987 m_cchUpdateParent = 0;
1988 m_pUpdateParent = NULL;
1989 break;
1990 }
1991 Assert(m_cchUpdateParent > pNode->cchName + 1);
1992 m_cchUpdateParent -= pNode->cchName + 1;
1993
1994 /* try advance */
1995 if (pNode->iSelf + 1 < pParent->cChildren)
1996 {
1997 pNode = pParent->papChildren[pNode->iSelf + 1];
1998 m_szUpdateParent[m_cchUpdateParent] = '\0';
1999 break;
2000 }
2001 }
2002 }
2003
2004 /* descend to a node containing data and finalize the globals. (ASSUMES leaf has data.) */
2005 if (m_iUpdateChild != UINT32_MAX)
2006 {
2007 while ( pNode->enmType == STAMTYPE_INVALID
2008 && pNode->cChildren > 0)
2009 {
2010 Assert(pNode->enmState == kDbgGuiStatsNodeState_kVisible);
2011
2012 Assert(m_cchUpdateParent + pNode->cchName + 2 < sizeof(m_szUpdateParent));
2013 memcpy(&m_szUpdateParent[m_cchUpdateParent], pNode->pszName, pNode->cchName);
2014 m_cchUpdateParent += pNode->cchName;
2015 m_szUpdateParent[m_cchUpdateParent++] = '/';
2016 m_szUpdateParent[m_cchUpdateParent] = '\0';
2017
2018 pNode = pNode->papChildren[0];
2019 }
2020 Assert(pNode->enmType != STAMTYPE_INVALID);
2021 m_iUpdateChild = pNode->iSelf;
2022 m_pUpdateParent = pNode->pParent;
2023 Assert(pNode == pCorrectNext);
2024 }
2025 }
2026 /* else: we're at the end */
2027}
2028
2029
2030/*static*/ DECLCALLBACK(int)
2031VBoxDbgStatsModel::updateCallback(const char *pszName, STAMTYPE enmType, void *pvSample, STAMUNIT enmUnit,
2032 const char *pszUnit, STAMVISIBILITY enmVisibility, const char *pszDesc, void *pvUser)
2033{
2034 VBoxDbgStatsModelVM *pThis = (VBoxDbgStatsModelVM *)pvUser;
2035 Log3(("updateCallback: %s\n", pszName));
2036 RT_NOREF(enmUnit);
2037
2038 /*
2039 * Skip the ones which shouldn't be visible in the GUI.
2040 */
2041 if (enmVisibility == STAMVISIBILITY_NOT_GUI)
2042 return 0;
2043
2044 /*
2045 * The default assumption is that nothing has changed.
2046 * For now we'll reset the model when ever something changes.
2047 */
2048 PDBGGUISTATSNODE pNode;
2049 if (pThis->m_iUpdateChild != UINT32_MAX)
2050 {
2051 pNode = pThis->m_pUpdateParent->papChildren[pThis->m_iUpdateChild];
2052 if ( !strncmp(pszName, pThis->m_szUpdateParent, pThis->m_cchUpdateParent)
2053 && !strcmp(pszName + pThis->m_cchUpdateParent, pNode->pszName))
2054 /* got it! */;
2055 else
2056 {
2057 /* insert/remove */
2058 pNode = pThis->updateCallbackHandleOutOfOrder(pszName);
2059 if (!pNode)
2060 return VERR_NO_MEMORY;
2061 }
2062 }
2063 else
2064 {
2065 /* append */
2066 pNode = pThis->updateCallbackHandleTail(pszName);
2067 if (!pNode)
2068 return VERR_NO_MEMORY;
2069 }
2070
2071 /*
2072 * Perform the update and advance to the next one.
2073 */
2074 updateNode(pNode, enmType, pvSample, pszUnit, pszDesc);
2075 pThis->updateCallbackAdvance(pNode);
2076
2077 return VINF_SUCCESS;
2078}
2079
2080
2081bool
2082VBoxDbgStatsModel::updatePrepare(PDBGGUISTATSNODE a_pSubTree /*= NULL*/)
2083{
2084 /*
2085 * Find the first child with data and set it up as the 'next'
2086 * node to be updated.
2087 */
2088 PDBGGUISTATSNODE pFirst;
2089 Assert(m_pRoot);
2090 Assert(m_pRoot->enmType == STAMTYPE_INVALID);
2091 if (!a_pSubTree)
2092 pFirst = nextDataNode(m_pRoot);
2093 else
2094 pFirst = a_pSubTree->enmType != STAMTYPE_INVALID ? a_pSubTree : nextDataNode(a_pSubTree);
2095 if (pFirst)
2096 {
2097 m_iUpdateChild = pFirst->iSelf;
2098 m_pUpdateParent = pFirst->pParent; Assert(m_pUpdateParent);
2099 m_cchUpdateParent = getNodePath(m_pUpdateParent, m_szUpdateParent, sizeof(m_szUpdateParent) - 1);
2100 AssertReturn(m_cchUpdateParent >= 1, false);
2101 m_szUpdateParent[m_cchUpdateParent++] = '/';
2102 m_szUpdateParent[m_cchUpdateParent] = '\0';
2103 }
2104 else
2105 {
2106 m_iUpdateChild = UINT32_MAX;
2107 m_pUpdateParent = NULL;
2108 m_szUpdateParent[0] = '\0';
2109 m_cchUpdateParent = 0;
2110 }
2111
2112 /*
2113 * Set the flag and signal possible layout change.
2114 */
2115 m_fUpdateInsertRemove = false;
2116 /* emit layoutAboutToBeChanged(); - debug this, it gets stuck... */
2117 return true;
2118}
2119
2120
2121bool
2122VBoxDbgStatsModel::updateDone(bool a_fSuccess, PDBGGUISTATSNODE a_pSubTree /*= NULL*/)
2123{
2124 /*
2125 * Remove any nodes following the last in the update (unless the update failed).
2126 */
2127 if ( a_fSuccess
2128 && m_iUpdateChild != UINT32_MAX
2129 && a_pSubTree == NULL)
2130 {
2131 PDBGGUISTATSNODE const pLast = prevDataNode(m_pUpdateParent->papChildren[m_iUpdateChild]);
2132 if (!pLast)
2133 {
2134 /* nuking the whole tree. */
2135 setRootNode(createRootNode());
2136 m_fUpdateInsertRemove = true;
2137 }
2138 else
2139 {
2140 PDBGGUISTATSNODE pNode;
2141 while ((pNode = nextNode(pLast)))
2142 {
2143 Assert(pNode != m_pRoot);
2144 removeAndDestroy(pNode);
2145 }
2146 }
2147 }
2148
2149 /*
2150 * We're done making layout changes (if I understood it correctly), so,
2151 * signal this and then see what to do next. If we did too many removals
2152 * we'll just reset the whole shebang.
2153 */
2154 if (m_fUpdateInsertRemove)
2155 {
2156#if 0 /* hrmpf, layoutChanged() didn't work reliably at some point so doing this as well... */
2157 beginResetModel();
2158 endResetModel();
2159#else
2160 emit layoutChanged();
2161#endif
2162 }
2163 else
2164 {
2165 /*
2166 * Send dataChanged events.
2167 *
2168 * We do this here instead of from the updateCallback because it reduces
2169 * the clutter in that method and allow us to emit bulk signals in an
2170 * easier way because we can traverse the tree in a different fashion.
2171 */
2172 DBGGUISTATSSTACK Stack;
2173 Stack.a[0].pNode = !a_pSubTree ? m_pRoot : a_pSubTree;
2174 Stack.a[0].iChild = -1;
2175 Stack.iTop = 0;
2176
2177 while (Stack.iTop >= 0)
2178 {
2179 /* get top element */
2180 PDBGGUISTATSNODE pNode = Stack.a[Stack.iTop].pNode;
2181 uint32_t iChild = ++Stack.a[Stack.iTop].iChild;
2182 if (iChild < pNode->cChildren)
2183 {
2184 /* push */
2185 Stack.iTop++;
2186 Assert(Stack.iTop < (int32_t)RT_ELEMENTS(Stack.a));
2187 Stack.a[Stack.iTop].pNode = pNode->papChildren[iChild];
2188 Stack.a[Stack.iTop].iChild = -1;
2189 }
2190 else
2191 {
2192 /* pop */
2193 Stack.iTop--;
2194
2195 /* do the actual work. */
2196 iChild = 0;
2197 while (iChild < pNode->cChildren)
2198 {
2199 /* skip to the first needing updating. */
2200 while ( iChild < pNode->cChildren
2201 && pNode->papChildren[iChild]->enmState != kDbgGuiStatsNodeState_kRefresh)
2202 iChild++;
2203 if (iChild >= pNode->cChildren)
2204 break;
2205 PDBGGUISTATSNODE pChild = pNode->papChildren[iChild];
2206 QModelIndex const TopLeft = createIndex(iChild, 2, pChild);
2207 pChild->enmState = kDbgGuiStatsNodeState_kVisible;
2208
2209 /* Any subsequent nodes that also needs refreshing? */
2210 int const iRightCol = pChild->enmType != STAMTYPE_PROFILE && pChild->enmType != STAMTYPE_PROFILE_ADV ? 4 : 7;
2211 if (iRightCol == 4)
2212 while ( iChild + 1 < pNode->cChildren
2213 && (pChild = pNode->papChildren[iChild + 1])->enmState == kDbgGuiStatsNodeState_kRefresh
2214 && pChild->enmType != STAMTYPE_PROFILE
2215 && pChild->enmType != STAMTYPE_PROFILE_ADV)
2216 iChild++;
2217 else
2218 while ( iChild + 1 < pNode->cChildren
2219 && (pChild = pNode->papChildren[iChild + 1])->enmState == kDbgGuiStatsNodeState_kRefresh
2220 && ( pChild->enmType == STAMTYPE_PROFILE
2221 || pChild->enmType == STAMTYPE_PROFILE_ADV))
2222 iChild++;
2223
2224 /* emit the refresh signal */
2225 QModelIndex const BottomRight = createIndex(iChild, iRightCol, pNode->papChildren[iChild]);
2226 emit dataChanged(TopLeft, BottomRight);
2227 iChild++;
2228 }
2229 }
2230 }
2231
2232 /*
2233 * If a_pSubTree is not an intermediate node, invalidate it explicitly.
2234 */
2235 if (a_pSubTree && a_pSubTree->enmType != STAMTYPE_INVALID)
2236 {
2237 int const iRightCol = a_pSubTree->enmType != STAMTYPE_PROFILE && a_pSubTree->enmType != STAMTYPE_PROFILE_ADV
2238 ? 4 : 7;
2239 QModelIndex const BottomRight = createIndex(a_pSubTree->iSelf, iRightCol, a_pSubTree);
2240 QModelIndex const TopLeft = createIndex(a_pSubTree->iSelf, 2, a_pSubTree);
2241 emit dataChanged(TopLeft, BottomRight);
2242 }
2243 }
2244
2245 return m_fUpdateInsertRemove;
2246}
2247
2248
2249bool
2250VBoxDbgStatsModel::updateStatsByPattern(const QString &a_rPatStr, PDBGGUISTATSNODE a_pSubTree /*= NULL*/)
2251{
2252 /* stub */
2253 RT_NOREF(a_rPatStr, a_pSubTree);
2254 return false;
2255}
2256
2257
2258void
2259VBoxDbgStatsModel::updateStatsByIndex(QModelIndex const &a_rIndex)
2260{
2261 PDBGGUISTATSNODE pNode = nodeFromIndex(a_rIndex);
2262 if (pNode == m_pRoot || !a_rIndex.isValid())
2263 updateStatsByPattern(QString());
2264 else if (pNode)
2265 /** @todo this doesn't quite work if pNode is excluded by the m_PatStr. */
2266 updateStatsByPattern(getNodePattern(pNode, true /*fSubTree*/), pNode);
2267}
2268
2269
2270void
2271VBoxDbgStatsModel::resetStatsByPattern(QString const &a_rPatStr)
2272{
2273 /* stub */
2274 NOREF(a_rPatStr);
2275}
2276
2277
2278void
2279VBoxDbgStatsModel::resetStatsByIndex(QModelIndex const &a_rIndex, bool fSubTree /*= true*/)
2280{
2281 PCDBGGUISTATSNODE pNode = nodeFromIndex(a_rIndex);
2282 if (pNode == m_pRoot || !a_rIndex.isValid())
2283 {
2284 /* The root can't be reset, so only take action if fSubTree is set. */
2285 if (fSubTree)
2286 resetStatsByPattern(QString());
2287 }
2288 else if (pNode)
2289 resetStatsByPattern(getNodePattern(pNode, fSubTree));
2290}
2291
2292
2293void
2294VBoxDbgStatsModel::iterateStatsByPattern(QString const &a_rPatStr, VBoxDbgStatsModel::FNITERATOR *a_pfnCallback, void *a_pvUser,
2295 bool a_fMatchChildren /*= true*/)
2296{
2297 const QByteArray &PatBytes = a_rPatStr.toUtf8();
2298 const char * const pszPattern = PatBytes.constData();
2299 size_t const cchPattern = strlen(pszPattern);
2300
2301 DBGGUISTATSSTACK Stack;
2302 Stack.a[0].pNode = m_pRoot;
2303 Stack.a[0].iChild = 0;
2304 Stack.a[0].cchName = 0;
2305 Stack.iTop = 0;
2306
2307 char szName[1024];
2308 szName[0] = '\0';
2309
2310 while (Stack.iTop >= 0)
2311 {
2312 /* get top element */
2313 PDBGGUISTATSNODE const pNode = Stack.a[Stack.iTop].pNode;
2314 uint16_t cchName = Stack.a[Stack.iTop].cchName;
2315 uint32_t const iChild = Stack.a[Stack.iTop].iChild++;
2316 if (iChild < pNode->cChildren)
2317 {
2318 PDBGGUISTATSNODE pChild = pNode->papChildren[iChild];
2319
2320 /* Build the name and match the pattern. */
2321 Assert(cchName + 1 + pChild->cchName < sizeof(szName));
2322 szName[cchName++] = '/';
2323 memcpy(&szName[cchName], pChild->pszName, pChild->cchName);
2324 cchName += (uint16_t)pChild->cchName;
2325 szName[cchName] = '\0';
2326
2327 if (RTStrSimplePatternMultiMatch(pszPattern, cchPattern, szName, cchName, NULL))
2328 {
2329 /* Do callback. */
2330 QModelIndex const Index = createIndex(iChild, 0, pChild);
2331 if (!a_pfnCallback(pChild, Index, szName, a_pvUser))
2332 return;
2333 if (!a_fMatchChildren)
2334 continue;
2335 }
2336
2337 /* push */
2338 Stack.iTop++;
2339 Assert(Stack.iTop < (int32_t)RT_ELEMENTS(Stack.a));
2340 Stack.a[Stack.iTop].pNode = pChild;
2341 Stack.a[Stack.iTop].iChild = 0;
2342 Stack.a[Stack.iTop].cchName = cchName;
2343 }
2344 else
2345 {
2346 /* pop */
2347 Stack.iTop--;
2348 }
2349 }
2350}
2351
2352
2353QModelIndex
2354VBoxDbgStatsModel::getRootIndex(void) const
2355{
2356 if (!m_pRoot)
2357 return QModelIndex();
2358 return createIndex(0, 0, m_pRoot);
2359}
2360
2361
2362void
2363VBoxDbgStatsModel::setRootNode(PDBGGUISTATSNODE a_pRoot)
2364{
2365 PDBGGUISTATSNODE pOldTree = m_pRoot;
2366 m_pRoot = a_pRoot;
2367 destroyTree(pOldTree);
2368 beginResetModel();
2369 endResetModel();
2370}
2371
2372
2373Qt::ItemFlags
2374VBoxDbgStatsModel::flags(const QModelIndex &a_rIndex) const
2375{
2376 Qt::ItemFlags fFlags = QAbstractItemModel::flags(a_rIndex);
2377 return fFlags;
2378}
2379
2380
2381int
2382VBoxDbgStatsModel::columnCount(const QModelIndex &a_rParent) const
2383{
2384 NOREF(a_rParent);
2385 return DBGGUI_STATS_COLUMNS;
2386}
2387
2388
2389int
2390VBoxDbgStatsModel::rowCount(const QModelIndex &a_rParent) const
2391{
2392 PDBGGUISTATSNODE pParent = nodeFromIndex(a_rParent);
2393 return pParent ? pParent->cChildren : 1 /* root */;
2394}
2395
2396
2397bool
2398VBoxDbgStatsModel::hasChildren(const QModelIndex &a_rParent) const
2399{
2400 PDBGGUISTATSNODE pParent = nodeFromIndex(a_rParent);
2401 return pParent ? pParent->cChildren > 0 : true /* root */;
2402}
2403
2404
2405QModelIndex
2406VBoxDbgStatsModel::index(int iRow, int iColumn, const QModelIndex &a_rParent) const
2407{
2408 PDBGGUISTATSNODE pParent = nodeFromIndex(a_rParent);
2409 if (pParent)
2410 {
2411 AssertMsgReturn((unsigned)iRow < pParent->cChildren,
2412 ("iRow=%d >= cChildren=%u (iColumn=%d)\n", iRow, (unsigned)pParent->cChildren, iColumn),
2413 QModelIndex());
2414 AssertMsgReturn((unsigned)iColumn < DBGGUI_STATS_COLUMNS, ("iColumn=%d (iRow=%d)\n", iColumn, iRow), QModelIndex());
2415
2416 PDBGGUISTATSNODE pChild = pParent->papChildren[iRow];
2417 return createIndex(iRow, iColumn, pChild);
2418 }
2419
2420 /* root? */
2421 AssertReturn(a_rParent.isValid() || (iRow == 0 && iColumn >= 0), QModelIndex());
2422 AssertMsgReturn(iRow == 0 && (unsigned)iColumn < DBGGUI_STATS_COLUMNS, ("iRow=%d iColumn=%d", iRow, iColumn), QModelIndex());
2423 return createIndex(0, iColumn, m_pRoot);
2424}
2425
2426
2427QModelIndex
2428VBoxDbgStatsModel::parent(const QModelIndex &a_rChild) const
2429{
2430 PDBGGUISTATSNODE pChild = nodeFromIndex(a_rChild);
2431 if (!pChild)
2432 {
2433 Log(("parent: invalid child\n"));
2434 return QModelIndex(); /* bug */
2435 }
2436 PDBGGUISTATSNODE pParent = pChild->pParent;
2437 if (!pParent)
2438 return QModelIndex(); /* ultimate root */
2439
2440 return createIndex(pParent->iSelf, 0, pParent);
2441}
2442
2443
2444QVariant
2445VBoxDbgStatsModel::headerData(int a_iSection, Qt::Orientation a_eOrientation, int a_eRole) const
2446{
2447 if ( a_eOrientation == Qt::Horizontal
2448 && a_eRole == Qt::DisplayRole)
2449 switch (a_iSection)
2450 {
2451 case 0: return tr("Name");
2452 case 1: return tr("Unit");
2453 case 2: return tr("Value/Times");
2454 case 3: return tr("dInt");
2455 case 4: return tr("Min");
2456 case 5: return tr("Average");
2457 case 6: return tr("Max");
2458 case 7: return tr("Total");
2459 case 8: return tr("Description");
2460 default:
2461 AssertCompile(DBGGUI_STATS_COLUMNS == 9);
2462 return QVariant(); /* bug */
2463 }
2464 else if ( a_eOrientation == Qt::Horizontal
2465 && a_eRole == Qt::TextAlignmentRole)
2466 switch (a_iSection)
2467 {
2468 case 0:
2469 case 1:
2470 return QVariant();
2471 case 2:
2472 case 3:
2473 case 4:
2474 case 5:
2475 case 6:
2476 case 7:
2477 return (int)(Qt::AlignRight | Qt::AlignVCenter);
2478 case 8:
2479 return QVariant();
2480 default:
2481 AssertCompile(DBGGUI_STATS_COLUMNS == 9);
2482 return QVariant(); /* bug */
2483 }
2484
2485 return QVariant();
2486}
2487
2488
2489/*static*/ QString
2490VBoxDbgStatsModel::strUnit(PCDBGGUISTATSNODE pNode)
2491{
2492 return pNode->pszUnit;
2493}
2494
2495
2496/*static*/ QString
2497VBoxDbgStatsModel::strValueTimes(PCDBGGUISTATSNODE pNode)
2498{
2499 char sz[128];
2500
2501 switch (pNode->enmType)
2502 {
2503 case STAMTYPE_COUNTER:
2504 return formatNumber(sz, pNode->Data.Counter.c);
2505
2506 case STAMTYPE_PROFILE:
2507 case STAMTYPE_PROFILE_ADV:
2508 return formatNumber(sz, pNode->Data.Profile.cPeriods);
2509
2510 case STAMTYPE_RATIO_U32:
2511 case STAMTYPE_RATIO_U32_RESET:
2512 {
2513 char szTmp[64];
2514 char *psz = formatNumber(szTmp, pNode->Data.RatioU32.u32A);
2515 size_t off = strlen(psz);
2516 memcpy(sz, psz, off);
2517 sz[off++] = ':';
2518 strcpy(&sz[off], formatNumber(szTmp, pNode->Data.RatioU32.u32B));
2519 return sz;
2520 }
2521
2522 case STAMTYPE_CALLBACK:
2523 return *pNode->Data.pStr;
2524
2525 case STAMTYPE_U8:
2526 case STAMTYPE_U8_RESET:
2527 return formatNumber(sz, pNode->Data.u8);
2528
2529 case STAMTYPE_X8:
2530 case STAMTYPE_X8_RESET:
2531 return formatHexNumber(sz, pNode->Data.u8, 2);
2532
2533 case STAMTYPE_U16:
2534 case STAMTYPE_U16_RESET:
2535 return formatNumber(sz, pNode->Data.u16);
2536
2537 case STAMTYPE_X16:
2538 case STAMTYPE_X16_RESET:
2539 return formatHexNumber(sz, pNode->Data.u16, 4);
2540
2541 case STAMTYPE_U32:
2542 case STAMTYPE_U32_RESET:
2543 return formatNumber(sz, pNode->Data.u32);
2544
2545 case STAMTYPE_X32:
2546 case STAMTYPE_X32_RESET:
2547 return formatHexNumber(sz, pNode->Data.u32, 8);
2548
2549 case STAMTYPE_U64:
2550 case STAMTYPE_U64_RESET:
2551 return formatNumber(sz, pNode->Data.u64);
2552
2553 case STAMTYPE_X64:
2554 case STAMTYPE_X64_RESET:
2555 return formatHexNumber(sz, pNode->Data.u64, 16);
2556
2557 case STAMTYPE_BOOL:
2558 case STAMTYPE_BOOL_RESET:
2559 return pNode->Data.f ? "true" : "false";
2560
2561 default:
2562 AssertMsgFailed(("%d\n", pNode->enmType));
2563 RT_FALL_THRU();
2564 case STAMTYPE_INVALID:
2565 return "";
2566 }
2567}
2568
2569
2570/*static*/ uint64_t
2571VBoxDbgStatsModel::getValueTimesAsUInt(PCDBGGUISTATSNODE pNode)
2572{
2573 switch (pNode->enmType)
2574 {
2575 case STAMTYPE_COUNTER:
2576 return pNode->Data.Counter.c;
2577
2578 case STAMTYPE_PROFILE:
2579 case STAMTYPE_PROFILE_ADV:
2580 return pNode->Data.Profile.cPeriods;
2581
2582 case STAMTYPE_RATIO_U32:
2583 case STAMTYPE_RATIO_U32_RESET:
2584 return RT_MAKE_U64(pNode->Data.RatioU32.u32A, pNode->Data.RatioU32.u32B);
2585
2586 case STAMTYPE_CALLBACK:
2587 return UINT64_MAX;
2588
2589 case STAMTYPE_U8:
2590 case STAMTYPE_U8_RESET:
2591 case STAMTYPE_X8:
2592 case STAMTYPE_X8_RESET:
2593 return pNode->Data.u8;
2594
2595 case STAMTYPE_U16:
2596 case STAMTYPE_U16_RESET:
2597 case STAMTYPE_X16:
2598 case STAMTYPE_X16_RESET:
2599 return pNode->Data.u16;
2600
2601 case STAMTYPE_U32:
2602 case STAMTYPE_U32_RESET:
2603 case STAMTYPE_X32:
2604 case STAMTYPE_X32_RESET:
2605 return pNode->Data.u32;
2606
2607 case STAMTYPE_U64:
2608 case STAMTYPE_U64_RESET:
2609 case STAMTYPE_X64:
2610 case STAMTYPE_X64_RESET:
2611 return pNode->Data.u64;
2612
2613 case STAMTYPE_BOOL:
2614 case STAMTYPE_BOOL_RESET:
2615 return pNode->Data.f;
2616
2617 default:
2618 AssertMsgFailed(("%d\n", pNode->enmType));
2619 RT_FALL_THRU();
2620 case STAMTYPE_INVALID:
2621 return UINT64_MAX;
2622 }
2623}
2624
2625
2626/*static*/ uint64_t
2627VBoxDbgStatsModel::getValueOrAvgAsUInt(PCDBGGUISTATSNODE pNode)
2628{
2629 switch (pNode->enmType)
2630 {
2631 case STAMTYPE_COUNTER:
2632 return pNode->Data.Counter.c;
2633
2634 case STAMTYPE_PROFILE:
2635 case STAMTYPE_PROFILE_ADV:
2636 if (pNode->Data.Profile.cPeriods)
2637 return pNode->Data.Profile.cTicks / pNode->Data.Profile.cPeriods;
2638 return 0;
2639
2640 case STAMTYPE_RATIO_U32:
2641 case STAMTYPE_RATIO_U32_RESET:
2642 return RT_MAKE_U64(pNode->Data.RatioU32.u32A, pNode->Data.RatioU32.u32B);
2643
2644 case STAMTYPE_CALLBACK:
2645 return UINT64_MAX;
2646
2647 case STAMTYPE_U8:
2648 case STAMTYPE_U8_RESET:
2649 case STAMTYPE_X8:
2650 case STAMTYPE_X8_RESET:
2651 return pNode->Data.u8;
2652
2653 case STAMTYPE_U16:
2654 case STAMTYPE_U16_RESET:
2655 case STAMTYPE_X16:
2656 case STAMTYPE_X16_RESET:
2657 return pNode->Data.u16;
2658
2659 case STAMTYPE_U32:
2660 case STAMTYPE_U32_RESET:
2661 case STAMTYPE_X32:
2662 case STAMTYPE_X32_RESET:
2663 return pNode->Data.u32;
2664
2665 case STAMTYPE_U64:
2666 case STAMTYPE_U64_RESET:
2667 case STAMTYPE_X64:
2668 case STAMTYPE_X64_RESET:
2669 return pNode->Data.u64;
2670
2671 case STAMTYPE_BOOL:
2672 case STAMTYPE_BOOL_RESET:
2673 return pNode->Data.f;
2674
2675 default:
2676 AssertMsgFailed(("%d\n", pNode->enmType));
2677 RT_FALL_THRU();
2678 case STAMTYPE_INVALID:
2679 return UINT64_MAX;
2680 }
2681}
2682
2683
2684/*static*/ QString
2685VBoxDbgStatsModel::strMinValue(PCDBGGUISTATSNODE pNode)
2686{
2687 char sz[128];
2688
2689 switch (pNode->enmType)
2690 {
2691 case STAMTYPE_PROFILE:
2692 case STAMTYPE_PROFILE_ADV:
2693 if (pNode->Data.Profile.cPeriods)
2694 return formatNumber(sz, pNode->Data.Profile.cTicksMin);
2695 return "0"; /* cTicksMin is set to UINT64_MAX */
2696 default:
2697 return "";
2698 }
2699}
2700
2701
2702/*static*/ uint64_t
2703VBoxDbgStatsModel::getMinValueAsUInt(PCDBGGUISTATSNODE pNode)
2704{
2705 switch (pNode->enmType)
2706 {
2707 case STAMTYPE_PROFILE:
2708 case STAMTYPE_PROFILE_ADV:
2709 if (pNode->Data.Profile.cPeriods)
2710 return pNode->Data.Profile.cTicksMin;
2711 return 0; /* cTicksMin is set to UINT64_MAX */
2712 default:
2713 return UINT64_MAX;
2714 }
2715}
2716
2717
2718/*static*/ QString
2719VBoxDbgStatsModel::strAvgValue(PCDBGGUISTATSNODE pNode)
2720{
2721 char sz[128];
2722
2723 switch (pNode->enmType)
2724 {
2725 case STAMTYPE_PROFILE:
2726 case STAMTYPE_PROFILE_ADV:
2727 if (pNode->Data.Profile.cPeriods)
2728 return formatNumber(sz, pNode->Data.Profile.cTicks / pNode->Data.Profile.cPeriods);
2729 return "0";
2730 default:
2731 return "";
2732 }
2733}
2734
2735
2736/*static*/ uint64_t
2737VBoxDbgStatsModel::getAvgValueAsUInt(PCDBGGUISTATSNODE pNode)
2738{
2739 switch (pNode->enmType)
2740 {
2741 case STAMTYPE_PROFILE:
2742 case STAMTYPE_PROFILE_ADV:
2743 if (pNode->Data.Profile.cPeriods)
2744 return pNode->Data.Profile.cTicks / pNode->Data.Profile.cPeriods;
2745 return 0;
2746 default:
2747 return UINT64_MAX;
2748 }
2749}
2750
2751
2752
2753/*static*/ QString
2754VBoxDbgStatsModel::strMaxValue(PCDBGGUISTATSNODE pNode)
2755{
2756 char sz[128];
2757
2758 switch (pNode->enmType)
2759 {
2760 case STAMTYPE_PROFILE:
2761 case STAMTYPE_PROFILE_ADV:
2762 return formatNumber(sz, pNode->Data.Profile.cTicksMax);
2763 default:
2764 return "";
2765 }
2766}
2767
2768
2769/*static*/ uint64_t
2770VBoxDbgStatsModel::getMaxValueAsUInt(PCDBGGUISTATSNODE pNode)
2771{
2772 switch (pNode->enmType)
2773 {
2774 case STAMTYPE_PROFILE:
2775 case STAMTYPE_PROFILE_ADV:
2776 return pNode->Data.Profile.cTicksMax;
2777 default:
2778 return UINT64_MAX;
2779 }
2780}
2781
2782
2783/*static*/ QString
2784VBoxDbgStatsModel::strTotalValue(PCDBGGUISTATSNODE pNode)
2785{
2786 char sz[128];
2787
2788 switch (pNode->enmType)
2789 {
2790 case STAMTYPE_PROFILE:
2791 case STAMTYPE_PROFILE_ADV:
2792 return formatNumber(sz, pNode->Data.Profile.cTicks);
2793 default:
2794 return "";
2795 }
2796}
2797
2798
2799/*static*/ uint64_t
2800VBoxDbgStatsModel::getTotalValueAsUInt(PCDBGGUISTATSNODE pNode)
2801{
2802 switch (pNode->enmType)
2803 {
2804 case STAMTYPE_PROFILE:
2805 case STAMTYPE_PROFILE_ADV:
2806 return pNode->Data.Profile.cTicks;
2807 default:
2808 return UINT64_MAX;
2809 }
2810}
2811
2812
2813/*static*/ QString
2814VBoxDbgStatsModel::strDeltaValue(PCDBGGUISTATSNODE pNode)
2815{
2816 switch (pNode->enmType)
2817 {
2818 case STAMTYPE_PROFILE:
2819 case STAMTYPE_PROFILE_ADV:
2820 case STAMTYPE_COUNTER:
2821 case STAMTYPE_RATIO_U32:
2822 case STAMTYPE_RATIO_U32_RESET:
2823 case STAMTYPE_U8:
2824 case STAMTYPE_U8_RESET:
2825 case STAMTYPE_X8:
2826 case STAMTYPE_X8_RESET:
2827 case STAMTYPE_U16:
2828 case STAMTYPE_U16_RESET:
2829 case STAMTYPE_X16:
2830 case STAMTYPE_X16_RESET:
2831 case STAMTYPE_U32:
2832 case STAMTYPE_U32_RESET:
2833 case STAMTYPE_X32:
2834 case STAMTYPE_X32_RESET:
2835 case STAMTYPE_U64:
2836 case STAMTYPE_U64_RESET:
2837 case STAMTYPE_X64:
2838 case STAMTYPE_X64_RESET:
2839 case STAMTYPE_BOOL:
2840 case STAMTYPE_BOOL_RESET:
2841 if (pNode->i64Delta)
2842 {
2843 char sz[128];
2844 return formatNumberSigned(sz, pNode->i64Delta, true /*fPositivePlus*/);
2845 }
2846 return "0";
2847 case STAMTYPE_INTERNAL_SUM:
2848 case STAMTYPE_INTERNAL_PCT_OF_SUM:
2849 case STAMTYPE_END:
2850 AssertFailed(); RT_FALL_THRU();
2851 case STAMTYPE_CALLBACK:
2852 case STAMTYPE_INVALID:
2853 break;
2854 }
2855 return "";
2856}
2857
2858
2859QVariant
2860VBoxDbgStatsModel::data(const QModelIndex &a_rIndex, int a_eRole) const
2861{
2862 unsigned iCol = a_rIndex.column();
2863 AssertMsgReturn(iCol < DBGGUI_STATS_COLUMNS, ("%d\n", iCol), QVariant());
2864 Log4(("Model::data(%p(%d,%d), %d)\n", nodeFromIndex(a_rIndex), iCol, a_rIndex.row(), a_eRole));
2865
2866 if (a_eRole == Qt::DisplayRole)
2867 {
2868 PDBGGUISTATSNODE pNode = nodeFromIndex(a_rIndex);
2869 AssertReturn(pNode, QVariant());
2870
2871 switch (iCol)
2872 {
2873 case 0:
2874 if (!pNode->pFilter)
2875 return QString(pNode->pszName);
2876 return QString(pNode->pszName) + " (*)";
2877 case 1:
2878 return strUnit(pNode);
2879 case 2:
2880 return strValueTimes(pNode);
2881 case 3:
2882 return strDeltaValue(pNode);
2883 case 4:
2884 return strMinValue(pNode);
2885 case 5:
2886 return strAvgValue(pNode);
2887 case 6:
2888 return strMaxValue(pNode);
2889 case 7:
2890 return strTotalValue(pNode);
2891 case 8:
2892 return pNode->pDescStr ? QString(*pNode->pDescStr) : QString("");
2893 default:
2894 AssertCompile(DBGGUI_STATS_COLUMNS == 9);
2895 return QVariant();
2896 }
2897 }
2898 else if (a_eRole == Qt::TextAlignmentRole)
2899 switch (iCol)
2900 {
2901 case 0:
2902 case 1:
2903 return QVariant();
2904 case 2:
2905 case 3:
2906 case 4:
2907 case 5:
2908 case 6:
2909 case 7:
2910 return (int)(Qt::AlignRight | Qt::AlignVCenter);
2911 case 8:
2912 return QVariant();
2913 default:
2914 AssertCompile(DBGGUI_STATS_COLUMNS == 9);
2915 return QVariant(); /* bug */
2916 }
2917 return QVariant();
2918}
2919
2920
2921/*static*/ void
2922VBoxDbgStatsModel::stringifyNodeNoRecursion(PDBGGUISTATSNODE a_pNode, QString &a_rString)
2923{
2924 /*
2925 * Get the path, padding it to 32-chars and add it to the string.
2926 */
2927 char szBuf[1024];
2928 ssize_t off = getNodePath(a_pNode, szBuf, sizeof(szBuf) - 2);
2929 AssertReturnVoid(off >= 0);
2930 if (off < 32)
2931 {
2932 memset(&szBuf[off], ' ', 32 - off);
2933 szBuf[32] = '\0';
2934 off = 32;
2935 }
2936 szBuf[off++] = ' ';
2937 szBuf[off] = '\0';
2938 a_rString += szBuf;
2939
2940 /*
2941 * The following is derived from stamR3PrintOne, except
2942 * we print to szBuf, do no visibility checks and can skip
2943 * the path bit.
2944 */
2945 switch (a_pNode->enmType)
2946 {
2947 case STAMTYPE_COUNTER:
2948 RTStrPrintf(szBuf, sizeof(szBuf), "%8llu %s", a_pNode->Data.Counter.c, a_pNode->pszUnit);
2949 break;
2950
2951 case STAMTYPE_PROFILE:
2952 case STAMTYPE_PROFILE_ADV:
2953 {
2954 uint64_t u64 = a_pNode->Data.Profile.cPeriods ? a_pNode->Data.Profile.cPeriods : 1;
2955 RTStrPrintf(szBuf, sizeof(szBuf),
2956 "%8llu %s (%12llu ticks, %7llu times, max %9llu, min %7lld)",
2957 a_pNode->Data.Profile.cTicks / u64, a_pNode->pszUnit,
2958 a_pNode->Data.Profile.cTicks, a_pNode->Data.Profile.cPeriods, a_pNode->Data.Profile.cTicksMax, a_pNode->Data.Profile.cTicksMin);
2959 break;
2960 }
2961
2962 case STAMTYPE_RATIO_U32:
2963 case STAMTYPE_RATIO_U32_RESET:
2964 RTStrPrintf(szBuf, sizeof(szBuf),
2965 "%8u:%-8u %s",
2966 a_pNode->Data.RatioU32.u32A, a_pNode->Data.RatioU32.u32B, a_pNode->pszUnit);
2967 break;
2968
2969 case STAMTYPE_CALLBACK:
2970 if (a_pNode->Data.pStr)
2971 a_rString += *a_pNode->Data.pStr;
2972 RTStrPrintf(szBuf, sizeof(szBuf), " %s", a_pNode->pszUnit);
2973 break;
2974
2975 case STAMTYPE_U8:
2976 case STAMTYPE_U8_RESET:
2977 RTStrPrintf(szBuf, sizeof(szBuf), "%8u %s", a_pNode->Data.u8, a_pNode->pszUnit);
2978 break;
2979
2980 case STAMTYPE_X8:
2981 case STAMTYPE_X8_RESET:
2982 RTStrPrintf(szBuf, sizeof(szBuf), "%8x %s", a_pNode->Data.u8, a_pNode->pszUnit);
2983 break;
2984
2985 case STAMTYPE_U16:
2986 case STAMTYPE_U16_RESET:
2987 RTStrPrintf(szBuf, sizeof(szBuf), "%8u %s", a_pNode->Data.u16, a_pNode->pszUnit);
2988 break;
2989
2990 case STAMTYPE_X16:
2991 case STAMTYPE_X16_RESET:
2992 RTStrPrintf(szBuf, sizeof(szBuf), "%8x %s", a_pNode->Data.u16, a_pNode->pszUnit);
2993 break;
2994
2995 case STAMTYPE_U32:
2996 case STAMTYPE_U32_RESET:
2997 RTStrPrintf(szBuf, sizeof(szBuf), "%8u %s", a_pNode->Data.u32, a_pNode->pszUnit);
2998 break;
2999
3000 case STAMTYPE_X32:
3001 case STAMTYPE_X32_RESET:
3002 RTStrPrintf(szBuf, sizeof(szBuf), "%8x %s", a_pNode->Data.u32, a_pNode->pszUnit);
3003 break;
3004
3005 case STAMTYPE_U64:
3006 case STAMTYPE_U64_RESET:
3007 RTStrPrintf(szBuf, sizeof(szBuf), "%8llu %s", a_pNode->Data.u64, a_pNode->pszUnit);
3008 break;
3009
3010 case STAMTYPE_X64:
3011 case STAMTYPE_X64_RESET:
3012 RTStrPrintf(szBuf, sizeof(szBuf), "%8llx %s", a_pNode->Data.u64, a_pNode->pszUnit);
3013 break;
3014
3015 case STAMTYPE_BOOL:
3016 case STAMTYPE_BOOL_RESET:
3017 RTStrPrintf(szBuf, sizeof(szBuf), "%s %s", a_pNode->Data.f ? "true " : "false ", a_pNode->pszUnit);
3018 break;
3019
3020 default:
3021 AssertMsgFailed(("enmType=%d\n", a_pNode->enmType));
3022 return;
3023 }
3024
3025 a_rString += szBuf;
3026}
3027
3028
3029/*static*/ void
3030VBoxDbgStatsModel::stringifyNode(PDBGGUISTATSNODE a_pNode, QString &a_rString)
3031{
3032 /* this node (if it has data) */
3033 if (a_pNode->enmType != STAMTYPE_INVALID)
3034 {
3035 if (!a_rString.isEmpty())
3036 a_rString += "\n";
3037 stringifyNodeNoRecursion(a_pNode, a_rString);
3038 }
3039
3040 /* the children */
3041 uint32_t const cChildren = a_pNode->cChildren;
3042 for (uint32_t i = 0; i < cChildren; i++)
3043 stringifyNode(a_pNode->papChildren[i], a_rString);
3044}
3045
3046
3047void
3048VBoxDbgStatsModel::stringifyTree(QModelIndex &a_rRoot, QString &a_rString) const
3049{
3050 PDBGGUISTATSNODE pRoot = a_rRoot.isValid() ? nodeFromIndex(a_rRoot) : m_pRoot;
3051 if (pRoot)
3052 stringifyNode(pRoot, a_rString);
3053}
3054
3055
3056void
3057VBoxDbgStatsModel::copyTreeToClipboard(QModelIndex &a_rRoot) const
3058{
3059 QString String;
3060 stringifyTree(a_rRoot, String);
3061
3062 QClipboard *pClipboard = QApplication::clipboard();
3063 if (pClipboard)
3064 pClipboard->setText(String, QClipboard::Clipboard);
3065}
3066
3067
3068/*static*/ void
3069VBoxDbgStatsModel::logNode(PDBGGUISTATSNODE a_pNode, bool a_fReleaseLog)
3070{
3071 /* this node (if it has data) */
3072 if (a_pNode->enmType != STAMTYPE_INVALID)
3073 {
3074 QString SelfStr;
3075 stringifyNodeNoRecursion(a_pNode, SelfStr);
3076 QByteArray SelfByteArray = SelfStr.toUtf8();
3077 if (a_fReleaseLog)
3078 RTLogRelPrintf("%s\n", SelfByteArray.constData());
3079 else
3080 RTLogPrintf("%s\n", SelfByteArray.constData());
3081 }
3082
3083 /* the children */
3084 uint32_t const cChildren = a_pNode->cChildren;
3085 for (uint32_t i = 0; i < cChildren; i++)
3086 logNode(a_pNode->papChildren[i], a_fReleaseLog);
3087}
3088
3089
3090void
3091VBoxDbgStatsModel::logTree(QModelIndex &a_rRoot, bool a_fReleaseLog) const
3092{
3093 PDBGGUISTATSNODE pRoot = a_rRoot.isValid() ? nodeFromIndex(a_rRoot) : m_pRoot;
3094 if (pRoot)
3095 logNode(pRoot, a_fReleaseLog);
3096}
3097
3098
3099
3100
3101
3102
3103
3104/*
3105 *
3106 * V B o x D b g S t a t s M o d e l V M
3107 * V B o x D b g S t a t s M o d e l V M
3108 * V B o x D b g S t a t s M o d e l V M
3109 *
3110 *
3111 */
3112
3113
3114VBoxDbgStatsModelVM::VBoxDbgStatsModelVM(VBoxDbgGui *a_pDbgGui, QString &a_rPatStr, QObject *a_pParent, PCVMMR3VTABLE a_pVMM)
3115 : VBoxDbgStatsModel(a_pParent), VBoxDbgBase(a_pDbgGui), m_pVMM(a_pVMM)
3116{
3117 /*
3118 * Create a model containing the STAM entries matching the pattern.
3119 * (The original idea was to get everything and rely on some hide/visible
3120 * flag that it turned out didn't exist.)
3121 */
3122 PDBGGUISTATSNODE pTree = createNewTree(a_rPatStr);
3123 setRootNode(pTree);
3124}
3125
3126
3127VBoxDbgStatsModelVM::~VBoxDbgStatsModelVM()
3128{
3129 /* nothing to do here. */
3130}
3131
3132
3133bool
3134VBoxDbgStatsModelVM::updateStatsByPattern(const QString &a_rPatStr, PDBGGUISTATSNODE a_pSubTree /*= NULL*/)
3135{
3136 /** @todo the way we update this stuff is independent of the source (XML, file, STAM), our only
3137 * ASSUMPTION is that the input is strictly ordered by (fully slashed) name. So, all this stuff
3138 * should really move up into the parent class. */
3139 bool fRc = updatePrepare(a_pSubTree);
3140 if (fRc)
3141 {
3142 int rc = stamEnum(a_rPatStr, updateCallback, this);
3143 fRc = updateDone(RT_SUCCESS(rc), a_pSubTree);
3144 }
3145 return fRc;
3146}
3147
3148
3149void
3150VBoxDbgStatsModelVM::resetStatsByPattern(QString const &a_rPatStr)
3151{
3152 stamReset(a_rPatStr);
3153}
3154
3155
3156/*static*/ DECLCALLBACK(int)
3157VBoxDbgStatsModelVM::createNewTreeCallback(const char *pszName, STAMTYPE enmType, void *pvSample, STAMUNIT enmUnit,
3158 const char *pszUnit, STAMVISIBILITY enmVisibility, const char *pszDesc, void *pvUser)
3159{
3160 PDBGGUISTATSNODE pRoot = (PDBGGUISTATSNODE)pvUser;
3161 Log3(("createNewTreeCallback: %s\n", pszName));
3162 RT_NOREF(enmUnit);
3163
3164 /*
3165 * Skip the ones which shouldn't be visible in the GUI.
3166 */
3167 if (enmVisibility == STAMVISIBILITY_NOT_GUI)
3168 return 0;
3169
3170 /*
3171 * Perform a mkdir -p like operation till we've walked / created the entire path down
3172 * to the node specfied node. Remember the last node as that will be the one we will
3173 * stuff the data into.
3174 */
3175 AssertReturn(*pszName == '/' && pszName[1] != '/', VERR_INTERNAL_ERROR);
3176 PDBGGUISTATSNODE pNode = pRoot;
3177 const char *pszCur = pszName + 1;
3178 while (*pszCur)
3179 {
3180 /* find the end of this component. */
3181 const char *pszNext = strchr(pszCur, '/');
3182 if (!pszNext)
3183 pszNext = strchr(pszCur, '\0');
3184 size_t cchCur = pszNext - pszCur;
3185
3186 /* Create it if it doesn't exist (it will be last if it exists). */
3187 if ( !pNode->cChildren
3188 || strncmp(pNode->papChildren[pNode->cChildren - 1]->pszName, pszCur, cchCur)
3189 || pNode->papChildren[pNode->cChildren - 1]->pszName[cchCur])
3190 {
3191 pNode = createAndInsertNode(pNode, pszCur, pszNext - pszCur, UINT32_MAX);
3192 if (!pNode)
3193 return VERR_NO_MEMORY;
3194 }
3195 else
3196 pNode = pNode->papChildren[pNode->cChildren - 1];
3197
3198 /* Advance */
3199 pszCur = *pszNext ? pszNext + 1 : pszNext;
3200 }
3201
3202 /*
3203 * Save the data.
3204 */
3205 return initNode(pNode, enmType, pvSample, pszUnit, pszDesc);
3206}
3207
3208
3209PDBGGUISTATSNODE
3210VBoxDbgStatsModelVM::createNewTree(QString &a_rPatStr)
3211{
3212 PDBGGUISTATSNODE pRoot = createRootNode();
3213 if (pRoot)
3214 {
3215 int rc = stamEnum(a_rPatStr, createNewTreeCallback, pRoot);
3216 if (RT_SUCCESS(rc))
3217 return pRoot;
3218
3219 /* failed, cleanup. */
3220 destroyTree(pRoot);
3221 }
3222
3223 return NULL;
3224}
3225
3226
3227
3228
3229
3230
3231
3232
3233/*
3234 *
3235 * V B o x D b g S t a t s S o r t F i l e P r o x y M o d e l
3236 * V B o x D b g S t a t s S o r t F i l e P r o x y M o d e l
3237 * V B o x D b g S t a t s S o r t F i l e P r o x y M o d e l
3238 *
3239 *
3240 */
3241
3242#ifdef VBOXDBG_WITH_SORTED_AND_FILTERED_STATS
3243
3244VBoxDbgStatsSortFileProxyModel::VBoxDbgStatsSortFileProxyModel(QObject *a_pParent)
3245 : QSortFilterProxyModel(a_pParent)
3246 , m_fShowUnusedRows(false)
3247{
3248
3249}
3250
3251
3252bool
3253VBoxDbgStatsSortFileProxyModel::filterAcceptsRow(int a_iSrcRow, const QModelIndex &a_rSrcParent) const
3254{
3255 /*
3256 * Locate the node.
3257 */
3258 PDBGGUISTATSNODE pParent = nodeFromIndex(a_rSrcParent);
3259 if (pParent)
3260 {
3261 if ((unsigned)a_iSrcRow < pParent->cChildren)
3262 {
3263 PDBGGUISTATSNODE const pNode = pParent->papChildren[a_iSrcRow];
3264 if (pNode) /* paranoia */
3265 {
3266 /*
3267 * Apply the global unused-row filter.
3268 */
3269 if (!m_fShowUnusedRows)
3270 {
3271 /* Only relevant for leaf nodes. */
3272 if (pNode->cChildren == 0)
3273 {
3274 if (pNode->enmState != kDbgGuiStatsNodeState_kInvalid)
3275 {
3276 if (pNode->i64Delta == 0)
3277 {
3278 /* Is the cached statistics value zero? */
3279 switch (pNode->enmType)
3280 {
3281 case STAMTYPE_COUNTER:
3282 if (pNode->Data.Counter.c != 0)
3283 break;
3284 return false;
3285
3286 case STAMTYPE_PROFILE:
3287 case STAMTYPE_PROFILE_ADV:
3288 if (pNode->Data.Profile.cPeriods)
3289 break;
3290 return false;
3291
3292 case STAMTYPE_RATIO_U32:
3293 case STAMTYPE_RATIO_U32_RESET:
3294 if (pNode->Data.RatioU32.u32A || pNode->Data.RatioU32.u32B)
3295 break;
3296 return false;
3297
3298 case STAMTYPE_CALLBACK:
3299 if (pNode->Data.pStr && !pNode->Data.pStr->isEmpty())
3300 break;
3301 return false;
3302
3303 case STAMTYPE_U8:
3304 case STAMTYPE_U8_RESET:
3305 case STAMTYPE_X8:
3306 case STAMTYPE_X8_RESET:
3307 if (pNode->Data.u8)
3308 break;
3309 return false;
3310
3311 case STAMTYPE_U16:
3312 case STAMTYPE_U16_RESET:
3313 case STAMTYPE_X16:
3314 case STAMTYPE_X16_RESET:
3315 if (pNode->Data.u16)
3316 break;
3317 return false;
3318
3319 case STAMTYPE_U32:
3320 case STAMTYPE_U32_RESET:
3321 case STAMTYPE_X32:
3322 case STAMTYPE_X32_RESET:
3323 if (pNode->Data.u32)
3324 break;
3325 return false;
3326
3327 case STAMTYPE_U64:
3328 case STAMTYPE_U64_RESET:
3329 case STAMTYPE_X64:
3330 case STAMTYPE_X64_RESET:
3331 if (pNode->Data.u64)
3332 break;
3333 return false;
3334
3335 case STAMTYPE_BOOL:
3336 case STAMTYPE_BOOL_RESET:
3337 /* not possible to detect */
3338 return false;
3339
3340 case STAMTYPE_INVALID:
3341 break;
3342
3343 default:
3344 AssertMsgFailedBreak(("enmType=%d\n", pNode->enmType));
3345 }
3346 }
3347 }
3348 else
3349 return false;
3350 }
3351 }
3352
3353 /*
3354 * Look for additional filtering rules among the ancestors.
3355 */
3356 if (VBoxGuiStatsFilterData::s_cInstances > 0 /* quick & dirty optimization */)
3357 {
3358 VBoxGuiStatsFilterData const *pFilter = pParent->pFilter;
3359 while (!pFilter && (pParent = pParent->pParent) != NULL)
3360 pFilter = pParent->pFilter;
3361 if (pFilter)
3362 {
3363 if (pFilter->uMinValue > 0 || pFilter->uMaxValue != UINT64_MAX)
3364 {
3365 uint64_t const uValue = VBoxDbgStatsModel::getValueTimesAsUInt(pNode);
3366 if ( uValue < pFilter->uMinValue
3367 || uValue > pFilter->uMaxValue)
3368 return false;
3369 }
3370 if (pFilter->pRegexName)
3371 {
3372 if (!pFilter->pRegexName->match(pNode->pszName).hasMatch())
3373 return false;
3374 }
3375 }
3376 }
3377 }
3378 }
3379 }
3380 return true;
3381}
3382
3383
3384bool
3385VBoxDbgStatsSortFileProxyModel::lessThan(const QModelIndex &a_rSrcLeft, const QModelIndex &a_rSrcRight) const
3386{
3387 PCDBGGUISTATSNODE const pLeft = nodeFromIndex(a_rSrcLeft);
3388 PCDBGGUISTATSNODE const pRight = nodeFromIndex(a_rSrcRight);
3389 if (pLeft == pRight)
3390 return false;
3391 if (pLeft && pRight)
3392 {
3393 if (pLeft->pParent == pRight->pParent)
3394 {
3395 switch (a_rSrcLeft.column())
3396 {
3397 case 0:
3398 return RTStrCmp(pLeft->pszName, pRight->pszName) < 0;
3399 case 1:
3400 return RTStrCmp(pLeft->pszUnit, pRight->pszUnit) < 0;
3401 case 2:
3402 return VBoxDbgStatsModel::getValueTimesAsUInt(pLeft) < VBoxDbgStatsModel::getValueTimesAsUInt(pRight);
3403 case 3:
3404 return pLeft->i64Delta < pRight->i64Delta;
3405 case 4:
3406 return VBoxDbgStatsModel::getMinValueAsUInt(pLeft) < VBoxDbgStatsModel::getMinValueAsUInt(pRight);
3407 case 5:
3408 return VBoxDbgStatsModel::getAvgValueAsUInt(pLeft) < VBoxDbgStatsModel::getAvgValueAsUInt(pRight);
3409 case 6:
3410 return VBoxDbgStatsModel::getMaxValueAsUInt(pLeft) < VBoxDbgStatsModel::getMaxValueAsUInt(pRight);
3411 case 7:
3412 return VBoxDbgStatsModel::getTotalValueAsUInt(pLeft) < VBoxDbgStatsModel::getTotalValueAsUInt(pRight);
3413 case 8:
3414 if (pLeft->pDescStr == pRight->pDescStr)
3415 return false;
3416 if (!pLeft->pDescStr)
3417 return true;
3418 if (!pRight->pDescStr)
3419 return false;
3420 return *pLeft->pDescStr < *pRight->pDescStr;
3421 default:
3422 AssertCompile(DBGGUI_STATS_COLUMNS == 9);
3423 return true;
3424 }
3425 }
3426 return false;
3427 }
3428 return !pLeft;
3429}
3430
3431
3432void
3433VBoxDbgStatsSortFileProxyModel::setShowUnusedRows(bool a_fHide)
3434{
3435 if (a_fHide != m_fShowUnusedRows)
3436 {
3437 m_fShowUnusedRows = a_fHide;
3438 invalidateRowsFilter();
3439 }
3440}
3441
3442
3443void
3444VBoxDbgStatsSortFileProxyModel::notifyFilterChanges()
3445{
3446 invalidateRowsFilter();
3447}
3448
3449
3450#endif /* VBOXDBG_WITH_SORTED_AND_FILTERED_STATS */
3451
3452
3453
3454/*
3455 *
3456 * V B o x D b g S t a t s V i e w
3457 * V B o x D b g S t a t s V i e w
3458 * V B o x D b g S t a t s V i e w
3459 *
3460 *
3461 */
3462
3463
3464VBoxDbgStatsView::VBoxDbgStatsView(VBoxDbgGui *a_pDbgGui, VBoxDbgStatsModel *a_pVBoxModel,
3465 VBoxDbgStatsSortFileProxyModel *a_pProxyModel, VBoxDbgStats *a_pParent/* = NULL*/)
3466 : QTreeView(a_pParent)
3467 , VBoxDbgBase(a_pDbgGui)
3468 , m_pVBoxModel(a_pVBoxModel)
3469 , m_pProxyModel(a_pProxyModel)
3470 , m_pModel(NULL)
3471 , m_PatStr()
3472 , m_pParent(a_pParent)
3473 , m_pLeafMenu(NULL)
3474 , m_pBranchMenu(NULL)
3475 , m_pViewMenu(NULL)
3476 , m_pCurMenu(NULL)
3477 , m_CurIndex()
3478{
3479 /*
3480 * Set the model and view defaults.
3481 */
3482 setRootIsDecorated(true);
3483 if (a_pProxyModel)
3484 {
3485 m_pModel = a_pProxyModel;
3486 a_pProxyModel->setSourceModel(a_pVBoxModel);
3487 }
3488 else
3489 m_pModel = a_pVBoxModel;
3490 setModel(m_pModel);
3491 QModelIndex RootIdx = myGetRootIndex(); /* This should really be QModelIndex(), but Qt on darwin does wrong things then. */
3492 setRootIndex(RootIdx);
3493 setItemsExpandable(true);
3494 setAlternatingRowColors(true);
3495 setSelectionBehavior(SelectRows);
3496 setSelectionMode(SingleSelection);
3497 if (a_pProxyModel)
3498 setSortingEnabled(true);
3499
3500 /*
3501 * Create and setup the actions.
3502 */
3503 m_pExpandAct = new QAction("Expand Tree", this);
3504 m_pCollapseAct = new QAction("Collapse Tree", this);
3505 m_pRefreshAct = new QAction("&Refresh", this);
3506 m_pResetAct = new QAction("Rese&t", this);
3507 m_pCopyAct = new QAction("&Copy", this);
3508 m_pToLogAct = new QAction("To &Log", this);
3509 m_pToRelLogAct = new QAction("T&o Release Log", this);
3510 m_pAdjColumnsAct = new QAction("&Adjust Columns", this);
3511#ifdef VBOXDBG_WITH_SORTED_AND_FILTERED_STATS
3512 m_pFilterAct = new QAction("&Filter...", this);
3513#endif
3514
3515 m_pCopyAct->setShortcut(QKeySequence::Copy);
3516 m_pExpandAct->setShortcut(QKeySequence("Ctrl+E"));
3517 m_pCollapseAct->setShortcut(QKeySequence("Ctrl+D"));
3518 m_pRefreshAct->setShortcut(QKeySequence("Ctrl+R"));
3519 m_pResetAct->setShortcut(QKeySequence("Alt+R"));
3520 m_pToLogAct->setShortcut(QKeySequence("Ctrl+Z"));
3521 m_pToRelLogAct->setShortcut(QKeySequence("Alt+Z"));
3522 m_pAdjColumnsAct->setShortcut(QKeySequence("Ctrl+A"));
3523#ifdef VBOXDBG_WITH_SORTED_AND_FILTERED_STATS
3524 //m_pFilterAct->setShortcut(QKeySequence("Ctrl+?"));
3525#endif
3526
3527 addAction(m_pCopyAct);
3528 addAction(m_pExpandAct);
3529 addAction(m_pCollapseAct);
3530 addAction(m_pRefreshAct);
3531 addAction(m_pResetAct);
3532 addAction(m_pToLogAct);
3533 addAction(m_pToRelLogAct);
3534 addAction(m_pAdjColumnsAct);
3535#ifdef VBOXDBG_WITH_SORTED_AND_FILTERED_STATS
3536 addAction(m_pFilterAct);
3537#endif
3538
3539 connect(m_pExpandAct, SIGNAL(triggered(bool)), this, SLOT(actExpand()));
3540 connect(m_pCollapseAct, SIGNAL(triggered(bool)), this, SLOT(actCollapse()));
3541 connect(m_pRefreshAct, SIGNAL(triggered(bool)), this, SLOT(actRefresh()));
3542 connect(m_pResetAct, SIGNAL(triggered(bool)), this, SLOT(actReset()));
3543 connect(m_pCopyAct, SIGNAL(triggered(bool)), this, SLOT(actCopy()));
3544 connect(m_pToLogAct, SIGNAL(triggered(bool)), this, SLOT(actToLog()));
3545 connect(m_pToRelLogAct, SIGNAL(triggered(bool)), this, SLOT(actToRelLog()));
3546 connect(m_pAdjColumnsAct, SIGNAL(triggered(bool)), this, SLOT(actAdjColumns()));
3547#ifdef VBOXDBG_WITH_SORTED_AND_FILTERED_STATS
3548 connect(m_pFilterAct, SIGNAL(triggered(bool)), this, SLOT(actFilter()));
3549#endif
3550
3551
3552 /*
3553 * Create the menus and populate them.
3554 */
3555 setContextMenuPolicy(Qt::DefaultContextMenu);
3556
3557 m_pLeafMenu = new QMenu();
3558 m_pLeafMenu->addAction(m_pCopyAct);
3559 m_pLeafMenu->addAction(m_pRefreshAct);
3560 m_pLeafMenu->addAction(m_pResetAct);
3561 m_pLeafMenu->addAction(m_pToLogAct);
3562 m_pLeafMenu->addAction(m_pToRelLogAct);
3563
3564 m_pBranchMenu = new QMenu(this);
3565 m_pBranchMenu->addAction(m_pCopyAct);
3566 m_pBranchMenu->addAction(m_pRefreshAct);
3567 m_pBranchMenu->addAction(m_pResetAct);
3568 m_pBranchMenu->addAction(m_pToLogAct);
3569 m_pBranchMenu->addAction(m_pToRelLogAct);
3570 m_pBranchMenu->addSeparator();
3571 m_pBranchMenu->addAction(m_pExpandAct);
3572 m_pBranchMenu->addAction(m_pCollapseAct);
3573#ifdef VBOXDBG_WITH_SORTED_AND_FILTERED_STATS
3574 m_pBranchMenu->addSeparator();
3575 m_pBranchMenu->addAction(m_pFilterAct);
3576#endif
3577
3578 m_pViewMenu = new QMenu();
3579 m_pViewMenu->addAction(m_pCopyAct);
3580 m_pViewMenu->addAction(m_pRefreshAct);
3581 m_pViewMenu->addAction(m_pResetAct);
3582 m_pViewMenu->addAction(m_pToLogAct);
3583 m_pViewMenu->addAction(m_pToRelLogAct);
3584 m_pViewMenu->addSeparator();
3585 m_pViewMenu->addAction(m_pExpandAct);
3586 m_pViewMenu->addAction(m_pCollapseAct);
3587 m_pViewMenu->addSeparator();
3588 m_pViewMenu->addAction(m_pAdjColumnsAct);
3589#ifdef VBOXDBG_WITH_SORTED_AND_FILTERED_STATS
3590 m_pViewMenu->addAction(m_pFilterAct);
3591#endif
3592
3593 /* the header menu */
3594 QHeaderView *pHdrView = header();
3595 pHdrView->setContextMenuPolicy(Qt::CustomContextMenu);
3596 connect(pHdrView, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(headerContextMenuRequested(const QPoint &)));
3597}
3598
3599
3600VBoxDbgStatsView::~VBoxDbgStatsView()
3601{
3602 m_pParent = NULL;
3603 m_pCurMenu = NULL;
3604 m_CurIndex = QModelIndex();
3605
3606#define DELETE_IT(m) if (m) { delete m; m = NULL; } else do {} while (0)
3607 DELETE_IT(m_pProxyModel);
3608 DELETE_IT(m_pVBoxModel);
3609
3610 DELETE_IT(m_pLeafMenu);
3611 DELETE_IT(m_pBranchMenu);
3612 DELETE_IT(m_pViewMenu);
3613
3614 DELETE_IT(m_pExpandAct);
3615 DELETE_IT(m_pCollapseAct);
3616 DELETE_IT(m_pRefreshAct);
3617 DELETE_IT(m_pResetAct);
3618 DELETE_IT(m_pCopyAct);
3619 DELETE_IT(m_pToLogAct);
3620 DELETE_IT(m_pToRelLogAct);
3621 DELETE_IT(m_pAdjColumnsAct);
3622 DELETE_IT(m_pFilterAct);
3623#undef DELETE_IT
3624}
3625
3626
3627void
3628VBoxDbgStatsView::updateStats(const QString &rPatStr)
3629{
3630 m_PatStr = rPatStr;
3631 if (m_pVBoxModel->updateStatsByPattern(rPatStr))
3632 setRootIndex(myGetRootIndex()); /* hack */
3633}
3634
3635
3636void
3637VBoxDbgStatsView::setShowUnusedRows(bool a_fHide)
3638{
3639#ifdef VBOXDBG_WITH_SORTED_AND_FILTERED_STATS
3640 if (m_pProxyModel)
3641 m_pProxyModel->setShowUnusedRows(a_fHide);
3642#else
3643 RT_NOREF_PV(a_fHide);
3644#endif
3645}
3646
3647
3648void
3649VBoxDbgStatsView::resizeColumnsToContent()
3650{
3651 for (int i = 0; i <= 8; i++)
3652 {
3653 resizeColumnToContents(i);
3654 /* Some extra room for distinguishing numbers better in Value, Min, Avg, Max, Total, dInt columns. */
3655 if (i >= 2 && i <= 7)
3656 setColumnWidth(i, columnWidth(i) + 10);
3657 }
3658}
3659
3660
3661/*static*/ bool
3662VBoxDbgStatsView::expandMatchingCallback(PDBGGUISTATSNODE pNode, QModelIndex const &a_rIndex,
3663 const char *pszFullName, void *pvUser)
3664{
3665 VBoxDbgStatsView *pThis = (VBoxDbgStatsView *)pvUser;
3666
3667 QModelIndex ParentIndex; /* this isn't 100% optimal */
3668 if (pThis->m_pProxyModel)
3669 {
3670 QModelIndex const ProxyIndex = pThis->m_pProxyModel->mapFromSource(a_rIndex);
3671
3672 ParentIndex = pThis->m_pModel->parent(ProxyIndex);
3673 }
3674 else
3675 {
3676 pThis->setExpanded(a_rIndex, true);
3677
3678 ParentIndex = pThis->m_pModel->parent(a_rIndex);
3679 }
3680 while (ParentIndex.isValid() && !pThis->isExpanded(ParentIndex))
3681 {
3682 pThis->setExpanded(ParentIndex, true);
3683 ParentIndex = pThis->m_pModel->parent(ParentIndex);
3684 }
3685
3686 RT_NOREF(pNode, pszFullName);
3687 return true;
3688}
3689
3690
3691void
3692VBoxDbgStatsView::expandMatching(const QString &rPatStr)
3693{
3694 m_pVBoxModel->iterateStatsByPattern(rPatStr, expandMatchingCallback, this);
3695}
3696
3697
3698void
3699VBoxDbgStatsView::setSubTreeExpanded(QModelIndex const &a_rIndex, bool a_fExpanded)
3700{
3701 int cRows = m_pModel->rowCount(a_rIndex);
3702 if (a_rIndex.model())
3703 for (int i = 0; i < cRows; i++)
3704 setSubTreeExpanded(a_rIndex.model()->index(i, 0, a_rIndex), a_fExpanded);
3705 setExpanded(a_rIndex, a_fExpanded);
3706}
3707
3708
3709void
3710VBoxDbgStatsView::contextMenuEvent(QContextMenuEvent *a_pEvt)
3711{
3712 /*
3713 * Get the selected item.
3714 * If it's a mouse event select the item under the cursor (if any).
3715 */
3716 QModelIndex Idx;
3717 if (a_pEvt->reason() == QContextMenuEvent::Mouse)
3718 {
3719 Idx = indexAt(a_pEvt->pos());
3720 if (Idx.isValid())
3721 setCurrentIndex(Idx);
3722 }
3723 else
3724 {
3725 QModelIndexList SelIdx = selectedIndexes();
3726 if (!SelIdx.isEmpty())
3727 Idx = SelIdx.at(0);
3728 }
3729
3730 /*
3731 * Popup the corresponding menu.
3732 */
3733 QMenu *pMenu;
3734 if (!Idx.isValid())
3735 pMenu = m_pViewMenu;
3736 else if (m_pModel->hasChildren(Idx))
3737 pMenu = m_pBranchMenu;
3738 else
3739 pMenu = m_pLeafMenu;
3740 if (pMenu)
3741 {
3742 m_pRefreshAct->setEnabled(!Idx.isValid() || Idx == myGetRootIndex());
3743 m_CurIndex = Idx;
3744 m_pCurMenu = pMenu;
3745
3746 pMenu->exec(a_pEvt->globalPos());
3747
3748 m_pCurMenu = NULL;
3749 m_CurIndex = QModelIndex();
3750 if (m_pRefreshAct)
3751 m_pRefreshAct->setEnabled(true);
3752 }
3753 a_pEvt->accept();
3754}
3755
3756
3757QModelIndex
3758VBoxDbgStatsView::myGetRootIndex(void) const
3759{
3760 if (!m_pProxyModel)
3761 return m_pVBoxModel->getRootIndex();
3762 return m_pProxyModel->mapFromSource(m_pVBoxModel->getRootIndex());
3763}
3764
3765
3766void
3767VBoxDbgStatsView::headerContextMenuRequested(const QPoint &a_rPos)
3768{
3769 /*
3770 * Show the view menu.
3771 */
3772 if (m_pViewMenu)
3773 {
3774 m_pRefreshAct->setEnabled(true);
3775 m_CurIndex = myGetRootIndex();
3776 m_pCurMenu = m_pViewMenu;
3777
3778 m_pViewMenu->exec(header()->mapToGlobal(a_rPos));
3779
3780 m_pCurMenu = NULL;
3781 m_CurIndex = QModelIndex();
3782 if (m_pRefreshAct)
3783 m_pRefreshAct->setEnabled(true);
3784 }
3785}
3786
3787
3788void
3789VBoxDbgStatsView::actExpand()
3790{
3791 QModelIndex Idx = m_pCurMenu ? m_CurIndex : currentIndex();
3792 if (Idx.isValid())
3793 setSubTreeExpanded(Idx, true /* a_fExpanded */);
3794}
3795
3796
3797void
3798VBoxDbgStatsView::actCollapse()
3799{
3800 QModelIndex Idx = m_pCurMenu ? m_CurIndex : currentIndex();
3801 if (Idx.isValid())
3802 setSubTreeExpanded(Idx, false /* a_fExpanded */);
3803}
3804
3805
3806void
3807VBoxDbgStatsView::actRefresh()
3808{
3809 QModelIndex Idx = m_pCurMenu ? m_CurIndex : currentIndex();
3810 if (!Idx.isValid() || Idx == myGetRootIndex())
3811 {
3812 if (m_pVBoxModel->updateStatsByPattern(m_PatStr))
3813 setRootIndex(myGetRootIndex()); /* hack */
3814 }
3815 else
3816 {
3817 if (m_pProxyModel)
3818 Idx = m_pProxyModel->mapToSource(Idx);
3819 m_pVBoxModel->updateStatsByIndex(Idx);
3820 }
3821}
3822
3823
3824void
3825VBoxDbgStatsView::actReset()
3826{
3827 QModelIndex Idx = m_pCurMenu ? m_CurIndex : currentIndex();
3828 if (!Idx.isValid() || Idx == myGetRootIndex())
3829 m_pVBoxModel->resetStatsByPattern(m_PatStr);
3830 else
3831 {
3832 if (m_pProxyModel)
3833 Idx = m_pProxyModel->mapToSource(Idx);
3834 m_pVBoxModel->resetStatsByIndex(Idx);
3835 }
3836}
3837
3838
3839void
3840VBoxDbgStatsView::actCopy()
3841{
3842 QModelIndex Idx = m_pCurMenu ? m_CurIndex : currentIndex();
3843 if (m_pProxyModel)
3844 Idx = m_pProxyModel->mapToSource(Idx);
3845 m_pVBoxModel->copyTreeToClipboard(Idx);
3846}
3847
3848
3849void
3850VBoxDbgStatsView::actToLog()
3851{
3852 QModelIndex Idx = m_pCurMenu ? m_CurIndex : currentIndex();
3853 if (m_pProxyModel)
3854 Idx = m_pProxyModel->mapToSource(Idx);
3855 m_pVBoxModel->logTree(Idx, false /* a_fReleaseLog */);
3856}
3857
3858
3859void
3860VBoxDbgStatsView::actToRelLog()
3861{
3862 QModelIndex Idx = m_pCurMenu ? m_CurIndex : currentIndex();
3863 if (m_pProxyModel)
3864 Idx = m_pProxyModel->mapToSource(Idx);
3865 m_pVBoxModel->logTree(Idx, true /* a_fReleaseLog */);
3866}
3867
3868
3869void
3870VBoxDbgStatsView::actAdjColumns()
3871{
3872 resizeColumnsToContent();
3873}
3874
3875
3876void
3877VBoxDbgStatsView::actFilter()
3878{
3879#ifdef VBOXDBG_WITH_SORTED_AND_FILTERED_STATS
3880 /*
3881 * Get the node it applies to.
3882 */
3883 QModelIndex Idx = m_pCurMenu ? m_CurIndex : currentIndex();
3884 if (Idx.isValid())
3885 Idx = myGetRootIndex();
3886 Idx = m_pProxyModel->mapToSource(Idx);
3887 PDBGGUISTATSNODE pNode = m_pVBoxModel->nodeFromIndex(Idx);
3888 if (pNode)
3889 {
3890 /*
3891 * Display dialog (modal).
3892 */
3893 VBoxDbgStatsFilterDialog Dialog(this, pNode);
3894 if (Dialog.exec() == QDialog::Accepted)
3895 {
3896 /** @todo it is possible that pNode is invalid now! */
3897 VBoxGuiStatsFilterData * const pOldFilter = pNode->pFilter;
3898 pNode->pFilter = Dialog.dupFilterData();
3899 if (pOldFilter)
3900 delete pOldFilter;
3901 m_pProxyModel->notifyFilterChanges();
3902 }
3903 }
3904#endif
3905}
3906
3907
3908
3909
3910
3911
3912/*
3913 *
3914 * V B o x D b g S t a t s F i l t e r D i a l o g
3915 * V B o x D b g S t a t s F i l t e r D i a l o g
3916 * V B o x D b g S t a t s F i l t e r D i a l o g
3917 *
3918 *
3919 */
3920
3921/* static */ QRegularExpression const VBoxDbgStatsFilterDialog::s_UInt64ValidatorRegExp("^([0-9]*|0[Xx][0-9a-fA-F]*)$");
3922
3923
3924/*static*/ QLineEdit *
3925VBoxDbgStatsFilterDialog::createUInt64LineEdit(uint64_t uValue)
3926{
3927 QLineEdit *pRet = new QLineEdit;
3928 if (uValue == 0 || uValue == UINT64_MAX)
3929 pRet->setText("");
3930 else
3931 pRet->setText(QString().number(uValue));
3932 pRet->setValidator(new QRegularExpressionValidator(s_UInt64ValidatorRegExp));
3933 return pRet;
3934}
3935
3936
3937VBoxDbgStatsFilterDialog::VBoxDbgStatsFilterDialog(QWidget *a_pParent, PCDBGGUISTATSNODE a_pNode)
3938 : QDialog(a_pParent)
3939{
3940 /* Set the window title. */
3941 static char s_szTitlePfx[] = "Filtering - ";
3942 char szTitle[1024 + 128];
3943 memcpy(szTitle, s_szTitlePfx, sizeof(s_szTitlePfx));
3944 VBoxDbgStatsModel::getNodePath(a_pNode, &szTitle[sizeof(s_szTitlePfx) - 1], sizeof(szTitle) - sizeof(s_szTitlePfx));
3945 setWindowTitle(szTitle);
3946
3947
3948 /* Copy the old data if any. */
3949 VBoxGuiStatsFilterData const * const pOldFilter = a_pNode->pFilter;
3950 if (pOldFilter)
3951 {
3952 m_Data.uMinValue = pOldFilter->uMinValue;
3953 m_Data.uMaxValue = pOldFilter->uMaxValue;
3954 if (pOldFilter->pRegexName)
3955 m_Data.pRegexName = new QRegularExpression(*pOldFilter->pRegexName);
3956 }
3957
3958 /* Configure the dialog... */
3959 QVBoxLayout *pMainLayout = new QVBoxLayout(this);
3960
3961 /* The value / average range: */
3962 QGroupBox *pValueAvgGrpBox = new QGroupBox("Value / Average");
3963 QGridLayout *pValAvgLayout = new QGridLayout;
3964 QLabel *pLabel = new QLabel("Min");
3965 m_pValueAvgMin = createUInt64LineEdit(m_Data.uMinValue);
3966 pLabel->setBuddy(m_pValueAvgMin);
3967 pValAvgLayout->addWidget(pLabel, 0, 0);
3968 pValAvgLayout->addWidget(m_pValueAvgMin, 0, 1);
3969
3970 pLabel = new QLabel("Max");
3971 m_pValueAvgMax = createUInt64LineEdit(m_Data.uMaxValue);
3972 pLabel->setBuddy(m_pValueAvgMax);
3973 pValAvgLayout->addWidget(pLabel, 1, 0);
3974 pValAvgLayout->addWidget(m_pValueAvgMax, 1, 1);
3975
3976 pValueAvgGrpBox->setLayout(pValAvgLayout);
3977 pMainLayout->addWidget(pValueAvgGrpBox);
3978
3979 /* The name filter. */
3980 QGroupBox *pNameGrpBox = new QGroupBox("Name RegExp");
3981 QHBoxLayout *pNameLayout = new QHBoxLayout();
3982 m_pNameRegExp = new QLineEdit;
3983 if (m_Data.pRegexName)
3984 m_pNameRegExp->setText(m_Data.pRegexName->pattern());
3985 else
3986 m_pNameRegExp->setText("");
3987 m_pNameRegExp->setToolTip("Regular expression matching basenames (no parent) to show.");
3988 pNameLayout->addWidget(m_pNameRegExp);
3989 pNameGrpBox->setLayout(pNameLayout);
3990 pMainLayout->addWidget(pNameGrpBox);
3991
3992 /* Buttons. */
3993 QDialogButtonBox *pButtonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this);
3994 pButtonBox->button(QDialogButtonBox::Ok)->setDefault(true);
3995 connect(pButtonBox, &QDialogButtonBox::rejected, this, &VBoxDbgStatsFilterDialog::reject);
3996 connect(pButtonBox, &QDialogButtonBox::accepted, this, &VBoxDbgStatsFilterDialog::validateAndAccept);
3997 pMainLayout->addWidget(pButtonBox);
3998}
3999
4000
4001VBoxDbgStatsFilterDialog::~VBoxDbgStatsFilterDialog()
4002{
4003 /* anything? */
4004}
4005
4006
4007VBoxGuiStatsFilterData *
4008VBoxDbgStatsFilterDialog::dupFilterData(void) const
4009{
4010 if (m_Data.isAllDefaults())
4011 return NULL;
4012 VBoxGuiStatsFilterData *pRet = new VBoxGuiStatsFilterData();
4013 pRet->uMinValue = m_Data.uMinValue;
4014 pRet->uMaxValue = m_Data.uMaxValue;
4015 if (m_Data.pRegexName)
4016 pRet->pRegexName = new QRegularExpression(*m_Data.pRegexName);
4017 return pRet;
4018}
4019
4020
4021uint64_t
4022VBoxDbgStatsFilterDialog::validateUInt64Field(QLineEdit const *a_pField, uint64_t a_uDefault,
4023 const char *a_pszField, QStringList *a_pLstErrors)
4024{
4025 QString Str = a_pField->text().trimmed();
4026 if (!Str.isEmpty())
4027 {
4028 QByteArray const StrAsUtf8 = Str.toUtf8();
4029 const char * const pszString = StrAsUtf8.constData();
4030 uint64_t uValue = a_uDefault;
4031 int vrc = RTStrToUInt64Full(pszString, 0, &uValue);
4032 if (vrc == VINF_SUCCESS)
4033 return uValue;
4034 char szMsg[128];
4035 RTStrPrintf(szMsg, sizeof(szMsg), "Invalid %s value: %Rrc - ", a_pszField, vrc);
4036 a_pLstErrors->append(QString(szMsg) + Str);
4037 }
4038
4039 return a_uDefault;
4040}
4041
4042
4043void
4044VBoxDbgStatsFilterDialog::validateAndAccept()
4045{
4046 QStringList LstErrors;
4047
4048 /* The numeric fields. */
4049 m_Data.uMinValue = validateUInt64Field(m_pValueAvgMin, 0, "minimum value/avg", &LstErrors);
4050 m_Data.uMaxValue = validateUInt64Field(m_pValueAvgMax, UINT64_MAX, "maximum value/avg", &LstErrors);
4051
4052 /* The name regexp. */
4053 QString Str = m_pNameRegExp->text().trimmed();
4054 if (!Str.isEmpty())
4055 {
4056 if (!m_Data.pRegexName)
4057 m_Data.pRegexName = new QRegularExpression();
4058 m_Data.pRegexName->setPattern(Str);
4059 if (!m_Data.pRegexName->isValid())
4060 LstErrors.append("Invalid regular expression");
4061 }
4062 else if (m_Data.pRegexName)
4063 {
4064 delete m_Data.pRegexName;
4065 m_Data.pRegexName = NULL;
4066 }
4067
4068 /* Dismiss the dialog if everything is fine, otherwise complain and keep it open. */
4069 if (LstErrors.isEmpty())
4070 emit accept();
4071 else
4072 {
4073 QMessageBox MsgBox(QMessageBox::Critical, "Invalid input", LstErrors.join("\n"), QMessageBox::Ok);
4074 MsgBox.exec();
4075 }
4076}
4077
4078
4079
4080
4081
4082/*
4083 *
4084 * V B o x D b g S t a t s
4085 * V B o x D b g S t a t s
4086 * V B o x D b g S t a t s
4087 *
4088 *
4089 */
4090
4091
4092VBoxDbgStats::VBoxDbgStats(VBoxDbgGui *a_pDbgGui, const char *pszFilter /*= NULL*/, const char *pszExpand /*= NULL*/,
4093 const char *pszAdvFilter /*= NULL*/, unsigned uRefreshRate/* = 0*/, QWidget *pParent/* = NULL*/)
4094 : VBoxDbgBaseWindow(a_pDbgGui, pParent, "Statistics")
4095 , m_PatStr(pszFilter), m_pPatCB(NULL), m_uRefreshRate(0), m_pTimer(NULL), m_pView(NULL)
4096{
4097 /* Delete dialog on close: */
4098 setAttribute(Qt::WA_DeleteOnClose);
4099
4100 /*
4101 * On top, a horizontal box with the pattern field, buttons and refresh interval.
4102 */
4103 QHBoxLayout *pHLayout = new QHBoxLayout;
4104
4105 QLabel *pLabel = new QLabel(" Pattern ");
4106 pHLayout->addWidget(pLabel);
4107 pLabel->setMaximumSize(pLabel->sizeHint());
4108 pLabel->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
4109
4110 m_pPatCB = new QComboBox();
4111 pHLayout->addWidget(m_pPatCB);
4112 if (!m_PatStr.isEmpty())
4113 m_pPatCB->addItem(m_PatStr);
4114 m_pPatCB->setDuplicatesEnabled(false);
4115 m_pPatCB->setEditable(true);
4116 m_pPatCB->setCompleter(0);
4117 connect(m_pPatCB, SIGNAL(textActivated(const QString &)), this, SLOT(apply(const QString &)));
4118
4119 QPushButton *pPB = new QPushButton("&All");
4120 pHLayout->addWidget(pPB);
4121 pPB->setMaximumSize(pPB->sizeHint());
4122 connect(pPB, SIGNAL(clicked()), this, SLOT(applyAll()));
4123
4124 pLabel = new QLabel(" Interval ");
4125 pHLayout->addWidget(pLabel);
4126 pLabel->setMaximumSize(pLabel->sizeHint());
4127 pLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
4128
4129 QSpinBox *pSB = new QSpinBox();
4130 pHLayout->addWidget(pSB);
4131 pSB->setMinimum(0);
4132 pSB->setMaximum(60);
4133 pSB->setSingleStep(1);
4134 pSB->setValue(uRefreshRate);
4135 pSB->setSuffix(" s");
4136 pSB->setWrapping(false);
4137 pSB->setButtonSymbols(QSpinBox::PlusMinus);
4138 pSB->setMaximumSize(pSB->sizeHint());
4139 connect(pSB, SIGNAL(valueChanged(int)), this, SLOT(setRefresh(int)));
4140
4141#ifdef VBOXDBG_WITH_SORTED_AND_FILTERED_STATS
4142 QCheckBox *pCheckBox = new QCheckBox("Show unused");
4143 pHLayout->addWidget(pCheckBox);
4144 pCheckBox->setMaximumSize(pCheckBox->sizeHint());
4145 connect(pCheckBox, SIGNAL(stateChanged(int)), this, SLOT(sltShowUnusedRowsChanged(int)));
4146#endif
4147
4148 /*
4149 * Create the tree view and setup the layout.
4150 */
4151 VBoxDbgStatsModelVM *pModel = new VBoxDbgStatsModelVM(a_pDbgGui, m_PatStr, NULL, a_pDbgGui->getVMMFunctionTable());
4152#ifdef VBOXDBG_WITH_SORTED_AND_FILTERED_STATS
4153 VBoxDbgStatsSortFileProxyModel *pProxyModel = new VBoxDbgStatsSortFileProxyModel(this);
4154 m_pView = new VBoxDbgStatsView(a_pDbgGui, pModel, pProxyModel, this);
4155 pCheckBox->setCheckState(pProxyModel->isShowingUnusedRows() ? Qt::Checked : Qt::Unchecked)
4156#else
4157 m_pView = new VBoxDbgStatsView(a_pDbgGui, pModel, NULL, this);
4158#endif
4159
4160 QWidget *pHBox = new QWidget;
4161 pHBox->setLayout(pHLayout);
4162
4163 QVBoxLayout *pVLayout = new QVBoxLayout;
4164 pVLayout->addWidget(pHBox);
4165 pVLayout->addWidget(m_pView);
4166 setLayout(pVLayout);
4167
4168 /*
4169 * Resize the columns.
4170 * Seems this has to be done with all nodes expanded.
4171 */
4172 m_pView->expandAll();
4173 m_pView->resizeColumnsToContent();
4174 m_pView->collapseAll();
4175
4176 if (pszExpand && *pszExpand)
4177 m_pView->expandMatching(QString(pszExpand));
4178
4179 /*
4180 * Create a refresh timer and start it.
4181 */
4182 m_pTimer = new QTimer(this);
4183 connect(m_pTimer, SIGNAL(timeout()), this, SLOT(refresh()));
4184 setRefresh(uRefreshRate);
4185
4186 /*
4187 * And some shortcuts.
4188 */
4189 m_pFocusToPat = new QAction("", this);
4190 m_pFocusToPat->setShortcut(QKeySequence("Ctrl+L"));
4191 addAction(m_pFocusToPat);
4192 connect(m_pFocusToPat, SIGNAL(triggered(bool)), this, SLOT(actFocusToPat()));
4193}
4194
4195
4196VBoxDbgStats::~VBoxDbgStats()
4197{
4198 if (m_pTimer)
4199 {
4200 delete m_pTimer;
4201 m_pTimer = NULL;
4202 }
4203
4204 if (m_pPatCB)
4205 {
4206 delete m_pPatCB;
4207 m_pPatCB = NULL;
4208 }
4209
4210 if (m_pView)
4211 {
4212 delete m_pView;
4213 m_pView = NULL;
4214 }
4215}
4216
4217
4218void
4219VBoxDbgStats::closeEvent(QCloseEvent *a_pCloseEvt)
4220{
4221 a_pCloseEvt->accept();
4222}
4223
4224
4225void
4226VBoxDbgStats::apply(const QString &Str)
4227{
4228 m_PatStr = Str;
4229 refresh();
4230}
4231
4232
4233void
4234VBoxDbgStats::applyAll()
4235{
4236 apply("");
4237}
4238
4239
4240
4241void
4242VBoxDbgStats::refresh()
4243{
4244 m_pView->updateStats(m_PatStr);
4245}
4246
4247
4248void
4249VBoxDbgStats::setRefresh(int iRefresh)
4250{
4251 if ((unsigned)iRefresh != m_uRefreshRate)
4252 {
4253 if (!m_uRefreshRate || iRefresh)
4254 m_pTimer->start(iRefresh * 1000);
4255 else
4256 m_pTimer->stop();
4257 m_uRefreshRate = iRefresh;
4258 }
4259}
4260
4261
4262void
4263VBoxDbgStats::actFocusToPat()
4264{
4265 if (!m_pPatCB->hasFocus())
4266 m_pPatCB->setFocus(Qt::ShortcutFocusReason);
4267}
4268
4269
4270void
4271VBoxDbgStats::sltShowUnusedRowsChanged(int a_iState)
4272{
4273#ifdef VBOXDBG_WITH_SORTED_AND_FILTERED_STATS
4274 m_pView->setShowUnusedRows(a_iState != Qt::Unchecked);
4275#else
4276 RT_NOREF_PV(a_iState);
4277#endif
4278}
4279
Note: See TracBrowser for help on using the repository browser.

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