VirtualBox

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

Last change on this file was 105422, checked in by vboxsync, 5 weeks ago

VBoxDbg: Use a fixed font for the values in the statistics viewer to make the numbers line up better.

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

© 2024 Oracle
ContactPrivacy/Do Not Sell My InfoTerms of Use