VBoxServiceStats.cpp revision c5dcc851f65b0d1a63e37d75c84a17403e77b3ba
4974N/A/* $Id$ */
4974N/A/** @file
4974N/A * VBoxStats - Guest statistics notification
4974N/A */
4974N/A
4974N/A/*
4974N/A * Copyright (C) 2006-2007 Sun Microsystems, Inc.
4974N/A *
4974N/A * This file is part of VirtualBox Open Source Edition (OSE), as
4974N/A * available from http://www.virtualbox.org. This file is free software;
4974N/A * you can redistribute it and/or modify it under the terms of the GNU
4974N/A * General Public License (GPL) as published by the Free Software
4974N/A * Foundation, in version 2 as it comes in the "COPYING" file of the
4974N/A * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
4974N/A * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
4974N/A *
4974N/A * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
4974N/A * Clara, CA 95054 USA or visit http://www.sun.com if you need
4974N/A * additional information or have any questions.
4974N/A */
4974N/A#ifdef TARGET_NT4
4974N/A#undef _WIN32_WINNT
4974N/A#define _WIN32_WINNT 0x501
4974N/A#endif
4974N/A
4974N/A#ifdef RT_OS_WINDOWS
4974N/A# include <windows.h>
4974N/A# include <psapi.h>
4974N/A# include <winternl.h>
4974N/A#endif
4974N/A
4974N/A#include <iprt/assert.h>
4974N/A#include <iprt/mem.h>
4974N/A#include <iprt/thread.h>
4974N/A#include <iprt/string.h>
4974N/A#include <iprt/semaphore.h>
4974N/A#include <iprt/system.h>
4974N/A#include <iprt/time.h>
4974N/A#include <VBox/VBoxGuestLib.h>
4974N/A#include "VBoxServiceInternal.h"
4974N/A#include "VBoxServiceUtils.h"
4974N/A
4974N/Atypedef struct _VBOXSTATSCONTEXT
4974N/A{
4974N/A RTMSINTERVAL cMsStatInterval;
4974N/A
4974N/A uint64_t ullLastCpuLoad_Idle;
4974N/A uint64_t ullLastCpuLoad_Kernel;
4974N/A uint64_t ullLastCpuLoad_User;
4974N/A
4974N/A#ifdef RT_OS_WINDOWS
4974N/A NTSTATUS (WINAPI *pfnNtQuerySystemInformation)(SYSTEM_INFORMATION_CLASS SystemInformationClass, PVOID SystemInformation, ULONG SystemInformationLength, PULONG ReturnLength);
4974N/A void (WINAPI *pfnGlobalMemoryStatusEx)(LPMEMORYSTATUSEX lpBuffer);
4974N/A BOOL (WINAPI *pfnGetPerformanceInfo)(PPERFORMANCE_INFORMATION pPerformanceInformation, DWORD cb);
4974N/A#endif
4974N/A} VBOXSTATSCONTEXT;
4974N/A
4974N/A/*******************************************************************************
4974N/A* Global Variables *
4974N/A*******************************************************************************/
4974N/Astatic VBOXSTATSCONTEXT gCtx = {0};
4974N/A
4974N/A/** The semaphore we're blocking on. */
4974N/Astatic RTSEMEVENTMULTI g_VMStatEvent = NIL_RTSEMEVENTMULTI;
4974N/A
4974N/A
4974N/A/** @copydoc VBOXSERVICE::pfnPreInit */
4974N/Astatic DECLCALLBACK(int) VBoxServiceVMStatsPreInit(void)
4974N/A{
4974N/A return VINF_SUCCESS;
4974N/A}
4974N/A
4974N/A
4974N/A/** @copydoc VBOXSERVICE::pfnOption */
4974N/Astatic DECLCALLBACK(int) VBoxServiceVMStatsOption(const char **ppszShort, int argc, char **argv, int *pi)
4974N/A{
4974N/A NOREF(ppszShort);
4974N/A NOREF(argc);
4974N/A NOREF(argv);
4974N/A NOREF(pi);
4974N/A return VINF_SUCCESS;
4974N/A}
4974N/A
4974N/A
4974N/A/** @copydoc VBOXSERVICE::pfnInit */
4974N/Astatic DECLCALLBACK(int) VBoxServiceVMStatsInit(void)
4974N/A{
4974N/A VBoxServiceVerbose(3, "VBoxServiceVMStatsInit\n");
4974N/A
4974N/A int rc = RTSemEventMultiCreate(&g_VMStatEvent);
4974N/A AssertRCReturn(rc, rc);
4974N/A
4974N/A gCtx.cMsStatInterval = 0; /* default; update disabled */
4974N/A gCtx.ullLastCpuLoad_Idle = 0;
4974N/A gCtx.ullLastCpuLoad_Kernel = 0;
gCtx.ullLastCpuLoad_User = 0;
rc = VbglR3StatQueryInterval(&gCtx.cMsStatInterval);
if (RT_SUCCESS(rc))
VBoxServiceVerbose(3, "VBoxStatsInit: new statistics interval %u seconds\n", gCtx.cMsStatInterval);
else
VBoxServiceVerbose(3, "VBoxStatsInit: DeviceIoControl failed with %d\n", rc);
#ifdef RT_OS_WINDOWS
/* NtQuerySystemInformation might be dropped in future releases, so load it dynamically as per Microsoft's recommendation */
HMODULE hMod = LoadLibrary("NTDLL.DLL");
if (hMod)
{
*(uintptr_t *)&gCtx.pfnNtQuerySystemInformation = (uintptr_t)GetProcAddress(hMod, "NtQuerySystemInformation");
if (gCtx.pfnNtQuerySystemInformation)
VBoxServiceVerbose(3, "VBoxStatsInit: gCtx.pfnNtQuerySystemInformation = %x\n", gCtx.pfnNtQuerySystemInformation);
else
{
VBoxServiceError("VBoxStatsInit: NTDLL.NtQuerySystemInformation not found!!\n");
return VERR_NOT_IMPLEMENTED;
}
}
/* GlobalMemoryStatus is win2k and up, so load it dynamically */
hMod = LoadLibrary("KERNEL32.DLL");
if (hMod)
{
*(uintptr_t *)&gCtx.pfnGlobalMemoryStatusEx = (uintptr_t)GetProcAddress(hMod, "GlobalMemoryStatusEx");
if (gCtx.pfnGlobalMemoryStatusEx)
VBoxServiceVerbose(3, "VBoxStatsInit: gCtx.GlobalMemoryStatusEx = %x\n", gCtx.pfnGlobalMemoryStatusEx);
else
{
/** @todo now fails in NT4; do we care? */
VBoxServiceError("VBoxStatsInit: KERNEL32.GlobalMemoryStatusEx not found!!\n");
return VERR_NOT_IMPLEMENTED;
}
}
/* GetPerformanceInfo is xp and up, so load it dynamically */
hMod = LoadLibrary("PSAPI.DLL");
if (hMod)
{
*(uintptr_t *)&gCtx.pfnGetPerformanceInfo = (uintptr_t)GetProcAddress(hMod, "GetPerformanceInfo");
if (gCtx.pfnGetPerformanceInfo)
VBoxServiceVerbose(3, "VBoxStatsInit: gCtx.pfnGetPerformanceInfo= %x\n", gCtx.pfnGetPerformanceInfo);
/* failure is not fatal */
}
#endif /* RT_OS_WINDOWS */
return VINF_SUCCESS;
}
static void VBoxServiceVMStatsReport()
{
#ifdef RT_OS_WINDOWS
SYSTEM_INFO systemInfo;
PSYSTEM_PROCESSOR_PERFORMANCE_INFORMATION pProcInfo;
MEMORYSTATUSEX memStatus;
VMMDevReportGuestStats req;
uint32_t cbStruct;
DWORD cbReturned;
Assert(gCtx.pfnGlobalMemoryStatusEx && gCtx.pfnNtQuerySystemInformation);
if ( !gCtx.pfnGlobalMemoryStatusEx
|| !gCtx.pfnNtQuerySystemInformation)
return;
/* Query and report guest statistics */
GetSystemInfo(&systemInfo);
memStatus.dwLength = sizeof(memStatus);
gCtx.pfnGlobalMemoryStatusEx(&memStatus);
req.guestStats.u32PageSize = systemInfo.dwPageSize;
req.guestStats.u32PhysMemTotal = (uint32_t)(memStatus.ullTotalPhys / systemInfo.dwPageSize);
req.guestStats.u32PhysMemAvail = (uint32_t)(memStatus.ullAvailPhys / systemInfo.dwPageSize);
/* The current size of the committed memory limit, in bytes. This is physical memory plus the size of the page file, minus a small overhead. */
req.guestStats.u32PageFileSize = (uint32_t)(memStatus.ullTotalPageFile / systemInfo.dwPageSize) - req.guestStats.u32PhysMemTotal;
req.guestStats.u32MemoryLoad = memStatus.dwMemoryLoad;
req.guestStats.u32PhysMemBalloon = VBoxServiceBalloonQueryChunks() * (_1M/systemInfo.dwPageSize); /* was in megabytes */
req.guestStats.u32StatCaps = VBOX_GUEST_STAT_PHYS_MEM_TOTAL | VBOX_GUEST_STAT_PHYS_MEM_AVAIL | VBOX_GUEST_STAT_PAGE_FILE_SIZE | VBOX_GUEST_STAT_MEMORY_LOAD | VBOX_GUEST_STAT_PHYS_MEM_BALLOON;
if (gCtx.pfnGetPerformanceInfo)
{
PERFORMANCE_INFORMATION perfInfo;
if (gCtx.pfnGetPerformanceInfo(&perfInfo, sizeof(perfInfo)))
{
req.guestStats.u32Processes = perfInfo.ProcessCount;
req.guestStats.u32Threads = perfInfo.ThreadCount;
req.guestStats.u32Handles = perfInfo.HandleCount;
req.guestStats.u32MemCommitTotal = perfInfo.CommitTotal; /* already in pages */
req.guestStats.u32MemKernelTotal = perfInfo.KernelTotal; /* already in pages */
req.guestStats.u32MemKernelPaged = perfInfo.KernelPaged; /* already in pages */
req.guestStats.u32MemKernelNonPaged = perfInfo.KernelNonpaged; /* already in pages */
req.guestStats.u32MemSystemCache = perfInfo.SystemCache; /* already in pages */
req.guestStats.u32StatCaps |= VBOX_GUEST_STAT_PROCESSES | VBOX_GUEST_STAT_THREADS | VBOX_GUEST_STAT_HANDLES | VBOX_GUEST_STAT_MEM_COMMIT_TOTAL | VBOX_GUEST_STAT_MEM_KERNEL_TOTAL | VBOX_GUEST_STAT_MEM_KERNEL_PAGED | VBOX_GUEST_STAT_MEM_KERNEL_NONPAGED | VBOX_GUEST_STAT_MEM_SYSTEM_CACHE;
}
else
VBoxServiceVerbose(3, "GetPerformanceInfo failed with %d\n", GetLastError());
}
/* Query CPU load information */
cbStruct = systemInfo.dwNumberOfProcessors*sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION);
pProcInfo = (PSYSTEM_PROCESSOR_PERFORMANCE_INFORMATION)malloc(cbStruct);
Assert(pProcInfo);
if (!pProcInfo)
return;
/* Unfortunately GetSystemTimes is XP SP1 and up only, so we need to use the semi-undocumented NtQuerySystemInformation */
NTSTATUS rc = gCtx.pfnNtQuerySystemInformation(SystemProcessorPerformanceInformation, pProcInfo, cbStruct, &cbReturned);
if ( !rc
&& cbReturned == cbStruct)
{
if (gCtx.ullLastCpuLoad_Kernel == 0)
{
/* first time */
gCtx.ullLastCpuLoad_Idle = pProcInfo->IdleTime.QuadPart;
gCtx.ullLastCpuLoad_Kernel = pProcInfo->KernelTime.QuadPart;
gCtx.ullLastCpuLoad_User = pProcInfo->UserTime.QuadPart;
Sleep(250);
rc = gCtx.pfnNtQuerySystemInformation(SystemProcessorPerformanceInformation, pProcInfo, cbStruct, &cbReturned);
Assert(!rc);
}
uint64_t deltaIdle = (pProcInfo->IdleTime.QuadPart - gCtx.ullLastCpuLoad_Idle);
uint64_t deltaKernel = (pProcInfo->KernelTime.QuadPart - gCtx.ullLastCpuLoad_Kernel);
uint64_t deltaUser = (pProcInfo->UserTime.QuadPart - gCtx.ullLastCpuLoad_User);
deltaKernel -= deltaIdle; /* idle time is added to kernel time */
uint64_t ullTotalTime = deltaIdle + deltaKernel + deltaUser;
req.guestStats.u32CpuLoad_Idle = (uint32_t)(deltaIdle * 100 / ullTotalTime);
req.guestStats.u32CpuLoad_Kernel = (uint32_t)(deltaKernel* 100 / ullTotalTime);
req.guestStats.u32CpuLoad_User = (uint32_t)(deltaUser * 100 / ullTotalTime);
req.guestStats.u32StatCaps |= VBOX_GUEST_STAT_CPU_LOAD_IDLE | VBOX_GUEST_STAT_CPU_LOAD_KERNEL | VBOX_GUEST_STAT_CPU_LOAD_USER;
gCtx.ullLastCpuLoad_Idle = pProcInfo->IdleTime.QuadPart;
gCtx.ullLastCpuLoad_Kernel = pProcInfo->KernelTime.QuadPart;
gCtx.ullLastCpuLoad_User = pProcInfo->UserTime.QuadPart;
}
for (uint32_t i=0;i<systemInfo.dwNumberOfProcessors;i++)
{
req.guestStats.u32CpuId = i;
rc = VbglR3StatReport(&req);
if (RT_SUCCESS(rc))
{
VBoxServiceVerbose(3, "VBoxStatsReportStatistics: new statistics reported successfully!\n");
}
else
VBoxServiceVerbose(3, "VBoxStatsReportStatistics: DeviceIoControl (stats report) failed with %d\n", GetLastError());
}
free(pProcInfo);
#else
/* todo: implement for other platforms. */
return;
#endif
}
/** @copydoc VBOXSERVICE::pfnWorker */
DECLCALLBACK(int) VBoxServiceVMStatsWorker(bool volatile *pfShutdown)
{
int rc = VINF_SUCCESS;
/* Start monitoring of the stat event change event. */
rc = VbglR3CtlFilterMask(VMMDEV_EVENT_STATISTICS_INTERVAL_CHANGE_REQUEST, 0);
if (RT_FAILURE(rc))
{
VBoxServiceVerbose(3, "VBoxServiceVMStatsWorker: VbglR3CtlFilterMask failed with %d\n", rc);
return rc;
}
/*
* Tell the control thread that it can continue
* spawning services.
*/
RTThreadUserSignal(RTThreadSelf());
/*
* Now enter the loop retrieving runtime data continuously.
*/
for (;;)
{
uint32_t fEvents = 0;
RTMSINTERVAL cWaitMillies;
/* Check if an update interval change is pending. */
rc = VbglR3WaitEvent(VMMDEV_EVENT_STATISTICS_INTERVAL_CHANGE_REQUEST, 0 /* no wait */, &fEvents);
if ( RT_SUCCESS(rc)
&& (fEvents & VMMDEV_EVENT_STATISTICS_INTERVAL_CHANGE_REQUEST))
{
VbglR3StatQueryInterval(&gCtx.cMsStatInterval);
}
if (gCtx.cMsStatInterval)
{
VBoxServiceVMStatsReport();
cWaitMillies = gCtx.cMsStatInterval;
}
else
cWaitMillies = 3000;
/*
* Block for a while.
*
* The event semaphore takes care of ignoring interruptions and it
* allows us to implement service wakeup later.
*/
if (*pfShutdown)
break;
int rc2 = RTSemEventMultiWait(g_VMStatEvent, cWaitMillies);
if (*pfShutdown)
break;
if (rc2 != VERR_TIMEOUT && RT_FAILURE(rc2))
{
VBoxServiceError("RTSemEventMultiWait failed; rc2=%Rrc\n", rc2);
rc = rc2;
break;
}
}
/* Cancel monitoring of the stat event change event. */
rc = VbglR3CtlFilterMask(0, VMMDEV_EVENT_STATISTICS_INTERVAL_CHANGE_REQUEST);
if (RT_FAILURE(rc))
VBoxServiceVerbose(3, "VBoxServiceVMStatsWorker: VbglR3CtlFilterMask failed with %d\n", rc);
RTSemEventMultiDestroy(g_VMStatEvent);
g_VMStatEvent = NIL_RTSEMEVENTMULTI;
VBoxServiceVerbose(3, "VBoxStatsThread: finished statistics change request thread\n");
return 0;
}
/** @copydoc VBOXSERVICE::pfnTerm */
static DECLCALLBACK(void) VBoxServiceVMStatsTerm(void)
{
VBoxServiceVerbose(3, "VBoxServiceVMStatsTerm\n");
return;
}
/** @copydoc VBOXSERVICE::pfnStop */
static DECLCALLBACK(void) VBoxServiceVMStatsStop(void)
{
RTSemEventMultiSignal(g_VMStatEvent);
}
/**
* The 'vminfo' service description.
*/
VBOXSERVICE g_VMStatistics =
{
/* pszName. */
"vmstats",
/* pszDescription. */
"Virtual Machine Statistics",
/* pszUsage. */
NULL,
/* pszOptions. */
NULL,
/* methods */
VBoxServiceVMStatsPreInit,
VBoxServiceVMStatsOption,
VBoxServiceVMStatsInit,
VBoxServiceVMStatsWorker,
VBoxServiceVMStatsStop,
VBoxServiceVMStatsTerm
};