/* $Id: STAM.cpp $ */
/** @file
 * STAM - The Statistics Manager.
 */

/*
 * Copyright (C) 2006-2020 Oracle Corporation
 *
 * This file is part of VirtualBox Open Source Edition (OSE), as
 * available from http://www.virtualbox.org. This file is free software;
 * you can redistribute it and/or modify it under the terms of the GNU
 * General Public License (GPL) as published by the Free Software
 * Foundation, in version 2 as it comes in the "COPYING" file of the
 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
 */

/** @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 <VBox/vmm/stam.h>
#include "STAMInternal.h"
#include <VBox/vmm/vmcc.h>

#include <VBox/err.h>
#include <VBox/dbg.h>
#include <VBox/log.h>

#include <iprt/assert.h>
#include <iprt/asm.h>
#include <iprt/mem.h>
#include <iprt/stream.h>
#include <iprt/string.h>


/*********************************************************************************************************************************
*   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_CALLS, "/GVMM/VMs", "The number of VMs accessible to the caller." },
    { RT_UOFFSETOF(GVMMSTATS, cEMTs),                     STAMTYPE_U32,       STAMUNIT_CALLS, "/GVMM/EMTs", "The number of emulation threads." },
    { RT_UOFFSETOF(GVMMSTATS, cHostCpus),                 STAMTYPE_U32,       STAMUNIT_CALLS, "/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).
     */
    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 "/<component>/<sample>".
 *                      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 "/<component>/<sample>".
 *                      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;
    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, "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n");

    /*
     * Write the content.
     */
    stamR3SnapshotPrintf(&State, "<Statistics>\n");
    int rc = stamR3EnumU(pUVM, pszPat, true /* fUpdateRing0 */, stamR3SnapshotOne, &State);
    stamR3SnapshotPrintf(&State, "</Statistics>\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, "<Counter c=\"%lld\"", pDesc->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, "<Profile cPeriods=\"%lld\" cTicks=\"%lld\" cTicksMin=\"%lld\" cTicksMax=\"%lld\"",
                                 pDesc->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, "<Ratio32 u32A=\"%lld\" u32B=\"%lld\"",
                                 pDesc->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, "<Callback val=\"%s\"", szBuf);
            break;
        }

        case STAMTYPE_U8:
        case STAMTYPE_U8_RESET:
            if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu8 == 0)
                return VINF_SUCCESS;
            stamR3SnapshotPrintf(pThis, "<U8 val=\"%u\"", *pDesc->u.pu8);
            break;

        case STAMTYPE_X8:
        case STAMTYPE_X8_RESET:
            if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu8 == 0)
                return VINF_SUCCESS;
            stamR3SnapshotPrintf(pThis, "<X8 val=\"%#x\"", *pDesc->u.pu8);
            break;

        case STAMTYPE_U16:
        case STAMTYPE_U16_RESET:
            if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu16 == 0)
                return VINF_SUCCESS;
            stamR3SnapshotPrintf(pThis, "<U16 val=\"%u\"", *pDesc->u.pu16);
            break;

        case STAMTYPE_X16:
        case STAMTYPE_X16_RESET:
            if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu16 == 0)
                return VINF_SUCCESS;
            stamR3SnapshotPrintf(pThis, "<X16 val=\"%#x\"", *pDesc->u.pu16);
            break;

        case STAMTYPE_U32:
        case STAMTYPE_U32_RESET:
            if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu32 == 0)
                return VINF_SUCCESS;
            stamR3SnapshotPrintf(pThis, "<U32 val=\"%u\"", *pDesc->u.pu32);
            break;

        case STAMTYPE_X32:
        case STAMTYPE_X32_RESET:
            if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu32 == 0)
                return VINF_SUCCESS;
            stamR3SnapshotPrintf(pThis, "<X32 val=\"%#x\"", *pDesc->u.pu32);
            break;

        case STAMTYPE_U64:
        case STAMTYPE_U64_RESET:
            if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu64 == 0)
                return VINF_SUCCESS;
            stamR3SnapshotPrintf(pThis, "<U64 val=\"%llu\"", *pDesc->u.pu64);
            break;

        case STAMTYPE_X64:
        case STAMTYPE_X64_RESET:
            if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pu64 == 0)
                return VINF_SUCCESS;
            stamR3SnapshotPrintf(pThis, "<X64 val=\"%#llx\"", *pDesc->u.pu64);
            break;

        case STAMTYPE_BOOL:
        case STAMTYPE_BOOL_RESET:
            if (pDesc->enmVisibility == STAMVISIBILITY_USED && *pDesc->u.pf == false)
                return VINF_SUCCESS;
            stamR3SnapshotPrintf(pThis, "<BOOL val=\"%RTbool\"", *pDesc->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, "&amp;");   break;
                case '<':   stamR3SnapshotPrintf(pThis, "&lt;");    break;
                case '>':   stamR3SnapshotPrintf(pThis, "&gt;");    break;
                case '"':   stamR3SnapshotPrintf(pThis, "&quot;");  break;
                case '\'':  stamR3SnapshotPrintf(pThis, "&apos;");  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 ticks, %7llu times, max %9llu, min %7lld)\n", pDesc->pszName,
                             pDesc->u.pProfile->cTicks / u64, STAMR3GetUnit(pDesc->enmUnit),
                             pDesc->u.pProfile->cTicks, pDesc->u.pProfile->cPeriods, 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;
    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,
                            pDesc->enmVisibility, pDesc->pszDesc, pArgs->pvUser);
    }
    else
        rc = pArgs->pfnEnum(pDesc->pszName, pDesc->enmType, pDesc->u.pv, pDesc->enmUnit,
                            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);
    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_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?)";
    }
}

#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 */

