summaryrefslogtreecommitdiffstats
path: root/src/VBox/Main/src-server/Performance.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Main/src-server/Performance.cpp')
-rw-r--r--src/VBox/Main/src-server/Performance.cpp1511
1 files changed, 1511 insertions, 0 deletions
diff --git a/src/VBox/Main/src-server/Performance.cpp b/src/VBox/Main/src-server/Performance.cpp
new file mode 100644
index 00000000..2ab42815
--- /dev/null
+++ b/src/VBox/Main/src-server/Performance.cpp
@@ -0,0 +1,1511 @@
+/* $Id: Performance.cpp $ */
+/** @file
+ * VBox Performance Classes implementation.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/**
+ * @todo list:
+ *
+ * 1) Detection of erroneous metric names
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_PERFORMANCECOLLECTOR
+#ifndef VBOX_COLLECTOR_TEST_CASE
+# include "VirtualBoxImpl.h"
+# include "MachineImpl.h"
+# include "MediumImpl.h"
+# include "AutoCaller.h"
+#endif
+#include "Performance.h"
+#include "HostNetworkInterfaceImpl.h"
+#include "netif.h"
+
+#include <VBox/com/array.h>
+#include <VBox/com/ptr.h>
+#include <VBox/com/string.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+#include <iprt/mem.h>
+#include <iprt/cpuset.h>
+
+#include <algorithm>
+
+#include "LoggingNew.h"
+
+using namespace pm;
+
+// Stubs for non-pure virtual methods
+
+int CollectorHAL::getHostCpuLoad(ULONG * /* user */, ULONG * /* kernel */, ULONG * /* idle */)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+int CollectorHAL::getProcessCpuLoad(RTPROCESS /* process */, ULONG * /* user */, ULONG * /* kernel */)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+int CollectorHAL::getRawHostCpuLoad(uint64_t * /* user */, uint64_t * /* kernel */, uint64_t * /* idle */)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+int CollectorHAL::getRawHostNetworkLoad(const char * /* name */, uint64_t * /* rx */, uint64_t * /* tx */)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+int CollectorHAL::getRawHostDiskLoad(const char * /* name */, uint64_t * /* disk_ms */, uint64_t * /* total_ms */)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+int CollectorHAL::getRawProcessCpuLoad(RTPROCESS /* process */, uint64_t * /* user */,
+ uint64_t * /* kernel */, uint64_t * /* total */)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+int CollectorHAL::getHostMemoryUsage(ULONG * /* total */, ULONG * /* used */, ULONG * /* available */)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+int CollectorHAL::getHostFilesystemUsage(const char * /* name */, ULONG * /* total */, ULONG * /* used */,
+ ULONG * /* available */)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+int CollectorHAL::getHostDiskSize(const char * /* name */, uint64_t * /* size */)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+int CollectorHAL::getProcessMemoryUsage(RTPROCESS /* process */, ULONG * /* used */)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+int CollectorHAL::getDiskListByFs(const char * /* name */, DiskList& /* listUsage */, DiskList& /* listLoad */)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+/* Generic implementations */
+
+int CollectorHAL::getHostCpuMHz(ULONG *mhz)
+{
+ unsigned cCpus = 0;
+ uint64_t u64TotalMHz = 0;
+ RTCPUSET OnlineSet;
+ RTMpGetOnlineSet(&OnlineSet);
+ for (int iCpu = 0; iCpu < RTCPUSET_MAX_CPUS; iCpu++)
+ {
+ Log7Func(("{%p}: Checking if CPU %d is member of online set...\n", this, (int)iCpu));
+ if (RTCpuSetIsMemberByIndex(&OnlineSet, iCpu))
+ {
+ Log7Func(("{%p}: Getting frequency for CPU %d...\n", this, (int)iCpu));
+ uint32_t uMHz = RTMpGetCurFrequency(RTMpCpuIdFromSetIndex(iCpu));
+ if (uMHz != 0)
+ {
+ Log7Func(("{%p}: CPU %d %u MHz\n", this, (int)iCpu, uMHz));
+ u64TotalMHz += uMHz;
+ cCpus++;
+ }
+ }
+ }
+
+ if (cCpus)
+ {
+ *mhz = (ULONG)(u64TotalMHz / cCpus);
+ return VINF_SUCCESS;
+ }
+
+ /* This is always the case on darwin, so don't assert there. */
+#ifndef RT_OS_DARWIN
+ AssertFailed();
+#endif
+ *mhz = 0;
+ return VERR_NOT_IMPLEMENTED;
+}
+
+#ifndef VBOX_COLLECTOR_TEST_CASE
+
+CollectorGuestQueue::CollectorGuestQueue()
+{
+ mEvent = NIL_RTSEMEVENT;
+ RTSemEventCreate(&mEvent);
+}
+
+CollectorGuestQueue::~CollectorGuestQueue()
+{
+ RTSemEventDestroy(mEvent);
+}
+
+void CollectorGuestQueue::push(CollectorGuestRequest* rq)
+{
+ RTCLock lock(mLockMtx);
+
+ mQueue.push(rq);
+ RTSemEventSignal(mEvent);
+}
+
+CollectorGuestRequest* CollectorGuestQueue::pop()
+{
+ int vrc = VINF_SUCCESS;
+ CollectorGuestRequest *rq = NULL;
+
+ do
+ {
+ {
+ RTCLock lock(mLockMtx);
+
+ if (!mQueue.empty())
+ {
+ rq = mQueue.front();
+ mQueue.pop();
+ }
+ }
+
+ if (rq)
+ return rq;
+ vrc = RTSemEventWaitNoResume(mEvent, RT_INDEFINITE_WAIT);
+ } while (RT_SUCCESS(vrc));
+
+ return NULL;
+}
+
+HRESULT CGRQEnable::execute()
+{
+ Assert(mCGuest);
+ return mCGuest->enableInternal(mMask);
+}
+
+void CGRQEnable::debugPrint(void *aObject, const char *aFunction, const char *aText)
+{
+ NOREF(aObject);
+ NOREF(aFunction);
+ NOREF(aText);
+ Log7((LOG_FN_FMT ": {%p}: CGRQEnable(mask=0x%x) %s\n", aObject, aFunction, mMask, aText));
+}
+
+HRESULT CGRQDisable::execute()
+{
+ Assert(mCGuest);
+ return mCGuest->disableInternal(mMask);
+}
+
+void CGRQDisable::debugPrint(void *aObject, const char *aFunction, const char *aText)
+{
+ NOREF(aObject);
+ NOREF(aFunction);
+ NOREF(aText);
+ Log7((LOG_FN_FMT ": {%p}: CGRQDisable(mask=0x%x) %s\n", aObject, aFunction, mMask, aText));
+}
+
+HRESULT CGRQAbort::execute()
+{
+ return E_ABORT;
+}
+
+void CGRQAbort::debugPrint(void *aObject, const char *aFunction, const char *aText)
+{
+ NOREF(aObject);
+ NOREF(aFunction);
+ NOREF(aText);
+ Log7((LOG_FN_FMT ": {%p}: CGRQAbort %s\n", aObject, aFunction, aText));
+}
+
+CollectorGuest::CollectorGuest(Machine *machine, RTPROCESS process) :
+ mUnregistered(false), mEnabled(false), mValid(false), mMachine(machine), mProcess(process),
+ mCpuUser(0), mCpuKernel(0), mCpuIdle(0),
+ mMemTotal(0), mMemFree(0), mMemBalloon(0), mMemShared(0), mMemCache(0), mPageTotal(0),
+ mAllocVMM(0), mFreeVMM(0), mBalloonedVMM(0), mSharedVMM(0), mVmNetRx(0), mVmNetTx(0)
+{
+ Assert(mMachine);
+ /* cannot use ComObjPtr<Machine> in Performance.h, do it manually */
+ mMachine->AddRef();
+}
+
+CollectorGuest::~CollectorGuest()
+{
+ /* cannot use ComObjPtr<Machine> in Performance.h, do it manually */
+ mMachine->Release();
+ // Assert(!cEnabled); why?
+}
+
+HRESULT CollectorGuest::enableVMMStats(bool mCollectVMMStats)
+{
+ HRESULT hrc = S_OK;
+
+ if (mGuest)
+ {
+ /** @todo replace this with a direct call to mGuest in trunk! */
+ AutoCaller autoCaller(mMachine);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ ComPtr<IInternalSessionControl> directControl;
+
+ hrc = mMachine->i_getDirectControl(&directControl);
+ if (hrc != S_OK)
+ return hrc;
+
+ /* enable statistics collection; this is a remote call (!) */
+ hrc = directControl->EnableVMMStatistics(mCollectVMMStats);
+ Log7Func(("{%p}: %sable VMM stats (%s)\n",
+ this, mCollectVMMStats ? "En" : "Dis", SUCCEEDED(hrc) ? "success" : "failed"));
+ }
+
+ return hrc;
+}
+
+HRESULT CollectorGuest::enable(ULONG mask)
+{
+ return enqueueRequest(new CGRQEnable(mask));
+}
+
+HRESULT CollectorGuest::disable(ULONG mask)
+{
+ return enqueueRequest(new CGRQDisable(mask));
+}
+
+HRESULT CollectorGuest::enableInternal(ULONG mask)
+{
+ HRESULT ret = S_OK;
+
+ if ((mEnabled & mask) == mask)
+ return E_UNEXPECTED;
+
+ if (!mEnabled)
+ {
+ /* Must make sure that the machine object does not get uninitialized
+ * in the middle of enabling this collector. Causes timing-related
+ * behavior otherwise, which we don't want. In particular the
+ * GetRemoteConsole call below can hang if the VM didn't completely
+ * terminate (the VM processes stop processing events shortly before
+ * closing the session). This avoids the hang. */
+ AutoCaller autoCaller(mMachine);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ mMachineName = mMachine->i_getName();
+
+ ComPtr<IInternalSessionControl> directControl;
+
+ ret = mMachine->i_getDirectControl(&directControl);
+ if (ret != S_OK)
+ return ret;
+
+ /* get the associated console; this is a remote call (!) */
+ ret = directControl->COMGETTER(RemoteConsole)(mConsole.asOutParam());
+ if (ret != S_OK)
+ return ret;
+
+ ret = mConsole->COMGETTER(Guest)(mGuest.asOutParam());
+ if (ret == S_OK)
+ {
+ ret = mGuest->COMSETTER(StatisticsUpdateInterval)(1 /* 1 sec */);
+ Log7Func(("{%p}: Set guest statistics update interval to 1 sec (%s)\n",
+ this, SUCCEEDED(ret) ? "success" : "failed"));
+ }
+ }
+ if ((mask & VMSTATS_VMM_RAM) == VMSTATS_VMM_RAM)
+ enableVMMStats(true);
+ mEnabled |= mask;
+
+ return ret;
+}
+
+HRESULT CollectorGuest::disableInternal(ULONG mask)
+{
+ if (!(mEnabled & mask))
+ return E_UNEXPECTED;
+
+ if ((mask & VMSTATS_VMM_RAM) == VMSTATS_VMM_RAM)
+ enableVMMStats(false);
+ mEnabled &= ~mask;
+ if (!mEnabled)
+ {
+ Assert(mGuest && mConsole);
+ HRESULT ret = mGuest->COMSETTER(StatisticsUpdateInterval)(0 /* off */);
+ NOREF(ret);
+ Log7Func(("{%p}: Set guest statistics update interval to 0 sec (%s)\n",
+ this, SUCCEEDED(ret) ? "success" : "failed"));
+ invalidate(VMSTATS_ALL);
+ }
+
+ return S_OK;
+}
+
+HRESULT CollectorGuest::enqueueRequest(CollectorGuestRequest *aRequest)
+{
+ if (mManager)
+ {
+ aRequest->setGuest(this);
+ return mManager->enqueueRequest(aRequest);
+ }
+
+ Log7Func(("{%p}: Attempted enqueue guest request when mManager is null\n", this));
+ return E_POINTER;
+}
+
+void CollectorGuest::updateStats(ULONG aValidStats, ULONG aCpuUser,
+ ULONG aCpuKernel, ULONG aCpuIdle,
+ ULONG aMemTotal, ULONG aMemFree,
+ ULONG aMemBalloon, ULONG aMemShared,
+ ULONG aMemCache, ULONG aPageTotal,
+ ULONG aAllocVMM, ULONG aFreeVMM,
+ ULONG aBalloonedVMM, ULONG aSharedVMM,
+ ULONG aVmNetRx, ULONG aVmNetTx)
+{
+ if ((aValidStats & VMSTATS_GUEST_CPULOAD) == VMSTATS_GUEST_CPULOAD)
+ {
+ mCpuUser = aCpuUser;
+ mCpuKernel = aCpuKernel,
+ mCpuIdle = aCpuIdle;
+ }
+ if ((aValidStats & VMSTATS_GUEST_RAMUSAGE) == VMSTATS_GUEST_RAMUSAGE)
+ {
+ mMemTotal = aMemTotal;
+ mMemFree = aMemFree;
+ mMemBalloon = aMemBalloon;
+ mMemShared = aMemShared;
+ mMemCache = aMemCache;
+ mPageTotal = aPageTotal;
+ }
+ if ((aValidStats & VMSTATS_VMM_RAM) == VMSTATS_VMM_RAM)
+ {
+ mAllocVMM = aAllocVMM;
+ mFreeVMM = aFreeVMM;
+ mBalloonedVMM = aBalloonedVMM;
+ mSharedVMM = aSharedVMM;
+ }
+ if ((aValidStats & VMSTATS_NET_RATE) == VMSTATS_NET_RATE)
+ {
+ mVmNetRx = aVmNetRx;
+ mVmNetTx = aVmNetTx;
+ }
+ mValid = aValidStats;
+}
+
+CollectorGuestManager::CollectorGuestManager()
+ : mVMMStatsProvider(NULL), mGuestBeingCalled(NULL)
+{
+ int vrc = RTThreadCreate(&mThread, CollectorGuestManager::requestProcessingThread,
+ this, 0, RTTHREADTYPE_MAIN_WORKER, RTTHREADFLAGS_WAITABLE,
+ "CGMgr");
+ NOREF(vrc);
+ Log7Func(("{%p}: RTThreadCreate returned %Rrc (mThread=%p)\n", this, vrc, mThread));
+}
+
+CollectorGuestManager::~CollectorGuestManager()
+{
+ Assert(mGuests.size() == 0);
+ int rcThread = 0;
+ HRESULT hrc = enqueueRequest(new CGRQAbort());
+ if (SUCCEEDED(hrc))
+ {
+ /* We wait only if we were able to put the abort request to a queue */
+ Log7Func(("{%p}: Waiting for CGM request processing thread to stop...\n", this));
+ int vrc = RTThreadWait(mThread, 1000 /* 1 sec */, &rcThread);
+ Log7Func(("{%p}: RTThreadWait returned %Rrc (thread exit code: %Rrc)\n", this, vrc, rcThread));
+ RT_NOREF(vrc);
+ }
+}
+
+void CollectorGuestManager::registerGuest(CollectorGuest* pGuest)
+{
+ pGuest->setManager(this);
+ mGuests.push_back(pGuest);
+ /*
+ * If no VMM stats provider was elected previously than this is our
+ * candidate.
+ */
+ if (!mVMMStatsProvider)
+ mVMMStatsProvider = pGuest;
+ Log7Func(("{%p}: Registered guest=%p provider=%p\n", this, pGuest, mVMMStatsProvider));
+}
+
+void CollectorGuestManager::unregisterGuest(CollectorGuest* pGuest)
+{
+ Log7Func(("{%p}: About to unregister guest=%p provider=%p\n", this, pGuest, mVMMStatsProvider));
+ //mGuests.remove(pGuest); => destroyUnregistered()
+ pGuest->unregister();
+ if (pGuest == mVMMStatsProvider)
+ {
+ /* This was our VMM stats provider, it is time to re-elect */
+ CollectorGuestList::iterator it;
+ /* Assume that nobody can provide VMM stats */
+ mVMMStatsProvider = NULL;
+
+ for (it = mGuests.begin(); it != mGuests.end(); ++it)
+ {
+ /* Skip unregistered as they are about to be destroyed */
+ if ((*it)->isUnregistered())
+ continue;
+
+ if ((*it)->isEnabled())
+ {
+ /* Found the guest already collecting stats, elect it */
+ mVMMStatsProvider = *it;
+ HRESULT hrc = mVMMStatsProvider->enqueueRequest(new CGRQEnable(VMSTATS_VMM_RAM));
+ if (FAILED(hrc))
+ {
+ /* This is not a good candidate -- try to find another */
+ mVMMStatsProvider = NULL;
+ continue;
+ }
+ break;
+ }
+ }
+ if (!mVMMStatsProvider)
+ {
+ /* If nobody collects stats, take the first registered */
+ for (it = mGuests.begin(); it != mGuests.end(); ++it)
+ {
+ /* Skip unregistered as they are about to be destroyed */
+ if ((*it)->isUnregistered())
+ continue;
+
+ mVMMStatsProvider = *it;
+ //mVMMStatsProvider->enable(VMSTATS_VMM_RAM);
+ HRESULT hrc = mVMMStatsProvider->enqueueRequest(new CGRQEnable(VMSTATS_VMM_RAM));
+ if (SUCCEEDED(hrc))
+ break;
+ /* This was not a good candidate -- try to find another */
+ mVMMStatsProvider = NULL;
+ }
+ }
+ }
+ Log7Func(("[%p}: LEAVE new provider=%p\n", this, mVMMStatsProvider));
+}
+
+void CollectorGuestManager::destroyUnregistered()
+{
+ CollectorGuestList::iterator it;
+
+ for (it = mGuests.begin(); it != mGuests.end();)
+ if ((*it)->isUnregistered())
+ {
+ delete *it;
+ it = mGuests.erase(it);
+ Log7Func(("{%p}: Number of guests after erasing unregistered is %d\n",
+ this, mGuests.size()));
+ }
+ else
+ ++it;
+}
+
+HRESULT CollectorGuestManager::enqueueRequest(CollectorGuestRequest *aRequest)
+{
+#ifdef DEBUG
+ aRequest->debugPrint(this, __PRETTY_FUNCTION__, "added to CGM queue");
+#endif /* DEBUG */
+ /*
+ * It is very unlikely that we will get high frequency calls to configure
+ * guest metrics collection, so we rely on this fact to detect blocked
+ * guests. If the guest has not finished processing the previous request
+ * after half a second we consider it blocked.
+ */
+ if (aRequest->getGuest() && aRequest->getGuest() == mGuestBeingCalled)
+ {
+ /*
+ * Before we can declare a guest blocked we need to wait for a while
+ * and then check again as it may never had a chance to process
+ * the previous request. Half a second is an eternity for processes
+ * and is barely noticable by humans.
+ */
+ Log7Func(("{%p}: Suspecting %s is stalled. Waiting for .5 sec...\n",
+ this, aRequest->getGuest()->getVMName().c_str()));
+ RTThreadSleep(500 /* ms */);
+ if (aRequest->getGuest() == mGuestBeingCalled) {
+ Log7Func(("{%p}: Request processing stalled for %s\n",
+ this, aRequest->getGuest()->getVMName().c_str()));
+ /* Request execution got stalled for this guest -- report an error */
+ return E_FAIL;
+ }
+ }
+ mQueue.push(aRequest);
+ return S_OK;
+}
+
+/* static */
+DECLCALLBACK(int) CollectorGuestManager::requestProcessingThread(RTTHREAD /* aThread */, void *pvUser)
+{
+ CollectorGuestRequest *pReq;
+ CollectorGuestManager *mgr = static_cast<CollectorGuestManager*>(pvUser);
+
+ HRESULT rc = S_OK;
+
+ Log7Func(("{%p}: Starting request processing loop...\n", mgr));
+ while ((pReq = mgr->mQueue.pop()) != NULL)
+ {
+#ifdef DEBUG
+ pReq->debugPrint(mgr, __PRETTY_FUNCTION__, "is being executed...");
+#endif /* DEBUG */
+ mgr->mGuestBeingCalled = pReq->getGuest();
+ rc = pReq->execute();
+ mgr->mGuestBeingCalled = NULL;
+ delete pReq;
+ if (rc == E_ABORT)
+ break;
+ if (FAILED(rc))
+ Log7Func(("{%p}: request::execute returned %u\n", mgr, rc));
+ }
+ Log7Func(("{%p}: Exiting request processing loop... rc=%u\n", mgr, rc));
+
+ return VINF_SUCCESS;
+}
+
+
+#endif /* !VBOX_COLLECTOR_TEST_CASE */
+
+bool BaseMetric::collectorBeat(uint64_t nowAt)
+{
+ if (isEnabled())
+ {
+ if (mLastSampleTaken == 0)
+ {
+ mLastSampleTaken = nowAt;
+ Log4Func(("{%p}: Collecting %s for obj(%p)...\n",
+ this, getName(), (void *)mObject));
+ return true;
+ }
+ /*
+ * We use low resolution timers which may fire just a little bit early.
+ * We compensate for that by jumping into the future by several
+ * milliseconds (see @bugref{6345}).
+ */
+ if (nowAt - mLastSampleTaken + PM_SAMPLER_PRECISION_MS >= mPeriod * 1000)
+ {
+ /*
+ * We don't want the beat to drift. This is why the timestamp of
+ * the last taken sample is not the actual time but the time we
+ * should have taken the measurement at.
+ */
+ mLastSampleTaken += mPeriod * 1000;
+ Log4Func(("{%p}: Collecting %s for obj(%p)...\n",
+ this, getName(), (void *)mObject));
+ return true;
+ }
+ Log4Func(("{%p}: Enabled but too early to collect %s for obj(%p)\n",
+ this, getName(), (void *)mObject));
+ }
+ return false;
+}
+
+void HostCpuLoad::init(ULONG period, ULONG length)
+{
+ mPeriod = period;
+ mLength = length;
+ mUser->init(mLength);
+ mKernel->init(mLength);
+ mIdle->init(mLength);
+}
+
+void HostCpuLoad::collect()
+{
+ ULONG user, kernel, idle;
+ int vrc = mHAL->getHostCpuLoad(&user, &kernel, &idle);
+ if (RT_SUCCESS(vrc))
+ {
+ mUser->put(user);
+ mKernel->put(kernel);
+ mIdle->put(idle);
+ }
+}
+
+void HostCpuLoadRaw::init(ULONG period, ULONG length)
+{
+ HostCpuLoad::init(period, length);
+ mHAL->getRawHostCpuLoad(&mUserPrev, &mKernelPrev, &mIdlePrev);
+}
+
+void HostCpuLoadRaw::preCollect(CollectorHints& hints, uint64_t /* iTick */)
+{
+ hints.collectHostCpuLoad();
+}
+
+void HostCpuLoadRaw::collect()
+{
+ uint64_t user, kernel, idle;
+ uint64_t userDiff, kernelDiff, idleDiff, totalDiff;
+
+ int vrc = mHAL->getRawHostCpuLoad(&user, &kernel, &idle);
+ if (RT_SUCCESS(vrc))
+ {
+ userDiff = user - mUserPrev;
+ kernelDiff = kernel - mKernelPrev;
+ idleDiff = idle - mIdlePrev;
+ totalDiff = userDiff + kernelDiff + idleDiff;
+
+ if (totalDiff == 0)
+ {
+ /* This is only possible if none of counters has changed! */
+ LogFlowThisFunc(("Impossible! User, kernel and idle raw "
+ "counters has not changed since last sample.\n" ));
+ mUser->put(0);
+ mKernel->put(0);
+ mIdle->put(0);
+ }
+ else
+ {
+ mUser->put((ULONG)(PM_CPU_LOAD_MULTIPLIER * userDiff / totalDiff));
+ mKernel->put((ULONG)(PM_CPU_LOAD_MULTIPLIER * kernelDiff / totalDiff));
+ mIdle->put((ULONG)(PM_CPU_LOAD_MULTIPLIER * idleDiff / totalDiff));
+ }
+
+ mUserPrev = user;
+ mKernelPrev = kernel;
+ mIdlePrev = idle;
+ }
+}
+
+#ifndef VBOX_COLLECTOR_TEST_CASE
+static bool getLinkSpeed(const char *szShortName, uint32_t *pSpeed)
+{
+# ifdef VBOX_WITH_HOSTNETIF_API
+ NETIFSTATUS enmState = NETIF_S_UNKNOWN;
+ int vrc = NetIfGetState(szShortName, &enmState);
+ if (RT_FAILURE(vrc))
+ return false;
+ if (enmState != NETIF_S_UP)
+ *pSpeed = 0;
+ else
+ {
+ vrc = NetIfGetLinkSpeed(szShortName, pSpeed);
+ if (RT_FAILURE(vrc))
+ return false;
+ }
+ return true;
+# else /* !VBOX_WITH_HOSTNETIF_API */
+ RT_NOREF(szShortName, pSpeed);
+ return false;
+# endif /* VBOX_WITH_HOSTNETIF_API */
+}
+
+void HostNetworkSpeed::init(ULONG period, ULONG length)
+{
+ mPeriod = period;
+ mLength = length;
+ mLinkSpeed->init(length);
+ /*
+ * Retrieve the link speed now as it may be wrong if the metric was
+ * registered at boot (see @bugref{6613}).
+ */
+ getLinkSpeed(mShortName.c_str(), &mSpeed);
+}
+
+void HostNetworkLoadRaw::init(ULONG period, ULONG length)
+{
+ mPeriod = period;
+ mLength = length;
+ mRx->init(mLength);
+ mTx->init(mLength);
+ /*
+ * Retrieve the link speed now as it may be wrong if the metric was
+ * registered at boot (see @bugref{6613}).
+ */
+ uint32_t uSpeedMbit = 65535;
+ if (getLinkSpeed(mShortName.c_str(), &uSpeedMbit))
+ mSpeed = (uint64_t)uSpeedMbit * (1000000/8); /* Convert to bytes/sec */
+ /*int vrc =*/ mHAL->getRawHostNetworkLoad(mShortName.c_str(), &mRxPrev, &mTxPrev);
+ //AssertRC(vrc);
+}
+
+void HostNetworkLoadRaw::preCollect(CollectorHints& /* hints */, uint64_t /* iTick */)
+{
+ if (RT_FAILURE(mRc))
+ {
+ ComPtr<IHostNetworkInterface> networkInterface;
+ ComPtr<IHost> host = getObject();
+ HRESULT hrc = host->FindHostNetworkInterfaceByName(com::Bstr(mInterfaceName).raw(), networkInterface.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ static uint32_t s_tsLogRelLast;
+ uint32_t tsNow = RTTimeProgramSecTS();
+ if ( tsNow < RT_SEC_1HOUR
+ || (tsNow - s_tsLogRelLast >= 60))
+ {
+ s_tsLogRelLast = tsNow;
+ LogRel(("Failed to collect network metrics for %s: %Rrc (%d). Max one msg/min.\n", mInterfaceName.c_str(), mRc, mRc));
+ }
+ mRc = VINF_SUCCESS;
+ }
+ }
+}
+
+void HostNetworkLoadRaw::collect()
+{
+ uint64_t rx = mRxPrev;
+ uint64_t tx = mTxPrev;
+
+ if (RT_UNLIKELY(mSpeed * getPeriod() == 0))
+ {
+ LogFlowThisFunc(("Check cable for %s! speed=%llu period=%d.\n", mShortName.c_str(), mSpeed, getPeriod()));
+ /* We do not collect host network metrics for unplugged interfaces! */
+ return;
+ }
+ mRc = mHAL->getRawHostNetworkLoad(mShortName.c_str(), &rx, &tx);
+ if (RT_SUCCESS(mRc))
+ {
+ uint64_t rxDiff = rx - mRxPrev;
+ uint64_t txDiff = tx - mTxPrev;
+
+ mRx->put((ULONG)(PM_NETWORK_LOAD_MULTIPLIER * rxDiff / (mSpeed * getPeriod())));
+ mTx->put((ULONG)(PM_NETWORK_LOAD_MULTIPLIER * txDiff / (mSpeed * getPeriod())));
+
+ mRxPrev = rx;
+ mTxPrev = tx;
+ }
+ else
+ LogFlowThisFunc(("Failed to collect data: %Rrc (%d)."
+ " Will update the list of interfaces...\n", mRc,mRc));
+}
+#endif /* !VBOX_COLLECTOR_TEST_CASE */
+
+void HostDiskLoadRaw::init(ULONG period, ULONG length)
+{
+ mPeriod = period;
+ mLength = length;
+ mUtil->init(mLength);
+ int vrc = mHAL->getRawHostDiskLoad(mDiskName.c_str(), &mDiskPrev, &mTotalPrev);
+ AssertRC(vrc);
+}
+
+void HostDiskLoadRaw::preCollect(CollectorHints& hints, uint64_t /* iTick */)
+{
+ hints.collectHostCpuLoad();
+}
+
+void HostDiskLoadRaw::collect()
+{
+ uint64_t disk, total;
+
+ int vrc = mHAL->getRawHostDiskLoad(mDiskName.c_str(), &disk, &total);
+ if (RT_SUCCESS(vrc))
+ {
+ uint64_t diskDiff = disk - mDiskPrev;
+ uint64_t totalDiff = total - mTotalPrev;
+
+ if (RT_UNLIKELY(totalDiff == 0))
+ {
+ Assert(totalDiff);
+ LogFlowThisFunc(("Improbable! Less than millisecond passed! Disk=%s\n", mDiskName.c_str()));
+ mUtil->put(0);
+ }
+ else if (diskDiff > totalDiff)
+ {
+ /*
+ * It is possible that the disk spent more time than CPU because
+ * CPU measurements are taken during the pre-collect phase. We try
+ * to compensate for than by adding the extra to the next round of
+ * measurements.
+ */
+ mUtil->put(PM_NETWORK_LOAD_MULTIPLIER);
+ Assert((diskDiff - totalDiff) < mPeriod * 1000);
+ if ((diskDiff - totalDiff) > mPeriod * 1000)
+ {
+ LogRel(("Disk utilization time exceeds CPU time by more"
+ " than the collection period (%llu ms)\n", diskDiff - totalDiff));
+ }
+ else
+ {
+ disk = mDiskPrev + totalDiff;
+ LogFlowThisFunc(("Moved %u milliseconds to the next period.\n", (unsigned)(diskDiff - totalDiff)));
+ }
+ }
+ else
+ {
+ mUtil->put((ULONG)(PM_NETWORK_LOAD_MULTIPLIER * diskDiff / totalDiff));
+ }
+
+ mDiskPrev = disk;
+ mTotalPrev = total;
+ }
+ else
+ LogFlowThisFunc(("Failed to collect data: %Rrc (%d)\n", vrc, vrc));
+}
+
+void HostCpuMhz::init(ULONG period, ULONG length)
+{
+ mPeriod = period;
+ mLength = length;
+ mMHz->init(mLength);
+}
+
+void HostCpuMhz::collect()
+{
+ ULONG mhz;
+ int vrc = mHAL->getHostCpuMHz(&mhz);
+ if (RT_SUCCESS(vrc))
+ mMHz->put(mhz);
+}
+
+void HostRamUsage::init(ULONG period, ULONG length)
+{
+ mPeriod = period;
+ mLength = length;
+ mTotal->init(mLength);
+ mUsed->init(mLength);
+ mAvailable->init(mLength);
+}
+
+void HostRamUsage::preCollect(CollectorHints& hints, uint64_t /* iTick */)
+{
+ hints.collectHostRamUsage();
+}
+
+void HostRamUsage::collect()
+{
+ ULONG total, used, available;
+ int vrc = mHAL->getHostMemoryUsage(&total, &used, &available);
+ if (RT_SUCCESS(vrc))
+ {
+ mTotal->put(total);
+ mUsed->put(used);
+ mAvailable->put(available);
+ }
+}
+
+void HostFilesystemUsage::init(ULONG period, ULONG length)
+{
+ mPeriod = period;
+ mLength = length;
+ mTotal->init(mLength);
+ mUsed->init(mLength);
+ mAvailable->init(mLength);
+}
+
+void HostFilesystemUsage::preCollect(CollectorHints& /* hints */, uint64_t /* iTick */)
+{
+}
+
+void HostFilesystemUsage::collect()
+{
+ ULONG total, used, available;
+ int vrc = mHAL->getHostFilesystemUsage(mFsName.c_str(), &total, &used, &available);
+ if (RT_SUCCESS(vrc))
+ {
+ mTotal->put(total);
+ mUsed->put(used);
+ mAvailable->put(available);
+ }
+}
+
+void HostDiskUsage::init(ULONG period, ULONG length)
+{
+ mPeriod = period;
+ mLength = length;
+ mTotal->init(mLength);
+}
+
+void HostDiskUsage::preCollect(CollectorHints& /* hints */, uint64_t /* iTick */)
+{
+}
+
+void HostDiskUsage::collect()
+{
+ uint64_t total;
+ int vrc = mHAL->getHostDiskSize(mDiskName.c_str(), &total);
+ if (RT_SUCCESS(vrc))
+ mTotal->put((ULONG)(total / _1M));
+}
+
+#ifndef VBOX_COLLECTOR_TEST_CASE
+
+void HostRamVmm::init(ULONG period, ULONG length)
+{
+ mPeriod = period;
+ mLength = length;
+ mAllocVMM->init(mLength);
+ mFreeVMM->init(mLength);
+ mBalloonVMM->init(mLength);
+ mSharedVMM->init(mLength);
+}
+
+HRESULT HostRamVmm::enable()
+{
+ HRESULT hrc = S_OK;
+ CollectorGuest *provider = mCollectorGuestManager->getVMMStatsProvider();
+ if (provider)
+ hrc = provider->enable(VMSTATS_VMM_RAM);
+ BaseMetric::enable();
+ return hrc;
+}
+
+HRESULT HostRamVmm::disable()
+{
+ HRESULT rc = S_OK;
+ BaseMetric::disable();
+ CollectorGuest *provider = mCollectorGuestManager->getVMMStatsProvider();
+ if (provider)
+ rc = provider->disable(VMSTATS_VMM_RAM);
+ return rc;
+}
+
+void HostRamVmm::preCollect(CollectorHints& hints, uint64_t /* iTick */)
+{
+ hints.collectHostRamVmm();
+}
+
+void HostRamVmm::collect()
+{
+ CollectorGuest *provider = mCollectorGuestManager->getVMMStatsProvider();
+ if (provider)
+ {
+ Log7Func(("{%p}: provider=%p enabled=%RTbool valid=%RTbool...\n",
+ this, provider, provider->isEnabled(), provider->isValid(VMSTATS_VMM_RAM) ));
+ if (provider->isValid(VMSTATS_VMM_RAM))
+ {
+ /* Provider is ready, get updated stats */
+ mAllocCurrent = provider->getAllocVMM();
+ mFreeCurrent = provider->getFreeVMM();
+ mBalloonedCurrent = provider->getBalloonedVMM();
+ mSharedCurrent = provider->getSharedVMM();
+ provider->invalidate(VMSTATS_VMM_RAM);
+ }
+ /*
+ * Note that if there are no new values from the provider we will use
+ * the ones most recently provided instead of zeros, which is probably
+ * a desirable behavior.
+ */
+ }
+ else
+ {
+ mAllocCurrent = 0;
+ mFreeCurrent = 0;
+ mBalloonedCurrent = 0;
+ mSharedCurrent = 0;
+ }
+ Log7Func(("{%p}: mAllocCurrent=%u mFreeCurrent=%u mBalloonedCurrent=%u mSharedCurrent=%u\n",
+ this, mAllocCurrent, mFreeCurrent, mBalloonedCurrent, mSharedCurrent));
+ mAllocVMM->put(mAllocCurrent);
+ mFreeVMM->put(mFreeCurrent);
+ mBalloonVMM->put(mBalloonedCurrent);
+ mSharedVMM->put(mSharedCurrent);
+}
+
+#endif /* !VBOX_COLLECTOR_TEST_CASE */
+
+
+
+void MachineCpuLoad::init(ULONG period, ULONG length)
+{
+ mPeriod = period;
+ mLength = length;
+ mUser->init(mLength);
+ mKernel->init(mLength);
+}
+
+void MachineCpuLoad::collect()
+{
+ ULONG user, kernel;
+ int vrc = mHAL->getProcessCpuLoad(mProcess, &user, &kernel);
+ if (RT_SUCCESS(vrc))
+ {
+ mUser->put(user);
+ mKernel->put(kernel);
+ }
+}
+
+void MachineCpuLoadRaw::preCollect(CollectorHints& hints, uint64_t /* iTick */)
+{
+ hints.collectProcessCpuLoad(mProcess);
+}
+
+void MachineCpuLoadRaw::collect()
+{
+ uint64_t processUser, processKernel, hostTotal;
+
+ int vrc = mHAL->getRawProcessCpuLoad(mProcess, &processUser, &processKernel, &hostTotal);
+ if (RT_SUCCESS(vrc))
+ {
+ if (hostTotal == mHostTotalPrev)
+ {
+ /* Nearly impossible, but... */
+ mUser->put(0);
+ mKernel->put(0);
+ }
+ else
+ {
+ mUser->put((ULONG)(PM_CPU_LOAD_MULTIPLIER * (processUser - mProcessUserPrev) / (hostTotal - mHostTotalPrev)));
+ mKernel->put((ULONG)(PM_CPU_LOAD_MULTIPLIER * (processKernel - mProcessKernelPrev ) / (hostTotal - mHostTotalPrev)));
+ }
+
+ mHostTotalPrev = hostTotal;
+ mProcessUserPrev = processUser;
+ mProcessKernelPrev = processKernel;
+ }
+}
+
+void MachineRamUsage::init(ULONG period, ULONG length)
+{
+ mPeriod = period;
+ mLength = length;
+ mUsed->init(mLength);
+}
+
+void MachineRamUsage::preCollect(CollectorHints& hints, uint64_t /* iTick */)
+{
+ hints.collectProcessRamUsage(mProcess);
+}
+
+void MachineRamUsage::collect()
+{
+ ULONG used;
+ int vrc = mHAL->getProcessMemoryUsage(mProcess, &used);
+ if (RT_SUCCESS(vrc))
+ mUsed->put(used);
+}
+
+
+#ifndef VBOX_COLLECTOR_TEST_CASE
+
+void MachineDiskUsage::init(ULONG period, ULONG length)
+{
+ mPeriod = period;
+ mLength = length;
+ mUsed->init(mLength);
+}
+
+void MachineDiskUsage::preCollect(CollectorHints& /* hints */, uint64_t /* iTick */)
+{
+}
+
+void MachineDiskUsage::collect()
+{
+ ULONG used = 0;
+
+ for (MediaList::iterator it = mDisks.begin(); it != mDisks.end(); ++it)
+ {
+ ComObjPtr<Medium> pMedium = *it;
+
+ /* just in case */
+ AssertContinue(!pMedium.isNull());
+
+ AutoCaller localAutoCaller(pMedium);
+ if (FAILED(localAutoCaller.rc())) continue;
+
+ AutoReadLock local_alock(pMedium COMMA_LOCKVAL_SRC_POS);
+
+ used += (ULONG)(pMedium->i_getSize() / _1M);
+ }
+
+ mUsed->put(used);
+}
+
+void MachineNetRate::init(ULONG period, ULONG length)
+{
+ mPeriod = period;
+ mLength = length;
+
+ mRx->init(mLength);
+ mTx->init(mLength);
+}
+
+void MachineNetRate::collect()
+{
+ if (mCGuest->isValid(VMSTATS_NET_RATE))
+ {
+ mRx->put(mCGuest->getVmNetRx());
+ mTx->put(mCGuest->getVmNetTx());
+ mCGuest->invalidate(VMSTATS_NET_RATE);
+ }
+}
+
+HRESULT MachineNetRate::enable()
+{
+ HRESULT rc = mCGuest->enable(VMSTATS_NET_RATE);
+ BaseMetric::enable();
+ return rc;
+}
+
+HRESULT MachineNetRate::disable()
+{
+ BaseMetric::disable();
+ return mCGuest->disable(VMSTATS_NET_RATE);
+}
+
+void MachineNetRate::preCollect(CollectorHints& hints, uint64_t /* iTick */)
+{
+ hints.collectGuestStats(mCGuest->getProcess());
+}
+
+void GuestCpuLoad::init(ULONG period, ULONG length)
+{
+ mPeriod = period;
+ mLength = length;
+
+ mUser->init(mLength);
+ mKernel->init(mLength);
+ mIdle->init(mLength);
+}
+
+void GuestCpuLoad::preCollect(CollectorHints& hints, uint64_t /* iTick */)
+{
+ hints.collectGuestStats(mCGuest->getProcess());
+}
+
+void GuestCpuLoad::collect()
+{
+ if (mCGuest->isValid(VMSTATS_GUEST_CPULOAD))
+ {
+ mUser->put((ULONG)(PM_CPU_LOAD_MULTIPLIER * mCGuest->getCpuUser()) / 100);
+ mKernel->put((ULONG)(PM_CPU_LOAD_MULTIPLIER * mCGuest->getCpuKernel()) / 100);
+ mIdle->put((ULONG)(PM_CPU_LOAD_MULTIPLIER * mCGuest->getCpuIdle()) / 100);
+ mCGuest->invalidate(VMSTATS_GUEST_CPULOAD);
+ }
+}
+
+HRESULT GuestCpuLoad::enable()
+{
+ HRESULT rc = mCGuest->enable(VMSTATS_GUEST_CPULOAD);
+ BaseMetric::enable();
+ return rc;
+}
+
+HRESULT GuestCpuLoad::disable()
+{
+ BaseMetric::disable();
+ return mCGuest->disable(VMSTATS_GUEST_CPULOAD);
+}
+
+void GuestRamUsage::init(ULONG period, ULONG length)
+{
+ mPeriod = period;
+ mLength = length;
+
+ mTotal->init(mLength);
+ mFree->init(mLength);
+ mBallooned->init(mLength);
+ mShared->init(mLength);
+ mCache->init(mLength);
+ mPagedTotal->init(mLength);
+}
+
+void GuestRamUsage::collect()
+{
+ if (mCGuest->isValid(VMSTATS_GUEST_RAMUSAGE))
+ {
+ mTotal->put(mCGuest->getMemTotal());
+ mFree->put(mCGuest->getMemFree());
+ mBallooned->put(mCGuest->getMemBalloon());
+ mShared->put(mCGuest->getMemShared());
+ mCache->put(mCGuest->getMemCache());
+ mPagedTotal->put(mCGuest->getPageTotal());
+ mCGuest->invalidate(VMSTATS_GUEST_RAMUSAGE);
+ }
+}
+
+HRESULT GuestRamUsage::enable()
+{
+ HRESULT rc = mCGuest->enable(VMSTATS_GUEST_RAMUSAGE);
+ BaseMetric::enable();
+ return rc;
+}
+
+HRESULT GuestRamUsage::disable()
+{
+ BaseMetric::disable();
+ return mCGuest->disable(VMSTATS_GUEST_RAMUSAGE);
+}
+
+void GuestRamUsage::preCollect(CollectorHints& hints, uint64_t /* iTick */)
+{
+ hints.collectGuestStats(mCGuest->getProcess());
+}
+
+#endif /* !VBOX_COLLECTOR_TEST_CASE */
+
+void CircularBuffer::init(ULONG ulLength)
+{
+ if (mData)
+ RTMemFree(mData);
+ mLength = ulLength;
+ if (mLength)
+ mData = (ULONG*)RTMemAllocZ(ulLength * sizeof(ULONG));
+ else
+ mData = NULL;
+ mWrapped = false;
+ mEnd = 0;
+ mSequenceNumber = 0;
+}
+
+ULONG CircularBuffer::length()
+{
+ return mWrapped ? mLength : mEnd;
+}
+
+void CircularBuffer::put(ULONG value)
+{
+ if (mData)
+ {
+ mData[mEnd++] = value;
+ if (mEnd >= mLength)
+ {
+ mEnd = 0;
+ mWrapped = true;
+ }
+ ++mSequenceNumber;
+ }
+}
+
+void CircularBuffer::copyTo(ULONG *data)
+{
+ if (mWrapped)
+ {
+ memcpy(data, mData + mEnd, (mLength - mEnd) * sizeof(ULONG));
+ // Copy the wrapped part
+ if (mEnd)
+ memcpy(data + (mLength - mEnd), mData, mEnd * sizeof(ULONG));
+ }
+ else
+ memcpy(data, mData, mEnd * sizeof(ULONG));
+}
+
+void SubMetric::query(ULONG *data)
+{
+ copyTo(data);
+}
+
+void Metric::query(ULONG **data, ULONG *count, ULONG *sequenceNumber)
+{
+ ULONG length;
+ ULONG *tmpData;
+
+ length = mSubMetric->length();
+ *sequenceNumber = mSubMetric->getSequenceNumber() - length;
+ if (length)
+ {
+ tmpData = (ULONG*)RTMemAlloc(sizeof(*tmpData)*length);
+ mSubMetric->query(tmpData);
+ if (mAggregate)
+ {
+ *count = 1;
+ *data = (ULONG*)RTMemAlloc(sizeof(**data));
+ **data = mAggregate->compute(tmpData, length);
+ RTMemFree(tmpData);
+ }
+ else
+ {
+ *count = length;
+ *data = tmpData;
+ }
+ }
+ else
+ {
+ *count = 0;
+ *data = 0;
+ }
+}
+
+ULONG AggregateAvg::compute(ULONG *data, ULONG length)
+{
+ uint64_t tmp = 0;
+ for (ULONG i = 0; i < length; ++i)
+ tmp += data[i];
+ return (ULONG)(tmp / length);
+}
+
+const char * AggregateAvg::getName()
+{
+ return "avg";
+}
+
+ULONG AggregateMin::compute(ULONG *data, ULONG length)
+{
+ ULONG tmp = *data;
+ for (ULONG i = 0; i < length; ++i)
+ if (data[i] < tmp)
+ tmp = data[i];
+ return tmp;
+}
+
+const char * AggregateMin::getName()
+{
+ return "min";
+}
+
+ULONG AggregateMax::compute(ULONG *data, ULONG length)
+{
+ ULONG tmp = *data;
+ for (ULONG i = 0; i < length; ++i)
+ if (data[i] > tmp)
+ tmp = data[i];
+ return tmp;
+}
+
+const char * AggregateMax::getName()
+{
+ return "max";
+}
+
+Filter::Filter(const std::vector<com::Utf8Str> &metricNames,
+ const std::vector<ComPtr<IUnknown> > &objects)
+{
+ if (!objects.size())
+ {
+ if (metricNames.size())
+ {
+ for (size_t i = 0; i < metricNames.size(); ++i)
+ processMetricList(metricNames[i], ComPtr<IUnknown>());
+ }
+ else
+ processMetricList("*", ComPtr<IUnknown>());
+ }
+ else
+ {
+ for (size_t i = 0; i < objects.size(); ++i)
+ switch (metricNames.size())
+ {
+ case 0:
+ processMetricList("*", objects[i]);
+ break;
+ case 1:
+ processMetricList(metricNames[0], objects[i]);
+ break;
+ default:
+ processMetricList(metricNames[i], objects[i]);
+ break;
+ }
+ }
+}
+
+Filter::Filter(const com::Utf8Str &name, const ComPtr<IUnknown> &aObject)
+{
+ processMetricList(name, aObject);
+}
+
+void Filter::processMetricList(const com::Utf8Str &name, const ComPtr<IUnknown> object)
+{
+ size_t startPos = 0;
+
+ for (size_t pos = name.find(",");
+ pos != com::Utf8Str::npos;
+ pos = name.find(",", startPos))
+ {
+ mElements.push_back(std::make_pair(object, RTCString(name.substr(startPos, pos - startPos).c_str())));
+ startPos = pos + 1;
+ }
+ mElements.push_back(std::make_pair(object, RTCString(name.substr(startPos).c_str())));
+}
+
+/**
+ * The following method was borrowed from stamR3Match (VMM/STAM.cpp) and
+ * modified to handle the special case of trailing colon in the pattern.
+ *
+ * @returns True if matches, false if not.
+ * @param pszPat Pattern.
+ * @param pszName Name to match against the pattern.
+ * @param fSeenColon Seen colon (':').
+ */
+bool Filter::patternMatch(const char *pszPat, const char *pszName,
+ bool fSeenColon)
+{
+ /* ASSUMES ASCII */
+ for (;;)
+ {
+ char chPat = *pszPat;
+ switch (chPat)
+ {
+ default:
+ if (*pszName != chPat)
+ return false;
+ break;
+
+ case '*':
+ {
+ while ((chPat = *++pszPat) == '*' || chPat == '?')
+ /* nothing */;
+
+ /* Handle a special case, the mask terminating with a colon. */
+ if (chPat == ':')
+ {
+ if (!fSeenColon && !pszPat[1])
+ return !strchr(pszName, ':');
+ fSeenColon = true;
+ }
+
+ for (;;)
+ {
+ char ch = *pszName++;
+ if ( ch == chPat
+ && ( !chPat
+ || patternMatch(pszPat + 1, pszName, fSeenColon)))
+ return true;
+ if (!ch)
+ return false;
+ }
+ /* won't ever get here */
+ break;
+ }
+
+ case '?':
+ if (!*pszName)
+ return false;
+ break;
+
+ /* Handle a special case, the mask terminating with a colon. */
+ case ':':
+ if (!fSeenColon && !pszPat[1])
+ return !*pszName;
+ if (*pszName != ':')
+ return false;
+ fSeenColon = true;
+ break;
+
+ case '\0':
+ return !*pszName;
+ }
+ pszName++;
+ pszPat++;
+ }
+ /* not reached */
+}
+
+bool Filter::match(const ComPtr<IUnknown> object, const RTCString &name) const
+{
+ ElementList::const_iterator it;
+
+ //Log7(("Filter::match(%p, %s)\n", static_cast<const IUnknown*> (object), name.c_str()));
+ for (it = mElements.begin(); it != mElements.end(); ++it)
+ {
+ //Log7(("...matching against(%p, %s)\n", static_cast<const IUnknown*> ((*it).first), (*it).second.c_str()));
+ if ((*it).first.isNull() || (*it).first == object)
+ {
+ // Objects match, compare names
+ if (patternMatch((*it).second.c_str(), name.c_str()))
+ {
+ //LogFlowThisFunc(("...found!\n"));
+ return true;
+ }
+ }
+ }
+ //Log7(("...no matches!\n"));
+ return false;
+}
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */