VirtualBox

source: vbox/trunk/src/VBox/VMM/VMMR3/STAM.cpp@ 102077

Last change on this file since 102077 was 102077, checked in by vboxsync, 14 months ago

VMM/IEM,STAM: Native translation of IEM_MC_REF_EFLAGS, IEM_MC_REF_GREG_U16, IEM_MC_CALL_VOID_AIMPL_X and IEM_MC_CALL_AIMPL_X. Added STAMUNIT_BYTES_PER_TB. bugref:10371

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 118.2 KB
Line 
1/* $Id: STAM.cpp 102077 2023-11-13 11:52:34Z vboxsync $ */
2/** @file
3 * STAM - The Statistics Manager.
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/** @page pg_stam STAM - The Statistics Manager
29 *
30 * The purpose for the statistics manager is to present the rest of the system
31 * with a somewhat uniform way of accessing VMM statistics. STAM sports a
32 * couple of different APIs for accessing them: STAMR3EnumU, STAMR3SnapshotU,
33 * STAMR3DumpU, STAMR3DumpToReleaseLogU and the debugger. Main is exposing the
34 * XML based one, STAMR3SnapshotU.
35 *
36 * The rest of the VMM together with the devices and drivers registers their
37 * statistics with STAM giving them a name. The name is hierarchical, the
38 * components separated by slashes ('/') and must start with a slash.
39 *
40 * Each item registered with STAM - also, half incorrectly, called a sample -
41 * has a type, unit, visibility, data pointer and description associated with it
42 * in addition to the name (described above). The type tells STAM what kind of
43 * structure the pointer is pointing to. The visibility allows unused
44 * statistics from cluttering the output or showing up in the GUI. All the bits
45 * together makes STAM able to present the items in a sensible way to the user.
46 * Some types also allows STAM to reset the data, which is very convenient when
47 * digging into specific operations and such.
48 *
49 * PS. The VirtualBox Debugger GUI has a viewer for inspecting the statistics
50 * STAM provides. You will also find statistics in the release and debug logs.
51 * And as mentioned in the introduction, the debugger console features a couple
52 * of command: .stats and .statsreset.
53 *
54 * @see grp_stam
55 */
56
57
58/*********************************************************************************************************************************
59* Header Files *
60*********************************************************************************************************************************/
61#define LOG_GROUP LOG_GROUP_STAM
62#include <VBox/vmm/stam.h>
63#include "STAMInternal.h"
64#include <VBox/vmm/vmcc.h>
65
66#include <VBox/err.h>
67#include <VBox/dbg.h>
68#include <VBox/log.h>
69
70#include <iprt/assert.h>
71#include <iprt/asm.h>
72#include <iprt/mem.h>
73#include <iprt/stream.h>
74#include <iprt/string.h>
75
76
77/*********************************************************************************************************************************
78* Defined Constants And Macros *
79*********************************************************************************************************************************/
80/** The maximum name length excluding the terminator. */
81#define STAM_MAX_NAME_LEN 239
82
83
84/*********************************************************************************************************************************
85* Structures and Typedefs *
86*********************************************************************************************************************************/
87/**
88 * Argument structure for stamR3PrintOne().
89 */
90typedef struct STAMR3PRINTONEARGS
91{
92 PUVM pUVM;
93 void *pvArg;
94 DECLCALLBACKMEMBER(void, pfnPrintf,(struct STAMR3PRINTONEARGS *pvArg, const char *pszFormat, ...));
95} STAMR3PRINTONEARGS, *PSTAMR3PRINTONEARGS;
96
97
98/**
99 * Argument structure to stamR3EnumOne().
100 */
101typedef struct STAMR3ENUMONEARGS
102{
103 PVM pVM;
104 PFNSTAMR3ENUM pfnEnum;
105 void *pvUser;
106} STAMR3ENUMONEARGS, *PSTAMR3ENUMONEARGS;
107
108
109/**
110 * The snapshot status structure.
111 * Argument package passed to stamR3SnapshotOne, stamR3SnapshotPrintf and stamR3SnapshotOutput.
112 */
113typedef struct STAMR3SNAPSHOTONE
114{
115 /** Pointer to the buffer start. */
116 char *pszStart;
117 /** Pointer to the buffer end. */
118 char *pszEnd;
119 /** Pointer to the current buffer position. */
120 char *psz;
121 /** Pointer to the VM. */
122 PVM pVM;
123 /** The number of bytes allocated. */
124 size_t cbAllocated;
125 /** The status code. */
126 int rc;
127 /** Whether to include the description strings. */
128 bool fWithDesc;
129} STAMR3SNAPSHOTONE, *PSTAMR3SNAPSHOTONE;
130
131
132/**
133 * Init record for a ring-0 statistic sample.
134 */
135typedef struct STAMR0SAMPLE
136{
137 /** The GVMMSTATS structure offset of the variable. */
138 unsigned offVar;
139 /** The type. */
140 STAMTYPE enmType;
141 /** The unit. */
142 STAMUNIT enmUnit;
143 /** The name. */
144 const char *pszName;
145 /** The description. */
146 const char *pszDesc;
147} STAMR0SAMPLE;
148
149
150/*********************************************************************************************************************************
151* Internal Functions *
152*********************************************************************************************************************************/
153static void stamR3LookupDestroyTree(PSTAMLOOKUP pRoot);
154static int stamR3RegisterU(PUVM pUVM, void *pvSample, PFNSTAMR3CALLBACKRESET pfnReset,
155 PFNSTAMR3CALLBACKPRINT pfnPrint, STAMTYPE enmType, STAMVISIBILITY enmVisibility,
156 const char *pszName, STAMUNIT enmUnit, const char *pszDesc, uint8_t iRefreshGrp);
157static int stamR3ResetOne(PSTAMDESC pDesc, void *pvArg);
158static DECLCALLBACK(void) stamR3EnumLogPrintf(PSTAMR3PRINTONEARGS pvArg, const char *pszFormat, ...);
159static DECLCALLBACK(void) stamR3EnumRelLogPrintf(PSTAMR3PRINTONEARGS pvArg, const char *pszFormat, ...);
160static DECLCALLBACK(void) stamR3EnumPrintf(PSTAMR3PRINTONEARGS pvArg, const char *pszFormat, ...);
161static int stamR3SnapshotOne(PSTAMDESC pDesc, void *pvArg);
162static int stamR3SnapshotPrintf(PSTAMR3SNAPSHOTONE pThis, const char *pszFormat, ...);
163static int stamR3PrintOne(PSTAMDESC pDesc, void *pvArg);
164static int stamR3EnumOne(PSTAMDESC pDesc, void *pvArg);
165static bool stamR3MultiMatch(const char * const *papszExpressions, unsigned cExpressions, unsigned *piExpression, const char *pszName);
166static char ** stamR3SplitPattern(const char *pszPat, unsigned *pcExpressions, char **ppszCopy);
167static int stamR3EnumU(PUVM pUVM, const char *pszPat, bool fUpdateRing0, int (pfnCallback)(PSTAMDESC pDesc, void *pvArg), void *pvArg);
168static void stamR3Ring0StatsRegisterU(PUVM pUVM);
169
170#ifdef VBOX_WITH_DEBUGGER
171static FNDBGCCMD stamR3CmdStats;
172static DECLCALLBACK(void) stamR3EnumDbgfPrintf(PSTAMR3PRINTONEARGS pArgs, const char *pszFormat, ...);
173static FNDBGCCMD stamR3CmdStatsReset;
174#endif
175
176
177/*********************************************************************************************************************************
178* Global Variables *
179*********************************************************************************************************************************/
180#ifdef VBOX_WITH_DEBUGGER
181/** Pattern argument. */
182static const DBGCVARDESC g_aArgPat[] =
183{
184 /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
185 { 0, 1, DBGCVAR_CAT_STRING, 0, "pattern", "Which samples the command shall be applied to. Use '*' as wildcard. Use ';' to separate expression." }
186};
187
188/** Command descriptors. */
189static const DBGCCMD g_aCmds[] =
190{
191 /* pszCmd, cArgsMin, cArgsMax, paArgDesc, cArgDescs, fFlags, pfnHandler pszSyntax, ....pszDescription */
192 { "stats", 0, 1, &g_aArgPat[0], RT_ELEMENTS(g_aArgPat), 0, stamR3CmdStats, "[pattern]", "Display statistics." },
193 { "statsreset", 0, 1, &g_aArgPat[0], RT_ELEMENTS(g_aArgPat), 0, stamR3CmdStatsReset,"[pattern]", "Resets statistics." }
194};
195#endif
196
197
198/**
199 * The GVMM mapping records - sans the host cpus.
200 */
201static const STAMR0SAMPLE g_aGVMMStats[] =
202{
203 { RT_UOFFSETOF(GVMMSTATS, SchedVM.cHaltCalls), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/VM/HaltCalls", "The number of calls to GVMMR0SchedHalt." },
204 { RT_UOFFSETOF(GVMMSTATS, SchedVM.cHaltBlocking), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/VM/HaltBlocking", "The number of times we did go to sleep in GVMMR0SchedHalt." },
205 { RT_UOFFSETOF(GVMMSTATS, SchedVM.cHaltTimeouts), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/VM/HaltTimeouts", "The number of times we timed out in GVMMR0SchedHalt." },
206 { RT_UOFFSETOF(GVMMSTATS, SchedVM.cHaltNotBlocking), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/VM/HaltNotBlocking", "The number of times we didn't go to sleep in GVMMR0SchedHalt." },
207 { RT_UOFFSETOF(GVMMSTATS, SchedVM.cHaltWakeUps), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/VM/HaltWakeUps", "The number of wake ups done during GVMMR0SchedHalt." },
208 { RT_UOFFSETOF(GVMMSTATS, SchedVM.cWakeUpCalls), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/VM/WakeUpCalls", "The number of calls to GVMMR0WakeUp." },
209 { RT_UOFFSETOF(GVMMSTATS, SchedVM.cWakeUpNotHalted), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/VM/WakeUpNotHalted", "The number of times the EMT thread wasn't actually halted when GVMMR0WakeUp was called." },
210 { RT_UOFFSETOF(GVMMSTATS, SchedVM.cWakeUpWakeUps), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/VM/WakeUpWakeUps", "The number of wake ups done during GVMMR0WakeUp (not counting the explicit one)." },
211 { RT_UOFFSETOF(GVMMSTATS, SchedVM.cPokeCalls), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/VM/PokeCalls", "The number of calls to GVMMR0Poke." },
212 { RT_UOFFSETOF(GVMMSTATS, SchedVM.cPokeNotBusy), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/VM/PokeNotBusy", "The number of times the EMT thread wasn't actually busy when GVMMR0Poke was called." },
213 { RT_UOFFSETOF(GVMMSTATS, SchedVM.cPollCalls), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/VM/PollCalls", "The number of calls to GVMMR0SchedPoll." },
214 { RT_UOFFSETOF(GVMMSTATS, SchedVM.cPollHalts), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/VM/PollHalts", "The number of times the EMT has halted in a GVMMR0SchedPoll call." },
215 { RT_UOFFSETOF(GVMMSTATS, SchedVM.cPollWakeUps), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/VM/PollWakeUps", "The number of wake ups done during GVMMR0SchedPoll." },
216
217 { RT_UOFFSETOF(GVMMSTATS, SchedSum.cHaltCalls), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/Sum/HaltCalls", "The number of calls to GVMMR0SchedHalt." },
218 { RT_UOFFSETOF(GVMMSTATS, SchedSum.cHaltBlocking), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/Sum/HaltBlocking", "The number of times we did go to sleep in GVMMR0SchedHalt." },
219 { RT_UOFFSETOF(GVMMSTATS, SchedSum.cHaltTimeouts), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/Sum/HaltTimeouts", "The number of times we timed out in GVMMR0SchedHalt." },
220 { RT_UOFFSETOF(GVMMSTATS, SchedSum.cHaltNotBlocking), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/Sum/HaltNotBlocking", "The number of times we didn't go to sleep in GVMMR0SchedHalt." },
221 { RT_UOFFSETOF(GVMMSTATS, SchedSum.cHaltWakeUps), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/Sum/HaltWakeUps", "The number of wake ups done during GVMMR0SchedHalt." },
222 { RT_UOFFSETOF(GVMMSTATS, SchedSum.cWakeUpCalls), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/Sum/WakeUpCalls", "The number of calls to GVMMR0WakeUp." },
223 { RT_UOFFSETOF(GVMMSTATS, SchedSum.cWakeUpNotHalted), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/Sum/WakeUpNotHalted", "The number of times the EMT thread wasn't actually halted when GVMMR0WakeUp was called." },
224 { RT_UOFFSETOF(GVMMSTATS, SchedSum.cWakeUpWakeUps), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/Sum/WakeUpWakeUps", "The number of wake ups done during GVMMR0WakeUp (not counting the explicit one)." },
225 { RT_UOFFSETOF(GVMMSTATS, SchedSum.cPokeCalls), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/Sum/PokeCalls", "The number of calls to GVMMR0Poke." },
226 { RT_UOFFSETOF(GVMMSTATS, SchedSum.cPokeNotBusy), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/Sum/PokeNotBusy", "The number of times the EMT thread wasn't actually busy when GVMMR0Poke was called." },
227 { RT_UOFFSETOF(GVMMSTATS, SchedSum.cPollCalls), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/Sum/PollCalls", "The number of calls to GVMMR0SchedPoll." },
228 { RT_UOFFSETOF(GVMMSTATS, SchedSum.cPollHalts), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/Sum/PollHalts", "The number of times the EMT has halted in a GVMMR0SchedPoll call." },
229 { RT_UOFFSETOF(GVMMSTATS, SchedSum.cPollWakeUps), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/Sum/PollWakeUps", "The number of wake ups done during GVMMR0SchedPoll." },
230
231 { RT_UOFFSETOF(GVMMSTATS, cVMs), STAMTYPE_U32, STAMUNIT_COUNT, "/GVMM/VMs", "The number of VMs accessible to the caller." },
232 { RT_UOFFSETOF(GVMMSTATS, cEMTs), STAMTYPE_U32, STAMUNIT_COUNT, "/GVMM/EMTs", "The number of emulation threads." },
233 { RT_UOFFSETOF(GVMMSTATS, cHostCpus), STAMTYPE_U32, STAMUNIT_COUNT, "/GVMM/HostCPUs", "The number of host CPUs." },
234};
235
236
237/**
238 * The GMM mapping records.
239 */
240static const STAMR0SAMPLE g_aGMMStats[] =
241{
242 { RT_UOFFSETOF(GMMSTATS, cMaxPages), STAMTYPE_U64, STAMUNIT_PAGES, "/GMM/cMaxPages", "The maximum number of pages GMM is allowed to allocate." },
243 { RT_UOFFSETOF(GMMSTATS, cReservedPages), STAMTYPE_U64, STAMUNIT_PAGES, "/GMM/cReservedPages", "The number of pages that has been reserved." },
244 { RT_UOFFSETOF(GMMSTATS, cOverCommittedPages), STAMTYPE_U64, STAMUNIT_PAGES, "/GMM/cOverCommittedPages", "The number of pages that we have over-committed in reservations." },
245 { RT_UOFFSETOF(GMMSTATS, cAllocatedPages), STAMTYPE_U64, STAMUNIT_PAGES, "/GMM/cAllocatedPages", "The number of actually allocated (committed if you like) pages." },
246 { RT_UOFFSETOF(GMMSTATS, cSharedPages), STAMTYPE_U64, STAMUNIT_PAGES, "/GMM/cSharedPages", "The number of pages that are shared. A subset of cAllocatedPages." },
247 { RT_UOFFSETOF(GMMSTATS, cDuplicatePages), STAMTYPE_U64, STAMUNIT_PAGES, "/GMM/cDuplicatePages", "The number of pages that are actually shared between VMs." },
248 { RT_UOFFSETOF(GMMSTATS, cLeftBehindSharedPages), STAMTYPE_U64, STAMUNIT_PAGES, "/GMM/cLeftBehindSharedPages", "The number of pages that are shared that has been left behind by VMs not doing proper cleanups." },
249 { RT_UOFFSETOF(GMMSTATS, cBalloonedPages), STAMTYPE_U64, STAMUNIT_PAGES, "/GMM/cBalloonedPages", "The number of current ballooned pages." },
250 { RT_UOFFSETOF(GMMSTATS, cChunks), STAMTYPE_U32, STAMUNIT_COUNT, "/GMM/cChunks", "The number of allocation chunks." },
251 { RT_UOFFSETOF(GMMSTATS, cFreedChunks), STAMTYPE_U32, STAMUNIT_COUNT, "/GMM/cFreedChunks", "The number of freed chunks ever." },
252 { RT_UOFFSETOF(GMMSTATS, cShareableModules), STAMTYPE_U32, STAMUNIT_COUNT, "/GMM/cShareableModules", "The number of shareable modules." },
253 { RT_UOFFSETOF(GMMSTATS, idFreeGeneration), STAMTYPE_U64, STAMUNIT_NONE, "/GMM/idFreeGeneration", "The current chunk freeing generation number (for per-VM chunk lookup TLB versioning)." },
254 { RT_UOFFSETOF(GMMSTATS, VMStats.Reserved.cBasePages), STAMTYPE_U64, STAMUNIT_PAGES, "/GMM/VM/Reserved/cBasePages", "The amount of base memory (RAM, ROM, ++) reserved by the VM." },
255 { RT_UOFFSETOF(GMMSTATS, VMStats.Reserved.cShadowPages), STAMTYPE_U32, STAMUNIT_PAGES, "/GMM/VM/Reserved/cShadowPages", "The amount of memory reserved for shadow/nested page tables." },
256 { RT_UOFFSETOF(GMMSTATS, VMStats.Reserved.cFixedPages), STAMTYPE_U32, STAMUNIT_PAGES, "/GMM/VM/Reserved/cFixedPages", "The amount of memory reserved for fixed allocations like MMIO2 and the hyper heap." },
257 { RT_UOFFSETOF(GMMSTATS, VMStats.Allocated.cBasePages), STAMTYPE_U64, STAMUNIT_PAGES, "/GMM/VM/Allocated/cBasePages", "The amount of base memory (RAM, ROM, ++) allocated by the VM." },
258 { RT_UOFFSETOF(GMMSTATS, VMStats.Allocated.cShadowPages), STAMTYPE_U32, STAMUNIT_PAGES, "/GMM/VM/Allocated/cShadowPages", "The amount of memory allocated for shadow/nested page tables." },
259 { RT_UOFFSETOF(GMMSTATS, VMStats.Allocated.cFixedPages), STAMTYPE_U32, STAMUNIT_PAGES, "/GMM/VM/Allocated/cFixedPages", "The amount of memory allocated for fixed allocations like MMIO2 and the hyper heap." },
260 { RT_UOFFSETOF(GMMSTATS, VMStats.cPrivatePages), STAMTYPE_U64, STAMUNIT_PAGES, "/GMM/VM/cPrivatePages", "The current number of private pages." },
261 { RT_UOFFSETOF(GMMSTATS, VMStats.cSharedPages), STAMTYPE_U64, STAMUNIT_PAGES, "/GMM/VM/cSharedPages", "The current number of shared pages." },
262 { RT_UOFFSETOF(GMMSTATS, VMStats.cBalloonedPages), STAMTYPE_U64, STAMUNIT_PAGES, "/GMM/VM/cBalloonedPages", "The current number of ballooned pages." },
263 { RT_UOFFSETOF(GMMSTATS, VMStats.cMaxBalloonedPages), STAMTYPE_U64, STAMUNIT_PAGES, "/GMM/VM/cMaxBalloonedPages", "The max number of pages that can be ballooned." },
264 { RT_UOFFSETOF(GMMSTATS, VMStats.cReqBalloonedPages), STAMTYPE_U64, STAMUNIT_PAGES, "/GMM/VM/cReqBalloonedPages", "The number of pages we've currently requested the guest to give us." },
265 { RT_UOFFSETOF(GMMSTATS, VMStats.cReqActuallyBalloonedPages),STAMTYPE_U64, STAMUNIT_PAGES, "/GMM/VM/cReqActuallyBalloonedPages","The number of pages the guest has given us in response to the request." },
266 { RT_UOFFSETOF(GMMSTATS, VMStats.cReqDeflatePages), STAMTYPE_U64, STAMUNIT_PAGES, "/GMM/VM/cReqDeflatePages", "The number of pages we've currently requested the guest to take back." },
267 { RT_UOFFSETOF(GMMSTATS, VMStats.cShareableModules), STAMTYPE_U32, STAMUNIT_COUNT, "/GMM/VM/cShareableModules", "The number of shareable modules traced by the VM." },
268 { RT_UOFFSETOF(GMMSTATS, VMStats.enmPolicy), STAMTYPE_U32, STAMUNIT_NONE, "/GMM/VM/enmPolicy", "The current over-commit policy." },
269 { RT_UOFFSETOF(GMMSTATS, VMStats.enmPriority), STAMTYPE_U32, STAMUNIT_NONE, "/GMM/VM/enmPriority", "The VM priority for arbitrating VMs in low and out of memory situation." },
270 { RT_UOFFSETOF(GMMSTATS, VMStats.fBallooningEnabled), STAMTYPE_BOOL, STAMUNIT_NONE, "/GMM/VM/fBallooningEnabled", "Whether ballooning is enabled or not." },
271 { RT_UOFFSETOF(GMMSTATS, VMStats.fSharedPagingEnabled), STAMTYPE_BOOL, STAMUNIT_NONE, "/GMM/VM/fSharedPagingEnabled", "Whether shared paging is enabled or not." },
272 { RT_UOFFSETOF(GMMSTATS, VMStats.fMayAllocate), STAMTYPE_BOOL, STAMUNIT_NONE, "/GMM/VM/fMayAllocate", "Whether the VM is allowed to allocate memory or not." },
273};
274
275
276/**
277 * Initializes the STAM.
278 *
279 * @returns VBox status code.
280 * @param pUVM The user mode VM structure.
281 */
282VMMR3DECL(int) STAMR3InitUVM(PUVM pUVM)
283{
284 LogFlow(("STAMR3Init\n"));
285
286 /*
287 * Assert alignment and sizes.
288 */
289 AssertCompile(sizeof(pUVM->stam.s) <= sizeof(pUVM->stam.padding));
290 AssertRelease(sizeof(pUVM->stam.s) <= sizeof(pUVM->stam.padding));
291
292 /*
293 * Initialize the read/write lock and list.
294 */
295 int rc = RTSemRWCreate(&pUVM->stam.s.RWSem);
296 AssertRCReturn(rc, rc);
297
298 RTListInit(&pUVM->stam.s.List);
299
300 /*
301 * Initialize the root node.
302 */
303 PSTAMLOOKUP pRoot = (PSTAMLOOKUP)RTMemAlloc(sizeof(STAMLOOKUP));
304 if (!pRoot)
305 {
306 RTSemRWDestroy(pUVM->stam.s.RWSem);
307 pUVM->stam.s.RWSem = NIL_RTSEMRW;
308 return VERR_NO_MEMORY;
309 }
310 pRoot->pParent = NULL;
311 pRoot->papChildren = NULL;
312 pRoot->pDesc = NULL;
313 pRoot->cDescsInTree = 0;
314 pRoot->cChildren = 0;
315 pRoot->iParent = UINT16_MAX;
316 pRoot->off = 0;
317 pRoot->cch = 0;
318 pRoot->szName[0] = '\0';
319
320 pUVM->stam.s.pRoot = pRoot;
321
322 /*
323 * Register the ring-0 statistics (GVMM/GMM).
324 */
325 if (!SUPR3IsDriverless())
326 stamR3Ring0StatsRegisterU(pUVM);
327
328#ifdef VBOX_WITH_DEBUGGER
329 /*
330 * Register debugger commands.
331 */
332 static bool fRegisteredCmds = false;
333 if (!fRegisteredCmds)
334 {
335 rc = DBGCRegisterCommands(&g_aCmds[0], RT_ELEMENTS(g_aCmds));
336 if (RT_SUCCESS(rc))
337 fRegisteredCmds = true;
338 }
339#endif
340
341 return VINF_SUCCESS;
342}
343
344
345/**
346 * Terminates the STAM.
347 *
348 * @param pUVM Pointer to the user mode VM structure.
349 */
350VMMR3DECL(void) STAMR3TermUVM(PUVM pUVM)
351{
352 /*
353 * Free used memory and the RWLock.
354 */
355 PSTAMDESC pCur, pNext;
356 RTListForEachSafe(&pUVM->stam.s.List, pCur, pNext, STAMDESC, ListEntry)
357 {
358 pCur->pLookup->pDesc = NULL;
359 RTMemFree(pCur);
360 }
361
362 stamR3LookupDestroyTree(pUVM->stam.s.pRoot);
363 pUVM->stam.s.pRoot = NULL;
364
365 Assert(pUVM->stam.s.RWSem != NIL_RTSEMRW);
366 RTSemRWDestroy(pUVM->stam.s.RWSem);
367 pUVM->stam.s.RWSem = NIL_RTSEMRW;
368}
369
370
371/**
372 * Registers a sample with the statistics manager.
373 *
374 * Statistics are maintained on a per VM basis and is normally registered
375 * during the VM init stage, but there is nothing preventing you from
376 * register them at runtime.
377 *
378 * Use STAMR3Deregister() to deregister statistics at runtime, however do
379 * not bother calling at termination time.
380 *
381 * It is not possible to register the same sample twice.
382 *
383 * @returns VBox status code.
384 * @param pUVM Pointer to the user mode VM structure.
385 * @param pvSample Pointer to the sample.
386 * @param enmType Sample type. This indicates what pvSample is pointing at.
387 * @param enmVisibility Visibility type specifying whether unused statistics should be visible or not.
388 * @param pszName Sample name. The name is on this form "/<component>/<sample>".
389 * Further nesting is possible.
390 * @param enmUnit Sample unit.
391 * @param pszDesc Sample description.
392 */
393VMMR3DECL(int) STAMR3RegisterU(PUVM pUVM, void *pvSample, STAMTYPE enmType, STAMVISIBILITY enmVisibility, const char *pszName,
394 STAMUNIT enmUnit, const char *pszDesc)
395{
396 AssertReturn(enmType != STAMTYPE_CALLBACK, VERR_INVALID_PARAMETER);
397 UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE);
398 return stamR3RegisterU(pUVM, pvSample, NULL, NULL, enmType, enmVisibility, pszName, enmUnit, pszDesc, STAM_REFRESH_GRP_NONE);
399}
400
401
402/**
403 * Registers a sample with the statistics manager.
404 *
405 * Statistics are maintained on a per VM basis and is normally registered
406 * during the VM init stage, but there is nothing preventing you from
407 * register them at runtime.
408 *
409 * Use STAMR3Deregister() to deregister statistics at runtime, however do
410 * not bother calling at termination time.
411 *
412 * It is not possible to register the same sample twice.
413 *
414 * @returns VBox status code.
415 * @param pVM The cross context VM structure.
416 * @param pvSample Pointer to the sample.
417 * @param enmType Sample type. This indicates what pvSample is pointing at.
418 * @param enmVisibility Visibility type specifying whether unused statistics should be visible or not.
419 * @param pszName Sample name. The name is on this form "/<component>/<sample>".
420 * Further nesting is possible.
421 * @param enmUnit Sample unit.
422 * @param pszDesc Sample description.
423 */
424VMMR3DECL(int) STAMR3Register(PVM pVM, void *pvSample, STAMTYPE enmType, STAMVISIBILITY enmVisibility, const char *pszName,
425 STAMUNIT enmUnit, const char *pszDesc)
426{
427 AssertReturn(enmType != STAMTYPE_CALLBACK, VERR_INVALID_PARAMETER);
428 return stamR3RegisterU(pVM->pUVM, pvSample, NULL, NULL, enmType, enmVisibility, pszName, enmUnit, pszDesc,
429 STAM_REFRESH_GRP_NONE);
430}
431
432
433/**
434 * Same as STAMR3RegisterU except that the name is specified in a
435 * RTStrPrintf like fashion.
436 *
437 * @returns VBox status code.
438 * @param pUVM Pointer to the user mode VM structure.
439 * @param pvSample Pointer to the sample.
440 * @param enmType Sample type. This indicates what pvSample is pointing at.
441 * @param enmVisibility Visibility type specifying whether unused statistics should be visible or not.
442 * @param enmUnit Sample unit.
443 * @param pszDesc Sample description.
444 * @param pszName The sample name format string.
445 * @param ... Arguments to the format string.
446 */
447VMMR3DECL(int) STAMR3RegisterFU(PUVM pUVM, void *pvSample, STAMTYPE enmType, STAMVISIBILITY enmVisibility, STAMUNIT enmUnit,
448 const char *pszDesc, const char *pszName, ...)
449{
450 va_list args;
451 va_start(args, pszName);
452 int rc = STAMR3RegisterVU(pUVM, pvSample, enmType, enmVisibility, enmUnit, pszDesc, pszName, args);
453 va_end(args);
454 return rc;
455}
456
457
458/**
459 * Same as STAMR3Register except that the name is specified in a
460 * RTStrPrintf like fashion.
461 *
462 * @returns VBox status code.
463 * @param pVM The cross context VM structure.
464 * @param pvSample Pointer to the sample.
465 * @param enmType Sample type. This indicates what pvSample is pointing at.
466 * @param enmVisibility Visibility type specifying whether unused statistics should be visible or not.
467 * @param enmUnit Sample unit.
468 * @param pszDesc Sample description.
469 * @param pszName The sample name format string.
470 * @param ... Arguments to the format string.
471 */
472VMMR3DECL(int) STAMR3RegisterF(PVM pVM, void *pvSample, STAMTYPE enmType, STAMVISIBILITY enmVisibility, STAMUNIT enmUnit,
473 const char *pszDesc, const char *pszName, ...)
474{
475 va_list args;
476 va_start(args, pszName);
477 int rc = STAMR3RegisterVU(pVM->pUVM, pvSample, enmType, enmVisibility, enmUnit, pszDesc, pszName, args);
478 va_end(args);
479 return rc;
480}
481
482
483/**
484 * Same as STAMR3Register except that the name is specified in a
485 * RTStrPrintfV like fashion.
486 *
487 * @returns VBox status code.
488 * @param pUVM The user mode VM structure.
489 * @param pvSample Pointer to the sample.
490 * @param enmType Sample type. This indicates what pvSample is pointing at.
491 * @param enmVisibility Visibility type specifying whether unused statistics should be visible or not.
492 * @param enmUnit Sample unit.
493 * @param pszDesc Sample description.
494 * @param pszName The sample name format string.
495 * @param args Arguments to the format string.
496 */
497VMMR3DECL(int) STAMR3RegisterVU(PUVM pUVM, void *pvSample, STAMTYPE enmType, STAMVISIBILITY enmVisibility, STAMUNIT enmUnit,
498 const char *pszDesc, const char *pszName, va_list args)
499{
500 AssertReturn(enmType != STAMTYPE_CALLBACK, VERR_INVALID_PARAMETER);
501
502 char szFormattedName[STAM_MAX_NAME_LEN + 8];
503 size_t cch = RTStrPrintfV(szFormattedName, sizeof(szFormattedName), pszName, args);
504 AssertReturn(cch <= STAM_MAX_NAME_LEN, VERR_OUT_OF_RANGE);
505
506 return STAMR3RegisterU(pUVM, pvSample, enmType, enmVisibility, szFormattedName, enmUnit, pszDesc);
507}
508
509
510/**
511 * Same as STAMR3Register except that the name is specified in a
512 * RTStrPrintfV like fashion.
513 *
514 * @returns VBox status code.
515 * @param pVM The cross context VM structure.
516 * @param pvSample Pointer to the sample.
517 * @param enmType Sample type. This indicates what pvSample is pointing at.
518 * @param enmVisibility Visibility type specifying whether unused statistics should be visible or not.
519 * @param enmUnit Sample unit.
520 * @param pszDesc Sample description.
521 * @param pszName The sample name format string.
522 * @param args Arguments to the format string.
523 */
524VMMR3DECL(int) STAMR3RegisterV(PVM pVM, void *pvSample, STAMTYPE enmType, STAMVISIBILITY enmVisibility, STAMUNIT enmUnit,
525 const char *pszDesc, const char *pszName, va_list args)
526{
527 return STAMR3RegisterVU(pVM->pUVM, pvSample, enmType, enmVisibility, enmUnit, pszDesc, pszName, args);
528}
529
530
531/**
532 * Similar to STAMR3Register except for the two callbacks, the implied type (STAMTYPE_CALLBACK),
533 * and name given in an RTStrPrintf like fashion.
534 *
535 * @returns VBox status code.
536 * @param pVM The cross context VM structure.
537 * @param pvSample Pointer to the sample.
538 * @param enmVisibility Visibility type specifying whether unused statistics should be visible or not.
539 * @param enmUnit Sample unit.
540 * @param pfnReset Callback for resetting the sample. NULL should be used if the sample can't be reset.
541 * @param pfnPrint Print the sample.
542 * @param pszDesc Sample description.
543 * @param pszName The sample name format string.
544 * @param ... Arguments to the format string.
545 * @remark There is currently no device or driver variant of this API. Add one if it should become necessary!
546 */
547VMMR3DECL(int) STAMR3RegisterCallback(PVM pVM, void *pvSample, STAMVISIBILITY enmVisibility, STAMUNIT enmUnit,
548 PFNSTAMR3CALLBACKRESET pfnReset, PFNSTAMR3CALLBACKPRINT pfnPrint,
549 const char *pszDesc, const char *pszName, ...)
550{
551 va_list args;
552 va_start(args, pszName);
553 int rc = STAMR3RegisterCallbackV(pVM, pvSample, enmVisibility, enmUnit, pfnReset, pfnPrint, pszDesc, pszName, args);
554 va_end(args);
555 return rc;
556}
557
558
559/**
560 * Same as STAMR3RegisterCallback() except for the ellipsis which is a va_list here.
561 *
562 * @returns VBox status code.
563 * @param pVM The cross context VM structure.
564 * @param pvSample Pointer to the sample.
565 * @param enmVisibility Visibility type specifying whether unused statistics should be visible or not.
566 * @param enmUnit Sample unit.
567 * @param pfnReset Callback for resetting the sample. NULL should be used if the sample can't be reset.
568 * @param pfnPrint Print the sample.
569 * @param pszDesc Sample description.
570 * @param pszName The sample name format string.
571 * @param args Arguments to the format string.
572 * @remark There is currently no device or driver variant of this API. Add one if it should become necessary!
573 */
574VMMR3DECL(int) STAMR3RegisterCallbackV(PVM pVM, void *pvSample, STAMVISIBILITY enmVisibility, STAMUNIT enmUnit,
575 PFNSTAMR3CALLBACKRESET pfnReset, PFNSTAMR3CALLBACKPRINT pfnPrint,
576 const char *pszDesc, const char *pszName, va_list args)
577{
578 char *pszFormattedName;
579 RTStrAPrintfV(&pszFormattedName, pszName, args);
580 if (!pszFormattedName)
581 return VERR_NO_MEMORY;
582
583 int rc = stamR3RegisterU(pVM->pUVM, pvSample, pfnReset, pfnPrint, STAMTYPE_CALLBACK, enmVisibility, pszFormattedName,
584 enmUnit, pszDesc, STAM_REFRESH_GRP_NONE);
585 RTStrFree(pszFormattedName);
586 return rc;
587}
588
589
590/**
591 * Same as STAMR3RegisterFU, except there is an extra refresh group parameter.
592 *
593 * @returns VBox status code.
594 * @param pUVM Pointer to the user mode VM structure.
595 * @param pvSample Pointer to the sample.
596 * @param enmType Sample type. This indicates what pvSample is pointing at.
597 * @param enmVisibility Visibility type specifying whether unused statistics should be visible or not.
598 * @param enmUnit Sample unit.
599 * @param iRefreshGrp The refresh group, STAM_REFRESH_GRP_XXX.
600 * @param pszDesc Sample description.
601 * @param pszName The sample name format string.
602 * @param ... Arguments to the format string.
603 */
604VMMR3DECL(int) STAMR3RegisterRefresh(PUVM pUVM, void *pvSample, STAMTYPE enmType, STAMVISIBILITY enmVisibility, STAMUNIT enmUnit,
605 uint8_t iRefreshGrp, const char *pszDesc, const char *pszName, ...)
606{
607 va_list args;
608 va_start(args, pszName);
609 int rc = STAMR3RegisterRefreshV(pUVM, pvSample, enmType, enmVisibility, enmUnit, iRefreshGrp, pszDesc, pszName, args);
610 va_end(args);
611 return rc;
612}
613
614
615/**
616 * Same as STAMR3RegisterVU, except there is an extra refresh group parameter.
617 *
618 * @returns VBox status code.
619 * @param pUVM The user mode VM structure.
620 * @param pvSample Pointer to the sample.
621 * @param enmType Sample type. This indicates what pvSample is pointing at.
622 * @param enmVisibility Visibility type specifying whether unused statistics should be visible or not.
623 * @param enmUnit Sample unit.
624 * @param iRefreshGrp The refresh group, STAM_REFRESH_GRP_XXX.
625 * @param pszDesc Sample description.
626 * @param pszName The sample name format string.
627 * @param va Arguments to the format string.
628 */
629VMMR3DECL(int) STAMR3RegisterRefreshV(PUVM pUVM, void *pvSample, STAMTYPE enmType, STAMVISIBILITY enmVisibility, STAMUNIT enmUnit,
630 uint8_t iRefreshGrp, const char *pszDesc, const char *pszName, va_list va)
631{
632 AssertReturn(enmType != STAMTYPE_CALLBACK, VERR_INVALID_PARAMETER);
633
634 char szFormattedName[STAM_MAX_NAME_LEN + 8];
635 size_t cch = RTStrPrintfV(szFormattedName, sizeof(szFormattedName), pszName, va);
636 AssertReturn(cch <= STAM_MAX_NAME_LEN, VERR_OUT_OF_RANGE);
637
638 UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE);
639 return stamR3RegisterU(pUVM, pvSample, NULL, NULL, enmType, enmVisibility, pszName, enmUnit, pszDesc, iRefreshGrp);
640}
641
642
643#ifdef VBOX_STRICT
644/**
645 * Divide the strings into sub-strings using '/' as delimiter
646 * and then compare them in strcmp fashion.
647 *
648 * @returns Difference.
649 * @retval 0 if equal.
650 * @retval < 0 if psz1 is less than psz2.
651 * @retval > 0 if psz1 greater than psz2.
652 *
653 * @param psz1 The first string.
654 * @param psz2 The second string.
655 */
656static int stamR3SlashCompare(const char *psz1, const char *psz2)
657{
658 for (;;)
659 {
660 unsigned int ch1 = *psz1++;
661 unsigned int ch2 = *psz2++;
662 if (ch1 != ch2)
663 {
664 /* slash is end-of-sub-string, so it trumps everything but '\0'. */
665 if (ch1 == '/')
666 return ch2 ? -1 : 1;
667 if (ch2 == '/')
668 return ch1 ? 1 : -1;
669 return ch1 - ch2;
670 }
671
672 /* done? */
673 if (ch1 == '\0')
674 return 0;
675 }
676}
677#endif /* VBOX_STRICT */
678
679
680/**
681 * Compares a lookup node with a name.
682 *
683 * @returns like strcmp and memcmp.
684 * @param pNode The lookup node.
685 * @param pchName The name, not necessarily terminated.
686 * @param cchName The length of the name.
687 */
688DECL_FORCE_INLINE(int) stamR3LookupCmp(PSTAMLOOKUP pNode, const char *pchName, uint32_t cchName)
689{
690 uint32_t cchComp = RT_MIN(pNode->cch, cchName);
691 int iDiff = memcmp(pNode->szName, pchName, cchComp);
692 if (!iDiff && pNode->cch != cchName)
693 iDiff = pNode->cch > cchName ? 2 : -2;
694 return iDiff;
695}
696
697
698/**
699 * Creates a new lookup child node.
700 *
701 * @returns Pointer to the newly created lookup node.
702 * @param pParent The parent node.
703 * @param pchName The name (not necessarily terminated).
704 * @param cchName The length of the name.
705 * @param offName The offset of the node in a path.
706 * @param iChild Child index of a node that's before the one
707 * we're inserting (returned by
708 * stamR3LookupFindChild).
709 */
710static PSTAMLOOKUP stamR3LookupNewChild(PSTAMLOOKUP pParent, const char *pchName, uint32_t cchName, uint32_t offName,
711 uint32_t iChild)
712{
713 Assert(cchName <= UINT8_MAX);
714 Assert(offName <= UINT8_MAX);
715 Assert(iChild < UINT16_MAX);
716
717 /*
718 * Allocate a new entry.
719 */
720 PSTAMLOOKUP pNew = (PSTAMLOOKUP)RTMemAlloc(RT_UOFFSETOF_DYN(STAMLOOKUP, szName[cchName + 1]));
721 if (!pNew)
722 return NULL;
723 pNew->pParent = pParent;
724 pNew->papChildren = NULL;
725 pNew->pDesc = NULL;
726 pNew->cDescsInTree = 0;
727 pNew->cChildren = 0;
728 pNew->cch = (uint16_t)cchName;
729 pNew->off = (uint16_t)offName;
730 memcpy(pNew->szName, pchName, cchName);
731 pNew->szName[cchName] = '\0';
732
733 /*
734 * Reallocate the array?
735 */
736 if (RT_IS_POWER_OF_TWO(pParent->cChildren))
737 {
738 uint32_t cNew = pParent->cChildren ? (uint32_t)pParent->cChildren * 2 : 8;
739 AssertReturnStmt(cNew <= 0x8000, RTMemFree(pNew), NULL);
740 void *pvNew = RTMemRealloc(pParent->papChildren, cNew * sizeof(pParent->papChildren[0]));
741 if (!pvNew)
742 {
743 RTMemFree(pNew);
744 return NULL;
745 }
746 pParent->papChildren = (PSTAMLOOKUP *)pvNew;
747 }
748
749 /*
750 * Find the exact insertion point using iChild as a very good clue from
751 * the find function.
752 */
753 if (!pParent->cChildren)
754 iChild = 0;
755 else
756 {
757 if (iChild >= pParent->cChildren)
758 iChild = pParent->cChildren - 1;
759 while ( iChild < pParent->cChildren
760 && stamR3LookupCmp(pParent->papChildren[iChild], pchName, cchName) < 0)
761 iChild++;
762 }
763
764 /*
765 * Insert it.
766 */
767 if (iChild < pParent->cChildren)
768 {
769 /* Do shift. */
770 uint32_t i = pParent->cChildren;
771 while (i > iChild)
772 {
773 PSTAMLOOKUP pNode = pParent->papChildren[i - 1];
774 pParent->papChildren[i] = pNode;
775 pNode->iParent = i;
776 i--;
777 }
778 }
779
780 pNew->iParent = iChild;
781 pParent->papChildren[iChild] = pNew;
782 pParent->cChildren++;
783
784 return pNew;
785}
786
787
788/**
789 * Looks up a child.
790 *
791 * @returns Pointer to child node if found, NULL if not.
792 * @param pParent The parent node.
793 * @param pchName The name (not necessarily terminated).
794 * @param cchName The length of the name.
795 * @param piChild Where to store a child index suitable for
796 * passing to stamR3LookupNewChild when NULL is
797 * returned.
798 */
799static PSTAMLOOKUP stamR3LookupFindChild(PSTAMLOOKUP pParent, const char *pchName, uint32_t cchName, uint32_t *piChild)
800{
801 uint32_t iChild = pParent->cChildren;
802 if (iChild > 4)
803 {
804 uint32_t iFirst = 0;
805 uint32_t iEnd = iChild;
806 iChild /= 2;
807 for (;;)
808 {
809 int iDiff = stamR3LookupCmp(pParent->papChildren[iChild], pchName, cchName);
810 if (!iDiff)
811 {
812 if (piChild)
813 *piChild = iChild;
814 return pParent->papChildren[iChild];
815 }
816
817 /* Split. */
818 if (iDiff < 0)
819 {
820 iFirst = iChild + 1;
821 if (iFirst >= iEnd)
822 {
823 if (piChild)
824 *piChild = iChild;
825 break;
826 }
827 }
828 else
829 {
830 if (iChild == iFirst)
831 {
832 if (piChild)
833 *piChild = iChild ? iChild - 1 : 0;
834 break;
835 }
836 iEnd = iChild;
837 }
838
839 /* Calc next child. */
840 iChild = (iEnd - iFirst) / 2 + iFirst;
841 }
842 return NULL;
843 }
844
845 /*
846 * Linear search.
847 */
848 while (iChild-- > 0)
849 {
850 int iDiff = stamR3LookupCmp(pParent->papChildren[iChild], pchName, cchName);
851 if (iDiff <= 0)
852 {
853 if (piChild)
854 *piChild = iChild;
855 return !iDiff ? pParent->papChildren[iChild] : NULL;
856 }
857 }
858 if (piChild)
859 *piChild = 0;
860 return NULL;
861}
862
863
864/**
865 * Find the next sample descriptor node.
866 *
867 * This is for use with insertion in the big list and pattern range lookups.
868 *
869 * @returns Pointer to the next sample descriptor. NULL if not found (i.e.
870 * we're at the end of the list).
871 * @param pLookup The current node.
872 */
873static PSTAMDESC stamR3LookupFindNextWithDesc(PSTAMLOOKUP pLookup)
874{
875 Assert(!pLookup->pDesc);
876 PSTAMLOOKUP pCur = pLookup;
877 uint32_t iCur = 0;
878 for (;;)
879 {
880 /*
881 * Check all children.
882 */
883 uint32_t cChildren = pCur->cChildren;
884 if (iCur < cChildren)
885 {
886 PSTAMLOOKUP *papChildren = pCur->papChildren;
887 do
888 {
889 PSTAMLOOKUP pChild = papChildren[iCur];
890 if (pChild->pDesc)
891 return pChild->pDesc;
892
893 if (pChild->cChildren > 0)
894 {
895 /* One level down. */
896 iCur = 0;
897 pCur = pChild;
898 break;
899 }
900 } while (++iCur < cChildren);
901 }
902 else
903 {
904 /*
905 * One level up, resuming after the current.
906 */
907 iCur = pCur->iParent + 1;
908 pCur = pCur->pParent;
909 if (!pCur)
910 return NULL;
911 }
912 }
913}
914
915
916/**
917 * Look up a sample descriptor by name.
918 *
919 * @returns Pointer to a sample descriptor.
920 * @param pRoot The root node.
921 * @param pszName The name to lookup.
922 */
923static PSTAMDESC stamR3LookupFindDesc(PSTAMLOOKUP pRoot, const char *pszName)
924{
925 Assert(!pRoot->pParent);
926 while (*pszName++ == '/')
927 {
928 const char *pszEnd = strchr(pszName, '/');
929 uint32_t cch = pszEnd ? pszEnd - pszName : (uint32_t)strlen(pszName);
930 PSTAMLOOKUP pChild = stamR3LookupFindChild(pRoot, pszName, cch, NULL);
931 if (!pChild)
932 break;
933 if (!pszEnd)
934 return pChild->pDesc;
935 pszName = pszEnd;
936 pRoot = pChild;
937 }
938
939 return NULL;
940}
941
942
943/**
944 * Finds the first sample descriptor for a given lookup range.
945 *
946 * This is for pattern range lookups.
947 *
948 * @returns Pointer to the first descriptor.
949 * @param pFirst The first node in the range.
950 * @param pLast The last node in the range.
951 */
952static PSTAMDESC stamR3LookupFindFirstDescForRange(PSTAMLOOKUP pFirst, PSTAMLOOKUP pLast)
953{
954 if (pFirst->pDesc)
955 return pFirst->pDesc;
956
957 PSTAMLOOKUP pCur = pFirst;
958 uint32_t iCur = 0;
959 for (;;)
960 {
961 uint32_t cChildren = pCur->cChildren;
962 if (iCur < pCur->cChildren)
963 {
964 /*
965 * Check all children.
966 */
967 PSTAMLOOKUP * const papChildren = pCur->papChildren;
968 do
969 {
970 PSTAMLOOKUP pChild = papChildren[iCur];
971 if (pChild->pDesc)
972 return pChild->pDesc;
973 if (pChild->cChildren > 0)
974 {
975 /* One level down. */
976 iCur = 0;
977 pCur = pChild;
978 break;
979 }
980 if (pChild == pLast)
981 return NULL;
982 } while (++iCur < cChildren);
983 }
984 else
985 {
986 /*
987 * One level up, checking current and its 'older' sibilings.
988 */
989 if (pCur == pLast)
990 return NULL;
991 iCur = pCur->iParent + 1;
992 pCur = pCur->pParent;
993 if (!pCur)
994 break;
995 }
996 }
997
998 return NULL;
999}
1000
1001
1002/**
1003 * Finds the last sample descriptor for a given lookup range.
1004 *
1005 * This is for pattern range lookups.
1006 *
1007 * @returns Pointer to the first descriptor.
1008 * @param pFirst The first node in the range.
1009 * @param pLast The last node in the range.
1010 */
1011static PSTAMDESC stamR3LookupFindLastDescForRange(PSTAMLOOKUP pFirst, PSTAMLOOKUP pLast)
1012{
1013 PSTAMLOOKUP pCur = pLast;
1014 uint32_t iCur = pCur->cChildren - 1;
1015 for (;;)
1016 {
1017 if (iCur < pCur->cChildren)
1018 {
1019 /*
1020 * Check children backwards, depth first.
1021 */
1022 PSTAMLOOKUP * const papChildren = pCur->papChildren;
1023 do
1024 {
1025 PSTAMLOOKUP pChild = papChildren[iCur];
1026 if (pChild->cChildren > 0)
1027 {
1028 /* One level down. */
1029 iCur = pChild->cChildren - 1;
1030 pCur = pChild;
1031 break;
1032 }
1033
1034 if (pChild->pDesc)
1035 return pChild->pDesc;
1036 if (pChild == pFirst)
1037 return NULL;
1038 } while (iCur-- > 0); /* (underflow handled above) */
1039 }
1040 else
1041 {
1042 /*
1043 * One level up, checking current and its 'older' sibilings.
1044 */
1045 if (pCur->pDesc)
1046 return pCur->pDesc;
1047 if (pCur == pFirst)
1048 return NULL;
1049 iCur = pCur->iParent - 1; /* (underflow handled above) */
1050 pCur = pCur->pParent;
1051 if (!pCur)
1052 break;
1053 }
1054 }
1055
1056 return NULL;
1057}
1058
1059
1060/**
1061 * Look up the first and last descriptors for a (single) pattern expression.
1062 *
1063 * This is used to optimize pattern enumerations and doesn't have to return 100%
1064 * accurate results if that costs too much.
1065 *
1066 * @returns Pointer to the first descriptor in the range.
1067 * @param pRoot The root node.
1068 * @param pList The descriptor list anchor.
1069 * @param pszPat The name patter to lookup.
1070 * @param ppLastDesc Where to store the address of the last
1071 * descriptor (approximate).
1072 */
1073static PSTAMDESC stamR3LookupFindPatternDescRange(PSTAMLOOKUP pRoot, PRTLISTANCHOR pList, const char *pszPat,
1074 PSTAMDESC *ppLastDesc)
1075{
1076 Assert(!pRoot->pParent);
1077
1078 /*
1079 * If there is an early enough wildcard, the whole list needs to be searched.
1080 */
1081 if ( pszPat[0] == '*' || pszPat[0] == '?'
1082 || pszPat[1] == '*' || pszPat[1] == '?')
1083 {
1084 *ppLastDesc = RTListGetLast(pList, STAMDESC, ListEntry);
1085 return RTListGetFirst(pList, STAMDESC, ListEntry);
1086 }
1087
1088 /*
1089 * All statistics starts with a slash.
1090 */
1091 while ( *pszPat++ == '/'
1092 && pRoot->cDescsInTree > 0
1093 && pRoot->cChildren > 0)
1094 {
1095 const char *pszEnd = strchr(pszPat, '/');
1096 uint32_t cch = pszEnd ? pszEnd - pszPat : (uint32_t)strlen(pszPat);
1097 if (!cch)
1098 break;
1099
1100 const char *pszPat1 = (const char *)memchr(pszPat, '*', cch);
1101 const char *pszPat2 = (const char *)memchr(pszPat, '?', cch);
1102 if (pszPat1 || pszPat2)
1103 {
1104 /* We've narrowed it down to a sub-tree now. */
1105 PSTAMLOOKUP pFirst = pRoot->papChildren[0];
1106 PSTAMLOOKUP pLast = pRoot->papChildren[pRoot->cChildren - 1];
1107 /** @todo narrow the range further if both pszPat1/2 != pszPat. */
1108
1109 *ppLastDesc = stamR3LookupFindLastDescForRange(pFirst, pLast);
1110 return stamR3LookupFindFirstDescForRange(pFirst, pLast);
1111 }
1112
1113 PSTAMLOOKUP pChild = stamR3LookupFindChild(pRoot, pszPat, cch, NULL);
1114 if (!pChild)
1115 break;
1116
1117 /* Advance */
1118 if (!pszEnd)
1119 return *ppLastDesc = pChild->pDesc;
1120 pszPat = pszEnd;
1121 pRoot = pChild;
1122 }
1123
1124 /* No match. */
1125 *ppLastDesc = NULL;
1126 return NULL;
1127}
1128
1129
1130/**
1131 * Look up the first descriptors for starts-with name string.
1132 *
1133 * This is used to optimize deletion.
1134 *
1135 * @returns Pointer to the first descriptor in the range.
1136 * @param pRoot The root node.
1137 * @param pchPrefix The name prefix.
1138 * @param cchPrefix The name prefix length (can be shorter than the
1139 * actual string).
1140 * @param ppLastDesc Where to store the address of the last descriptor.
1141 * @sa stamR3LookupFindPatternDescRange
1142 */
1143static PSTAMDESC stamR3LookupFindByPrefixRange(PSTAMLOOKUP pRoot, const char *pchPrefix, uint32_t cchPrefix,
1144 PSTAMDESC *ppLastDesc)
1145
1146{
1147 *ppLastDesc = NULL;
1148 Assert(!pRoot->pParent);
1149 AssertReturn(cchPrefix > 0, NULL);
1150
1151 /*
1152 * We start with a root slash.
1153 */
1154 if (!cchPrefix || *pchPrefix != '/')
1155 return NULL;
1156
1157 /*
1158 * Walk thru the prefix component by component, since that's how
1159 * the lookup tree is organized.
1160 */
1161 while ( cchPrefix
1162 && *pchPrefix == '/'
1163 && pRoot->cDescsInTree > 0
1164 && pRoot->cChildren > 0)
1165 {
1166 cchPrefix -= 1;
1167 pchPrefix += 1;
1168
1169 const char *pszEnd = (const char *)memchr(pchPrefix, '/', cchPrefix);
1170 if (!pszEnd)
1171 {
1172 /*
1173 * We've narrowed it down to a sub-tree now. If we've no more prefix to work
1174 * with now (e.g. '/Devices/'), the prefix matches all the children. Otherwise,
1175 * traverse the children to find the ones matching the prefix.
1176 */
1177 if (!cchPrefix)
1178 {
1179 *ppLastDesc = stamR3LookupFindLastDescForRange(pRoot->papChildren[0], pRoot->papChildren[pRoot->cChildren - 1]);
1180 return stamR3LookupFindFirstDescForRange(pRoot->papChildren[0], pRoot->papChildren[pRoot->cChildren - 1]);
1181 }
1182
1183 size_t iEnd = pRoot->cChildren;
1184 if (iEnd < 16)
1185 {
1186 /* Linear scan of the children: */
1187 for (size_t i = 0; i < pRoot->cChildren; i++)
1188 {
1189 PSTAMLOOKUP pCur = pRoot->papChildren[i];
1190 if (pCur->cch >= cchPrefix)
1191 {
1192 int iDiff = memcmp(pCur->szName, pchPrefix, cchPrefix);
1193 if (iDiff == 0)
1194 {
1195 size_t iLast = i;
1196 while (++iLast < pRoot->cChildren)
1197 {
1198 PSTAMLOOKUP pCur2 = pRoot->papChildren[iLast];
1199 if ( pCur2->cch < cchPrefix
1200 || memcmp(pCur2->szName, pchPrefix, cchPrefix) != 0)
1201 break;
1202 }
1203 iLast--;
1204
1205 *ppLastDesc = stamR3LookupFindLastDescForRange(pCur, pRoot->papChildren[iLast]);
1206 return stamR3LookupFindFirstDescForRange(pCur, pRoot->papChildren[iLast]);
1207 }
1208 if (iDiff > 0)
1209 break;
1210 }
1211 }
1212 }
1213 else
1214 {
1215 /* Binary search to find something matching the prefix, followed
1216 by a reverse scan to locate the first child: */
1217 size_t iFirst = 0;
1218 size_t i = iEnd / 2;
1219 for (;;)
1220 {
1221 PSTAMLOOKUP pCur = pRoot->papChildren[i];
1222 int iDiff;
1223 if (pCur->cch >= cchPrefix)
1224 iDiff = memcmp(pCur->szName, pchPrefix, cchPrefix);
1225 else
1226 {
1227 iDiff = memcmp(pCur->szName, pchPrefix, pCur->cch);
1228 if (!iDiff)
1229 iDiff = 1;
1230 }
1231 if (iDiff > 0)
1232 {
1233 if (iFirst < i)
1234 iEnd = i;
1235 else
1236 return NULL;
1237 }
1238 else if (iDiff < 0)
1239 {
1240 i += 1;
1241 if (i < iEnd)
1242 iFirst = i;
1243 else
1244 return NULL;
1245 }
1246 else
1247 {
1248 /* Match. Reverse scan to find the first. */
1249 iFirst = i;
1250 while ( iFirst > 0
1251 && (pCur = pRoot->papChildren[iFirst - 1])->cch >= cchPrefix
1252 && memcmp(pCur->szName, pchPrefix, cchPrefix) == 0)
1253 iFirst--;
1254
1255 /* Forward scan to find the last.*/
1256 size_t iLast = i;
1257 while (++iLast < pRoot->cChildren)
1258 {
1259 pCur = pRoot->papChildren[iLast];
1260 if ( pCur->cch < cchPrefix
1261 || memcmp(pCur->szName, pchPrefix, cchPrefix) != 0)
1262 break;
1263 }
1264 iLast--;
1265
1266 *ppLastDesc = stamR3LookupFindLastDescForRange(pRoot->papChildren[iFirst], pRoot->papChildren[iLast]);
1267 return stamR3LookupFindFirstDescForRange(pRoot->papChildren[iFirst], pRoot->papChildren[iLast]);
1268 }
1269
1270 i = iFirst + (iEnd - iFirst) / 2;
1271 }
1272 }
1273 break;
1274 }
1275
1276 /* Find child matching the path component: */
1277 uint32_t cchChild = pszEnd - pchPrefix;
1278 PSTAMLOOKUP pChild = stamR3LookupFindChild(pRoot, pchPrefix, cchChild, NULL);
1279 if (!pChild)
1280 break;
1281
1282 /* Advance: */
1283 cchPrefix -= cchChild;
1284 pchPrefix = pszEnd;
1285 pRoot = pChild;
1286 }
1287 return NULL;
1288}
1289
1290
1291/**
1292 * Increments the cDescInTree member of the given node an all ancestors.
1293 *
1294 * @param pLookup The lookup node.
1295 */
1296static void stamR3LookupIncUsage(PSTAMLOOKUP pLookup)
1297{
1298 Assert(pLookup->pDesc);
1299
1300 PSTAMLOOKUP pCur = pLookup;
1301 while (pCur != NULL)
1302 {
1303 pCur->cDescsInTree++;
1304 pCur = pCur->pParent;
1305 }
1306}
1307
1308
1309/**
1310 * Descrements the cDescInTree member of the given node an all ancestors.
1311 *
1312 * @param pLookup The lookup node.
1313 */
1314static void stamR3LookupDecUsage(PSTAMLOOKUP pLookup)
1315{
1316 Assert(!pLookup->pDesc);
1317
1318 PSTAMLOOKUP pCur = pLookup;
1319 while (pCur != NULL)
1320 {
1321 Assert(pCur->cDescsInTree > 0);
1322 pCur->cDescsInTree--;
1323 pCur = pCur->pParent;
1324 }
1325}
1326
1327
1328/**
1329 * Frees empty lookup nodes if it's worth it.
1330 *
1331 * @param pLookup The lookup node.
1332 */
1333static void stamR3LookupMaybeFree(PSTAMLOOKUP pLookup)
1334{
1335 Assert(!pLookup->pDesc);
1336
1337 /*
1338 * Free between two and three levels of nodes. Freeing too much most
1339 * likely wasted effort since we're either going to repopluate the tree
1340 * or quit the whole thing.
1341 */
1342 if (pLookup->cDescsInTree > 0)
1343 return;
1344
1345 PSTAMLOOKUP pCur = pLookup->pParent;
1346 if (!pCur)
1347 return;
1348 if (pCur->cDescsInTree > 0)
1349 return;
1350 PSTAMLOOKUP pParent = pCur->pParent;
1351 if (!pParent)
1352 return;
1353
1354 if (pParent->cDescsInTree == 0 && pParent->pParent)
1355 {
1356 pCur = pParent;
1357 pParent = pCur->pParent;
1358 }
1359
1360 /*
1361 * Remove pCur from pParent.
1362 */
1363 PSTAMLOOKUP *papChildren = pParent->papChildren;
1364 uint32_t cChildren = --pParent->cChildren;
1365 for (uint32_t i = pCur->iParent; i < cChildren; i++)
1366 {
1367 PSTAMLOOKUP pChild = papChildren[i + 1];
1368 pChild->iParent = i;
1369 papChildren[i] = pChild;
1370 }
1371 pCur->pParent = NULL;
1372 pCur->iParent = UINT16_MAX;
1373
1374 /*
1375 * Destroy pCur.
1376 */
1377 stamR3LookupDestroyTree(pCur);
1378}
1379
1380
1381/**
1382 * Destroys a lookup tree.
1383 *
1384 * This is used by STAMR3Term as well as stamR3LookupMaybeFree.
1385 *
1386 * @param pRoot The root of the tree (must have no parent).
1387 */
1388static void stamR3LookupDestroyTree(PSTAMLOOKUP pRoot)
1389{
1390 Assert(pRoot); Assert(!pRoot->pParent);
1391 PSTAMLOOKUP pCur = pRoot;
1392 for (;;)
1393 {
1394 uint32_t i = pCur->cChildren;
1395 if (i > 0)
1396 {
1397 /*
1398 * Push child (with leaf optimization).
1399 */
1400 PSTAMLOOKUP pChild = pCur->papChildren[--i];
1401 if (pChild->cChildren != 0)
1402 pCur = pChild;
1403 else
1404 {
1405 /* free leaves. */
1406 for (;;)
1407 {
1408 if (pChild->papChildren)
1409 {
1410 RTMemFree(pChild->papChildren);
1411 pChild->papChildren = NULL;
1412 }
1413 RTMemFree(pChild);
1414 pCur->papChildren[i] = NULL;
1415
1416 /* next */
1417 if (i == 0)
1418 {
1419 pCur->cChildren = 0;
1420 break;
1421 }
1422 pChild = pCur->papChildren[--i];
1423 if (pChild->cChildren != 0)
1424 {
1425 pCur->cChildren = i + 1;
1426 pCur = pChild;
1427 break;
1428 }
1429 }
1430 }
1431 }
1432 else
1433 {
1434 /*
1435 * Pop and free current.
1436 */
1437 Assert(!pCur->pDesc);
1438
1439 PSTAMLOOKUP pParent = pCur->pParent;
1440 Assert(pCur->iParent == (pParent ? pParent->cChildren - 1 : UINT16_MAX));
1441
1442 RTMemFree(pCur->papChildren);
1443 pCur->papChildren = NULL;
1444 RTMemFree(pCur);
1445
1446 pCur = pParent;
1447 if (!pCur)
1448 break;
1449 pCur->papChildren[--pCur->cChildren] = NULL;
1450 }
1451 }
1452}
1453
1454
1455/**
1456 * Internal worker for the different register calls.
1457 *
1458 * @returns VBox status code.
1459 * @param pUVM Pointer to the user mode VM structure.
1460 * @param pvSample Pointer to the sample.
1461 * @param pfnReset Callback for resetting the sample. NULL should be used if the sample can't be reset.
1462 * @param pfnPrint Print the sample.
1463 * @param enmType Sample type. This indicates what pvSample is pointing at.
1464 * @param enmVisibility Visibility type specifying whether unused statistics should be visible or not.
1465 * @param pszName The sample name format string.
1466 * @param enmUnit Sample unit.
1467 * @param pszDesc Sample description.
1468 * @param iRefreshGrp The refresh group, STAM_REFRESH_GRP_XXX.
1469 * @remark There is currently no device or driver variant of this API. Add one if it should become necessary!
1470 */
1471static int stamR3RegisterU(PUVM pUVM, void *pvSample, PFNSTAMR3CALLBACKRESET pfnReset, PFNSTAMR3CALLBACKPRINT pfnPrint,
1472 STAMTYPE enmType, STAMVISIBILITY enmVisibility,
1473 const char *pszName, STAMUNIT enmUnit, const char *pszDesc, uint8_t iRefreshGrp)
1474{
1475 AssertReturn(pszName[0] == '/', VERR_INVALID_NAME);
1476 AssertReturn(pszName[1] != '/' && pszName[1], VERR_INVALID_NAME);
1477 uint32_t const cchName = (uint32_t)strlen(pszName);
1478 AssertReturn(cchName <= STAM_MAX_NAME_LEN, VERR_OUT_OF_RANGE);
1479 AssertReturn(pszName[cchName - 1] != '/', VERR_INVALID_NAME);
1480 AssertReturn(memchr(pszName, '\\', cchName) == NULL, VERR_INVALID_NAME);
1481 AssertReturn(iRefreshGrp == STAM_REFRESH_GRP_NONE || iRefreshGrp < 64, VERR_INVALID_PARAMETER);
1482
1483 STAM_LOCK_WR(pUVM);
1484
1485 /*
1486 * Look up the tree location, populating the lookup tree as we walk it.
1487 */
1488 PSTAMLOOKUP pLookup = pUVM->stam.s.pRoot; Assert(pLookup);
1489 uint32_t offName = 1;
1490 for (;;)
1491 {
1492 /* Get the next part of the path. */
1493 const char *pszStart = &pszName[offName];
1494 const char *pszEnd = strchr(pszStart, '/');
1495 uint32_t cch = pszEnd ? (uint32_t)(pszEnd - pszStart) : cchName - offName;
1496 if (cch == 0)
1497 {
1498 STAM_UNLOCK_WR(pUVM);
1499 AssertMsgFailed(("No double or trailing slashes are allowed: '%s'\n", pszName));
1500 return VERR_INVALID_NAME;
1501 }
1502
1503 /* Do the looking up. */
1504 uint32_t iChild = 0;
1505 PSTAMLOOKUP pChild = stamR3LookupFindChild(pLookup, pszStart, cch, &iChild);
1506 if (!pChild)
1507 {
1508 pChild = stamR3LookupNewChild(pLookup, pszStart, cch, offName, iChild);
1509 if (!pChild)
1510 {
1511 STAM_UNLOCK_WR(pUVM);
1512 return VERR_NO_MEMORY;
1513 }
1514 }
1515
1516 /* Advance. */
1517 pLookup = pChild;
1518 if (!pszEnd)
1519 break;
1520 offName += cch + 1;
1521 }
1522 if (pLookup->pDesc)
1523 {
1524 STAM_UNLOCK_WR(pUVM);
1525 AssertMsgFailed(("Duplicate sample name: %s\n", pszName));
1526 return VERR_ALREADY_EXISTS;
1527 }
1528
1529 PSTAMDESC pCur = stamR3LookupFindNextWithDesc(pLookup);
1530
1531 /*
1532 * Check that the name doesn't screw up sorting order when taking
1533 * slashes into account. The QT GUI makes some assumptions.
1534 * Problematic chars are: !"#$%&'()*+,-.
1535 */
1536#ifdef VBOX_STRICT
1537 Assert(pszName[0] == '/');
1538 PSTAMDESC pPrev = pCur
1539 ? RTListGetPrev(&pUVM->stam.s.List, pCur, STAMDESC, ListEntry)
1540 : RTListGetLast(&pUVM->stam.s.List, STAMDESC, ListEntry);
1541 Assert(!pPrev || strcmp(pszName, pPrev->pszName) > 0);
1542 Assert(!pCur || strcmp(pszName, pCur->pszName) < 0);
1543 Assert(!pPrev || stamR3SlashCompare(pPrev->pszName, pszName) < 0);
1544 Assert(!pCur || stamR3SlashCompare(pCur->pszName, pszName) > 0);
1545
1546 /*
1547 * Check alignment requirements.
1548 */
1549 switch (enmType)
1550 {
1551 /* 8 byte / 64-bit */
1552 case STAMTYPE_U64:
1553 case STAMTYPE_U64_RESET:
1554 case STAMTYPE_X64:
1555 case STAMTYPE_X64_RESET:
1556 case STAMTYPE_COUNTER:
1557 case STAMTYPE_PROFILE:
1558 case STAMTYPE_PROFILE_ADV:
1559 AssertMsg(!((uintptr_t)pvSample & 7), ("%p - %s\n", pvSample, pszName));
1560 break;
1561
1562 /* 4 byte / 32-bit */
1563 case STAMTYPE_RATIO_U32:
1564 case STAMTYPE_RATIO_U32_RESET:
1565 case STAMTYPE_U32:
1566 case STAMTYPE_U32_RESET:
1567 case STAMTYPE_X32:
1568 case STAMTYPE_X32_RESET:
1569 AssertMsg(!((uintptr_t)pvSample & 3), ("%p - %s\n", pvSample, pszName));
1570 break;
1571
1572 /* 2 byte / 32-bit */
1573 case STAMTYPE_U16:
1574 case STAMTYPE_U16_RESET:
1575 case STAMTYPE_X16:
1576 case STAMTYPE_X16_RESET:
1577 AssertMsg(!((uintptr_t)pvSample & 1), ("%p - %s\n", pvSample, pszName));
1578 break;
1579
1580 /* 1 byte / 8-bit / unaligned */
1581 case STAMTYPE_U8:
1582 case STAMTYPE_U8_RESET:
1583 case STAMTYPE_X8:
1584 case STAMTYPE_X8_RESET:
1585 case STAMTYPE_BOOL:
1586 case STAMTYPE_BOOL_RESET:
1587 case STAMTYPE_CALLBACK:
1588 break;
1589
1590 default:
1591 AssertMsgFailed(("%d\n", enmType));
1592 break;
1593 }
1594#endif /* VBOX_STRICT */
1595
1596 /*
1597 * Create a new node and insert it at the current location.
1598 */
1599 int rc;
1600 size_t cbDesc = pszDesc ? strlen(pszDesc) + 1 : 0;
1601 PSTAMDESC pNew = (PSTAMDESC)RTMemAlloc(sizeof(*pNew) + cchName + 1 + cbDesc);
1602 if (pNew)
1603 {
1604 pNew->pszName = (char *)memcpy((char *)(pNew + 1), pszName, cchName + 1);
1605 pNew->enmType = enmType;
1606 pNew->enmVisibility = enmVisibility;
1607 if (enmType != STAMTYPE_CALLBACK)
1608 pNew->u.pv = pvSample;
1609 else
1610 {
1611 pNew->u.Callback.pvSample = pvSample;
1612 pNew->u.Callback.pfnReset = pfnReset;
1613 pNew->u.Callback.pfnPrint = pfnPrint;
1614 }
1615 pNew->enmUnit = enmUnit;
1616 pNew->iRefreshGroup = iRefreshGrp;
1617 pNew->pszDesc = NULL;
1618 if (pszDesc)
1619 pNew->pszDesc = (char *)memcpy((char *)(pNew + 1) + cchName + 1, pszDesc, cbDesc);
1620
1621 if (pCur)
1622 RTListNodeInsertBefore(&pCur->ListEntry, &pNew->ListEntry);
1623 else
1624 RTListAppend(&pUVM->stam.s.List, &pNew->ListEntry);
1625
1626 pNew->pLookup = pLookup;
1627 pLookup->pDesc = pNew;
1628 stamR3LookupIncUsage(pLookup);
1629
1630 stamR3ResetOne(pNew, pUVM->pVM);
1631 rc = VINF_SUCCESS;
1632 }
1633 else
1634 rc = VERR_NO_MEMORY;
1635
1636 STAM_UNLOCK_WR(pUVM);
1637 return rc;
1638}
1639
1640
1641/**
1642 * Destroys the statistics descriptor, unlinking it and freeing all resources.
1643 *
1644 * @returns VINF_SUCCESS
1645 * @param pCur The descriptor to destroy.
1646 */
1647static int stamR3DestroyDesc(PSTAMDESC pCur)
1648{
1649 RTListNodeRemove(&pCur->ListEntry);
1650 pCur->pLookup->pDesc = NULL; /** @todo free lookup nodes once it's working. */
1651 stamR3LookupDecUsage(pCur->pLookup);
1652 stamR3LookupMaybeFree(pCur->pLookup);
1653 RTMemFree(pCur);
1654
1655 return VINF_SUCCESS;
1656}
1657
1658
1659/**
1660 * Deregisters a sample previously registered by STAR3Register() given its
1661 * address.
1662 *
1663 * This is intended used for devices which can be unplugged and for
1664 * temporary samples.
1665 *
1666 * @returns VBox status code.
1667 * @param pUVM Pointer to the user mode VM structure.
1668 * @param pvSample Pointer to the sample registered with STAMR3Register().
1669 */
1670VMMR3DECL(int) STAMR3DeregisterByAddr(PUVM pUVM, void *pvSample)
1671{
1672 UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE);
1673
1674 /* This is a complete waste of time when shutting down. */
1675 VMSTATE enmState = VMR3GetStateU(pUVM);
1676 if (enmState >= VMSTATE_DESTROYING)
1677 return VINF_SUCCESS;
1678
1679 STAM_LOCK_WR(pUVM);
1680
1681 /*
1682 * Search for it.
1683 */
1684 int rc = VERR_INVALID_HANDLE;
1685 PSTAMDESC pCur, pNext;
1686 RTListForEachSafe(&pUVM->stam.s.List, pCur, pNext, STAMDESC, ListEntry)
1687 {
1688 if (pCur->u.pv == pvSample)
1689 rc = stamR3DestroyDesc(pCur);
1690 }
1691
1692 STAM_UNLOCK_WR(pUVM);
1693 return rc;
1694}
1695
1696
1697/**
1698 * Worker for STAMR3Deregister, STAMR3DeregisterV and STAMR3DeregisterF.
1699 *
1700 * @returns VBox status code.
1701 * @retval VWRN_NOT_FOUND if no matching names found.
1702 *
1703 * @param pUVM Pointer to the user mode VM structure.
1704 * @param pszPat The name pattern.
1705 */
1706static int stamR3DeregisterByPattern(PUVM pUVM, const char *pszPat)
1707{
1708 Assert(!strchr(pszPat, '|')); /* single pattern! */
1709
1710 int rc = VWRN_NOT_FOUND;
1711 STAM_LOCK_WR(pUVM);
1712
1713 PSTAMDESC pLast;
1714 PSTAMDESC pCur = stamR3LookupFindPatternDescRange(pUVM->stam.s.pRoot, &pUVM->stam.s.List, pszPat, &pLast);
1715 if (pCur)
1716 {
1717 for (;;)
1718 {
1719 PSTAMDESC pNext = RTListNodeGetNext(&pCur->ListEntry, STAMDESC, ListEntry);
1720
1721 if (RTStrSimplePatternMatch(pszPat, pCur->pszName))
1722 rc = stamR3DestroyDesc(pCur);
1723
1724 /* advance. */
1725 if (pCur == pLast)
1726 break;
1727 pCur = pNext;
1728 }
1729 Assert(pLast);
1730 }
1731 else
1732 Assert(!pLast);
1733
1734 STAM_UNLOCK_WR(pUVM);
1735 return rc;
1736}
1737
1738
1739/**
1740 * Deregister zero or more samples given a (single) pattern matching their
1741 * names.
1742 *
1743 * @returns VBox status code.
1744 * @param pUVM Pointer to the user mode VM structure.
1745 * @param pszPat The name pattern.
1746 * @sa STAMR3DeregisterF, STAMR3DeregisterV
1747 */
1748VMMR3DECL(int) STAMR3Deregister(PUVM pUVM, const char *pszPat)
1749{
1750 UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE);
1751
1752 /* This is a complete waste of time when shutting down. */
1753 VMSTATE enmState = VMR3GetStateU(pUVM);
1754 if (enmState >= VMSTATE_DESTROYING)
1755 return VINF_SUCCESS;
1756
1757 return stamR3DeregisterByPattern(pUVM, pszPat);
1758}
1759
1760
1761/**
1762 * Deregister zero or more samples given a (single) pattern matching their
1763 * names.
1764 *
1765 * @returns VBox status code.
1766 * @param pUVM Pointer to the user mode VM structure.
1767 * @param pszPatFmt The name pattern format string.
1768 * @param ... Format string arguments.
1769 * @sa STAMR3Deregister, STAMR3DeregisterV
1770 */
1771VMMR3DECL(int) STAMR3DeregisterF(PUVM pUVM, const char *pszPatFmt, ...)
1772{
1773 va_list va;
1774 va_start(va, pszPatFmt);
1775 int rc = STAMR3DeregisterV(pUVM, pszPatFmt, va);
1776 va_end(va);
1777 return rc;
1778}
1779
1780
1781/**
1782 * Deregister zero or more samples given a (single) pattern matching their
1783 * names.
1784 *
1785 * @returns VBox status code.
1786 * @param pUVM Pointer to the user mode VM structure.
1787 * @param pszPatFmt The name pattern format string.
1788 * @param va Format string arguments.
1789 * @sa STAMR3Deregister, STAMR3DeregisterF
1790 */
1791VMMR3DECL(int) STAMR3DeregisterV(PUVM pUVM, const char *pszPatFmt, va_list va)
1792{
1793 UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE);
1794
1795 /* This is a complete waste of time when shutting down. */
1796 VMSTATE enmState = VMR3GetStateU(pUVM);
1797 if (enmState >= VMSTATE_DESTROYING)
1798 return VINF_SUCCESS;
1799
1800 char szPat[STAM_MAX_NAME_LEN + 8];
1801 size_t cchPat = RTStrPrintfV(szPat, sizeof(szPat), pszPatFmt, va);
1802 AssertReturn(cchPat <= STAM_MAX_NAME_LEN, VERR_OUT_OF_RANGE);
1803
1804 return stamR3DeregisterByPattern(pUVM, szPat);
1805}
1806
1807
1808/**
1809 * Deregister zero or more samples given their name prefix.
1810 *
1811 * @returns VBox status code.
1812 * @param pUVM Pointer to the user mode VM structure.
1813 * @param pszPrefix The name prefix of the samples to remove.
1814 * @sa STAMR3Deregister, STAMR3DeregisterF, STAMR3DeregisterV
1815 */
1816VMMR3DECL(int) STAMR3DeregisterByPrefix(PUVM pUVM, const char *pszPrefix)
1817{
1818 UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE);
1819
1820 /* This is a complete waste of time when shutting down. */
1821 VMSTATE enmState = VMR3GetStateU(pUVM);
1822 if (enmState >= VMSTATE_DESTROYING)
1823 return VINF_SUCCESS;
1824
1825 size_t const cchPrefix = strlen(pszPrefix);
1826 int rc = VWRN_NOT_FOUND;
1827 STAM_LOCK_WR(pUVM);
1828
1829 PSTAMDESC pLast;
1830 PSTAMDESC pCur = stamR3LookupFindByPrefixRange(pUVM->stam.s.pRoot, pszPrefix, (uint32_t)cchPrefix, &pLast);
1831 if (pCur)
1832 for (;;)
1833 {
1834 PSTAMDESC const pNext = RTListNodeGetNext(&pCur->ListEntry, STAMDESC, ListEntry);
1835 Assert(strncmp(pCur->pszName, pszPrefix, cchPrefix) == 0);
1836
1837 rc = stamR3DestroyDesc(pCur);
1838
1839 /* advance. */
1840 if (pCur == pLast)
1841 break;
1842 pCur = pNext;
1843 }
1844
1845 STAM_UNLOCK_WR(pUVM);
1846 return rc;
1847}
1848
1849
1850/**
1851 * Resets statistics for the specified VM.
1852 * It's possible to select a subset of the samples.
1853 *
1854 * @returns VBox status code. (Basically, it cannot fail.)
1855 * @param pUVM The user mode VM handle.
1856 * @param pszPat The name matching pattern. See somewhere_where_this_is_described_in_detail.
1857 * If NULL all samples are reset.
1858 * @remarks Don't confuse this with the other 'XYZR3Reset' methods, it's not called at VM reset.
1859 */
1860VMMR3DECL(int) STAMR3Reset(PUVM pUVM, const char *pszPat)
1861{
1862 UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE);
1863 VM_ASSERT_VALID_EXT_RETURN(pUVM->pVM, VERR_INVALID_VM_HANDLE);
1864
1865 int rc = VINF_SUCCESS;
1866
1867 /* ring-0 */
1868 GVMMRESETSTATISTICSSREQ GVMMReq;
1869 GMMRESETSTATISTICSSREQ GMMReq;
1870 bool fGVMMMatched = (!pszPat || !*pszPat) && !SUPR3IsDriverless();
1871 bool fGMMMatched = fGVMMMatched;
1872 if (fGVMMMatched)
1873 {
1874 memset(&GVMMReq.Stats, 0xff, sizeof(GVMMReq.Stats));
1875 memset(&GMMReq.Stats, 0xff, sizeof(GMMReq.Stats));
1876 }
1877 else
1878 {
1879 char *pszCopy;
1880 unsigned cExpressions;
1881 char **papszExpressions = stamR3SplitPattern(pszPat, &cExpressions, &pszCopy);
1882 if (!papszExpressions)
1883 return VERR_NO_MEMORY;
1884
1885 /* GVMM */
1886 RT_ZERO(GVMMReq.Stats);
1887 for (unsigned i = 0; i < RT_ELEMENTS(g_aGVMMStats); i++)
1888 if (stamR3MultiMatch(papszExpressions, cExpressions, NULL, g_aGVMMStats[i].pszName))
1889 {
1890 *((uint8_t *)&GVMMReq.Stats + g_aGVMMStats[i].offVar) = 0xff;
1891 fGVMMMatched = true;
1892 }
1893 if (!fGVMMMatched)
1894 {
1895 /** @todo match cpu leaves some rainy day. */
1896 }
1897
1898 /* GMM */
1899 RT_ZERO(GMMReq.Stats);
1900 for (unsigned i = 0; i < RT_ELEMENTS(g_aGMMStats); i++)
1901 if (stamR3MultiMatch(papszExpressions, cExpressions, NULL, g_aGMMStats[i].pszName))
1902 {
1903 *((uint8_t *)&GMMReq.Stats + g_aGMMStats[i].offVar) = 0xff;
1904 fGMMMatched = true;
1905 }
1906
1907 RTMemTmpFree(papszExpressions);
1908 RTStrFree(pszCopy);
1909 }
1910
1911 STAM_LOCK_WR(pUVM);
1912
1913 if (fGVMMMatched)
1914 {
1915 PVM pVM = pUVM->pVM;
1916 GVMMReq.Hdr.cbReq = sizeof(GVMMReq);
1917 GVMMReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC;
1918 GVMMReq.pSession = pVM->pSession;
1919 rc = SUPR3CallVMMR0Ex(VMCC_GET_VMR0_FOR_CALL(pVM), NIL_VMCPUID, VMMR0_DO_GVMM_RESET_STATISTICS, 0, &GVMMReq.Hdr);
1920 }
1921
1922 if (fGMMMatched)
1923 {
1924 PVM pVM = pUVM->pVM;
1925 GMMReq.Hdr.cbReq = sizeof(GMMReq);
1926 GMMReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC;
1927 GMMReq.pSession = pVM->pSession;
1928 rc = SUPR3CallVMMR0Ex(VMCC_GET_VMR0_FOR_CALL(pVM), NIL_VMCPUID, VMMR0_DO_GMM_RESET_STATISTICS, 0, &GMMReq.Hdr);
1929 }
1930
1931 /* and the reset */
1932 stamR3EnumU(pUVM, pszPat, false /* fUpdateRing0 */, stamR3ResetOne, pUVM->pVM);
1933
1934 STAM_UNLOCK_WR(pUVM);
1935 return rc;
1936}
1937
1938
1939/**
1940 * Resets one statistics sample.
1941 * Callback for stamR3EnumU().
1942 *
1943 * @returns VINF_SUCCESS
1944 * @param pDesc Pointer to the current descriptor.
1945 * @param pvArg User argument - Pointer to the VM.
1946 */
1947static int stamR3ResetOne(PSTAMDESC pDesc, void *pvArg)
1948{
1949 switch (pDesc->enmType)
1950 {
1951 case STAMTYPE_COUNTER:
1952 ASMAtomicXchgU64(&pDesc->u.pCounter->c, 0);
1953 break;
1954
1955 case STAMTYPE_PROFILE:
1956 case STAMTYPE_PROFILE_ADV:
1957 ASMAtomicXchgU64(&pDesc->u.pProfile->cPeriods, 0);
1958 ASMAtomicXchgU64(&pDesc->u.pProfile->cTicks, 0);
1959 ASMAtomicXchgU64(&pDesc->u.pProfile->cTicksMax, 0);
1960 ASMAtomicXchgU64(&pDesc->u.pProfile->cTicksMin, UINT64_MAX);
1961 break;
1962
1963 case STAMTYPE_RATIO_U32_RESET:
1964 ASMAtomicXchgU32(&pDesc->u.pRatioU32->u32A, 0);
1965 ASMAtomicXchgU32(&pDesc->u.pRatioU32->u32B, 0);
1966 break;
1967
1968 case STAMTYPE_CALLBACK:
1969 if (pDesc->u.Callback.pfnReset)
1970 pDesc->u.Callback.pfnReset((PVM)pvArg, pDesc->u.Callback.pvSample);
1971 break;
1972
1973 case STAMTYPE_U8_RESET:
1974 case STAMTYPE_X8_RESET:
1975 ASMAtomicXchgU8(pDesc->u.pu8, 0);
1976 break;
1977
1978 case STAMTYPE_U16_RESET:
1979 case STAMTYPE_X16_RESET:
1980 ASMAtomicXchgU16(pDesc->u.pu16, 0);
1981 break;
1982
1983 case STAMTYPE_U32_RESET:
1984 case STAMTYPE_X32_RESET:
1985 ASMAtomicXchgU32(pDesc->u.pu32, 0);
1986 break;
1987
1988 case STAMTYPE_U64_RESET:
1989 case STAMTYPE_X64_RESET:
1990 ASMAtomicXchgU64(pDesc->u.pu64, 0);
1991 break;
1992
1993 case STAMTYPE_BOOL_RESET:
1994 ASMAtomicXchgBool(pDesc->u.pf, false);
1995 break;
1996
1997 /* These are custom and will not be touched. */
1998 case STAMTYPE_U8:
1999 case STAMTYPE_X8:
2000 case STAMTYPE_U16:
2001 case STAMTYPE_X16:
2002 case STAMTYPE_U32:
2003 case STAMTYPE_X32:
2004 case STAMTYPE_U64:
2005 case STAMTYPE_X64:
2006 case STAMTYPE_RATIO_U32:
2007 case STAMTYPE_BOOL:
2008 break;
2009
2010 default:
2011 AssertMsgFailed(("enmType=%d\n", pDesc->enmType));
2012 break;
2013 }
2014 NOREF(pvArg);
2015 return VINF_SUCCESS;
2016}
2017
2018
2019/**
2020 * Get a snapshot of the statistics.
2021 * It's possible to select a subset of the samples.
2022 *
2023 * @returns VBox status code. (Basically, it cannot fail.)
2024 * @param pUVM The user mode VM handle.
2025 * @param pszPat The name matching pattern. See somewhere_where_this_is_described_in_detail.
2026 * If NULL all samples are reset.
2027 * @param fWithDesc Whether to include the descriptions.
2028 * @param ppszSnapshot Where to store the pointer to the snapshot data.
2029 * The format of the snapshot should be XML, but that will have to be discussed
2030 * when this function is implemented.
2031 * The returned pointer must be freed by calling STAMR3SnapshotFree().
2032 * @param pcchSnapshot Where to store the size of the snapshot data. (Excluding the trailing '\0')
2033 */
2034VMMR3DECL(int) STAMR3Snapshot(PUVM pUVM, const char *pszPat, char **ppszSnapshot, size_t *pcchSnapshot, bool fWithDesc)
2035{
2036 UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE);
2037 VM_ASSERT_VALID_EXT_RETURN(pUVM->pVM, VERR_INVALID_VM_HANDLE);
2038
2039 STAMR3SNAPSHOTONE State = { NULL, NULL, NULL, pUVM->pVM, 0, VINF_SUCCESS, fWithDesc };
2040
2041 /*
2042 * Write the XML header.
2043 */
2044 /** @todo Make this proper & valid XML. */
2045 stamR3SnapshotPrintf(&State, "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n");
2046
2047 /*
2048 * Write the content.
2049 */
2050 stamR3SnapshotPrintf(&State, "<Statistics>\n");
2051 int rc = stamR3EnumU(pUVM, pszPat, true /* fUpdateRing0 */, stamR3SnapshotOne, &State);
2052 stamR3SnapshotPrintf(&State, "</Statistics>\n");
2053
2054 if (RT_SUCCESS(rc))
2055 rc = State.rc;
2056 else
2057 {
2058 RTMemFree(State.pszStart);
2059 State.pszStart = State.pszEnd = State.psz = NULL;
2060 State.cbAllocated = 0;
2061 }
2062
2063 /*
2064 * Done.
2065 */
2066 *ppszSnapshot = State.pszStart;
2067 if (pcchSnapshot)
2068 *pcchSnapshot = State.psz - State.pszStart;
2069 return rc;
2070}
2071
2072
2073/**
2074 * stamR3EnumU callback employed by STAMR3Snapshot.
2075 *
2076 * @returns VBox status code, but it's interpreted as 0 == success / !0 == failure by enmR3Enum.
2077 * @param pDesc The sample.
2078 * @param pvArg The snapshot status structure.
2079 */
2080static int stamR3SnapshotOne(PSTAMDESC pDesc, void *pvArg)
2081{
2082 PSTAMR3SNAPSHOTONE pThis = (PSTAMR3SNAPSHOTONE)pvArg;
2083
2084 switch (pDesc->enmType)
2085 {
2086 case STAMTYPE_COUNTER:
2087 if (pDesc->enmVisibility == STAMVISIBILITY_USED && pDesc->u.pCounter->c == 0)
2088 return VINF_SUCCESS;
2089 stamR3SnapshotPrintf(pThis, "<Counter c=\"%lld\"", pDesc->u.pCounter->c);
2090 break;
2091
2092 case STAMTYPE_PROFILE:
2093 case STAMTYPE_PROFILE_ADV:
2094 if (pDesc->enmVisibility == STAMVISIBILITY_USED && pDesc->u.pProfile->cPeriods == 0)
2095 return VINF_SUCCESS;
2096 stamR3SnapshotPrintf(pThis, "<Profile cPeriods=\"%lld\" cTicks=\"%lld\" cTicksMin=\"%lld\" cTicksMax=\"%lld\"",
2097 pDesc->u.pProfile->cPeriods, pDesc->u.pProfile->cTicks, pDesc->u.pProfile->cTicksMin,
2098 pDesc->u.pProfile->cTicksMax);
2099 break;
2100
2101 case STAMTYPE_RATIO_U32:
2102 case STAMTYPE_RATIO_U32_RESET:
2103 if (pDesc->enmVisibility == STAMVISIBILITY_USED && !pDesc->u.pRatioU32->u32A && !pDesc->u.pRatioU32->u32B)
2104 return VINF_SUCCESS;
2105 stamR3SnapshotPrintf(pThis, "<Ratio32 u32A=\"%lld\" u32B=\"%lld\"",
2106 pDesc->u.pRatioU32->u32A, pDesc->u.pRatioU32->u32B);
2107 break;
2108
2109 case STAMTYPE_CALLBACK:
2110 {
2111 char szBuf[512];
2112 pDesc->u.Callback.pfnPrint(pThis->pVM, pDesc->u.Callback.pvSample, szBuf, sizeof(szBuf));
2113 stamR3SnapshotPrintf(pThis, "<Callback val=\"%s\"", szBuf);
2114 break;
2115 }
2116
2117 case STAMTYPE_U8:
2118 case STAMTYPE_U8_RESET:
2119 if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu8 == 0)
2120 return VINF_SUCCESS;
2121 stamR3SnapshotPrintf(pThis, "<U8 val=\"%u\"", *pDesc->u.pu8);
2122 break;
2123
2124 case STAMTYPE_X8:
2125 case STAMTYPE_X8_RESET:
2126 if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu8 == 0)
2127 return VINF_SUCCESS;
2128 stamR3SnapshotPrintf(pThis, "<X8 val=\"%#x\"", *pDesc->u.pu8);
2129 break;
2130
2131 case STAMTYPE_U16:
2132 case STAMTYPE_U16_RESET:
2133 if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu16 == 0)
2134 return VINF_SUCCESS;
2135 stamR3SnapshotPrintf(pThis, "<U16 val=\"%u\"", *pDesc->u.pu16);
2136 break;
2137
2138 case STAMTYPE_X16:
2139 case STAMTYPE_X16_RESET:
2140 if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu16 == 0)
2141 return VINF_SUCCESS;
2142 stamR3SnapshotPrintf(pThis, "<X16 val=\"%#x\"", *pDesc->u.pu16);
2143 break;
2144
2145 case STAMTYPE_U32:
2146 case STAMTYPE_U32_RESET:
2147 if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu32 == 0)
2148 return VINF_SUCCESS;
2149 stamR3SnapshotPrintf(pThis, "<U32 val=\"%u\"", *pDesc->u.pu32);
2150 break;
2151
2152 case STAMTYPE_X32:
2153 case STAMTYPE_X32_RESET:
2154 if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu32 == 0)
2155 return VINF_SUCCESS;
2156 stamR3SnapshotPrintf(pThis, "<X32 val=\"%#x\"", *pDesc->u.pu32);
2157 break;
2158
2159 case STAMTYPE_U64:
2160 case STAMTYPE_U64_RESET:
2161 if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu64 == 0)
2162 return VINF_SUCCESS;
2163 stamR3SnapshotPrintf(pThis, "<U64 val=\"%llu\"", *pDesc->u.pu64);
2164 break;
2165
2166 case STAMTYPE_X64:
2167 case STAMTYPE_X64_RESET:
2168 if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu64 == 0)
2169 return VINF_SUCCESS;
2170 stamR3SnapshotPrintf(pThis, "<X64 val=\"%#llx\"", *pDesc->u.pu64);
2171 break;
2172
2173 case STAMTYPE_BOOL:
2174 case STAMTYPE_BOOL_RESET:
2175 if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pf == false)
2176 return VINF_SUCCESS;
2177 stamR3SnapshotPrintf(pThis, "<BOOL val=\"%RTbool\"", *pDesc->u.pf);
2178 break;
2179
2180 default:
2181 AssertMsgFailed(("%d\n", pDesc->enmType));
2182 return 0;
2183 }
2184
2185 stamR3SnapshotPrintf(pThis, " unit=\"%s\"", STAMR3GetUnit(pDesc->enmUnit));
2186
2187 switch (pDesc->enmVisibility)
2188 {
2189 default:
2190 case STAMVISIBILITY_ALWAYS:
2191 break;
2192 case STAMVISIBILITY_USED:
2193 stamR3SnapshotPrintf(pThis, " vis=\"used\"");
2194 break;
2195 case STAMVISIBILITY_NOT_GUI:
2196 stamR3SnapshotPrintf(pThis, " vis=\"not-gui\"");
2197 break;
2198 }
2199
2200 stamR3SnapshotPrintf(pThis, " name=\"%s\"", pDesc->pszName);
2201
2202 if (pThis->fWithDesc && pDesc->pszDesc)
2203 {
2204 /*
2205 * The description is a bit tricky as it may include chars that
2206 * xml requires to be escaped.
2207 */
2208 const char *pszBadChar = strpbrk(pDesc->pszDesc, "&<>\"'");
2209 if (!pszBadChar)
2210 return stamR3SnapshotPrintf(pThis, " desc=\"%s\"/>\n", pDesc->pszDesc);
2211
2212 stamR3SnapshotPrintf(pThis, " desc=\"");
2213 const char *pszCur = pDesc->pszDesc;
2214 do
2215 {
2216 stamR3SnapshotPrintf(pThis, "%.*s", pszBadChar - pszCur, pszCur);
2217 switch (*pszBadChar)
2218 {
2219 case '&': stamR3SnapshotPrintf(pThis, "&amp;"); break;
2220 case '<': stamR3SnapshotPrintf(pThis, "&lt;"); break;
2221 case '>': stamR3SnapshotPrintf(pThis, "&gt;"); break;
2222 case '"': stamR3SnapshotPrintf(pThis, "&quot;"); break;
2223 case '\'': stamR3SnapshotPrintf(pThis, "&apos;"); break;
2224 default: AssertMsgFailed(("%c", *pszBadChar)); break;
2225 }
2226 pszCur = pszBadChar + 1;
2227 pszBadChar = strpbrk(pszCur, "&<>\"'");
2228 } while (pszBadChar);
2229 return stamR3SnapshotPrintf(pThis, "%s\"/>\n", pszCur);
2230 }
2231 return stamR3SnapshotPrintf(pThis, "/>\n");
2232}
2233
2234
2235/**
2236 * Output callback for stamR3SnapshotPrintf.
2237 *
2238 * @returns number of bytes written.
2239 * @param pvArg The snapshot status structure.
2240 * @param pach Pointer to an array of characters (bytes).
2241 * @param cch The number or chars (bytes) to write from the array.
2242 */
2243static DECLCALLBACK(size_t) stamR3SnapshotOutput(void *pvArg, const char *pach, size_t cch)
2244{
2245 PSTAMR3SNAPSHOTONE pThis = (PSTAMR3SNAPSHOTONE)pvArg;
2246
2247 /*
2248 * Make sure we've got space for it.
2249 */
2250 if (RT_UNLIKELY((uintptr_t)pThis->pszEnd - (uintptr_t)pThis->psz < cch + 1))
2251 {
2252 if (RT_FAILURE(pThis->rc))
2253 return 0;
2254
2255 size_t cbNewSize = pThis->cbAllocated;
2256 if (cbNewSize > cch)
2257 cbNewSize *= 2;
2258 else
2259 cbNewSize += RT_ALIGN(cch + 1, 0x1000);
2260 char *pszNew = (char *)RTMemRealloc(pThis->pszStart, cbNewSize);
2261 if (!pszNew)
2262 {
2263 /*
2264 * Free up immediately, out-of-memory is bad news and this
2265 * isn't an important allocations / API.
2266 */
2267 pThis->rc = VERR_NO_MEMORY;
2268 RTMemFree(pThis->pszStart);
2269 pThis->pszStart = pThis->pszEnd = pThis->psz = NULL;
2270 pThis->cbAllocated = 0;
2271 return 0;
2272 }
2273
2274 pThis->psz = pszNew + (pThis->psz - pThis->pszStart);
2275 pThis->pszStart = pszNew;
2276 pThis->pszEnd = pszNew + cbNewSize;
2277 pThis->cbAllocated = cbNewSize;
2278 }
2279
2280 /*
2281 * Copy the chars to the buffer and terminate it.
2282 */
2283 if (cch)
2284 {
2285 memcpy(pThis->psz, pach, cch);
2286 pThis->psz += cch;
2287 }
2288 *pThis->psz = '\0';
2289 return cch;
2290}
2291
2292
2293/**
2294 * Wrapper around RTStrFormatV for use by the snapshot API.
2295 *
2296 * @returns VBox status code.
2297 * @param pThis The snapshot status structure.
2298 * @param pszFormat The format string.
2299 * @param ... Optional arguments.
2300 */
2301static int stamR3SnapshotPrintf(PSTAMR3SNAPSHOTONE pThis, const char *pszFormat, ...)
2302{
2303 va_list va;
2304 va_start(va, pszFormat);
2305 RTStrFormatV(stamR3SnapshotOutput, pThis, NULL, NULL, pszFormat, va);
2306 va_end(va);
2307 return pThis->rc;
2308}
2309
2310
2311/**
2312 * Releases a statistics snapshot returned by STAMR3Snapshot().
2313 *
2314 * @returns VBox status code.
2315 * @param pUVM The user mode VM handle.
2316 * @param pszSnapshot The snapshot data pointer returned by STAMR3Snapshot().
2317 * NULL is allowed.
2318 */
2319VMMR3DECL(int) STAMR3SnapshotFree(PUVM pUVM, char *pszSnapshot)
2320{
2321 if (pszSnapshot)
2322 RTMemFree(pszSnapshot);
2323 NOREF(pUVM);
2324 return VINF_SUCCESS;
2325}
2326
2327
2328/**
2329 * Dumps the selected statistics to the log.
2330 *
2331 * @returns VBox status code.
2332 * @param pUVM Pointer to the user mode VM structure.
2333 * @param pszPat The name matching pattern. See somewhere_where_this_is_described_in_detail.
2334 * If NULL all samples are written to the log.
2335 */
2336VMMR3DECL(int) STAMR3Dump(PUVM pUVM, const char *pszPat)
2337{
2338 UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE);
2339 VM_ASSERT_VALID_EXT_RETURN(pUVM->pVM, VERR_INVALID_VM_HANDLE);
2340
2341 STAMR3PRINTONEARGS Args;
2342 Args.pUVM = pUVM;
2343 Args.pvArg = NULL;
2344 Args.pfnPrintf = stamR3EnumLogPrintf;
2345
2346 stamR3EnumU(pUVM, pszPat, true /* fUpdateRing0 */, stamR3PrintOne, &Args);
2347 return VINF_SUCCESS;
2348}
2349
2350
2351/**
2352 * Prints to the log.
2353 *
2354 * @param pArgs Pointer to the print one argument structure.
2355 * @param pszFormat Format string.
2356 * @param ... Format arguments.
2357 */
2358static DECLCALLBACK(void) stamR3EnumLogPrintf(PSTAMR3PRINTONEARGS pArgs, const char *pszFormat, ...)
2359{
2360 va_list va;
2361 va_start(va, pszFormat);
2362 RTLogPrintfV(pszFormat, va);
2363 va_end(va);
2364 NOREF(pArgs);
2365}
2366
2367
2368/**
2369 * Dumps the selected statistics to the release log.
2370 *
2371 * @returns VBox status code.
2372 * @param pUVM Pointer to the user mode VM structure.
2373 * @param pszPat The name matching pattern. See somewhere_where_this_is_described_in_detail.
2374 * If NULL all samples are written to the log.
2375 */
2376VMMR3DECL(int) STAMR3DumpToReleaseLog(PUVM pUVM, const char *pszPat)
2377{
2378 UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE);
2379 VM_ASSERT_VALID_EXT_RETURN(pUVM->pVM, VERR_INVALID_VM_HANDLE);
2380
2381 STAMR3PRINTONEARGS Args;
2382 Args.pUVM = pUVM;
2383 Args.pvArg = NULL;
2384 Args.pfnPrintf = stamR3EnumRelLogPrintf;
2385
2386 stamR3EnumU(pUVM, pszPat, true /* fUpdateRing0 */, stamR3PrintOne, &Args);
2387 return VINF_SUCCESS;
2388}
2389
2390/**
2391 * Prints to the release log.
2392 *
2393 * @param pArgs Pointer to the print one argument structure.
2394 * @param pszFormat Format string.
2395 * @param ... Format arguments.
2396 */
2397static DECLCALLBACK(void) stamR3EnumRelLogPrintf(PSTAMR3PRINTONEARGS pArgs, const char *pszFormat, ...)
2398{
2399 va_list va;
2400 va_start(va, pszFormat);
2401 RTLogRelPrintfV(pszFormat, va);
2402 va_end(va);
2403 NOREF(pArgs);
2404}
2405
2406
2407/**
2408 * Prints the selected statistics to standard out.
2409 *
2410 * @returns VBox status code.
2411 * @param pUVM The user mode VM handle.
2412 * @param pszPat The name matching pattern. See somewhere_where_this_is_described_in_detail.
2413 * If NULL all samples are reset.
2414 */
2415VMMR3DECL(int) STAMR3Print(PUVM pUVM, const char *pszPat)
2416{
2417 UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE);
2418 VM_ASSERT_VALID_EXT_RETURN(pUVM->pVM, VERR_INVALID_VM_HANDLE);
2419
2420 STAMR3PRINTONEARGS Args;
2421 Args.pUVM = pUVM;
2422 Args.pvArg = NULL;
2423 Args.pfnPrintf = stamR3EnumPrintf;
2424
2425 stamR3EnumU(pUVM, pszPat, true /* fUpdateRing0 */, stamR3PrintOne, &Args);
2426 return VINF_SUCCESS;
2427}
2428
2429
2430/**
2431 * Prints to stdout.
2432 *
2433 * @param pArgs Pointer to the print one argument structure.
2434 * @param pszFormat Format string.
2435 * @param ... Format arguments.
2436 */
2437static DECLCALLBACK(void) stamR3EnumPrintf(PSTAMR3PRINTONEARGS pArgs, const char *pszFormat, ...)
2438{
2439 va_list va;
2440 va_start(va, pszFormat);
2441 RTPrintfV(pszFormat, va);
2442 va_end(va);
2443 NOREF(pArgs);
2444}
2445
2446
2447/**
2448 * Prints one sample.
2449 * Callback for stamR3EnumU().
2450 *
2451 * @returns VINF_SUCCESS
2452 * @param pDesc Pointer to the current descriptor.
2453 * @param pvArg User argument - STAMR3PRINTONEARGS.
2454 */
2455static int stamR3PrintOne(PSTAMDESC pDesc, void *pvArg)
2456{
2457 PSTAMR3PRINTONEARGS pArgs = (PSTAMR3PRINTONEARGS)pvArg;
2458
2459 switch (pDesc->enmType)
2460 {
2461 case STAMTYPE_COUNTER:
2462 if (pDesc->enmVisibility == STAMVISIBILITY_USED && pDesc->u.pCounter->c == 0)
2463 return VINF_SUCCESS;
2464
2465 pArgs->pfnPrintf(pArgs, "%-32s %8llu %s\n", pDesc->pszName, pDesc->u.pCounter->c, STAMR3GetUnit(pDesc->enmUnit));
2466 break;
2467
2468 case STAMTYPE_PROFILE:
2469 case STAMTYPE_PROFILE_ADV:
2470 {
2471 if (pDesc->enmVisibility == STAMVISIBILITY_USED && pDesc->u.pProfile->cPeriods == 0)
2472 return VINF_SUCCESS;
2473
2474 uint64_t u64 = pDesc->u.pProfile->cPeriods ? pDesc->u.pProfile->cPeriods : 1;
2475 pArgs->pfnPrintf(pArgs, "%-32s %8llu %s (%12llu %s, %7llu %s, max %9llu, min %7lld)\n", pDesc->pszName,
2476 pDesc->u.pProfile->cTicks / u64, STAMR3GetUnit(pDesc->enmUnit),
2477 pDesc->u.pProfile->cTicks, STAMR3GetUnit1(pDesc->enmUnit),
2478 pDesc->u.pProfile->cPeriods, STAMR3GetUnit2(pDesc->enmUnit),
2479 pDesc->u.pProfile->cTicksMax, pDesc->u.pProfile->cTicksMin);
2480 break;
2481 }
2482
2483 case STAMTYPE_RATIO_U32:
2484 case STAMTYPE_RATIO_U32_RESET:
2485 if (pDesc->enmVisibility == STAMVISIBILITY_USED && !pDesc->u.pRatioU32->u32A && !pDesc->u.pRatioU32->u32B)
2486 return VINF_SUCCESS;
2487 pArgs->pfnPrintf(pArgs, "%-32s %8u:%-8u %s\n", pDesc->pszName,
2488 pDesc->u.pRatioU32->u32A, pDesc->u.pRatioU32->u32B, STAMR3GetUnit(pDesc->enmUnit));
2489 break;
2490
2491 case STAMTYPE_CALLBACK:
2492 {
2493 char szBuf[512];
2494 pDesc->u.Callback.pfnPrint(pArgs->pUVM->pVM, pDesc->u.Callback.pvSample, szBuf, sizeof(szBuf));
2495 pArgs->pfnPrintf(pArgs, "%-32s %s %s\n", pDesc->pszName, szBuf, STAMR3GetUnit(pDesc->enmUnit));
2496 break;
2497 }
2498
2499 case STAMTYPE_U8:
2500 case STAMTYPE_U8_RESET:
2501 if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu8 == 0)
2502 return VINF_SUCCESS;
2503 pArgs->pfnPrintf(pArgs, "%-32s %8u %s\n", pDesc->pszName, *pDesc->u.pu8, STAMR3GetUnit(pDesc->enmUnit));
2504 break;
2505
2506 case STAMTYPE_X8:
2507 case STAMTYPE_X8_RESET:
2508 if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu8 == 0)
2509 return VINF_SUCCESS;
2510 pArgs->pfnPrintf(pArgs, "%-32s %8x %s\n", pDesc->pszName, *pDesc->u.pu8, STAMR3GetUnit(pDesc->enmUnit));
2511 break;
2512
2513 case STAMTYPE_U16:
2514 case STAMTYPE_U16_RESET:
2515 if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu16 == 0)
2516 return VINF_SUCCESS;
2517 pArgs->pfnPrintf(pArgs, "%-32s %8u %s\n", pDesc->pszName, *pDesc->u.pu16, STAMR3GetUnit(pDesc->enmUnit));
2518 break;
2519
2520 case STAMTYPE_X16:
2521 case STAMTYPE_X16_RESET:
2522 if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu16 == 0)
2523 return VINF_SUCCESS;
2524 pArgs->pfnPrintf(pArgs, "%-32s %8x %s\n", pDesc->pszName, *pDesc->u.pu16, STAMR3GetUnit(pDesc->enmUnit));
2525 break;
2526
2527 case STAMTYPE_U32:
2528 case STAMTYPE_U32_RESET:
2529 if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu32 == 0)
2530 return VINF_SUCCESS;
2531 pArgs->pfnPrintf(pArgs, "%-32s %8u %s\n", pDesc->pszName, *pDesc->u.pu32, STAMR3GetUnit(pDesc->enmUnit));
2532 break;
2533
2534 case STAMTYPE_X32:
2535 case STAMTYPE_X32_RESET:
2536 if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu32 == 0)
2537 return VINF_SUCCESS;
2538 pArgs->pfnPrintf(pArgs, "%-32s %8x %s\n", pDesc->pszName, *pDesc->u.pu32, STAMR3GetUnit(pDesc->enmUnit));
2539 break;
2540
2541 case STAMTYPE_U64:
2542 case STAMTYPE_U64_RESET:
2543 if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu64 == 0)
2544 return VINF_SUCCESS;
2545 pArgs->pfnPrintf(pArgs, "%-32s %8llu %s\n", pDesc->pszName, *pDesc->u.pu64, STAMR3GetUnit(pDesc->enmUnit));
2546 break;
2547
2548 case STAMTYPE_X64:
2549 case STAMTYPE_X64_RESET:
2550 if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu64 == 0)
2551 return VINF_SUCCESS;
2552 pArgs->pfnPrintf(pArgs, "%-32s %8llx %s\n", pDesc->pszName, *pDesc->u.pu64, STAMR3GetUnit(pDesc->enmUnit));
2553 break;
2554
2555 case STAMTYPE_BOOL:
2556 case STAMTYPE_BOOL_RESET:
2557 if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pf == false)
2558 return VINF_SUCCESS;
2559 pArgs->pfnPrintf(pArgs, "%-32s %s %s\n", pDesc->pszName, *pDesc->u.pf ? "true " : "false ", STAMR3GetUnit(pDesc->enmUnit));
2560 break;
2561
2562 default:
2563 AssertMsgFailed(("enmType=%d\n", pDesc->enmType));
2564 break;
2565 }
2566 NOREF(pvArg);
2567 return VINF_SUCCESS;
2568}
2569
2570
2571/**
2572 * Enumerate the statistics by the means of a callback function.
2573 *
2574 * @returns Whatever the callback returns.
2575 *
2576 * @param pUVM The user mode VM handle.
2577 * @param pszPat The pattern to match samples.
2578 * @param pfnEnum The callback function.
2579 * @param pvUser The pvUser argument of the callback function.
2580 */
2581VMMR3DECL(int) STAMR3Enum(PUVM pUVM, const char *pszPat, PFNSTAMR3ENUM pfnEnum, void *pvUser)
2582{
2583 UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE);
2584 VM_ASSERT_VALID_EXT_RETURN(pUVM->pVM, VERR_INVALID_VM_HANDLE);
2585
2586 STAMR3ENUMONEARGS Args;
2587 Args.pVM = pUVM->pVM;
2588 Args.pfnEnum = pfnEnum;
2589 Args.pvUser = pvUser;
2590
2591 return stamR3EnumU(pUVM, pszPat, true /* fUpdateRing0 */, stamR3EnumOne, &Args);
2592}
2593
2594
2595/**
2596 * Callback function for STARTR3Enum().
2597 *
2598 * @returns whatever the callback returns.
2599 * @param pDesc Pointer to the current descriptor.
2600 * @param pvArg Points to a STAMR3ENUMONEARGS structure.
2601 */
2602static int stamR3EnumOne(PSTAMDESC pDesc, void *pvArg)
2603{
2604 PSTAMR3ENUMONEARGS pArgs = (PSTAMR3ENUMONEARGS)pvArg;
2605 const char *pszUnit = STAMR3GetUnit(pDesc->enmUnit);
2606 int rc;
2607 if (pDesc->enmType == STAMTYPE_CALLBACK)
2608 {
2609 /* Give the enumerator something useful. */
2610 char szBuf[512];
2611 pDesc->u.Callback.pfnPrint(pArgs->pVM, pDesc->u.Callback.pvSample, szBuf, sizeof(szBuf));
2612 rc = pArgs->pfnEnum(pDesc->pszName, pDesc->enmType, szBuf, pDesc->enmUnit, pszUnit,
2613 pDesc->enmVisibility, pDesc->pszDesc, pArgs->pvUser);
2614 }
2615 else
2616 rc = pArgs->pfnEnum(pDesc->pszName, pDesc->enmType, pDesc->u.pv, pDesc->enmUnit, pszUnit,
2617 pDesc->enmVisibility, pDesc->pszDesc, pArgs->pvUser);
2618 return rc;
2619}
2620
2621static void stamR3RefreshGroup(PUVM pUVM, uint8_t iRefreshGroup, uint64_t *pbmRefreshedGroups)
2622{
2623 *pbmRefreshedGroups |= RT_BIT_64(iRefreshGroup);
2624
2625 PVM pVM = pUVM->pVM;
2626 if (pVM && pVM->pSession)
2627 {
2628 switch (iRefreshGroup)
2629 {
2630 /*
2631 * GVMM
2632 */
2633 case STAM_REFRESH_GRP_GVMM:
2634 {
2635 GVMMQUERYSTATISTICSSREQ Req;
2636 Req.Hdr.cbReq = sizeof(Req);
2637 Req.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC;
2638 Req.pSession = pVM->pSession;
2639 int rc = SUPR3CallVMMR0Ex(VMCC_GET_VMR0_FOR_CALL(pVM), NIL_VMCPUID, VMMR0_DO_GVMM_QUERY_STATISTICS, 0, &Req.Hdr);
2640 if (RT_SUCCESS(rc))
2641 {
2642 pUVM->stam.s.GVMMStats = Req.Stats;
2643
2644 /*
2645 * Check if the number of host CPUs has changed (it will the first
2646 * time around and normally never again).
2647 */
2648 if (RT_UNLIKELY(pUVM->stam.s.GVMMStats.cHostCpus > pUVM->stam.s.cRegisteredHostCpus))
2649 {
2650 if (RT_UNLIKELY(pUVM->stam.s.GVMMStats.cHostCpus > pUVM->stam.s.cRegisteredHostCpus))
2651 {
2652 STAM_UNLOCK_RD(pUVM);
2653 STAM_LOCK_WR(pUVM);
2654 uint32_t cCpus = pUVM->stam.s.GVMMStats.cHostCpus;
2655 for (uint32_t iCpu = pUVM->stam.s.cRegisteredHostCpus; iCpu < cCpus; iCpu++)
2656 {
2657 char szName[120];
2658 size_t cchBase = RTStrPrintf(szName, sizeof(szName), "/GVMM/HostCpus/%u", iCpu);
2659 stamR3RegisterU(pUVM, &pUVM->stam.s.GVMMStats.aHostCpus[iCpu].idCpu, NULL, NULL,
2660 STAMTYPE_U32, STAMVISIBILITY_ALWAYS, szName, STAMUNIT_NONE,
2661 "Host CPU ID", STAM_REFRESH_GRP_GVMM);
2662 strcpy(&szName[cchBase], "/idxCpuSet");
2663 stamR3RegisterU(pUVM, &pUVM->stam.s.GVMMStats.aHostCpus[iCpu].idxCpuSet, NULL, NULL,
2664 STAMTYPE_U32, STAMVISIBILITY_ALWAYS, szName, STAMUNIT_NONE,
2665 "CPU Set index", STAM_REFRESH_GRP_GVMM);
2666 strcpy(&szName[cchBase], "/DesiredHz");
2667 stamR3RegisterU(pUVM, &pUVM->stam.s.GVMMStats.aHostCpus[iCpu].uDesiredHz, NULL, NULL,
2668 STAMTYPE_U32, STAMVISIBILITY_ALWAYS, szName, STAMUNIT_HZ,
2669 "The desired frequency", STAM_REFRESH_GRP_GVMM);
2670 strcpy(&szName[cchBase], "/CurTimerHz");
2671 stamR3RegisterU(pUVM, &pUVM->stam.s.GVMMStats.aHostCpus[iCpu].uTimerHz, NULL, NULL,
2672 STAMTYPE_U32, STAMVISIBILITY_ALWAYS, szName, STAMUNIT_HZ,
2673 "The current timer frequency", STAM_REFRESH_GRP_GVMM);
2674 strcpy(&szName[cchBase], "/PPTChanges");
2675 stamR3RegisterU(pUVM, &pUVM->stam.s.GVMMStats.aHostCpus[iCpu].cChanges, NULL, NULL,
2676 STAMTYPE_U32, STAMVISIBILITY_ALWAYS, szName, STAMUNIT_OCCURENCES,
2677 "RTTimerChangeInterval calls", STAM_REFRESH_GRP_GVMM);
2678 strcpy(&szName[cchBase], "/PPTStarts");
2679 stamR3RegisterU(pUVM, &pUVM->stam.s.GVMMStats.aHostCpus[iCpu].cStarts, NULL, NULL,
2680 STAMTYPE_U32, STAMVISIBILITY_ALWAYS, szName, STAMUNIT_OCCURENCES,
2681 "RTTimerStart calls", STAM_REFRESH_GRP_GVMM);
2682 }
2683 pUVM->stam.s.cRegisteredHostCpus = cCpus;
2684 STAM_UNLOCK_WR(pUVM);
2685 STAM_LOCK_RD(pUVM);
2686 }
2687 }
2688 }
2689 break;
2690 }
2691
2692 /*
2693 * GMM
2694 */
2695 case STAM_REFRESH_GRP_GMM:
2696 {
2697 GMMQUERYSTATISTICSSREQ Req;
2698 Req.Hdr.cbReq = sizeof(Req);
2699 Req.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC;
2700 Req.pSession = pVM->pSession;
2701 int rc = SUPR3CallVMMR0Ex(VMCC_GET_VMR0_FOR_CALL(pVM), NIL_VMCPUID, VMMR0_DO_GMM_QUERY_STATISTICS, 0, &Req.Hdr);
2702 if (RT_SUCCESS(rc))
2703 pUVM->stam.s.GMMStats = Req.Stats;
2704 break;
2705 }
2706
2707 /*
2708 * NEM.
2709 */
2710 case STAM_REFRESH_GRP_NEM:
2711 SUPR3CallVMMR0(VMCC_GET_VMR0_FOR_CALL(pVM), NIL_VMCPUID, VMMR0_DO_NEM_UPDATE_STATISTICS, NULL);
2712 break;
2713
2714 default:
2715 AssertMsgFailed(("iRefreshGroup=%d\n", iRefreshGroup));
2716 }
2717 }
2718}
2719
2720
2721/**
2722 * Refreshes the statistics behind the given entry, if necessary.
2723 *
2724 * This helps implement fetching global ring-0 stats into ring-3 accessible
2725 * storage. GVMM, GMM and NEM makes use of this.
2726 *
2727 * @param pUVM The user mode VM handle.
2728 * @param pCur The statistics descriptor which group to check
2729 * and maybe update.
2730 * @param pbmRefreshedGroups Bitmap tracking what has already been updated.
2731 */
2732DECLINLINE(void) stamR3Refresh(PUVM pUVM, PSTAMDESC pCur, uint64_t *pbmRefreshedGroups)
2733{
2734 uint8_t const iRefreshGroup = pCur->iRefreshGroup;
2735 if (RT_LIKELY(iRefreshGroup == STAM_REFRESH_GRP_NONE))
2736 { /* likely */ }
2737 else if (!(*pbmRefreshedGroups & RT_BIT_64(iRefreshGroup)))
2738 stamR3RefreshGroup(pUVM, iRefreshGroup, pbmRefreshedGroups);
2739}
2740
2741
2742/**
2743 * Match a name against an array of patterns.
2744 *
2745 * @returns true if it matches, false if it doesn't match.
2746 * @param papszExpressions The array of pattern expressions.
2747 * @param cExpressions The number of array entries.
2748 * @param piExpression Where to read/store the current skip index. Optional.
2749 * @param pszName The name to match.
2750 */
2751static bool stamR3MultiMatch(const char * const *papszExpressions, unsigned cExpressions,
2752 unsigned *piExpression, const char *pszName)
2753{
2754 for (unsigned i = piExpression ? *piExpression : 0; i < cExpressions; i++)
2755 {
2756 const char *pszPat = papszExpressions[i];
2757 if (RTStrSimplePatternMatch(pszPat, pszName))
2758 {
2759 /* later:
2760 if (piExpression && i > *piExpression)
2761 {
2762 Check if we can skip some expressions.
2763 Requires the expressions to be sorted.
2764 }*/
2765 return true;
2766 }
2767 }
2768 return false;
2769}
2770
2771
2772/**
2773 * Splits a multi pattern into single ones.
2774 *
2775 * @returns Pointer to an array of single patterns. Free it with RTMemTmpFree.
2776 * @param pszPat The pattern to split.
2777 * @param pcExpressions The number of array elements.
2778 * @param ppszCopy The pattern copy to free using RTStrFree.
2779 */
2780static char **stamR3SplitPattern(const char *pszPat, unsigned *pcExpressions, char **ppszCopy)
2781{
2782 Assert(pszPat && *pszPat);
2783
2784 char *pszCopy = RTStrDup(pszPat);
2785 if (!pszCopy)
2786 return NULL;
2787
2788 /* count them & allocate array. */
2789 char *psz = pszCopy;
2790 unsigned cExpressions = 1;
2791 while ((psz = strchr(psz, '|')) != NULL)
2792 cExpressions++, psz++;
2793
2794 char **papszExpressions = (char **)RTMemTmpAllocZ((cExpressions + 1) * sizeof(char *));
2795 if (!papszExpressions)
2796 {
2797 RTStrFree(pszCopy);
2798 return NULL;
2799 }
2800
2801 /* split */
2802 psz = pszCopy;
2803 for (unsigned i = 0;;)
2804 {
2805 papszExpressions[i] = psz;
2806 if (++i >= cExpressions)
2807 break;
2808 psz = strchr(psz, '|');
2809 *psz++ = '\0';
2810 }
2811
2812 /* sort the array, putting '*' last. */
2813 /** @todo sort it... */
2814
2815 *pcExpressions = cExpressions;
2816 *ppszCopy = pszCopy;
2817 return papszExpressions;
2818}
2819
2820
2821/**
2822 * Enumerates the nodes selected by a pattern or all nodes if no pattern
2823 * is specified.
2824 *
2825 * The call may lock STAM for writing before calling this function, however do
2826 * not lock it for reading as this function may need to write lock STAM.
2827 *
2828 * @returns The rc from the callback.
2829 * @param pUVM Pointer to the user mode VM structure.
2830 * @param pszPat Pattern.
2831 * @param fUpdateRing0 Update the stats residing in ring-0.
2832 * @param pfnCallback Callback function which shall be called for matching nodes.
2833 * If it returns anything but VINF_SUCCESS the enumeration is
2834 * terminated and the status code returned to the caller.
2835 * @param pvArg User parameter for the callback.
2836 */
2837static int stamR3EnumU(PUVM pUVM, const char *pszPat, bool fUpdateRing0,
2838 int (*pfnCallback)(PSTAMDESC pDesc, void *pvArg), void *pvArg)
2839{
2840 size_t const cchPat = pszPat ? strlen(pszPat) : 0;
2841 int rc = VINF_SUCCESS;
2842 uint64_t bmRefreshedGroups = 0;
2843 PSTAMDESC pCur;
2844
2845 /*
2846 * All.
2847 */
2848 if ( cchPat < 1
2849 || ( cchPat == 1
2850 && *pszPat == '*'))
2851 {
2852 STAM_LOCK_RD(pUVM);
2853 RTListForEach(&pUVM->stam.s.List, pCur, STAMDESC, ListEntry)
2854 {
2855 if (fUpdateRing0)
2856 stamR3Refresh(pUVM, pCur, &bmRefreshedGroups);
2857 rc = pfnCallback(pCur, pvArg);
2858 if (rc)
2859 break;
2860 }
2861 STAM_UNLOCK_RD(pUVM);
2862 }
2863
2864 /*
2865 * Single expression pattern.
2866 */
2867 else if (memchr(pszPat, '|', cchPat) == NULL)
2868 {
2869 const char *pszAsterisk = (const char *)memchr(pszPat, '*', cchPat);
2870 const char *pszQuestion = (const char *)memchr(pszPat, '?', cchPat);
2871
2872 STAM_LOCK_RD(pUVM);
2873 if (!pszAsterisk && !pszQuestion)
2874 {
2875 pCur = stamR3LookupFindDesc(pUVM->stam.s.pRoot, pszPat);
2876 if (pCur)
2877 {
2878 if (fUpdateRing0)
2879 stamR3Refresh(pUVM, pCur, &bmRefreshedGroups);
2880 rc = pfnCallback(pCur, pvArg);
2881 }
2882 }
2883 /* Is this a prefix expression where we can use the lookup tree to
2884 efficiently figure out the exact range? */
2885 else if ( pszAsterisk == &pszPat[cchPat - 1]
2886 && pszPat[0] == '/'
2887 && !pszQuestion)
2888 {
2889 PSTAMDESC pLast;
2890 pCur = stamR3LookupFindByPrefixRange(pUVM->stam.s.pRoot, pszPat, (uint32_t)(cchPat - 1), &pLast);
2891 if (pCur)
2892 {
2893 for (;;)
2894 {
2895 Assert(strncmp(pCur->pszName, pszPat, cchPat - 1) == 0);
2896 if (fUpdateRing0)
2897 stamR3Refresh(pUVM, pCur, &bmRefreshedGroups);
2898 rc = pfnCallback(pCur, pvArg);
2899 if (rc)
2900 break;
2901 if (pCur == pLast)
2902 break;
2903 pCur = RTListNodeGetNext(&pCur->ListEntry, STAMDESC, ListEntry);
2904 }
2905 Assert(pLast);
2906 }
2907 else
2908 Assert(!pLast);
2909 }
2910 else
2911 {
2912 /* It's a more complicated pattern. Find the approximate range
2913 and scan it for matches. */
2914 PSTAMDESC pLast;
2915 pCur = stamR3LookupFindPatternDescRange(pUVM->stam.s.pRoot, &pUVM->stam.s.List, pszPat, &pLast);
2916 if (pCur)
2917 {
2918 for (;;)
2919 {
2920 if (RTStrSimplePatternMatch(pszPat, pCur->pszName))
2921 {
2922 if (fUpdateRing0)
2923 stamR3Refresh(pUVM, pCur, &bmRefreshedGroups);
2924 rc = pfnCallback(pCur, pvArg);
2925 if (rc)
2926 break;
2927 }
2928 if (pCur == pLast)
2929 break;
2930 pCur = RTListNodeGetNext(&pCur->ListEntry, STAMDESC, ListEntry);
2931 }
2932 Assert(pLast);
2933 }
2934 else
2935 Assert(!pLast);
2936 }
2937 STAM_UNLOCK_RD(pUVM);
2938 }
2939
2940 /*
2941 * Multi expression pattern.
2942 */
2943 else
2944 {
2945 /*
2946 * Split up the pattern first.
2947 */
2948 char *pszCopy;
2949 unsigned cExpressions;
2950 char **papszExpressions = stamR3SplitPattern(pszPat, &cExpressions, &pszCopy);
2951 if (!papszExpressions)
2952 return VERR_NO_MEMORY;
2953
2954 /*
2955 * Perform the enumeration.
2956 */
2957 STAM_LOCK_RD(pUVM);
2958 unsigned iExpression = 0;
2959 RTListForEach(&pUVM->stam.s.List, pCur, STAMDESC, ListEntry)
2960 {
2961 if (stamR3MultiMatch(papszExpressions, cExpressions, &iExpression, pCur->pszName))
2962 {
2963 if (fUpdateRing0)
2964 stamR3Refresh(pUVM, pCur, &bmRefreshedGroups);
2965 rc = pfnCallback(pCur, pvArg);
2966 if (rc)
2967 break;
2968 }
2969 }
2970 STAM_UNLOCK_RD(pUVM);
2971
2972 RTMemTmpFree(papszExpressions);
2973 RTStrFree(pszCopy);
2974 }
2975
2976 return rc;
2977}
2978
2979
2980/**
2981 * Registers the ring-0 statistics.
2982 *
2983 * @param pUVM Pointer to the user mode VM structure.
2984 */
2985static void stamR3Ring0StatsRegisterU(PUVM pUVM)
2986{
2987 /* GVMM */
2988 for (unsigned i = 0; i < RT_ELEMENTS(g_aGVMMStats); i++)
2989 stamR3RegisterU(pUVM, (uint8_t *)&pUVM->stam.s.GVMMStats + g_aGVMMStats[i].offVar, NULL, NULL,
2990 g_aGVMMStats[i].enmType, STAMVISIBILITY_ALWAYS, g_aGVMMStats[i].pszName,
2991 g_aGVMMStats[i].enmUnit, g_aGVMMStats[i].pszDesc, STAM_REFRESH_GRP_GVMM);
2992
2993 for (unsigned i = 0; i < pUVM->cCpus; i++)
2994 {
2995 char szName[120];
2996 size_t cchBase = RTStrPrintf(szName, sizeof(szName), pUVM->cCpus < 10 ? "/GVMM/VCpus/%u/" : "/GVMM/VCpus/%02u/", i);
2997
2998 strcpy(&szName[cchBase], "cWakeUpTimerHits");
2999 stamR3RegisterU(pUVM, &pUVM->stam.s.GVMMStats.aVCpus[i].cWakeUpTimerHits, NULL, NULL,
3000 STAMTYPE_U32, STAMVISIBILITY_ALWAYS, szName, STAMUNIT_OCCURENCES, "", STAM_REFRESH_GRP_GVMM);
3001
3002 strcpy(&szName[cchBase], "cWakeUpTimerMisses");
3003 stamR3RegisterU(pUVM, &pUVM->stam.s.GVMMStats.aVCpus[i].cWakeUpTimerMisses, NULL, NULL,
3004 STAMTYPE_U32, STAMVISIBILITY_ALWAYS, szName, STAMUNIT_OCCURENCES, "", STAM_REFRESH_GRP_GVMM);
3005
3006 strcpy(&szName[cchBase], "cWakeUpTimerCanceled");
3007 stamR3RegisterU(pUVM, &pUVM->stam.s.GVMMStats.aVCpus[i].cWakeUpTimerCanceled, NULL, NULL,
3008 STAMTYPE_U32, STAMVISIBILITY_ALWAYS, szName, STAMUNIT_OCCURENCES, "", STAM_REFRESH_GRP_GVMM);
3009
3010 strcpy(&szName[cchBase], "cWakeUpTimerSameCpu");
3011 stamR3RegisterU(pUVM, &pUVM->stam.s.GVMMStats.aVCpus[i].cWakeUpTimerSameCpu, NULL, NULL,
3012 STAMTYPE_U32, STAMVISIBILITY_ALWAYS, szName, STAMUNIT_OCCURENCES, "", STAM_REFRESH_GRP_GVMM);
3013
3014 strcpy(&szName[cchBase], "Start");
3015 stamR3RegisterU(pUVM, &pUVM->stam.s.GVMMStats.aVCpus[i].Start, NULL, NULL,
3016 STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, szName, STAMUNIT_TICKS_PER_CALL, "", STAM_REFRESH_GRP_GVMM);
3017
3018 strcpy(&szName[cchBase], "Stop");
3019 stamR3RegisterU(pUVM, &pUVM->stam.s.GVMMStats.aVCpus[i].Stop, NULL, NULL,
3020 STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, szName, STAMUNIT_TICKS_PER_CALL, "", STAM_REFRESH_GRP_GVMM);
3021 }
3022 pUVM->stam.s.cRegisteredHostCpus = 0;
3023
3024 /* GMM */
3025 for (unsigned i = 0; i < RT_ELEMENTS(g_aGMMStats); i++)
3026 stamR3RegisterU(pUVM, (uint8_t *)&pUVM->stam.s.GMMStats + g_aGMMStats[i].offVar, NULL, NULL,
3027 g_aGMMStats[i].enmType, STAMVISIBILITY_ALWAYS, g_aGMMStats[i].pszName,
3028 g_aGMMStats[i].enmUnit, g_aGMMStats[i].pszDesc, STAM_REFRESH_GRP_GMM);
3029}
3030
3031
3032/**
3033 * Get the unit string.
3034 *
3035 * @returns Pointer to read only unit string.
3036 * @param enmUnit The unit.
3037 */
3038VMMR3DECL(const char *) STAMR3GetUnit(STAMUNIT enmUnit)
3039{
3040 switch (enmUnit)
3041 {
3042 case STAMUNIT_NONE: return "";
3043 case STAMUNIT_CALLS: return "calls";
3044 case STAMUNIT_CALLS_PER_TB: return "calls/tb";
3045 case STAMUNIT_COUNT: return "count";
3046 case STAMUNIT_BYTES: return "bytes";
3047 case STAMUNIT_BYTES_PER_CALL: return "bytes/call";
3048 case STAMUNIT_PAGES: return "pages";
3049 case STAMUNIT_ERRORS: return "errors";
3050 case STAMUNIT_OCCURENCES: return "times";
3051 case STAMUNIT_TICKS: return "ticks";
3052 case STAMUNIT_TICKS_PER_CALL: return "ticks/call";
3053 case STAMUNIT_TICKS_PER_OCCURENCE: return "ticks/time";
3054 case STAMUNIT_GOOD_BAD: return "good:bad";
3055 case STAMUNIT_MEGABYTES: return "megabytes";
3056 case STAMUNIT_KILOBYTES: return "kilobytes";
3057 case STAMUNIT_NS: return "ns";
3058 case STAMUNIT_NS_PER_CALL: return "ns/call";
3059 case STAMUNIT_NS_PER_OCCURENCE: return "ns/time";
3060 case STAMUNIT_PCT: return "%";
3061 case STAMUNIT_HZ: return "Hz";
3062 case STAMUNIT_INSTR: return "instr";
3063 case STAMUNIT_INSTR_PER_TB: return "instr/tb";
3064 case STAMUNIT_BYTES_PER_TB: return "bytes/tb";
3065
3066 default:
3067 AssertMsgFailed(("Unknown unit %d\n", enmUnit));
3068 return "(?unit?)";
3069 }
3070}
3071
3072
3073/**
3074 * For something per something-else unit, get the first something.
3075 *
3076 * @returns Pointer to read only unit string.
3077 * @param enmUnit The unit.
3078 */
3079VMMR3DECL(const char *) STAMR3GetUnit1(STAMUNIT enmUnit)
3080{
3081 switch (enmUnit)
3082 {
3083 case STAMUNIT_NONE: return "";
3084 case STAMUNIT_CALLS: return "calls";
3085 case STAMUNIT_CALLS_PER_TB: return "calls";
3086 case STAMUNIT_COUNT: return "count";
3087 case STAMUNIT_BYTES: return "bytes";
3088 case STAMUNIT_BYTES_PER_CALL: return "bytes";
3089 case STAMUNIT_PAGES: return "pages";
3090 case STAMUNIT_ERRORS: return "errors";
3091 case STAMUNIT_OCCURENCES: return "times";
3092 case STAMUNIT_TICKS: return "ticks";
3093 case STAMUNIT_TICKS_PER_CALL: return "ticks";
3094 case STAMUNIT_TICKS_PER_OCCURENCE: return "ticks";
3095 case STAMUNIT_GOOD_BAD: return "good";
3096 case STAMUNIT_MEGABYTES: return "megabytes";
3097 case STAMUNIT_KILOBYTES: return "kilobytes";
3098 case STAMUNIT_NS: return "ns";
3099 case STAMUNIT_NS_PER_CALL: return "ns";
3100 case STAMUNIT_NS_PER_OCCURENCE: return "ns";
3101 case STAMUNIT_PCT: return "%";
3102 case STAMUNIT_HZ: return "Hz";
3103 case STAMUNIT_INSTR: return "instr";
3104 case STAMUNIT_INSTR_PER_TB: return "instr";
3105 case STAMUNIT_BYTES_PER_TB: return "bytes";
3106
3107 default:
3108 AssertMsgFailed(("Unknown unit %d\n", enmUnit));
3109 return "(?unit?)";
3110 }
3111}
3112
3113
3114/**
3115 * For something per something-else unit, get the something-else.
3116 *
3117 * @returns Pointer to read only unit string.
3118 * @param enmUnit The unit.
3119 */
3120VMMR3DECL(const char *) STAMR3GetUnit2(STAMUNIT enmUnit)
3121{
3122 switch (enmUnit)
3123 {
3124 case STAMUNIT_TICKS_PER_CALL: return "calls";
3125 case STAMUNIT_NS_PER_CALL: return "calls";
3126 case STAMUNIT_BYTES_PER_CALL: return "calls";
3127 case STAMUNIT_TICKS_PER_OCCURENCE: return "times";
3128 case STAMUNIT_NS_PER_OCCURENCE: return "times";
3129 case STAMUNIT_NONE: return "times";
3130 case STAMUNIT_GOOD_BAD: return "bad";
3131 case STAMUNIT_CALLS_PER_TB: return "tbs";
3132 case STAMUNIT_INSTR_PER_TB: return "tbs";
3133 case STAMUNIT_BYTES_PER_TB: return "tbs";
3134 default:
3135 AssertMsgFailed(("Wrong unit %d\n", enmUnit));
3136 return "times";
3137 }
3138}
3139
3140#ifdef VBOX_WITH_DEBUGGER
3141
3142/**
3143 * @callback_method_impl{FNDBGCCMD, The '.stats' command.}
3144 */
3145static DECLCALLBACK(int) stamR3CmdStats(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
3146{
3147 /*
3148 * Validate input.
3149 */
3150 DBGC_CMDHLP_REQ_UVM_RET(pCmdHlp, pCmd, pUVM);
3151 if (RTListIsEmpty(&pUVM->stam.s.List))
3152 return DBGCCmdHlpFail(pCmdHlp, pCmd, "No statistics present");
3153
3154 /*
3155 * Do the printing.
3156 */
3157 STAMR3PRINTONEARGS Args;
3158 Args.pUVM = pUVM;
3159 Args.pvArg = pCmdHlp;
3160 Args.pfnPrintf = stamR3EnumDbgfPrintf;
3161
3162 return stamR3EnumU(pUVM, cArgs ? paArgs[0].u.pszString : NULL, true /* fUpdateRing0 */, stamR3PrintOne, &Args);
3163}
3164
3165
3166/**
3167 * Display one sample in the debugger.
3168 *
3169 * @param pArgs Pointer to the print one argument structure.
3170 * @param pszFormat Format string.
3171 * @param ... Format arguments.
3172 */
3173static DECLCALLBACK(void) stamR3EnumDbgfPrintf(PSTAMR3PRINTONEARGS pArgs, const char *pszFormat, ...)
3174{
3175 PDBGCCMDHLP pCmdHlp = (PDBGCCMDHLP)pArgs->pvArg;
3176
3177 va_list va;
3178 va_start(va, pszFormat);
3179 pCmdHlp->pfnPrintfV(pCmdHlp, NULL, pszFormat, va);
3180 va_end(va);
3181 NOREF(pArgs);
3182}
3183
3184
3185/**
3186 * @callback_method_impl{FNDBGCCMD, The '.statsreset' command.}
3187 */
3188static DECLCALLBACK(int) stamR3CmdStatsReset(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
3189{
3190 /*
3191 * Validate input.
3192 */
3193 DBGC_CMDHLP_REQ_UVM_RET(pCmdHlp, pCmd, pUVM);
3194 if (RTListIsEmpty(&pUVM->stam.s.List))
3195 return DBGCCmdHlpFail(pCmdHlp, pCmd, "No statistics present");
3196
3197 /*
3198 * Execute reset.
3199 */
3200 int rc = STAMR3Reset(pUVM, cArgs ? paArgs[0].u.pszString : NULL);
3201 if (RT_SUCCESS(rc))
3202 return DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "STAMR3ResetU");
3203 return DBGCCmdHlpPrintf(pCmdHlp, "Statistics have been reset.\n");
3204}
3205
3206#endif /* VBOX_WITH_DEBUGGER */
3207
Note: See TracBrowser for help on using the repository browser.

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