/* $Id: STAM.cpp 96407 2022-08-22 17:43:14Z vboxsync $ */ /** @file * STAM - The Statistics Manager. */ /* * Copyright (C) 2006-2022 Oracle and/or its affiliates. * * This file is part of VirtualBox base platform packages, as * available from https://www.virtualbox.org. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation, in version 3 of the * License. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * * SPDX-License-Identifier: GPL-3.0-only */ /** @page pg_stam STAM - The Statistics Manager * * The purpose for the statistics manager is to present the rest of the system * with a somewhat uniform way of accessing VMM statistics. STAM sports a * couple of different APIs for accessing them: STAMR3EnumU, STAMR3SnapshotU, * STAMR3DumpU, STAMR3DumpToReleaseLogU and the debugger. Main is exposing the * XML based one, STAMR3SnapshotU. * * The rest of the VMM together with the devices and drivers registers their * statistics with STAM giving them a name. The name is hierarchical, the * components separated by slashes ('/') and must start with a slash. * * Each item registered with STAM - also, half incorrectly, called a sample - * has a type, unit, visibility, data pointer and description associated with it * in addition to the name (described above). The type tells STAM what kind of * structure the pointer is pointing to. The visibility allows unused * statistics from cluttering the output or showing up in the GUI. All the bits * together makes STAM able to present the items in a sensible way to the user. * Some types also allows STAM to reset the data, which is very convenient when * digging into specific operations and such. * * PS. The VirtualBox Debugger GUI has a viewer for inspecting the statistics * STAM provides. You will also find statistics in the release and debug logs. * And as mentioned in the introduction, the debugger console features a couple * of command: .stats and .statsreset. * * @see grp_stam */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #define LOG_GROUP LOG_GROUP_STAM #include #include "STAMInternal.h" #include #include #include #include #include #include #include #include #include /********************************************************************************************************************************* * Defined Constants And Macros * *********************************************************************************************************************************/ /** The maximum name length excluding the terminator. */ #define STAM_MAX_NAME_LEN 239 /********************************************************************************************************************************* * Structures and Typedefs * *********************************************************************************************************************************/ /** * Argument structure for stamR3PrintOne(). */ typedef struct STAMR3PRINTONEARGS { PUVM pUVM; void *pvArg; DECLCALLBACKMEMBER(void, pfnPrintf,(struct STAMR3PRINTONEARGS *pvArg, const char *pszFormat, ...)); } STAMR3PRINTONEARGS, *PSTAMR3PRINTONEARGS; /** * Argument structure to stamR3EnumOne(). */ typedef struct STAMR3ENUMONEARGS { PVM pVM; PFNSTAMR3ENUM pfnEnum; void *pvUser; } STAMR3ENUMONEARGS, *PSTAMR3ENUMONEARGS; /** * The snapshot status structure. * Argument package passed to stamR3SnapshotOne, stamR3SnapshotPrintf and stamR3SnapshotOutput. */ typedef struct STAMR3SNAPSHOTONE { /** Pointer to the buffer start. */ char *pszStart; /** Pointer to the buffer end. */ char *pszEnd; /** Pointer to the current buffer position. */ char *psz; /** Pointer to the VM. */ PVM pVM; /** The number of bytes allocated. */ size_t cbAllocated; /** The status code. */ int rc; /** Whether to include the description strings. */ bool fWithDesc; } STAMR3SNAPSHOTONE, *PSTAMR3SNAPSHOTONE; /** * Init record for a ring-0 statistic sample. */ typedef struct STAMR0SAMPLE { /** The GVMMSTATS structure offset of the variable. */ unsigned offVar; /** The type. */ STAMTYPE enmType; /** The unit. */ STAMUNIT enmUnit; /** The name. */ const char *pszName; /** The description. */ const char *pszDesc; } STAMR0SAMPLE; /********************************************************************************************************************************* * Internal Functions * *********************************************************************************************************************************/ static void stamR3LookupDestroyTree(PSTAMLOOKUP pRoot); static int stamR3RegisterU(PUVM pUVM, void *pvSample, PFNSTAMR3CALLBACKRESET pfnReset, PFNSTAMR3CALLBACKPRINT pfnPrint, STAMTYPE enmType, STAMVISIBILITY enmVisibility, const char *pszName, STAMUNIT enmUnit, const char *pszDesc, uint8_t iRefreshGrp); static int stamR3ResetOne(PSTAMDESC pDesc, void *pvArg); static DECLCALLBACK(void) stamR3EnumLogPrintf(PSTAMR3PRINTONEARGS pvArg, const char *pszFormat, ...); static DECLCALLBACK(void) stamR3EnumRelLogPrintf(PSTAMR3PRINTONEARGS pvArg, const char *pszFormat, ...); static DECLCALLBACK(void) stamR3EnumPrintf(PSTAMR3PRINTONEARGS pvArg, const char *pszFormat, ...); static int stamR3SnapshotOne(PSTAMDESC pDesc, void *pvArg); static int stamR3SnapshotPrintf(PSTAMR3SNAPSHOTONE pThis, const char *pszFormat, ...); static int stamR3PrintOne(PSTAMDESC pDesc, void *pvArg); static int stamR3EnumOne(PSTAMDESC pDesc, void *pvArg); static bool stamR3MultiMatch(const char * const *papszExpressions, unsigned cExpressions, unsigned *piExpression, const char *pszName); static char ** stamR3SplitPattern(const char *pszPat, unsigned *pcExpressions, char **ppszCopy); static int stamR3EnumU(PUVM pUVM, const char *pszPat, bool fUpdateRing0, int (pfnCallback)(PSTAMDESC pDesc, void *pvArg), void *pvArg); static void stamR3Ring0StatsRegisterU(PUVM pUVM); #ifdef VBOX_WITH_DEBUGGER static FNDBGCCMD stamR3CmdStats; static DECLCALLBACK(void) stamR3EnumDbgfPrintf(PSTAMR3PRINTONEARGS pArgs, const char *pszFormat, ...); static FNDBGCCMD stamR3CmdStatsReset; #endif /********************************************************************************************************************************* * Global Variables * *********************************************************************************************************************************/ #ifdef VBOX_WITH_DEBUGGER /** Pattern argument. */ static const DBGCVARDESC g_aArgPat[] = { /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */ { 0, 1, DBGCVAR_CAT_STRING, 0, "pattern", "Which samples the command shall be applied to. Use '*' as wildcard. Use ';' to separate expression." } }; /** Command descriptors. */ static const DBGCCMD g_aCmds[] = { /* pszCmd, cArgsMin, cArgsMax, paArgDesc, cArgDescs, fFlags, pfnHandler pszSyntax, ....pszDescription */ { "stats", 0, 1, &g_aArgPat[0], RT_ELEMENTS(g_aArgPat), 0, stamR3CmdStats, "[pattern]", "Display statistics." }, { "statsreset", 0, 1, &g_aArgPat[0], RT_ELEMENTS(g_aArgPat), 0, stamR3CmdStatsReset,"[pattern]", "Resets statistics." } }; #endif /** * The GVMM mapping records - sans the host cpus. */ static const STAMR0SAMPLE g_aGVMMStats[] = { { RT_UOFFSETOF(GVMMSTATS, SchedVM.cHaltCalls), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/VM/HaltCalls", "The number of calls to GVMMR0SchedHalt." }, { RT_UOFFSETOF(GVMMSTATS, SchedVM.cHaltBlocking), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/VM/HaltBlocking", "The number of times we did go to sleep in GVMMR0SchedHalt." }, { RT_UOFFSETOF(GVMMSTATS, SchedVM.cHaltTimeouts), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/VM/HaltTimeouts", "The number of times we timed out in GVMMR0SchedHalt." }, { 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." }, { RT_UOFFSETOF(GVMMSTATS, SchedVM.cHaltWakeUps), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/VM/HaltWakeUps", "The number of wake ups done during GVMMR0SchedHalt." }, { RT_UOFFSETOF(GVMMSTATS, SchedVM.cWakeUpCalls), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/VM/WakeUpCalls", "The number of calls to GVMMR0WakeUp." }, { 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." }, { 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)." }, { RT_UOFFSETOF(GVMMSTATS, SchedVM.cPokeCalls), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/VM/PokeCalls", "The number of calls to GVMMR0Poke." }, { 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." }, { RT_UOFFSETOF(GVMMSTATS, SchedVM.cPollCalls), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/VM/PollCalls", "The number of calls to GVMMR0SchedPoll." }, { 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." }, { RT_UOFFSETOF(GVMMSTATS, SchedVM.cPollWakeUps), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/VM/PollWakeUps", "The number of wake ups done during GVMMR0SchedPoll." }, { RT_UOFFSETOF(GVMMSTATS, SchedSum.cHaltCalls), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/Sum/HaltCalls", "The number of calls to GVMMR0SchedHalt." }, { RT_UOFFSETOF(GVMMSTATS, SchedSum.cHaltBlocking), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/Sum/HaltBlocking", "The number of times we did go to sleep in GVMMR0SchedHalt." }, { RT_UOFFSETOF(GVMMSTATS, SchedSum.cHaltTimeouts), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/Sum/HaltTimeouts", "The number of times we timed out in GVMMR0SchedHalt." }, { 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." }, { RT_UOFFSETOF(GVMMSTATS, SchedSum.cHaltWakeUps), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/Sum/HaltWakeUps", "The number of wake ups done during GVMMR0SchedHalt." }, { RT_UOFFSETOF(GVMMSTATS, SchedSum.cWakeUpCalls), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/Sum/WakeUpCalls", "The number of calls to GVMMR0WakeUp." }, { 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." }, { 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)." }, { RT_UOFFSETOF(GVMMSTATS, SchedSum.cPokeCalls), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/Sum/PokeCalls", "The number of calls to GVMMR0Poke." }, { 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." }, { RT_UOFFSETOF(GVMMSTATS, SchedSum.cPollCalls), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/Sum/PollCalls", "The number of calls to GVMMR0SchedPoll." }, { 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." }, { RT_UOFFSETOF(GVMMSTATS, SchedSum.cPollWakeUps), STAMTYPE_U64_RESET, STAMUNIT_CALLS, "/GVMM/Sum/PollWakeUps", "The number of wake ups done during GVMMR0SchedPoll." }, { RT_UOFFSETOF(GVMMSTATS, cVMs), STAMTYPE_U32, STAMUNIT_COUNT, "/GVMM/VMs", "The number of VMs accessible to the caller." }, { RT_UOFFSETOF(GVMMSTATS, cEMTs), STAMTYPE_U32, STAMUNIT_COUNT, "/GVMM/EMTs", "The number of emulation threads." }, { RT_UOFFSETOF(GVMMSTATS, cHostCpus), STAMTYPE_U32, STAMUNIT_COUNT, "/GVMM/HostCPUs", "The number of host CPUs." }, }; /** * The GMM mapping records. */ static const STAMR0SAMPLE g_aGMMStats[] = { { RT_UOFFSETOF(GMMSTATS, cMaxPages), STAMTYPE_U64, STAMUNIT_PAGES, "/GMM/cMaxPages", "The maximum number of pages GMM is allowed to allocate." }, { RT_UOFFSETOF(GMMSTATS, cReservedPages), STAMTYPE_U64, STAMUNIT_PAGES, "/GMM/cReservedPages", "The number of pages that has been reserved." }, { RT_UOFFSETOF(GMMSTATS, cOverCommittedPages), STAMTYPE_U64, STAMUNIT_PAGES, "/GMM/cOverCommittedPages", "The number of pages that we have over-committed in reservations." }, { RT_UOFFSETOF(GMMSTATS, cAllocatedPages), STAMTYPE_U64, STAMUNIT_PAGES, "/GMM/cAllocatedPages", "The number of actually allocated (committed if you like) pages." }, { RT_UOFFSETOF(GMMSTATS, cSharedPages), STAMTYPE_U64, STAMUNIT_PAGES, "/GMM/cSharedPages", "The number of pages that are shared. A subset of cAllocatedPages." }, { RT_UOFFSETOF(GMMSTATS, cDuplicatePages), STAMTYPE_U64, STAMUNIT_PAGES, "/GMM/cDuplicatePages", "The number of pages that are actually shared between VMs." }, { 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." }, { RT_UOFFSETOF(GMMSTATS, cBalloonedPages), STAMTYPE_U64, STAMUNIT_PAGES, "/GMM/cBalloonedPages", "The number of current ballooned pages." }, { RT_UOFFSETOF(GMMSTATS, cChunks), STAMTYPE_U32, STAMUNIT_COUNT, "/GMM/cChunks", "The number of allocation chunks." }, { RT_UOFFSETOF(GMMSTATS, cFreedChunks), STAMTYPE_U32, STAMUNIT_COUNT, "/GMM/cFreedChunks", "The number of freed chunks ever." }, { RT_UOFFSETOF(GMMSTATS, cShareableModules), STAMTYPE_U32, STAMUNIT_COUNT, "/GMM/cShareableModules", "The number of shareable modules." }, { RT_UOFFSETOF(GMMSTATS, idFreeGeneration), STAMTYPE_U64, STAMUNIT_NONE, "/GMM/idFreeGeneration", "The current chunk freeing generation number (for per-VM chunk lookup TLB versioning)." }, { 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." }, { RT_UOFFSETOF(GMMSTATS, VMStats.Reserved.cShadowPages), STAMTYPE_U32, STAMUNIT_PAGES, "/GMM/VM/Reserved/cShadowPages", "The amount of memory reserved for shadow/nested page tables." }, { 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." }, { 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." }, { RT_UOFFSETOF(GMMSTATS, VMStats.Allocated.cShadowPages), STAMTYPE_U32, STAMUNIT_PAGES, "/GMM/VM/Allocated/cShadowPages", "The amount of memory allocated for shadow/nested page tables." }, { 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." }, { RT_UOFFSETOF(GMMSTATS, VMStats.cPrivatePages), STAMTYPE_U64, STAMUNIT_PAGES, "/GMM/VM/cPrivatePages", "The current number of private pages." }, { RT_UOFFSETOF(GMMSTATS, VMStats.cSharedPages), STAMTYPE_U64, STAMUNIT_PAGES, "/GMM/VM/cSharedPages", "The current number of shared pages." }, { RT_UOFFSETOF(GMMSTATS, VMStats.cBalloonedPages), STAMTYPE_U64, STAMUNIT_PAGES, "/GMM/VM/cBalloonedPages", "The current number of ballooned pages." }, { RT_UOFFSETOF(GMMSTATS, VMStats.cMaxBalloonedPages), STAMTYPE_U64, STAMUNIT_PAGES, "/GMM/VM/cMaxBalloonedPages", "The max number of pages that can be ballooned." }, { 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." }, { 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." }, { 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." }, { RT_UOFFSETOF(GMMSTATS, VMStats.cShareableModules), STAMTYPE_U32, STAMUNIT_COUNT, "/GMM/VM/cShareableModules", "The number of shareable modules traced by the VM." }, { RT_UOFFSETOF(GMMSTATS, VMStats.enmPolicy), STAMTYPE_U32, STAMUNIT_NONE, "/GMM/VM/enmPolicy", "The current over-commit policy." }, { 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." }, { RT_UOFFSETOF(GMMSTATS, VMStats.fBallooningEnabled), STAMTYPE_BOOL, STAMUNIT_NONE, "/GMM/VM/fBallooningEnabled", "Whether ballooning is enabled or not." }, { RT_UOFFSETOF(GMMSTATS, VMStats.fSharedPagingEnabled), STAMTYPE_BOOL, STAMUNIT_NONE, "/GMM/VM/fSharedPagingEnabled", "Whether shared paging is enabled or not." }, { RT_UOFFSETOF(GMMSTATS, VMStats.fMayAllocate), STAMTYPE_BOOL, STAMUNIT_NONE, "/GMM/VM/fMayAllocate", "Whether the VM is allowed to allocate memory or not." }, }; /** * Initializes the STAM. * * @returns VBox status code. * @param pUVM The user mode VM structure. */ VMMR3DECL(int) STAMR3InitUVM(PUVM pUVM) { LogFlow(("STAMR3Init\n")); /* * Assert alignment and sizes. */ AssertCompile(sizeof(pUVM->stam.s) <= sizeof(pUVM->stam.padding)); AssertRelease(sizeof(pUVM->stam.s) <= sizeof(pUVM->stam.padding)); /* * Initialize the read/write lock and list. */ int rc = RTSemRWCreate(&pUVM->stam.s.RWSem); AssertRCReturn(rc, rc); RTListInit(&pUVM->stam.s.List); /* * Initialize the root node. */ PSTAMLOOKUP pRoot = (PSTAMLOOKUP)RTMemAlloc(sizeof(STAMLOOKUP)); if (!pRoot) { RTSemRWDestroy(pUVM->stam.s.RWSem); pUVM->stam.s.RWSem = NIL_RTSEMRW; return VERR_NO_MEMORY; } pRoot->pParent = NULL; pRoot->papChildren = NULL; pRoot->pDesc = NULL; pRoot->cDescsInTree = 0; pRoot->cChildren = 0; pRoot->iParent = UINT16_MAX; pRoot->off = 0; pRoot->cch = 0; pRoot->szName[0] = '\0'; pUVM->stam.s.pRoot = pRoot; /* * Register the ring-0 statistics (GVMM/GMM). */ if (!SUPR3IsDriverless()) stamR3Ring0StatsRegisterU(pUVM); #ifdef VBOX_WITH_DEBUGGER /* * Register debugger commands. */ static bool fRegisteredCmds = false; if (!fRegisteredCmds) { rc = DBGCRegisterCommands(&g_aCmds[0], RT_ELEMENTS(g_aCmds)); if (RT_SUCCESS(rc)) fRegisteredCmds = true; } #endif return VINF_SUCCESS; } /** * Terminates the STAM. * * @param pUVM Pointer to the user mode VM structure. */ VMMR3DECL(void) STAMR3TermUVM(PUVM pUVM) { /* * Free used memory and the RWLock. */ PSTAMDESC pCur, pNext; RTListForEachSafe(&pUVM->stam.s.List, pCur, pNext, STAMDESC, ListEntry) { pCur->pLookup->pDesc = NULL; RTMemFree(pCur); } stamR3LookupDestroyTree(pUVM->stam.s.pRoot); pUVM->stam.s.pRoot = NULL; Assert(pUVM->stam.s.RWSem != NIL_RTSEMRW); RTSemRWDestroy(pUVM->stam.s.RWSem); pUVM->stam.s.RWSem = NIL_RTSEMRW; } /** * Registers a sample with the statistics manager. * * Statistics are maintained on a per VM basis and is normally registered * during the VM init stage, but there is nothing preventing you from * register them at runtime. * * Use STAMR3Deregister() to deregister statistics at runtime, however do * not bother calling at termination time. * * It is not possible to register the same sample twice. * * @returns VBox status code. * @param pUVM Pointer to the user mode VM structure. * @param pvSample Pointer to the sample. * @param enmType Sample type. This indicates what pvSample is pointing at. * @param enmVisibility Visibility type specifying whether unused statistics should be visible or not. * @param pszName Sample name. The name is on this form "//". * Further nesting is possible. * @param enmUnit Sample unit. * @param pszDesc Sample description. */ VMMR3DECL(int) STAMR3RegisterU(PUVM pUVM, void *pvSample, STAMTYPE enmType, STAMVISIBILITY enmVisibility, const char *pszName, STAMUNIT enmUnit, const char *pszDesc) { AssertReturn(enmType != STAMTYPE_CALLBACK, VERR_INVALID_PARAMETER); UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE); return stamR3RegisterU(pUVM, pvSample, NULL, NULL, enmType, enmVisibility, pszName, enmUnit, pszDesc, STAM_REFRESH_GRP_NONE); } /** * Registers a sample with the statistics manager. * * Statistics are maintained on a per VM basis and is normally registered * during the VM init stage, but there is nothing preventing you from * register them at runtime. * * Use STAMR3Deregister() to deregister statistics at runtime, however do * not bother calling at termination time. * * It is not possible to register the same sample twice. * * @returns VBox status code. * @param pVM The cross context VM structure. * @param pvSample Pointer to the sample. * @param enmType Sample type. This indicates what pvSample is pointing at. * @param enmVisibility Visibility type specifying whether unused statistics should be visible or not. * @param pszName Sample name. The name is on this form "//". * Further nesting is possible. * @param enmUnit Sample unit. * @param pszDesc Sample description. */ VMMR3DECL(int) STAMR3Register(PVM pVM, void *pvSample, STAMTYPE enmType, STAMVISIBILITY enmVisibility, const char *pszName, STAMUNIT enmUnit, const char *pszDesc) { AssertReturn(enmType != STAMTYPE_CALLBACK, VERR_INVALID_PARAMETER); return stamR3RegisterU(pVM->pUVM, pvSample, NULL, NULL, enmType, enmVisibility, pszName, enmUnit, pszDesc, STAM_REFRESH_GRP_NONE); } /** * Same as STAMR3RegisterU except that the name is specified in a * RTStrPrintf like fashion. * * @returns VBox status code. * @param pUVM Pointer to the user mode VM structure. * @param pvSample Pointer to the sample. * @param enmType Sample type. This indicates what pvSample is pointing at. * @param enmVisibility Visibility type specifying whether unused statistics should be visible or not. * @param enmUnit Sample unit. * @param pszDesc Sample description. * @param pszName The sample name format string. * @param ... Arguments to the format string. */ VMMR3DECL(int) STAMR3RegisterFU(PUVM pUVM, void *pvSample, STAMTYPE enmType, STAMVISIBILITY enmVisibility, STAMUNIT enmUnit, const char *pszDesc, const char *pszName, ...) { va_list args; va_start(args, pszName); int rc = STAMR3RegisterVU(pUVM, pvSample, enmType, enmVisibility, enmUnit, pszDesc, pszName, args); va_end(args); return rc; } /** * Same as STAMR3Register except that the name is specified in a * RTStrPrintf like fashion. * * @returns VBox status code. * @param pVM The cross context VM structure. * @param pvSample Pointer to the sample. * @param enmType Sample type. This indicates what pvSample is pointing at. * @param enmVisibility Visibility type specifying whether unused statistics should be visible or not. * @param enmUnit Sample unit. * @param pszDesc Sample description. * @param pszName The sample name format string. * @param ... Arguments to the format string. */ VMMR3DECL(int) STAMR3RegisterF(PVM pVM, void *pvSample, STAMTYPE enmType, STAMVISIBILITY enmVisibility, STAMUNIT enmUnit, const char *pszDesc, const char *pszName, ...) { va_list args; va_start(args, pszName); int rc = STAMR3RegisterVU(pVM->pUVM, pvSample, enmType, enmVisibility, enmUnit, pszDesc, pszName, args); va_end(args); return rc; } /** * Same as STAMR3Register except that the name is specified in a * RTStrPrintfV like fashion. * * @returns VBox status code. * @param pUVM The user mode VM structure. * @param pvSample Pointer to the sample. * @param enmType Sample type. This indicates what pvSample is pointing at. * @param enmVisibility Visibility type specifying whether unused statistics should be visible or not. * @param enmUnit Sample unit. * @param pszDesc Sample description. * @param pszName The sample name format string. * @param args Arguments to the format string. */ VMMR3DECL(int) STAMR3RegisterVU(PUVM pUVM, void *pvSample, STAMTYPE enmType, STAMVISIBILITY enmVisibility, STAMUNIT enmUnit, const char *pszDesc, const char *pszName, va_list args) { AssertReturn(enmType != STAMTYPE_CALLBACK, VERR_INVALID_PARAMETER); char szFormattedName[STAM_MAX_NAME_LEN + 8]; size_t cch = RTStrPrintfV(szFormattedName, sizeof(szFormattedName), pszName, args); AssertReturn(cch <= STAM_MAX_NAME_LEN, VERR_OUT_OF_RANGE); return STAMR3RegisterU(pUVM, pvSample, enmType, enmVisibility, szFormattedName, enmUnit, pszDesc); } /** * Same as STAMR3Register except that the name is specified in a * RTStrPrintfV like fashion. * * @returns VBox status code. * @param pVM The cross context VM structure. * @param pvSample Pointer to the sample. * @param enmType Sample type. This indicates what pvSample is pointing at. * @param enmVisibility Visibility type specifying whether unused statistics should be visible or not. * @param enmUnit Sample unit. * @param pszDesc Sample description. * @param pszName The sample name format string. * @param args Arguments to the format string. */ VMMR3DECL(int) STAMR3RegisterV(PVM pVM, void *pvSample, STAMTYPE enmType, STAMVISIBILITY enmVisibility, STAMUNIT enmUnit, const char *pszDesc, const char *pszName, va_list args) { return STAMR3RegisterVU(pVM->pUVM, pvSample, enmType, enmVisibility, enmUnit, pszDesc, pszName, args); } /** * Similar to STAMR3Register except for the two callbacks, the implied type (STAMTYPE_CALLBACK), * and name given in an RTStrPrintf like fashion. * * @returns VBox status code. * @param pVM The cross context VM structure. * @param pvSample Pointer to the sample. * @param enmVisibility Visibility type specifying whether unused statistics should be visible or not. * @param enmUnit Sample unit. * @param pfnReset Callback for resetting the sample. NULL should be used if the sample can't be reset. * @param pfnPrint Print the sample. * @param pszDesc Sample description. * @param pszName The sample name format string. * @param ... Arguments to the format string. * @remark There is currently no device or driver variant of this API. Add one if it should become necessary! */ VMMR3DECL(int) STAMR3RegisterCallback(PVM pVM, void *pvSample, STAMVISIBILITY enmVisibility, STAMUNIT enmUnit, PFNSTAMR3CALLBACKRESET pfnReset, PFNSTAMR3CALLBACKPRINT pfnPrint, const char *pszDesc, const char *pszName, ...) { va_list args; va_start(args, pszName); int rc = STAMR3RegisterCallbackV(pVM, pvSample, enmVisibility, enmUnit, pfnReset, pfnPrint, pszDesc, pszName, args); va_end(args); return rc; } /** * Same as STAMR3RegisterCallback() except for the ellipsis which is a va_list here. * * @returns VBox status code. * @param pVM The cross context VM structure. * @param pvSample Pointer to the sample. * @param enmVisibility Visibility type specifying whether unused statistics should be visible or not. * @param enmUnit Sample unit. * @param pfnReset Callback for resetting the sample. NULL should be used if the sample can't be reset. * @param pfnPrint Print the sample. * @param pszDesc Sample description. * @param pszName The sample name format string. * @param args Arguments to the format string. * @remark There is currently no device or driver variant of this API. Add one if it should become necessary! */ VMMR3DECL(int) STAMR3RegisterCallbackV(PVM pVM, void *pvSample, STAMVISIBILITY enmVisibility, STAMUNIT enmUnit, PFNSTAMR3CALLBACKRESET pfnReset, PFNSTAMR3CALLBACKPRINT pfnPrint, const char *pszDesc, const char *pszName, va_list args) { char *pszFormattedName; RTStrAPrintfV(&pszFormattedName, pszName, args); if (!pszFormattedName) return VERR_NO_MEMORY; int rc = stamR3RegisterU(pVM->pUVM, pvSample, pfnReset, pfnPrint, STAMTYPE_CALLBACK, enmVisibility, pszFormattedName, enmUnit, pszDesc, STAM_REFRESH_GRP_NONE); RTStrFree(pszFormattedName); return rc; } /** * Same as STAMR3RegisterFU, except there is an extra refresh group parameter. * * @returns VBox status code. * @param pUVM Pointer to the user mode VM structure. * @param pvSample Pointer to the sample. * @param enmType Sample type. This indicates what pvSample is pointing at. * @param enmVisibility Visibility type specifying whether unused statistics should be visible or not. * @param enmUnit Sample unit. * @param iRefreshGrp The refresh group, STAM_REFRESH_GRP_XXX. * @param pszDesc Sample description. * @param pszName The sample name format string. * @param ... Arguments to the format string. */ VMMR3DECL(int) STAMR3RegisterRefresh(PUVM pUVM, void *pvSample, STAMTYPE enmType, STAMVISIBILITY enmVisibility, STAMUNIT enmUnit, uint8_t iRefreshGrp, const char *pszDesc, const char *pszName, ...) { va_list args; va_start(args, pszName); int rc = STAMR3RegisterRefreshV(pUVM, pvSample, enmType, enmVisibility, enmUnit, iRefreshGrp, pszDesc, pszName, args); va_end(args); return rc; } /** * Same as STAMR3RegisterVU, except there is an extra refresh group parameter. * * @returns VBox status code. * @param pUVM The user mode VM structure. * @param pvSample Pointer to the sample. * @param enmType Sample type. This indicates what pvSample is pointing at. * @param enmVisibility Visibility type specifying whether unused statistics should be visible or not. * @param enmUnit Sample unit. * @param iRefreshGrp The refresh group, STAM_REFRESH_GRP_XXX. * @param pszDesc Sample description. * @param pszName The sample name format string. * @param va Arguments to the format string. */ VMMR3DECL(int) STAMR3RegisterRefreshV(PUVM pUVM, void *pvSample, STAMTYPE enmType, STAMVISIBILITY enmVisibility, STAMUNIT enmUnit, uint8_t iRefreshGrp, const char *pszDesc, const char *pszName, va_list va) { AssertReturn(enmType != STAMTYPE_CALLBACK, VERR_INVALID_PARAMETER); char szFormattedName[STAM_MAX_NAME_LEN + 8]; size_t cch = RTStrPrintfV(szFormattedName, sizeof(szFormattedName), pszName, va); AssertReturn(cch <= STAM_MAX_NAME_LEN, VERR_OUT_OF_RANGE); UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE); return stamR3RegisterU(pUVM, pvSample, NULL, NULL, enmType, enmVisibility, pszName, enmUnit, pszDesc, iRefreshGrp); } #ifdef VBOX_STRICT /** * Divide the strings into sub-strings using '/' as delimiter * and then compare them in strcmp fashion. * * @returns Difference. * @retval 0 if equal. * @retval < 0 if psz1 is less than psz2. * @retval > 0 if psz1 greater than psz2. * * @param psz1 The first string. * @param psz2 The second string. */ static int stamR3SlashCompare(const char *psz1, const char *psz2) { for (;;) { unsigned int ch1 = *psz1++; unsigned int ch2 = *psz2++; if (ch1 != ch2) { /* slash is end-of-sub-string, so it trumps everything but '\0'. */ if (ch1 == '/') return ch2 ? -1 : 1; if (ch2 == '/') return ch1 ? 1 : -1; return ch1 - ch2; } /* done? */ if (ch1 == '\0') return 0; } } #endif /* VBOX_STRICT */ /** * Compares a lookup node with a name. * * @returns like strcmp and memcmp. * @param pNode The lookup node. * @param pchName The name, not necessarily terminated. * @param cchName The length of the name. */ DECL_FORCE_INLINE(int) stamR3LookupCmp(PSTAMLOOKUP pNode, const char *pchName, uint32_t cchName) { uint32_t cchComp = RT_MIN(pNode->cch, cchName); int iDiff = memcmp(pNode->szName, pchName, cchComp); if (!iDiff && pNode->cch != cchName) iDiff = pNode->cch > cchName ? 2 : -2; return iDiff; } /** * Creates a new lookup child node. * * @returns Pointer to the newly created lookup node. * @param pParent The parent node. * @param pchName The name (not necessarily terminated). * @param cchName The length of the name. * @param offName The offset of the node in a path. * @param iChild Child index of a node that's before the one * we're inserting (returned by * stamR3LookupFindChild). */ static PSTAMLOOKUP stamR3LookupNewChild(PSTAMLOOKUP pParent, const char *pchName, uint32_t cchName, uint32_t offName, uint32_t iChild) { Assert(cchName <= UINT8_MAX); Assert(offName <= UINT8_MAX); Assert(iChild < UINT16_MAX); /* * Allocate a new entry. */ PSTAMLOOKUP pNew = (PSTAMLOOKUP)RTMemAlloc(RT_UOFFSETOF_DYN(STAMLOOKUP, szName[cchName + 1])); if (!pNew) return NULL; pNew->pParent = pParent; pNew->papChildren = NULL; pNew->pDesc = NULL; pNew->cDescsInTree = 0; pNew->cChildren = 0; pNew->cch = (uint16_t)cchName; pNew->off = (uint16_t)offName; memcpy(pNew->szName, pchName, cchName); pNew->szName[cchName] = '\0'; /* * Reallocate the array? */ if (RT_IS_POWER_OF_TWO(pParent->cChildren)) { uint32_t cNew = pParent->cChildren ? (uint32_t)pParent->cChildren * 2 : 8; AssertReturnStmt(cNew <= 0x8000, RTMemFree(pNew), NULL); void *pvNew = RTMemRealloc(pParent->papChildren, cNew * sizeof(pParent->papChildren[0])); if (!pvNew) { RTMemFree(pNew); return NULL; } pParent->papChildren = (PSTAMLOOKUP *)pvNew; } /* * Find the exact insertion point using iChild as a very good clue from * the find function. */ if (!pParent->cChildren) iChild = 0; else { if (iChild >= pParent->cChildren) iChild = pParent->cChildren - 1; while ( iChild < pParent->cChildren && stamR3LookupCmp(pParent->papChildren[iChild], pchName, cchName) < 0) iChild++; } /* * Insert it. */ if (iChild < pParent->cChildren) { /* Do shift. */ uint32_t i = pParent->cChildren; while (i > iChild) { PSTAMLOOKUP pNode = pParent->papChildren[i - 1]; pParent->papChildren[i] = pNode; pNode->iParent = i; i--; } } pNew->iParent = iChild; pParent->papChildren[iChild] = pNew; pParent->cChildren++; return pNew; } /** * Looks up a child. * * @returns Pointer to child node if found, NULL if not. * @param pParent The parent node. * @param pchName The name (not necessarily terminated). * @param cchName The length of the name. * @param piChild Where to store a child index suitable for * passing to stamR3LookupNewChild when NULL is * returned. */ static PSTAMLOOKUP stamR3LookupFindChild(PSTAMLOOKUP pParent, const char *pchName, uint32_t cchName, uint32_t *piChild) { uint32_t iChild = pParent->cChildren; if (iChild > 4) { uint32_t iFirst = 0; uint32_t iEnd = iChild; iChild /= 2; for (;;) { int iDiff = stamR3LookupCmp(pParent->papChildren[iChild], pchName, cchName); if (!iDiff) { if (piChild) *piChild = iChild; return pParent->papChildren[iChild]; } /* Split. */ if (iDiff < 0) { iFirst = iChild + 1; if (iFirst >= iEnd) { if (piChild) *piChild = iChild; break; } } else { if (iChild == iFirst) { if (piChild) *piChild = iChild ? iChild - 1 : 0; break; } iEnd = iChild; } /* Calc next child. */ iChild = (iEnd - iFirst) / 2 + iFirst; } return NULL; } /* * Linear search. */ while (iChild-- > 0) { int iDiff = stamR3LookupCmp(pParent->papChildren[iChild], pchName, cchName); if (iDiff <= 0) { if (piChild) *piChild = iChild; return !iDiff ? pParent->papChildren[iChild] : NULL; } } if (piChild) *piChild = 0; return NULL; } /** * Find the next sample descriptor node. * * This is for use with insertion in the big list and pattern range lookups. * * @returns Pointer to the next sample descriptor. NULL if not found (i.e. * we're at the end of the list). * @param pLookup The current node. */ static PSTAMDESC stamR3LookupFindNextWithDesc(PSTAMLOOKUP pLookup) { Assert(!pLookup->pDesc); PSTAMLOOKUP pCur = pLookup; uint32_t iCur = 0; for (;;) { /* * Check all children. */ uint32_t cChildren = pCur->cChildren; if (iCur < cChildren) { PSTAMLOOKUP *papChildren = pCur->papChildren; do { PSTAMLOOKUP pChild = papChildren[iCur]; if (pChild->pDesc) return pChild->pDesc; if (pChild->cChildren > 0) { /* One level down. */ iCur = 0; pCur = pChild; break; } } while (++iCur < cChildren); } else { /* * One level up, resuming after the current. */ iCur = pCur->iParent + 1; pCur = pCur->pParent; if (!pCur) return NULL; } } } /** * Look up a sample descriptor by name. * * @returns Pointer to a sample descriptor. * @param pRoot The root node. * @param pszName The name to lookup. */ static PSTAMDESC stamR3LookupFindDesc(PSTAMLOOKUP pRoot, const char *pszName) { Assert(!pRoot->pParent); while (*pszName++ == '/') { const char *pszEnd = strchr(pszName, '/'); uint32_t cch = pszEnd ? pszEnd - pszName : (uint32_t)strlen(pszName); PSTAMLOOKUP pChild = stamR3LookupFindChild(pRoot, pszName, cch, NULL); if (!pChild) break; if (!pszEnd) return pChild->pDesc; pszName = pszEnd; pRoot = pChild; } return NULL; } /** * Finds the first sample descriptor for a given lookup range. * * This is for pattern range lookups. * * @returns Pointer to the first descriptor. * @param pFirst The first node in the range. * @param pLast The last node in the range. */ static PSTAMDESC stamR3LookupFindFirstDescForRange(PSTAMLOOKUP pFirst, PSTAMLOOKUP pLast) { if (pFirst->pDesc) return pFirst->pDesc; PSTAMLOOKUP pCur = pFirst; uint32_t iCur = 0; for (;;) { uint32_t cChildren = pCur->cChildren; if (iCur < pCur->cChildren) { /* * Check all children. */ PSTAMLOOKUP * const papChildren = pCur->papChildren; do { PSTAMLOOKUP pChild = papChildren[iCur]; if (pChild->pDesc) return pChild->pDesc; if (pChild->cChildren > 0) { /* One level down. */ iCur = 0; pCur = pChild; break; } if (pChild == pLast) return NULL; } while (++iCur < cChildren); } else { /* * One level up, checking current and its 'older' sibilings. */ if (pCur == pLast) return NULL; iCur = pCur->iParent + 1; pCur = pCur->pParent; if (!pCur) break; } } return NULL; } /** * Finds the last sample descriptor for a given lookup range. * * This is for pattern range lookups. * * @returns Pointer to the first descriptor. * @param pFirst The first node in the range. * @param pLast The last node in the range. */ static PSTAMDESC stamR3LookupFindLastDescForRange(PSTAMLOOKUP pFirst, PSTAMLOOKUP pLast) { PSTAMLOOKUP pCur = pLast; uint32_t iCur = pCur->cChildren - 1; for (;;) { if (iCur < pCur->cChildren) { /* * Check children backwards, depth first. */ PSTAMLOOKUP * const papChildren = pCur->papChildren; do { PSTAMLOOKUP pChild = papChildren[iCur]; if (pChild->cChildren > 0) { /* One level down. */ iCur = pChild->cChildren - 1; pCur = pChild; break; } if (pChild->pDesc) return pChild->pDesc; if (pChild == pFirst) return NULL; } while (iCur-- > 0); /* (underflow handled above) */ } else { /* * One level up, checking current and its 'older' sibilings. */ if (pCur->pDesc) return pCur->pDesc; if (pCur == pFirst) return NULL; iCur = pCur->iParent - 1; /* (underflow handled above) */ pCur = pCur->pParent; if (!pCur) break; } } return NULL; } /** * Look up the first and last descriptors for a (single) pattern expression. * * This is used to optimize pattern enumerations and doesn't have to return 100% * accurate results if that costs too much. * * @returns Pointer to the first descriptor in the range. * @param pRoot The root node. * @param pList The descriptor list anchor. * @param pszPat The name patter to lookup. * @param ppLastDesc Where to store the address of the last * descriptor (approximate). */ static PSTAMDESC stamR3LookupFindPatternDescRange(PSTAMLOOKUP pRoot, PRTLISTANCHOR pList, const char *pszPat, PSTAMDESC *ppLastDesc) { Assert(!pRoot->pParent); /* * If there is an early enough wildcard, the whole list needs to be searched. */ if ( pszPat[0] == '*' || pszPat[0] == '?' || pszPat[1] == '*' || pszPat[1] == '?') { *ppLastDesc = RTListGetLast(pList, STAMDESC, ListEntry); return RTListGetFirst(pList, STAMDESC, ListEntry); } /* * All statistics starts with a slash. */ while ( *pszPat++ == '/' && pRoot->cDescsInTree > 0 && pRoot->cChildren > 0) { const char *pszEnd = strchr(pszPat, '/'); uint32_t cch = pszEnd ? pszEnd - pszPat : (uint32_t)strlen(pszPat); if (!cch) break; const char *pszPat1 = (const char *)memchr(pszPat, '*', cch); const char *pszPat2 = (const char *)memchr(pszPat, '?', cch); if (pszPat1 || pszPat2) { /* We've narrowed it down to a sub-tree now. */ PSTAMLOOKUP pFirst = pRoot->papChildren[0]; PSTAMLOOKUP pLast = pRoot->papChildren[pRoot->cChildren - 1]; /** @todo narrow the range further if both pszPat1/2 != pszPat. */ *ppLastDesc = stamR3LookupFindLastDescForRange(pFirst, pLast); return stamR3LookupFindFirstDescForRange(pFirst, pLast); } PSTAMLOOKUP pChild = stamR3LookupFindChild(pRoot, pszPat, cch, NULL); if (!pChild) break; /* Advance */ if (!pszEnd) return *ppLastDesc = pChild->pDesc; pszPat = pszEnd; pRoot = pChild; } /* No match. */ *ppLastDesc = NULL; return NULL; } /** * Look up the first descriptors for starts-with name string. * * This is used to optimize deletion. * * @returns Pointer to the first descriptor in the range. * @param pRoot The root node. * @param pchPrefix The name prefix. * @param cchPrefix The name prefix length (can be shorter than the * actual string). * @param ppLastDesc Where to store the address of the last descriptor. * @sa stamR3LookupFindPatternDescRange */ static PSTAMDESC stamR3LookupFindByPrefixRange(PSTAMLOOKUP pRoot, const char *pchPrefix, uint32_t cchPrefix, PSTAMDESC *ppLastDesc) { *ppLastDesc = NULL; Assert(!pRoot->pParent); AssertReturn(cchPrefix > 0, NULL); /* * We start with a root slash. */ if (!cchPrefix || *pchPrefix != '/') return NULL; /* * Walk thru the prefix component by component, since that's how * the lookup tree is organized. */ while ( cchPrefix && *pchPrefix == '/' && pRoot->cDescsInTree > 0 && pRoot->cChildren > 0) { cchPrefix -= 1; pchPrefix += 1; const char *pszEnd = (const char *)memchr(pchPrefix, '/', cchPrefix); if (!pszEnd) { /* * We've narrowed it down to a sub-tree now. If we've no more prefix to work * with now (e.g. '/Devices/'), the prefix matches all the children. Otherwise, * traverse the children to find the ones matching the prefix. */ if (!cchPrefix) { *ppLastDesc = stamR3LookupFindLastDescForRange(pRoot->papChildren[0], pRoot->papChildren[pRoot->cChildren - 1]); return stamR3LookupFindFirstDescForRange(pRoot->papChildren[0], pRoot->papChildren[pRoot->cChildren - 1]); } size_t iEnd = pRoot->cChildren; if (iEnd < 16) { /* Linear scan of the children: */ for (size_t i = 0; i < pRoot->cChildren; i++) { PSTAMLOOKUP pCur = pRoot->papChildren[i]; if (pCur->cch >= cchPrefix) { int iDiff = memcmp(pCur->szName, pchPrefix, cchPrefix); if (iDiff == 0) { size_t iLast = i; while (++iLast < pRoot->cChildren) { PSTAMLOOKUP pCur2 = pRoot->papChildren[iLast]; if ( pCur2->cch < cchPrefix || memcmp(pCur2->szName, pchPrefix, cchPrefix) != 0) break; } iLast--; *ppLastDesc = stamR3LookupFindLastDescForRange(pCur, pRoot->papChildren[iLast]); return stamR3LookupFindFirstDescForRange(pCur, pRoot->papChildren[iLast]); } if (iDiff > 0) break; } } } else { /* Binary search to find something matching the prefix, followed by a reverse scan to locate the first child: */ size_t iFirst = 0; size_t i = iEnd / 2; for (;;) { PSTAMLOOKUP pCur = pRoot->papChildren[i]; int iDiff; if (pCur->cch >= cchPrefix) iDiff = memcmp(pCur->szName, pchPrefix, cchPrefix); else { iDiff = memcmp(pCur->szName, pchPrefix, pCur->cch); if (!iDiff) iDiff = 1; } if (iDiff > 0) { if (iFirst < i) iEnd = i; else return NULL; } else if (iDiff < 0) { i += 1; if (i < iEnd) iFirst = i; else return NULL; } else { /* Match. Reverse scan to find the first. */ iFirst = i; while ( iFirst > 0 && (pCur = pRoot->papChildren[iFirst - 1])->cch >= cchPrefix && memcmp(pCur->szName, pchPrefix, cchPrefix) == 0) iFirst--; /* Forward scan to find the last.*/ size_t iLast = i; while (++iLast < pRoot->cChildren) { pCur = pRoot->papChildren[iLast]; if ( pCur->cch < cchPrefix || memcmp(pCur->szName, pchPrefix, cchPrefix) != 0) break; } iLast--; *ppLastDesc = stamR3LookupFindLastDescForRange(pRoot->papChildren[iFirst], pRoot->papChildren[iLast]); return stamR3LookupFindFirstDescForRange(pRoot->papChildren[iFirst], pRoot->papChildren[iLast]); } i = iFirst + (iEnd - iFirst) / 2; } } break; } /* Find child matching the path component: */ uint32_t cchChild = pszEnd - pchPrefix; PSTAMLOOKUP pChild = stamR3LookupFindChild(pRoot, pchPrefix, cchChild, NULL); if (!pChild) break; /* Advance: */ cchPrefix -= cchChild; pchPrefix = pszEnd; pRoot = pChild; } return NULL; } /** * Increments the cDescInTree member of the given node an all ancestors. * * @param pLookup The lookup node. */ static void stamR3LookupIncUsage(PSTAMLOOKUP pLookup) { Assert(pLookup->pDesc); PSTAMLOOKUP pCur = pLookup; while (pCur != NULL) { pCur->cDescsInTree++; pCur = pCur->pParent; } } /** * Descrements the cDescInTree member of the given node an all ancestors. * * @param pLookup The lookup node. */ static void stamR3LookupDecUsage(PSTAMLOOKUP pLookup) { Assert(!pLookup->pDesc); PSTAMLOOKUP pCur = pLookup; while (pCur != NULL) { Assert(pCur->cDescsInTree > 0); pCur->cDescsInTree--; pCur = pCur->pParent; } } /** * Frees empty lookup nodes if it's worth it. * * @param pLookup The lookup node. */ static void stamR3LookupMaybeFree(PSTAMLOOKUP pLookup) { Assert(!pLookup->pDesc); /* * Free between two and three levels of nodes. Freeing too much most * likely wasted effort since we're either going to repopluate the tree * or quit the whole thing. */ if (pLookup->cDescsInTree > 0) return; PSTAMLOOKUP pCur = pLookup->pParent; if (!pCur) return; if (pCur->cDescsInTree > 0) return; PSTAMLOOKUP pParent = pCur->pParent; if (!pParent) return; if (pParent->cDescsInTree == 0 && pParent->pParent) { pCur = pParent; pParent = pCur->pParent; } /* * Remove pCur from pParent. */ PSTAMLOOKUP *papChildren = pParent->papChildren; uint32_t cChildren = --pParent->cChildren; for (uint32_t i = pCur->iParent; i < cChildren; i++) { PSTAMLOOKUP pChild = papChildren[i + 1]; pChild->iParent = i; papChildren[i] = pChild; } pCur->pParent = NULL; pCur->iParent = UINT16_MAX; /* * Destroy pCur. */ stamR3LookupDestroyTree(pCur); } /** * Destroys a lookup tree. * * This is used by STAMR3Term as well as stamR3LookupMaybeFree. * * @param pRoot The root of the tree (must have no parent). */ static void stamR3LookupDestroyTree(PSTAMLOOKUP pRoot) { Assert(pRoot); Assert(!pRoot->pParent); PSTAMLOOKUP pCur = pRoot; for (;;) { uint32_t i = pCur->cChildren; if (i > 0) { /* * Push child (with leaf optimization). */ PSTAMLOOKUP pChild = pCur->papChildren[--i]; if (pChild->cChildren != 0) pCur = pChild; else { /* free leaves. */ for (;;) { if (pChild->papChildren) { RTMemFree(pChild->papChildren); pChild->papChildren = NULL; } RTMemFree(pChild); pCur->papChildren[i] = NULL; /* next */ if (i == 0) { pCur->cChildren = 0; break; } pChild = pCur->papChildren[--i]; if (pChild->cChildren != 0) { pCur->cChildren = i + 1; pCur = pChild; break; } } } } else { /* * Pop and free current. */ Assert(!pCur->pDesc); PSTAMLOOKUP pParent = pCur->pParent; Assert(pCur->iParent == (pParent ? pParent->cChildren - 1 : UINT16_MAX)); RTMemFree(pCur->papChildren); pCur->papChildren = NULL; RTMemFree(pCur); pCur = pParent; if (!pCur) break; pCur->papChildren[--pCur->cChildren] = NULL; } } } /** * Internal worker for the different register calls. * * @returns VBox status code. * @param pUVM Pointer to the user mode VM structure. * @param pvSample Pointer to the sample. * @param pfnReset Callback for resetting the sample. NULL should be used if the sample can't be reset. * @param pfnPrint Print the sample. * @param enmType Sample type. This indicates what pvSample is pointing at. * @param enmVisibility Visibility type specifying whether unused statistics should be visible or not. * @param pszName The sample name format string. * @param enmUnit Sample unit. * @param pszDesc Sample description. * @param iRefreshGrp The refresh group, STAM_REFRESH_GRP_XXX. * @remark There is currently no device or driver variant of this API. Add one if it should become necessary! */ static int stamR3RegisterU(PUVM pUVM, void *pvSample, PFNSTAMR3CALLBACKRESET pfnReset, PFNSTAMR3CALLBACKPRINT pfnPrint, STAMTYPE enmType, STAMVISIBILITY enmVisibility, const char *pszName, STAMUNIT enmUnit, const char *pszDesc, uint8_t iRefreshGrp) { AssertReturn(pszName[0] == '/', VERR_INVALID_NAME); AssertReturn(pszName[1] != '/' && pszName[1], VERR_INVALID_NAME); uint32_t const cchName = (uint32_t)strlen(pszName); AssertReturn(cchName <= STAM_MAX_NAME_LEN, VERR_OUT_OF_RANGE); AssertReturn(pszName[cchName - 1] != '/', VERR_INVALID_NAME); AssertReturn(memchr(pszName, '\\', cchName) == NULL, VERR_INVALID_NAME); AssertReturn(iRefreshGrp == STAM_REFRESH_GRP_NONE || iRefreshGrp < 64, VERR_INVALID_PARAMETER); STAM_LOCK_WR(pUVM); /* * Look up the tree location, populating the lookup tree as we walk it. */ PSTAMLOOKUP pLookup = pUVM->stam.s.pRoot; Assert(pLookup); uint32_t offName = 1; for (;;) { /* Get the next part of the path. */ const char *pszStart = &pszName[offName]; const char *pszEnd = strchr(pszStart, '/'); uint32_t cch = pszEnd ? (uint32_t)(pszEnd - pszStart) : cchName - offName; if (cch == 0) { STAM_UNLOCK_WR(pUVM); AssertMsgFailed(("No double or trailing slashes are allowed: '%s'\n", pszName)); return VERR_INVALID_NAME; } /* Do the looking up. */ uint32_t iChild = 0; PSTAMLOOKUP pChild = stamR3LookupFindChild(pLookup, pszStart, cch, &iChild); if (!pChild) { pChild = stamR3LookupNewChild(pLookup, pszStart, cch, offName, iChild); if (!pChild) { STAM_UNLOCK_WR(pUVM); return VERR_NO_MEMORY; } } /* Advance. */ pLookup = pChild; if (!pszEnd) break; offName += cch + 1; } if (pLookup->pDesc) { STAM_UNLOCK_WR(pUVM); AssertMsgFailed(("Duplicate sample name: %s\n", pszName)); return VERR_ALREADY_EXISTS; } PSTAMDESC pCur = stamR3LookupFindNextWithDesc(pLookup); /* * Check that the name doesn't screw up sorting order when taking * slashes into account. The QT GUI makes some assumptions. * Problematic chars are: !"#$%&'()*+,-. */ #ifdef VBOX_STRICT Assert(pszName[0] == '/'); PSTAMDESC pPrev = pCur ? RTListGetPrev(&pUVM->stam.s.List, pCur, STAMDESC, ListEntry) : RTListGetLast(&pUVM->stam.s.List, STAMDESC, ListEntry); Assert(!pPrev || strcmp(pszName, pPrev->pszName) > 0); Assert(!pCur || strcmp(pszName, pCur->pszName) < 0); Assert(!pPrev || stamR3SlashCompare(pPrev->pszName, pszName) < 0); Assert(!pCur || stamR3SlashCompare(pCur->pszName, pszName) > 0); /* * Check alignment requirements. */ switch (enmType) { /* 8 byte / 64-bit */ case STAMTYPE_U64: case STAMTYPE_U64_RESET: case STAMTYPE_X64: case STAMTYPE_X64_RESET: case STAMTYPE_COUNTER: case STAMTYPE_PROFILE: case STAMTYPE_PROFILE_ADV: AssertMsg(!((uintptr_t)pvSample & 7), ("%p - %s\n", pvSample, pszName)); break; /* 4 byte / 32-bit */ case STAMTYPE_RATIO_U32: case STAMTYPE_RATIO_U32_RESET: case STAMTYPE_U32: case STAMTYPE_U32_RESET: case STAMTYPE_X32: case STAMTYPE_X32_RESET: AssertMsg(!((uintptr_t)pvSample & 3), ("%p - %s\n", pvSample, pszName)); break; /* 2 byte / 32-bit */ case STAMTYPE_U16: case STAMTYPE_U16_RESET: case STAMTYPE_X16: case STAMTYPE_X16_RESET: AssertMsg(!((uintptr_t)pvSample & 1), ("%p - %s\n", pvSample, pszName)); break; /* 1 byte / 8-bit / unaligned */ case STAMTYPE_U8: case STAMTYPE_U8_RESET: case STAMTYPE_X8: case STAMTYPE_X8_RESET: case STAMTYPE_BOOL: case STAMTYPE_BOOL_RESET: case STAMTYPE_CALLBACK: break; default: AssertMsgFailed(("%d\n", enmType)); break; } #endif /* VBOX_STRICT */ /* * Create a new node and insert it at the current location. */ int rc; size_t cbDesc = pszDesc ? strlen(pszDesc) + 1 : 0; PSTAMDESC pNew = (PSTAMDESC)RTMemAlloc(sizeof(*pNew) + cchName + 1 + cbDesc); if (pNew) { pNew->pszName = (char *)memcpy((char *)(pNew + 1), pszName, cchName + 1); pNew->enmType = enmType; pNew->enmVisibility = enmVisibility; if (enmType != STAMTYPE_CALLBACK) pNew->u.pv = pvSample; else { pNew->u.Callback.pvSample = pvSample; pNew->u.Callback.pfnReset = pfnReset; pNew->u.Callback.pfnPrint = pfnPrint; } pNew->enmUnit = enmUnit; pNew->iRefreshGroup = iRefreshGrp; pNew->pszDesc = NULL; if (pszDesc) pNew->pszDesc = (char *)memcpy((char *)(pNew + 1) + cchName + 1, pszDesc, cbDesc); if (pCur) RTListNodeInsertBefore(&pCur->ListEntry, &pNew->ListEntry); else RTListAppend(&pUVM->stam.s.List, &pNew->ListEntry); pNew->pLookup = pLookup; pLookup->pDesc = pNew; stamR3LookupIncUsage(pLookup); stamR3ResetOne(pNew, pUVM->pVM); rc = VINF_SUCCESS; } else rc = VERR_NO_MEMORY; STAM_UNLOCK_WR(pUVM); return rc; } /** * Destroys the statistics descriptor, unlinking it and freeing all resources. * * @returns VINF_SUCCESS * @param pCur The descriptor to destroy. */ static int stamR3DestroyDesc(PSTAMDESC pCur) { RTListNodeRemove(&pCur->ListEntry); pCur->pLookup->pDesc = NULL; /** @todo free lookup nodes once it's working. */ stamR3LookupDecUsage(pCur->pLookup); stamR3LookupMaybeFree(pCur->pLookup); RTMemFree(pCur); return VINF_SUCCESS; } /** * Deregisters a sample previously registered by STAR3Register() given its * address. * * This is intended used for devices which can be unplugged and for * temporary samples. * * @returns VBox status code. * @param pUVM Pointer to the user mode VM structure. * @param pvSample Pointer to the sample registered with STAMR3Register(). */ VMMR3DECL(int) STAMR3DeregisterByAddr(PUVM pUVM, void *pvSample) { UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE); /* This is a complete waste of time when shutting down. */ VMSTATE enmState = VMR3GetStateU(pUVM); if (enmState >= VMSTATE_DESTROYING) return VINF_SUCCESS; STAM_LOCK_WR(pUVM); /* * Search for it. */ int rc = VERR_INVALID_HANDLE; PSTAMDESC pCur, pNext; RTListForEachSafe(&pUVM->stam.s.List, pCur, pNext, STAMDESC, ListEntry) { if (pCur->u.pv == pvSample) rc = stamR3DestroyDesc(pCur); } STAM_UNLOCK_WR(pUVM); return rc; } /** * Worker for STAMR3Deregister, STAMR3DeregisterV and STAMR3DeregisterF. * * @returns VBox status code. * @retval VWRN_NOT_FOUND if no matching names found. * * @param pUVM Pointer to the user mode VM structure. * @param pszPat The name pattern. */ static int stamR3DeregisterByPattern(PUVM pUVM, const char *pszPat) { Assert(!strchr(pszPat, '|')); /* single pattern! */ int rc = VWRN_NOT_FOUND; STAM_LOCK_WR(pUVM); PSTAMDESC pLast; PSTAMDESC pCur = stamR3LookupFindPatternDescRange(pUVM->stam.s.pRoot, &pUVM->stam.s.List, pszPat, &pLast); if (pCur) { for (;;) { PSTAMDESC pNext = RTListNodeGetNext(&pCur->ListEntry, STAMDESC, ListEntry); if (RTStrSimplePatternMatch(pszPat, pCur->pszName)) rc = stamR3DestroyDesc(pCur); /* advance. */ if (pCur == pLast) break; pCur = pNext; } Assert(pLast); } else Assert(!pLast); STAM_UNLOCK_WR(pUVM); return rc; } /** * Deregister zero or more samples given a (single) pattern matching their * names. * * @returns VBox status code. * @param pUVM Pointer to the user mode VM structure. * @param pszPat The name pattern. * @sa STAMR3DeregisterF, STAMR3DeregisterV */ VMMR3DECL(int) STAMR3Deregister(PUVM pUVM, const char *pszPat) { UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE); /* This is a complete waste of time when shutting down. */ VMSTATE enmState = VMR3GetStateU(pUVM); if (enmState >= VMSTATE_DESTROYING) return VINF_SUCCESS; return stamR3DeregisterByPattern(pUVM, pszPat); } /** * Deregister zero or more samples given a (single) pattern matching their * names. * * @returns VBox status code. * @param pUVM Pointer to the user mode VM structure. * @param pszPatFmt The name pattern format string. * @param ... Format string arguments. * @sa STAMR3Deregister, STAMR3DeregisterV */ VMMR3DECL(int) STAMR3DeregisterF(PUVM pUVM, const char *pszPatFmt, ...) { va_list va; va_start(va, pszPatFmt); int rc = STAMR3DeregisterV(pUVM, pszPatFmt, va); va_end(va); return rc; } /** * Deregister zero or more samples given a (single) pattern matching their * names. * * @returns VBox status code. * @param pUVM Pointer to the user mode VM structure. * @param pszPatFmt The name pattern format string. * @param va Format string arguments. * @sa STAMR3Deregister, STAMR3DeregisterF */ VMMR3DECL(int) STAMR3DeregisterV(PUVM pUVM, const char *pszPatFmt, va_list va) { UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE); /* This is a complete waste of time when shutting down. */ VMSTATE enmState = VMR3GetStateU(pUVM); if (enmState >= VMSTATE_DESTROYING) return VINF_SUCCESS; char szPat[STAM_MAX_NAME_LEN + 8]; size_t cchPat = RTStrPrintfV(szPat, sizeof(szPat), pszPatFmt, va); AssertReturn(cchPat <= STAM_MAX_NAME_LEN, VERR_OUT_OF_RANGE); return stamR3DeregisterByPattern(pUVM, szPat); } /** * Deregister zero or more samples given their name prefix. * * @returns VBox status code. * @param pUVM Pointer to the user mode VM structure. * @param pszPrefix The name prefix of the samples to remove. * @sa STAMR3Deregister, STAMR3DeregisterF, STAMR3DeregisterV */ VMMR3DECL(int) STAMR3DeregisterByPrefix(PUVM pUVM, const char *pszPrefix) { UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE); /* This is a complete waste of time when shutting down. */ VMSTATE enmState = VMR3GetStateU(pUVM); if (enmState >= VMSTATE_DESTROYING) return VINF_SUCCESS; size_t const cchPrefix = strlen(pszPrefix); int rc = VWRN_NOT_FOUND; STAM_LOCK_WR(pUVM); PSTAMDESC pLast; PSTAMDESC pCur = stamR3LookupFindByPrefixRange(pUVM->stam.s.pRoot, pszPrefix, (uint32_t)cchPrefix, &pLast); if (pCur) for (;;) { PSTAMDESC const pNext = RTListNodeGetNext(&pCur->ListEntry, STAMDESC, ListEntry); Assert(strncmp(pCur->pszName, pszPrefix, cchPrefix) == 0); rc = stamR3DestroyDesc(pCur); /* advance. */ if (pCur == pLast) break; pCur = pNext; } STAM_UNLOCK_WR(pUVM); return rc; } /** * Resets statistics for the specified VM. * It's possible to select a subset of the samples. * * @returns VBox status code. (Basically, it cannot fail.) * @param pUVM The user mode VM handle. * @param pszPat The name matching pattern. See somewhere_where_this_is_described_in_detail. * If NULL all samples are reset. * @remarks Don't confuse this with the other 'XYZR3Reset' methods, it's not called at VM reset. */ VMMR3DECL(int) STAMR3Reset(PUVM pUVM, const char *pszPat) { UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE); VM_ASSERT_VALID_EXT_RETURN(pUVM->pVM, VERR_INVALID_VM_HANDLE); int rc = VINF_SUCCESS; /* ring-0 */ GVMMRESETSTATISTICSSREQ GVMMReq; GMMRESETSTATISTICSSREQ GMMReq; bool fGVMMMatched = (!pszPat || !*pszPat) && !SUPR3IsDriverless(); bool fGMMMatched = fGVMMMatched; if (fGVMMMatched) { memset(&GVMMReq.Stats, 0xff, sizeof(GVMMReq.Stats)); memset(&GMMReq.Stats, 0xff, sizeof(GMMReq.Stats)); } else { char *pszCopy; unsigned cExpressions; char **papszExpressions = stamR3SplitPattern(pszPat, &cExpressions, &pszCopy); if (!papszExpressions) return VERR_NO_MEMORY; /* GVMM */ RT_ZERO(GVMMReq.Stats); for (unsigned i = 0; i < RT_ELEMENTS(g_aGVMMStats); i++) if (stamR3MultiMatch(papszExpressions, cExpressions, NULL, g_aGVMMStats[i].pszName)) { *((uint8_t *)&GVMMReq.Stats + g_aGVMMStats[i].offVar) = 0xff; fGVMMMatched = true; } if (!fGVMMMatched) { /** @todo match cpu leaves some rainy day. */ } /* GMM */ RT_ZERO(GMMReq.Stats); for (unsigned i = 0; i < RT_ELEMENTS(g_aGMMStats); i++) if (stamR3MultiMatch(papszExpressions, cExpressions, NULL, g_aGMMStats[i].pszName)) { *((uint8_t *)&GMMReq.Stats + g_aGMMStats[i].offVar) = 0xff; fGMMMatched = true; } RTMemTmpFree(papszExpressions); RTStrFree(pszCopy); } STAM_LOCK_WR(pUVM); if (fGVMMMatched) { PVM pVM = pUVM->pVM; GVMMReq.Hdr.cbReq = sizeof(GVMMReq); GVMMReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC; GVMMReq.pSession = pVM->pSession; rc = SUPR3CallVMMR0Ex(VMCC_GET_VMR0_FOR_CALL(pVM), NIL_VMCPUID, VMMR0_DO_GVMM_RESET_STATISTICS, 0, &GVMMReq.Hdr); } if (fGMMMatched) { PVM pVM = pUVM->pVM; GMMReq.Hdr.cbReq = sizeof(GMMReq); GMMReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC; GMMReq.pSession = pVM->pSession; rc = SUPR3CallVMMR0Ex(VMCC_GET_VMR0_FOR_CALL(pVM), NIL_VMCPUID, VMMR0_DO_GMM_RESET_STATISTICS, 0, &GMMReq.Hdr); } /* and the reset */ stamR3EnumU(pUVM, pszPat, false /* fUpdateRing0 */, stamR3ResetOne, pUVM->pVM); STAM_UNLOCK_WR(pUVM); return rc; } /** * Resets one statistics sample. * Callback for stamR3EnumU(). * * @returns VINF_SUCCESS * @param pDesc Pointer to the current descriptor. * @param pvArg User argument - Pointer to the VM. */ static int stamR3ResetOne(PSTAMDESC pDesc, void *pvArg) { switch (pDesc->enmType) { case STAMTYPE_COUNTER: ASMAtomicXchgU64(&pDesc->u.pCounter->c, 0); break; case STAMTYPE_PROFILE: case STAMTYPE_PROFILE_ADV: ASMAtomicXchgU64(&pDesc->u.pProfile->cPeriods, 0); ASMAtomicXchgU64(&pDesc->u.pProfile->cTicks, 0); ASMAtomicXchgU64(&pDesc->u.pProfile->cTicksMax, 0); ASMAtomicXchgU64(&pDesc->u.pProfile->cTicksMin, UINT64_MAX); break; case STAMTYPE_RATIO_U32_RESET: ASMAtomicXchgU32(&pDesc->u.pRatioU32->u32A, 0); ASMAtomicXchgU32(&pDesc->u.pRatioU32->u32B, 0); break; case STAMTYPE_CALLBACK: if (pDesc->u.Callback.pfnReset) pDesc->u.Callback.pfnReset((PVM)pvArg, pDesc->u.Callback.pvSample); break; case STAMTYPE_U8_RESET: case STAMTYPE_X8_RESET: ASMAtomicXchgU8(pDesc->u.pu8, 0); break; case STAMTYPE_U16_RESET: case STAMTYPE_X16_RESET: ASMAtomicXchgU16(pDesc->u.pu16, 0); break; case STAMTYPE_U32_RESET: case STAMTYPE_X32_RESET: ASMAtomicXchgU32(pDesc->u.pu32, 0); break; case STAMTYPE_U64_RESET: case STAMTYPE_X64_RESET: ASMAtomicXchgU64(pDesc->u.pu64, 0); break; case STAMTYPE_BOOL_RESET: ASMAtomicXchgBool(pDesc->u.pf, false); break; /* These are custom and will not be touched. */ case STAMTYPE_U8: case STAMTYPE_X8: case STAMTYPE_U16: case STAMTYPE_X16: case STAMTYPE_U32: case STAMTYPE_X32: case STAMTYPE_U64: case STAMTYPE_X64: case STAMTYPE_RATIO_U32: case STAMTYPE_BOOL: break; default: AssertMsgFailed(("enmType=%d\n", pDesc->enmType)); break; } NOREF(pvArg); return VINF_SUCCESS; } /** * Get a snapshot of the statistics. * It's possible to select a subset of the samples. * * @returns VBox status code. (Basically, it cannot fail.) * @param pUVM The user mode VM handle. * @param pszPat The name matching pattern. See somewhere_where_this_is_described_in_detail. * If NULL all samples are reset. * @param fWithDesc Whether to include the descriptions. * @param ppszSnapshot Where to store the pointer to the snapshot data. * The format of the snapshot should be XML, but that will have to be discussed * when this function is implemented. * The returned pointer must be freed by calling STAMR3SnapshotFree(). * @param pcchSnapshot Where to store the size of the snapshot data. (Excluding the trailing '\0') */ VMMR3DECL(int) STAMR3Snapshot(PUVM pUVM, const char *pszPat, char **ppszSnapshot, size_t *pcchSnapshot, bool fWithDesc) { UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE); VM_ASSERT_VALID_EXT_RETURN(pUVM->pVM, VERR_INVALID_VM_HANDLE); STAMR3SNAPSHOTONE State = { NULL, NULL, NULL, pUVM->pVM, 0, VINF_SUCCESS, fWithDesc }; /* * Write the XML header. */ /** @todo Make this proper & valid XML. */ stamR3SnapshotPrintf(&State, "\n"); /* * Write the content. */ stamR3SnapshotPrintf(&State, "\n"); int rc = stamR3EnumU(pUVM, pszPat, true /* fUpdateRing0 */, stamR3SnapshotOne, &State); stamR3SnapshotPrintf(&State, "\n"); if (RT_SUCCESS(rc)) rc = State.rc; else { RTMemFree(State.pszStart); State.pszStart = State.pszEnd = State.psz = NULL; State.cbAllocated = 0; } /* * Done. */ *ppszSnapshot = State.pszStart; if (pcchSnapshot) *pcchSnapshot = State.psz - State.pszStart; return rc; } /** * stamR3EnumU callback employed by STAMR3Snapshot. * * @returns VBox status code, but it's interpreted as 0 == success / !0 == failure by enmR3Enum. * @param pDesc The sample. * @param pvArg The snapshot status structure. */ static int stamR3SnapshotOne(PSTAMDESC pDesc, void *pvArg) { PSTAMR3SNAPSHOTONE pThis = (PSTAMR3SNAPSHOTONE)pvArg; switch (pDesc->enmType) { case STAMTYPE_COUNTER: if (pDesc->enmVisibility == STAMVISIBILITY_USED && pDesc->u.pCounter->c == 0) return VINF_SUCCESS; stamR3SnapshotPrintf(pThis, "u.pCounter->c); break; case STAMTYPE_PROFILE: case STAMTYPE_PROFILE_ADV: if (pDesc->enmVisibility == STAMVISIBILITY_USED && pDesc->u.pProfile->cPeriods == 0) return VINF_SUCCESS; stamR3SnapshotPrintf(pThis, "u.pProfile->cPeriods, pDesc->u.pProfile->cTicks, pDesc->u.pProfile->cTicksMin, pDesc->u.pProfile->cTicksMax); break; case STAMTYPE_RATIO_U32: case STAMTYPE_RATIO_U32_RESET: if (pDesc->enmVisibility == STAMVISIBILITY_USED && !pDesc->u.pRatioU32->u32A && !pDesc->u.pRatioU32->u32B) return VINF_SUCCESS; stamR3SnapshotPrintf(pThis, "u.pRatioU32->u32A, pDesc->u.pRatioU32->u32B); break; case STAMTYPE_CALLBACK: { char szBuf[512]; pDesc->u.Callback.pfnPrint(pThis->pVM, pDesc->u.Callback.pvSample, szBuf, sizeof(szBuf)); stamR3SnapshotPrintf(pThis, "enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu8 == 0) return VINF_SUCCESS; stamR3SnapshotPrintf(pThis, "u.pu8); break; case STAMTYPE_X8: case STAMTYPE_X8_RESET: if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu8 == 0) return VINF_SUCCESS; stamR3SnapshotPrintf(pThis, "u.pu8); break; case STAMTYPE_U16: case STAMTYPE_U16_RESET: if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu16 == 0) return VINF_SUCCESS; stamR3SnapshotPrintf(pThis, "u.pu16); break; case STAMTYPE_X16: case STAMTYPE_X16_RESET: if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu16 == 0) return VINF_SUCCESS; stamR3SnapshotPrintf(pThis, "u.pu16); break; case STAMTYPE_U32: case STAMTYPE_U32_RESET: if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu32 == 0) return VINF_SUCCESS; stamR3SnapshotPrintf(pThis, "u.pu32); break; case STAMTYPE_X32: case STAMTYPE_X32_RESET: if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu32 == 0) return VINF_SUCCESS; stamR3SnapshotPrintf(pThis, "u.pu32); break; case STAMTYPE_U64: case STAMTYPE_U64_RESET: if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu64 == 0) return VINF_SUCCESS; stamR3SnapshotPrintf(pThis, "u.pu64); break; case STAMTYPE_X64: case STAMTYPE_X64_RESET: if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu64 == 0) return VINF_SUCCESS; stamR3SnapshotPrintf(pThis, "u.pu64); break; case STAMTYPE_BOOL: case STAMTYPE_BOOL_RESET: if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pf == false) return VINF_SUCCESS; stamR3SnapshotPrintf(pThis, "u.pf); break; default: AssertMsgFailed(("%d\n", pDesc->enmType)); return 0; } stamR3SnapshotPrintf(pThis, " unit=\"%s\"", STAMR3GetUnit(pDesc->enmUnit)); switch (pDesc->enmVisibility) { default: case STAMVISIBILITY_ALWAYS: break; case STAMVISIBILITY_USED: stamR3SnapshotPrintf(pThis, " vis=\"used\""); break; case STAMVISIBILITY_NOT_GUI: stamR3SnapshotPrintf(pThis, " vis=\"not-gui\""); break; } stamR3SnapshotPrintf(pThis, " name=\"%s\"", pDesc->pszName); if (pThis->fWithDesc && pDesc->pszDesc) { /* * The description is a bit tricky as it may include chars that * xml requires to be escaped. */ const char *pszBadChar = strpbrk(pDesc->pszDesc, "&<>\"'"); if (!pszBadChar) return stamR3SnapshotPrintf(pThis, " desc=\"%s\"/>\n", pDesc->pszDesc); stamR3SnapshotPrintf(pThis, " desc=\""); const char *pszCur = pDesc->pszDesc; do { stamR3SnapshotPrintf(pThis, "%.*s", pszBadChar - pszCur, pszCur); switch (*pszBadChar) { case '&': stamR3SnapshotPrintf(pThis, "&"); break; case '<': stamR3SnapshotPrintf(pThis, "<"); break; case '>': stamR3SnapshotPrintf(pThis, ">"); break; case '"': stamR3SnapshotPrintf(pThis, """); break; case '\'': stamR3SnapshotPrintf(pThis, "'"); break; default: AssertMsgFailed(("%c", *pszBadChar)); break; } pszCur = pszBadChar + 1; pszBadChar = strpbrk(pszCur, "&<>\"'"); } while (pszBadChar); return stamR3SnapshotPrintf(pThis, "%s\"/>\n", pszCur); } return stamR3SnapshotPrintf(pThis, "/>\n"); } /** * Output callback for stamR3SnapshotPrintf. * * @returns number of bytes written. * @param pvArg The snapshot status structure. * @param pach Pointer to an array of characters (bytes). * @param cch The number or chars (bytes) to write from the array. */ static DECLCALLBACK(size_t) stamR3SnapshotOutput(void *pvArg, const char *pach, size_t cch) { PSTAMR3SNAPSHOTONE pThis = (PSTAMR3SNAPSHOTONE)pvArg; /* * Make sure we've got space for it. */ if (RT_UNLIKELY((uintptr_t)pThis->pszEnd - (uintptr_t)pThis->psz < cch + 1)) { if (RT_FAILURE(pThis->rc)) return 0; size_t cbNewSize = pThis->cbAllocated; if (cbNewSize > cch) cbNewSize *= 2; else cbNewSize += RT_ALIGN(cch + 1, 0x1000); char *pszNew = (char *)RTMemRealloc(pThis->pszStart, cbNewSize); if (!pszNew) { /* * Free up immediately, out-of-memory is bad news and this * isn't an important allocations / API. */ pThis->rc = VERR_NO_MEMORY; RTMemFree(pThis->pszStart); pThis->pszStart = pThis->pszEnd = pThis->psz = NULL; pThis->cbAllocated = 0; return 0; } pThis->psz = pszNew + (pThis->psz - pThis->pszStart); pThis->pszStart = pszNew; pThis->pszEnd = pszNew + cbNewSize; pThis->cbAllocated = cbNewSize; } /* * Copy the chars to the buffer and terminate it. */ if (cch) { memcpy(pThis->psz, pach, cch); pThis->psz += cch; } *pThis->psz = '\0'; return cch; } /** * Wrapper around RTStrFormatV for use by the snapshot API. * * @returns VBox status code. * @param pThis The snapshot status structure. * @param pszFormat The format string. * @param ... Optional arguments. */ static int stamR3SnapshotPrintf(PSTAMR3SNAPSHOTONE pThis, const char *pszFormat, ...) { va_list va; va_start(va, pszFormat); RTStrFormatV(stamR3SnapshotOutput, pThis, NULL, NULL, pszFormat, va); va_end(va); return pThis->rc; } /** * Releases a statistics snapshot returned by STAMR3Snapshot(). * * @returns VBox status code. * @param pUVM The user mode VM handle. * @param pszSnapshot The snapshot data pointer returned by STAMR3Snapshot(). * NULL is allowed. */ VMMR3DECL(int) STAMR3SnapshotFree(PUVM pUVM, char *pszSnapshot) { if (pszSnapshot) RTMemFree(pszSnapshot); NOREF(pUVM); return VINF_SUCCESS; } /** * Dumps the selected statistics to the log. * * @returns VBox status code. * @param pUVM Pointer to the user mode VM structure. * @param pszPat The name matching pattern. See somewhere_where_this_is_described_in_detail. * If NULL all samples are written to the log. */ VMMR3DECL(int) STAMR3Dump(PUVM pUVM, const char *pszPat) { UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE); VM_ASSERT_VALID_EXT_RETURN(pUVM->pVM, VERR_INVALID_VM_HANDLE); STAMR3PRINTONEARGS Args; Args.pUVM = pUVM; Args.pvArg = NULL; Args.pfnPrintf = stamR3EnumLogPrintf; stamR3EnumU(pUVM, pszPat, true /* fUpdateRing0 */, stamR3PrintOne, &Args); return VINF_SUCCESS; } /** * Prints to the log. * * @param pArgs Pointer to the print one argument structure. * @param pszFormat Format string. * @param ... Format arguments. */ static DECLCALLBACK(void) stamR3EnumLogPrintf(PSTAMR3PRINTONEARGS pArgs, const char *pszFormat, ...) { va_list va; va_start(va, pszFormat); RTLogPrintfV(pszFormat, va); va_end(va); NOREF(pArgs); } /** * Dumps the selected statistics to the release log. * * @returns VBox status code. * @param pUVM Pointer to the user mode VM structure. * @param pszPat The name matching pattern. See somewhere_where_this_is_described_in_detail. * If NULL all samples are written to the log. */ VMMR3DECL(int) STAMR3DumpToReleaseLog(PUVM pUVM, const char *pszPat) { UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE); VM_ASSERT_VALID_EXT_RETURN(pUVM->pVM, VERR_INVALID_VM_HANDLE); STAMR3PRINTONEARGS Args; Args.pUVM = pUVM; Args.pvArg = NULL; Args.pfnPrintf = stamR3EnumRelLogPrintf; stamR3EnumU(pUVM, pszPat, true /* fUpdateRing0 */, stamR3PrintOne, &Args); return VINF_SUCCESS; } /** * Prints to the release log. * * @param pArgs Pointer to the print one argument structure. * @param pszFormat Format string. * @param ... Format arguments. */ static DECLCALLBACK(void) stamR3EnumRelLogPrintf(PSTAMR3PRINTONEARGS pArgs, const char *pszFormat, ...) { va_list va; va_start(va, pszFormat); RTLogRelPrintfV(pszFormat, va); va_end(va); NOREF(pArgs); } /** * Prints the selected statistics to standard out. * * @returns VBox status code. * @param pUVM The user mode VM handle. * @param pszPat The name matching pattern. See somewhere_where_this_is_described_in_detail. * If NULL all samples are reset. */ VMMR3DECL(int) STAMR3Print(PUVM pUVM, const char *pszPat) { UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE); VM_ASSERT_VALID_EXT_RETURN(pUVM->pVM, VERR_INVALID_VM_HANDLE); STAMR3PRINTONEARGS Args; Args.pUVM = pUVM; Args.pvArg = NULL; Args.pfnPrintf = stamR3EnumPrintf; stamR3EnumU(pUVM, pszPat, true /* fUpdateRing0 */, stamR3PrintOne, &Args); return VINF_SUCCESS; } /** * Prints to stdout. * * @param pArgs Pointer to the print one argument structure. * @param pszFormat Format string. * @param ... Format arguments. */ static DECLCALLBACK(void) stamR3EnumPrintf(PSTAMR3PRINTONEARGS pArgs, const char *pszFormat, ...) { va_list va; va_start(va, pszFormat); RTPrintfV(pszFormat, va); va_end(va); NOREF(pArgs); } /** * Prints one sample. * Callback for stamR3EnumU(). * * @returns VINF_SUCCESS * @param pDesc Pointer to the current descriptor. * @param pvArg User argument - STAMR3PRINTONEARGS. */ static int stamR3PrintOne(PSTAMDESC pDesc, void *pvArg) { PSTAMR3PRINTONEARGS pArgs = (PSTAMR3PRINTONEARGS)pvArg; switch (pDesc->enmType) { case STAMTYPE_COUNTER: if (pDesc->enmVisibility == STAMVISIBILITY_USED && pDesc->u.pCounter->c == 0) return VINF_SUCCESS; pArgs->pfnPrintf(pArgs, "%-32s %8llu %s\n", pDesc->pszName, pDesc->u.pCounter->c, STAMR3GetUnit(pDesc->enmUnit)); break; case STAMTYPE_PROFILE: case STAMTYPE_PROFILE_ADV: { if (pDesc->enmVisibility == STAMVISIBILITY_USED && pDesc->u.pProfile->cPeriods == 0) return VINF_SUCCESS; uint64_t u64 = pDesc->u.pProfile->cPeriods ? pDesc->u.pProfile->cPeriods : 1; pArgs->pfnPrintf(pArgs, "%-32s %8llu %s (%12llu %s, %7llu %s, max %9llu, min %7lld)\n", pDesc->pszName, pDesc->u.pProfile->cTicks / u64, STAMR3GetUnit(pDesc->enmUnit), pDesc->u.pProfile->cTicks, STAMR3GetUnit1(pDesc->enmUnit), pDesc->u.pProfile->cPeriods, STAMR3GetUnit2(pDesc->enmUnit), pDesc->u.pProfile->cTicksMax, pDesc->u.pProfile->cTicksMin); break; } case STAMTYPE_RATIO_U32: case STAMTYPE_RATIO_U32_RESET: if (pDesc->enmVisibility == STAMVISIBILITY_USED && !pDesc->u.pRatioU32->u32A && !pDesc->u.pRatioU32->u32B) return VINF_SUCCESS; pArgs->pfnPrintf(pArgs, "%-32s %8u:%-8u %s\n", pDesc->pszName, pDesc->u.pRatioU32->u32A, pDesc->u.pRatioU32->u32B, STAMR3GetUnit(pDesc->enmUnit)); break; case STAMTYPE_CALLBACK: { char szBuf[512]; pDesc->u.Callback.pfnPrint(pArgs->pUVM->pVM, pDesc->u.Callback.pvSample, szBuf, sizeof(szBuf)); pArgs->pfnPrintf(pArgs, "%-32s %s %s\n", pDesc->pszName, szBuf, STAMR3GetUnit(pDesc->enmUnit)); break; } case STAMTYPE_U8: case STAMTYPE_U8_RESET: if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu8 == 0) return VINF_SUCCESS; pArgs->pfnPrintf(pArgs, "%-32s %8u %s\n", pDesc->pszName, *pDesc->u.pu8, STAMR3GetUnit(pDesc->enmUnit)); break; case STAMTYPE_X8: case STAMTYPE_X8_RESET: if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu8 == 0) return VINF_SUCCESS; pArgs->pfnPrintf(pArgs, "%-32s %8x %s\n", pDesc->pszName, *pDesc->u.pu8, STAMR3GetUnit(pDesc->enmUnit)); break; case STAMTYPE_U16: case STAMTYPE_U16_RESET: if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu16 == 0) return VINF_SUCCESS; pArgs->pfnPrintf(pArgs, "%-32s %8u %s\n", pDesc->pszName, *pDesc->u.pu16, STAMR3GetUnit(pDesc->enmUnit)); break; case STAMTYPE_X16: case STAMTYPE_X16_RESET: if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu16 == 0) return VINF_SUCCESS; pArgs->pfnPrintf(pArgs, "%-32s %8x %s\n", pDesc->pszName, *pDesc->u.pu16, STAMR3GetUnit(pDesc->enmUnit)); break; case STAMTYPE_U32: case STAMTYPE_U32_RESET: if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu32 == 0) return VINF_SUCCESS; pArgs->pfnPrintf(pArgs, "%-32s %8u %s\n", pDesc->pszName, *pDesc->u.pu32, STAMR3GetUnit(pDesc->enmUnit)); break; case STAMTYPE_X32: case STAMTYPE_X32_RESET: if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu32 == 0) return VINF_SUCCESS; pArgs->pfnPrintf(pArgs, "%-32s %8x %s\n", pDesc->pszName, *pDesc->u.pu32, STAMR3GetUnit(pDesc->enmUnit)); break; case STAMTYPE_U64: case STAMTYPE_U64_RESET: if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu64 == 0) return VINF_SUCCESS; pArgs->pfnPrintf(pArgs, "%-32s %8llu %s\n", pDesc->pszName, *pDesc->u.pu64, STAMR3GetUnit(pDesc->enmUnit)); break; case STAMTYPE_X64: case STAMTYPE_X64_RESET: if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu64 == 0) return VINF_SUCCESS; pArgs->pfnPrintf(pArgs, "%-32s %8llx %s\n", pDesc->pszName, *pDesc->u.pu64, STAMR3GetUnit(pDesc->enmUnit)); break; case STAMTYPE_BOOL: case STAMTYPE_BOOL_RESET: if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pf == false) return VINF_SUCCESS; pArgs->pfnPrintf(pArgs, "%-32s %s %s\n", pDesc->pszName, *pDesc->u.pf ? "true " : "false ", STAMR3GetUnit(pDesc->enmUnit)); break; default: AssertMsgFailed(("enmType=%d\n", pDesc->enmType)); break; } NOREF(pvArg); return VINF_SUCCESS; } /** * Enumerate the statistics by the means of a callback function. * * @returns Whatever the callback returns. * * @param pUVM The user mode VM handle. * @param pszPat The pattern to match samples. * @param pfnEnum The callback function. * @param pvUser The pvUser argument of the callback function. */ VMMR3DECL(int) STAMR3Enum(PUVM pUVM, const char *pszPat, PFNSTAMR3ENUM pfnEnum, void *pvUser) { UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE); VM_ASSERT_VALID_EXT_RETURN(pUVM->pVM, VERR_INVALID_VM_HANDLE); STAMR3ENUMONEARGS Args; Args.pVM = pUVM->pVM; Args.pfnEnum = pfnEnum; Args.pvUser = pvUser; return stamR3EnumU(pUVM, pszPat, true /* fUpdateRing0 */, stamR3EnumOne, &Args); } /** * Callback function for STARTR3Enum(). * * @returns whatever the callback returns. * @param pDesc Pointer to the current descriptor. * @param pvArg Points to a STAMR3ENUMONEARGS structure. */ static int stamR3EnumOne(PSTAMDESC pDesc, void *pvArg) { PSTAMR3ENUMONEARGS pArgs = (PSTAMR3ENUMONEARGS)pvArg; const char *pszUnit = STAMR3GetUnit(pDesc->enmUnit); int rc; if (pDesc->enmType == STAMTYPE_CALLBACK) { /* Give the enumerator something useful. */ char szBuf[512]; pDesc->u.Callback.pfnPrint(pArgs->pVM, pDesc->u.Callback.pvSample, szBuf, sizeof(szBuf)); rc = pArgs->pfnEnum(pDesc->pszName, pDesc->enmType, szBuf, pDesc->enmUnit, pszUnit, pDesc->enmVisibility, pDesc->pszDesc, pArgs->pvUser); } else rc = pArgs->pfnEnum(pDesc->pszName, pDesc->enmType, pDesc->u.pv, pDesc->enmUnit, pszUnit, pDesc->enmVisibility, pDesc->pszDesc, pArgs->pvUser); return rc; } static void stamR3RefreshGroup(PUVM pUVM, uint8_t iRefreshGroup, uint64_t *pbmRefreshedGroups) { *pbmRefreshedGroups |= RT_BIT_64(iRefreshGroup); PVM pVM = pUVM->pVM; if (pVM && pVM->pSession) { switch (iRefreshGroup) { /* * GVMM */ case STAM_REFRESH_GRP_GVMM: { GVMMQUERYSTATISTICSSREQ Req; Req.Hdr.cbReq = sizeof(Req); Req.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC; Req.pSession = pVM->pSession; int rc = SUPR3CallVMMR0Ex(VMCC_GET_VMR0_FOR_CALL(pVM), NIL_VMCPUID, VMMR0_DO_GVMM_QUERY_STATISTICS, 0, &Req.Hdr); if (RT_SUCCESS(rc)) { pUVM->stam.s.GVMMStats = Req.Stats; /* * Check if the number of host CPUs has changed (it will the first * time around and normally never again). */ if (RT_UNLIKELY(pUVM->stam.s.GVMMStats.cHostCpus > pUVM->stam.s.cRegisteredHostCpus)) { if (RT_UNLIKELY(pUVM->stam.s.GVMMStats.cHostCpus > pUVM->stam.s.cRegisteredHostCpus)) { STAM_UNLOCK_RD(pUVM); STAM_LOCK_WR(pUVM); uint32_t cCpus = pUVM->stam.s.GVMMStats.cHostCpus; for (uint32_t iCpu = pUVM->stam.s.cRegisteredHostCpus; iCpu < cCpus; iCpu++) { char szName[120]; size_t cchBase = RTStrPrintf(szName, sizeof(szName), "/GVMM/HostCpus/%u", iCpu); stamR3RegisterU(pUVM, &pUVM->stam.s.GVMMStats.aHostCpus[iCpu].idCpu, NULL, NULL, STAMTYPE_U32, STAMVISIBILITY_ALWAYS, szName, STAMUNIT_NONE, "Host CPU ID", STAM_REFRESH_GRP_GVMM); strcpy(&szName[cchBase], "/idxCpuSet"); stamR3RegisterU(pUVM, &pUVM->stam.s.GVMMStats.aHostCpus[iCpu].idxCpuSet, NULL, NULL, STAMTYPE_U32, STAMVISIBILITY_ALWAYS, szName, STAMUNIT_NONE, "CPU Set index", STAM_REFRESH_GRP_GVMM); strcpy(&szName[cchBase], "/DesiredHz"); stamR3RegisterU(pUVM, &pUVM->stam.s.GVMMStats.aHostCpus[iCpu].uDesiredHz, NULL, NULL, STAMTYPE_U32, STAMVISIBILITY_ALWAYS, szName, STAMUNIT_HZ, "The desired frequency", STAM_REFRESH_GRP_GVMM); strcpy(&szName[cchBase], "/CurTimerHz"); stamR3RegisterU(pUVM, &pUVM->stam.s.GVMMStats.aHostCpus[iCpu].uTimerHz, NULL, NULL, STAMTYPE_U32, STAMVISIBILITY_ALWAYS, szName, STAMUNIT_HZ, "The current timer frequency", STAM_REFRESH_GRP_GVMM); strcpy(&szName[cchBase], "/PPTChanges"); stamR3RegisterU(pUVM, &pUVM->stam.s.GVMMStats.aHostCpus[iCpu].cChanges, NULL, NULL, STAMTYPE_U32, STAMVISIBILITY_ALWAYS, szName, STAMUNIT_OCCURENCES, "RTTimerChangeInterval calls", STAM_REFRESH_GRP_GVMM); strcpy(&szName[cchBase], "/PPTStarts"); stamR3RegisterU(pUVM, &pUVM->stam.s.GVMMStats.aHostCpus[iCpu].cStarts, NULL, NULL, STAMTYPE_U32, STAMVISIBILITY_ALWAYS, szName, STAMUNIT_OCCURENCES, "RTTimerStart calls", STAM_REFRESH_GRP_GVMM); } pUVM->stam.s.cRegisteredHostCpus = cCpus; STAM_UNLOCK_WR(pUVM); STAM_LOCK_RD(pUVM); } } } break; } /* * GMM */ case STAM_REFRESH_GRP_GMM: { GMMQUERYSTATISTICSSREQ Req; Req.Hdr.cbReq = sizeof(Req); Req.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC; Req.pSession = pVM->pSession; int rc = SUPR3CallVMMR0Ex(VMCC_GET_VMR0_FOR_CALL(pVM), NIL_VMCPUID, VMMR0_DO_GMM_QUERY_STATISTICS, 0, &Req.Hdr); if (RT_SUCCESS(rc)) pUVM->stam.s.GMMStats = Req.Stats; break; } /* * NEM. */ case STAM_REFRESH_GRP_NEM: SUPR3CallVMMR0(VMCC_GET_VMR0_FOR_CALL(pVM), NIL_VMCPUID, VMMR0_DO_NEM_UPDATE_STATISTICS, NULL); break; default: AssertMsgFailed(("iRefreshGroup=%d\n", iRefreshGroup)); } } } /** * Refreshes the statistics behind the given entry, if necessary. * * This helps implement fetching global ring-0 stats into ring-3 accessible * storage. GVMM, GMM and NEM makes use of this. * * @param pUVM The user mode VM handle. * @param pCur The statistics descriptor which group to check * and maybe update. * @param pbmRefreshedGroups Bitmap tracking what has already been updated. */ DECLINLINE(void) stamR3Refresh(PUVM pUVM, PSTAMDESC pCur, uint64_t *pbmRefreshedGroups) { uint8_t const iRefreshGroup = pCur->iRefreshGroup; if (RT_LIKELY(iRefreshGroup == STAM_REFRESH_GRP_NONE)) { /* likely */ } else if (!(*pbmRefreshedGroups & RT_BIT_64(iRefreshGroup))) stamR3RefreshGroup(pUVM, iRefreshGroup, pbmRefreshedGroups); } /** * Match a name against an array of patterns. * * @returns true if it matches, false if it doesn't match. * @param papszExpressions The array of pattern expressions. * @param cExpressions The number of array entries. * @param piExpression Where to read/store the current skip index. Optional. * @param pszName The name to match. */ static bool stamR3MultiMatch(const char * const *papszExpressions, unsigned cExpressions, unsigned *piExpression, const char *pszName) { for (unsigned i = piExpression ? *piExpression : 0; i < cExpressions; i++) { const char *pszPat = papszExpressions[i]; if (RTStrSimplePatternMatch(pszPat, pszName)) { /* later: if (piExpression && i > *piExpression) { Check if we can skip some expressions. Requires the expressions to be sorted. }*/ return true; } } return false; } /** * Splits a multi pattern into single ones. * * @returns Pointer to an array of single patterns. Free it with RTMemTmpFree. * @param pszPat The pattern to split. * @param pcExpressions The number of array elements. * @param ppszCopy The pattern copy to free using RTStrFree. */ static char **stamR3SplitPattern(const char *pszPat, unsigned *pcExpressions, char **ppszCopy) { Assert(pszPat && *pszPat); char *pszCopy = RTStrDup(pszPat); if (!pszCopy) return NULL; /* count them & allocate array. */ char *psz = pszCopy; unsigned cExpressions = 1; while ((psz = strchr(psz, '|')) != NULL) cExpressions++, psz++; char **papszExpressions = (char **)RTMemTmpAllocZ((cExpressions + 1) * sizeof(char *)); if (!papszExpressions) { RTStrFree(pszCopy); return NULL; } /* split */ psz = pszCopy; for (unsigned i = 0;;) { papszExpressions[i] = psz; if (++i >= cExpressions) break; psz = strchr(psz, '|'); *psz++ = '\0'; } /* sort the array, putting '*' last. */ /** @todo sort it... */ *pcExpressions = cExpressions; *ppszCopy = pszCopy; return papszExpressions; } /** * Enumerates the nodes selected by a pattern or all nodes if no pattern * is specified. * * The call may lock STAM for writing before calling this function, however do * not lock it for reading as this function may need to write lock STAM. * * @returns The rc from the callback. * @param pUVM Pointer to the user mode VM structure. * @param pszPat Pattern. * @param fUpdateRing0 Update the stats residing in ring-0. * @param pfnCallback Callback function which shall be called for matching nodes. * If it returns anything but VINF_SUCCESS the enumeration is * terminated and the status code returned to the caller. * @param pvArg User parameter for the callback. */ static int stamR3EnumU(PUVM pUVM, const char *pszPat, bool fUpdateRing0, int (*pfnCallback)(PSTAMDESC pDesc, void *pvArg), void *pvArg) { size_t const cchPat = pszPat ? strlen(pszPat) : 0; int rc = VINF_SUCCESS; uint64_t bmRefreshedGroups = 0; PSTAMDESC pCur; /* * All. */ if ( cchPat < 1 || ( cchPat == 1 && *pszPat == '*')) { STAM_LOCK_RD(pUVM); RTListForEach(&pUVM->stam.s.List, pCur, STAMDESC, ListEntry) { if (fUpdateRing0) stamR3Refresh(pUVM, pCur, &bmRefreshedGroups); rc = pfnCallback(pCur, pvArg); if (rc) break; } STAM_UNLOCK_RD(pUVM); } /* * Single expression pattern. */ else if (memchr(pszPat, '|', cchPat) == NULL) { const char *pszAsterisk = (const char *)memchr(pszPat, '*', cchPat); const char *pszQuestion = (const char *)memchr(pszPat, '?', cchPat); STAM_LOCK_RD(pUVM); if (!pszAsterisk && !pszQuestion) { pCur = stamR3LookupFindDesc(pUVM->stam.s.pRoot, pszPat); if (pCur) { if (fUpdateRing0) stamR3Refresh(pUVM, pCur, &bmRefreshedGroups); rc = pfnCallback(pCur, pvArg); } } /* Is this a prefix expression where we can use the lookup tree to efficiently figure out the exact range? */ else if ( pszAsterisk == &pszPat[cchPat - 1] && pszPat[0] == '/' && !pszQuestion) { PSTAMDESC pLast; pCur = stamR3LookupFindByPrefixRange(pUVM->stam.s.pRoot, pszPat, (uint32_t)(cchPat - 1), &pLast); if (pCur) { for (;;) { Assert(strncmp(pCur->pszName, pszPat, cchPat - 1) == 0); if (fUpdateRing0) stamR3Refresh(pUVM, pCur, &bmRefreshedGroups); rc = pfnCallback(pCur, pvArg); if (rc) break; if (pCur == pLast) break; pCur = RTListNodeGetNext(&pCur->ListEntry, STAMDESC, ListEntry); } Assert(pLast); } else Assert(!pLast); } else { /* It's a more complicated pattern. Find the approximate range and scan it for matches. */ PSTAMDESC pLast; pCur = stamR3LookupFindPatternDescRange(pUVM->stam.s.pRoot, &pUVM->stam.s.List, pszPat, &pLast); if (pCur) { for (;;) { if (RTStrSimplePatternMatch(pszPat, pCur->pszName)) { if (fUpdateRing0) stamR3Refresh(pUVM, pCur, &bmRefreshedGroups); rc = pfnCallback(pCur, pvArg); if (rc) break; } if (pCur == pLast) break; pCur = RTListNodeGetNext(&pCur->ListEntry, STAMDESC, ListEntry); } Assert(pLast); } else Assert(!pLast); } STAM_UNLOCK_RD(pUVM); } /* * Multi expression pattern. */ else { /* * Split up the pattern first. */ char *pszCopy; unsigned cExpressions; char **papszExpressions = stamR3SplitPattern(pszPat, &cExpressions, &pszCopy); if (!papszExpressions) return VERR_NO_MEMORY; /* * Perform the enumeration. */ STAM_LOCK_RD(pUVM); unsigned iExpression = 0; RTListForEach(&pUVM->stam.s.List, pCur, STAMDESC, ListEntry) { if (stamR3MultiMatch(papszExpressions, cExpressions, &iExpression, pCur->pszName)) { if (fUpdateRing0) stamR3Refresh(pUVM, pCur, &bmRefreshedGroups); rc = pfnCallback(pCur, pvArg); if (rc) break; } } STAM_UNLOCK_RD(pUVM); RTMemTmpFree(papszExpressions); RTStrFree(pszCopy); } return rc; } /** * Registers the ring-0 statistics. * * @param pUVM Pointer to the user mode VM structure. */ static void stamR3Ring0StatsRegisterU(PUVM pUVM) { /* GVMM */ for (unsigned i = 0; i < RT_ELEMENTS(g_aGVMMStats); i++) stamR3RegisterU(pUVM, (uint8_t *)&pUVM->stam.s.GVMMStats + g_aGVMMStats[i].offVar, NULL, NULL, g_aGVMMStats[i].enmType, STAMVISIBILITY_ALWAYS, g_aGVMMStats[i].pszName, g_aGVMMStats[i].enmUnit, g_aGVMMStats[i].pszDesc, STAM_REFRESH_GRP_GVMM); for (unsigned i = 0; i < pUVM->cCpus; i++) { char szName[120]; size_t cchBase = RTStrPrintf(szName, sizeof(szName), pUVM->cCpus < 10 ? "/GVMM/VCpus/%u/" : "/GVMM/VCpus/%02u/", i); strcpy(&szName[cchBase], "cWakeUpTimerHits"); stamR3RegisterU(pUVM, &pUVM->stam.s.GVMMStats.aVCpus[i].cWakeUpTimerHits, NULL, NULL, STAMTYPE_U32, STAMVISIBILITY_ALWAYS, szName, STAMUNIT_OCCURENCES, "", STAM_REFRESH_GRP_GVMM); strcpy(&szName[cchBase], "cWakeUpTimerMisses"); stamR3RegisterU(pUVM, &pUVM->stam.s.GVMMStats.aVCpus[i].cWakeUpTimerMisses, NULL, NULL, STAMTYPE_U32, STAMVISIBILITY_ALWAYS, szName, STAMUNIT_OCCURENCES, "", STAM_REFRESH_GRP_GVMM); strcpy(&szName[cchBase], "cWakeUpTimerCanceled"); stamR3RegisterU(pUVM, &pUVM->stam.s.GVMMStats.aVCpus[i].cWakeUpTimerCanceled, NULL, NULL, STAMTYPE_U32, STAMVISIBILITY_ALWAYS, szName, STAMUNIT_OCCURENCES, "", STAM_REFRESH_GRP_GVMM); strcpy(&szName[cchBase], "cWakeUpTimerSameCpu"); stamR3RegisterU(pUVM, &pUVM->stam.s.GVMMStats.aVCpus[i].cWakeUpTimerSameCpu, NULL, NULL, STAMTYPE_U32, STAMVISIBILITY_ALWAYS, szName, STAMUNIT_OCCURENCES, "", STAM_REFRESH_GRP_GVMM); strcpy(&szName[cchBase], "Start"); stamR3RegisterU(pUVM, &pUVM->stam.s.GVMMStats.aVCpus[i].Start, NULL, NULL, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, szName, STAMUNIT_TICKS_PER_CALL, "", STAM_REFRESH_GRP_GVMM); strcpy(&szName[cchBase], "Stop"); stamR3RegisterU(pUVM, &pUVM->stam.s.GVMMStats.aVCpus[i].Stop, NULL, NULL, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, szName, STAMUNIT_TICKS_PER_CALL, "", STAM_REFRESH_GRP_GVMM); } pUVM->stam.s.cRegisteredHostCpus = 0; /* GMM */ for (unsigned i = 0; i < RT_ELEMENTS(g_aGMMStats); i++) stamR3RegisterU(pUVM, (uint8_t *)&pUVM->stam.s.GMMStats + g_aGMMStats[i].offVar, NULL, NULL, g_aGMMStats[i].enmType, STAMVISIBILITY_ALWAYS, g_aGMMStats[i].pszName, g_aGMMStats[i].enmUnit, g_aGMMStats[i].pszDesc, STAM_REFRESH_GRP_GMM); } /** * Get the unit string. * * @returns Pointer to read only unit string. * @param enmUnit The unit. */ VMMR3DECL(const char *) STAMR3GetUnit(STAMUNIT enmUnit) { switch (enmUnit) { case STAMUNIT_NONE: return ""; case STAMUNIT_CALLS: return "calls"; case STAMUNIT_COUNT: return "count"; case STAMUNIT_BYTES: return "bytes"; case STAMUNIT_BYTES_PER_CALL: return "bytes/call"; case STAMUNIT_PAGES: return "pages"; case STAMUNIT_ERRORS: return "errors"; case STAMUNIT_OCCURENCES: return "times"; case STAMUNIT_TICKS: return "ticks"; case STAMUNIT_TICKS_PER_CALL: return "ticks/call"; case STAMUNIT_TICKS_PER_OCCURENCE: return "ticks/time"; case STAMUNIT_GOOD_BAD: return "good:bad"; case STAMUNIT_MEGABYTES: return "megabytes"; case STAMUNIT_KILOBYTES: return "kilobytes"; case STAMUNIT_NS: return "ns"; case STAMUNIT_NS_PER_CALL: return "ns/call"; case STAMUNIT_NS_PER_OCCURENCE: return "ns/time"; case STAMUNIT_PCT: return "%"; case STAMUNIT_HZ: return "Hz"; default: AssertMsgFailed(("Unknown unit %d\n", enmUnit)); return "(?unit?)"; } } /** * For something per something-else unit, get the first something. * * @returns Pointer to read only unit string. * @param enmUnit The unit. */ VMMR3DECL(const char *) STAMR3GetUnit1(STAMUNIT enmUnit) { switch (enmUnit) { case STAMUNIT_NONE: return ""; case STAMUNIT_CALLS: return "calls"; case STAMUNIT_COUNT: return "count"; case STAMUNIT_BYTES: return "bytes"; case STAMUNIT_BYTES_PER_CALL: return "bytes"; case STAMUNIT_PAGES: return "pages"; case STAMUNIT_ERRORS: return "errors"; case STAMUNIT_OCCURENCES: return "times"; case STAMUNIT_TICKS: return "ticks"; case STAMUNIT_TICKS_PER_CALL: return "ticks"; case STAMUNIT_TICKS_PER_OCCURENCE: return "ticks"; case STAMUNIT_GOOD_BAD: return "good"; case STAMUNIT_MEGABYTES: return "megabytes"; case STAMUNIT_KILOBYTES: return "kilobytes"; case STAMUNIT_NS: return "ns"; case STAMUNIT_NS_PER_CALL: return "ns"; case STAMUNIT_NS_PER_OCCURENCE: return "ns"; case STAMUNIT_PCT: return "%"; case STAMUNIT_HZ: return "Hz"; default: AssertMsgFailed(("Unknown unit %d\n", enmUnit)); return "(?unit?)"; } } /** * For something per something-else unit, get the something-else. * * @returns Pointer to read only unit string. * @param enmUnit The unit. */ VMMR3DECL(const char *) STAMR3GetUnit2(STAMUNIT enmUnit) { switch (enmUnit) { case STAMUNIT_TICKS_PER_CALL: return "calls"; case STAMUNIT_NS_PER_CALL: return "calls"; case STAMUNIT_BYTES_PER_CALL: return "calls"; case STAMUNIT_TICKS_PER_OCCURENCE: return "times"; case STAMUNIT_NS_PER_OCCURENCE: return "times"; case STAMUNIT_NONE: return "times"; case STAMUNIT_GOOD_BAD: return "bad"; default: AssertMsgFailed(("Wrong unit %d\n", enmUnit)); return "times"; } } #ifdef VBOX_WITH_DEBUGGER /** * @callback_method_impl{FNDBGCCMD, The '.stats' command.} */ static DECLCALLBACK(int) stamR3CmdStats(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs) { /* * Validate input. */ DBGC_CMDHLP_REQ_UVM_RET(pCmdHlp, pCmd, pUVM); if (RTListIsEmpty(&pUVM->stam.s.List)) return DBGCCmdHlpFail(pCmdHlp, pCmd, "No statistics present"); /* * Do the printing. */ STAMR3PRINTONEARGS Args; Args.pUVM = pUVM; Args.pvArg = pCmdHlp; Args.pfnPrintf = stamR3EnumDbgfPrintf; return stamR3EnumU(pUVM, cArgs ? paArgs[0].u.pszString : NULL, true /* fUpdateRing0 */, stamR3PrintOne, &Args); } /** * Display one sample in the debugger. * * @param pArgs Pointer to the print one argument structure. * @param pszFormat Format string. * @param ... Format arguments. */ static DECLCALLBACK(void) stamR3EnumDbgfPrintf(PSTAMR3PRINTONEARGS pArgs, const char *pszFormat, ...) { PDBGCCMDHLP pCmdHlp = (PDBGCCMDHLP)pArgs->pvArg; va_list va; va_start(va, pszFormat); pCmdHlp->pfnPrintfV(pCmdHlp, NULL, pszFormat, va); va_end(va); NOREF(pArgs); } /** * @callback_method_impl{FNDBGCCMD, The '.statsreset' command.} */ static DECLCALLBACK(int) stamR3CmdStatsReset(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs) { /* * Validate input. */ DBGC_CMDHLP_REQ_UVM_RET(pCmdHlp, pCmd, pUVM); if (RTListIsEmpty(&pUVM->stam.s.List)) return DBGCCmdHlpFail(pCmdHlp, pCmd, "No statistics present"); /* * Execute reset. */ int rc = STAMR3Reset(pUVM, cArgs ? paArgs[0].u.pszString : NULL); if (RT_SUCCESS(rc)) return DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "STAMR3ResetU"); return DBGCCmdHlpPrintf(pCmdHlp, "Statistics have been reset.\n"); } #endif /* VBOX_WITH_DEBUGGER */