VirtualBox

source: vbox/trunk/src/VBox/Additions/common/VBoxService/VBoxServiceStats.cpp@ 27938

Last change on this file since 27938 was 27938, checked in by vboxsync, 15 years ago

Additions/VBoxService: guest memory/CPU statistics for Linux guests (untested)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 17.7 KB
Line 
1/* $Id: VBoxServiceStats.cpp 27938 2010-04-01 13:25:50Z vboxsync $ */
2/** @file
3 * VBoxStats - Guest statistics notification
4 */
5
6/*
7 * Copyright (C) 2006-2007 Sun Microsystems, Inc.
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
18 * Clara, CA 95054 USA or visit http://www.sun.com if you need
19 * additional information or have any questions.
20 */
21#ifdef TARGET_NT4
22#undef _WIN32_WINNT
23#define _WIN32_WINNT 0x501
24#endif
25
26#if defined(RT_OS_WINDOWS)
27# include <windows.h>
28# include <psapi.h>
29# include <winternl.h>
30#elif defined(RT_OS_LINUX)
31# include <iprt/ctype.h>
32# include <iprt/stream.h>
33#endif
34
35#include <iprt/assert.h>
36#include <iprt/mem.h>
37#include <iprt/thread.h>
38#include <iprt/string.h>
39#include <iprt/semaphore.h>
40#include <iprt/system.h>
41#include <iprt/time.h>
42#include <VBox/VBoxGuestLib.h>
43#include "VBoxServiceInternal.h"
44#include "VBoxServiceUtils.h"
45
46typedef struct _VBOXSTATSCONTEXT
47{
48 RTMSINTERVAL cMsStatInterval;
49
50 uint64_t ullLastCpuLoad_Idle;
51 uint64_t ullLastCpuLoad_Kernel;
52 uint64_t ullLastCpuLoad_User;
53
54#ifdef RT_OS_WINDOWS
55 NTSTATUS (WINAPI *pfnNtQuerySystemInformation)(SYSTEM_INFORMATION_CLASS SystemInformationClass, PVOID SystemInformation, ULONG SystemInformationLength, PULONG ReturnLength);
56 void (WINAPI *pfnGlobalMemoryStatusEx)(LPMEMORYSTATUSEX lpBuffer);
57 BOOL (WINAPI *pfnGetPerformanceInfo)(PPERFORMANCE_INFORMATION pPerformanceInformation, DWORD cb);
58#endif
59} VBOXSTATSCONTEXT;
60
61/*******************************************************************************
62* Global Variables *
63*******************************************************************************/
64static VBOXSTATSCONTEXT gCtx = {0};
65
66/** The semaphore we're blocking on. */
67static RTSEMEVENTMULTI g_VMStatEvent = NIL_RTSEMEVENTMULTI;
68
69
70/** @copydoc VBOXSERVICE::pfnPreInit */
71static DECLCALLBACK(int) VBoxServiceVMStatsPreInit(void)
72{
73 return VINF_SUCCESS;
74}
75
76
77/** @copydoc VBOXSERVICE::pfnOption */
78static DECLCALLBACK(int) VBoxServiceVMStatsOption(const char **ppszShort, int argc, char **argv, int *pi)
79{
80 NOREF(ppszShort);
81 NOREF(argc);
82 NOREF(argv);
83 NOREF(pi);
84 return VINF_SUCCESS;
85}
86
87
88/** @copydoc VBOXSERVICE::pfnInit */
89static DECLCALLBACK(int) VBoxServiceVMStatsInit(void)
90{
91 VBoxServiceVerbose(3, "VBoxServiceVMStatsInit\n");
92
93 int rc = RTSemEventMultiCreate(&g_VMStatEvent);
94 AssertRCReturn(rc, rc);
95
96 gCtx.cMsStatInterval = 0; /* default; update disabled */
97 gCtx.ullLastCpuLoad_Idle = 0;
98 gCtx.ullLastCpuLoad_Kernel = 0;
99 gCtx.ullLastCpuLoad_User = 0;
100
101 rc = VbglR3StatQueryInterval(&gCtx.cMsStatInterval);
102 if (RT_SUCCESS(rc))
103 VBoxServiceVerbose(3, "VBoxStatsInit: new statistics interval %u seconds\n", gCtx.cMsStatInterval);
104 else
105 VBoxServiceVerbose(3, "VBoxStatsInit: DeviceIoControl failed with %d\n", rc);
106
107#ifdef RT_OS_WINDOWS
108 /* NtQuerySystemInformation might be dropped in future releases, so load it dynamically as per Microsoft's recommendation */
109 HMODULE hMod = LoadLibrary("NTDLL.DLL");
110 if (hMod)
111 {
112 *(uintptr_t *)&gCtx.pfnNtQuerySystemInformation = (uintptr_t)GetProcAddress(hMod, "NtQuerySystemInformation");
113 if (gCtx.pfnNtQuerySystemInformation)
114 VBoxServiceVerbose(3, "VBoxStatsInit: gCtx.pfnNtQuerySystemInformation = %x\n", gCtx.pfnNtQuerySystemInformation);
115 else
116 {
117 VBoxServiceError("VBoxStatsInit: NTDLL.NtQuerySystemInformation not found!!\n");
118 return VERR_NOT_IMPLEMENTED;
119 }
120 }
121
122 /* GlobalMemoryStatus is win2k and up, so load it dynamically */
123 hMod = LoadLibrary("KERNEL32.DLL");
124 if (hMod)
125 {
126 *(uintptr_t *)&gCtx.pfnGlobalMemoryStatusEx = (uintptr_t)GetProcAddress(hMod, "GlobalMemoryStatusEx");
127 if (gCtx.pfnGlobalMemoryStatusEx)
128 VBoxServiceVerbose(3, "VBoxStatsInit: gCtx.GlobalMemoryStatusEx = %x\n", gCtx.pfnGlobalMemoryStatusEx);
129 else
130 {
131 /** @todo now fails in NT4; do we care? */
132 VBoxServiceError("VBoxStatsInit: KERNEL32.GlobalMemoryStatusEx not found!!\n");
133 return VERR_NOT_IMPLEMENTED;
134 }
135 }
136 /* GetPerformanceInfo is xp and up, so load it dynamically */
137 hMod = LoadLibrary("PSAPI.DLL");
138 if (hMod)
139 {
140 *(uintptr_t *)&gCtx.pfnGetPerformanceInfo = (uintptr_t)GetProcAddress(hMod, "GetPerformanceInfo");
141 if (gCtx.pfnGetPerformanceInfo)
142 VBoxServiceVerbose(3, "VBoxStatsInit: gCtx.pfnGetPerformanceInfo= %x\n", gCtx.pfnGetPerformanceInfo);
143 /* failure is not fatal */
144 }
145#endif /* RT_OS_WINDOWS */
146
147 return VINF_SUCCESS;
148}
149
150
151static void VBoxServiceVMStatsReport()
152{
153#if defined(RT_OS_WINDOWS)
154 SYSTEM_INFO systemInfo;
155 PSYSTEM_PROCESSOR_PERFORMANCE_INFORMATION pProcInfo;
156 MEMORYSTATUSEX memStatus;
157 VMMDevReportGuestStats req;
158 uint32_t cbStruct;
159 DWORD cbReturned;
160
161 Assert(gCtx.pfnGlobalMemoryStatusEx && gCtx.pfnNtQuerySystemInformation);
162 if ( !gCtx.pfnGlobalMemoryStatusEx
163 || !gCtx.pfnNtQuerySystemInformation)
164 return;
165
166 /* Query and report guest statistics */
167 GetSystemInfo(&systemInfo);
168
169 memStatus.dwLength = sizeof(memStatus);
170 gCtx.pfnGlobalMemoryStatusEx(&memStatus);
171
172 req.guestStats.u32PageSize = systemInfo.dwPageSize;
173 req.guestStats.u32PhysMemTotal = (uint32_t)(memStatus.ullTotalPhys / systemInfo.dwPageSize);
174 req.guestStats.u32PhysMemAvail = (uint32_t)(memStatus.ullAvailPhys / systemInfo.dwPageSize);
175 /* 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. */
176 req.guestStats.u32PageFileSize = (uint32_t)(memStatus.ullTotalPageFile / systemInfo.dwPageSize) - req.guestStats.u32PhysMemTotal;
177 req.guestStats.u32MemoryLoad = memStatus.dwMemoryLoad;
178 req.guestStats.u32PhysMemBalloon = VBoxServiceBalloonQueryChunks() * (_1M/systemInfo.dwPageSize); /* was in megabytes */
179 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;
180
181 if (gCtx.pfnGetPerformanceInfo)
182 {
183 PERFORMANCE_INFORMATION perfInfo;
184
185 if (gCtx.pfnGetPerformanceInfo(&perfInfo, sizeof(perfInfo)))
186 {
187 req.guestStats.u32Processes = perfInfo.ProcessCount;
188 req.guestStats.u32Threads = perfInfo.ThreadCount;
189 req.guestStats.u32Handles = perfInfo.HandleCount;
190 req.guestStats.u32MemCommitTotal = perfInfo.CommitTotal; /* already in pages */
191 req.guestStats.u32MemKernelTotal = perfInfo.KernelTotal; /* already in pages */
192 req.guestStats.u32MemKernelPaged = perfInfo.KernelPaged; /* already in pages */
193 req.guestStats.u32MemKernelNonPaged = perfInfo.KernelNonpaged; /* already in pages */
194 req.guestStats.u32MemSystemCache = perfInfo.SystemCache; /* already in pages */
195 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;
196 }
197 else
198 VBoxServiceVerbose(3, "GetPerformanceInfo failed with %d\n", GetLastError());
199 }
200
201 /* Query CPU load information */
202 cbStruct = systemInfo.dwNumberOfProcessors*sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION);
203 pProcInfo = (PSYSTEM_PROCESSOR_PERFORMANCE_INFORMATION)malloc(cbStruct);
204 Assert(pProcInfo);
205 if (!pProcInfo)
206 return;
207
208 /* Unfortunately GetSystemTimes is XP SP1 and up only, so we need to use the semi-undocumented NtQuerySystemInformation */
209 NTSTATUS rc = gCtx.pfnNtQuerySystemInformation(SystemProcessorPerformanceInformation, pProcInfo, cbStruct, &cbReturned);
210 if ( !rc
211 && cbReturned == cbStruct)
212 {
213 if (gCtx.ullLastCpuLoad_Kernel == 0)
214 {
215 /* first time */
216 gCtx.ullLastCpuLoad_Idle = pProcInfo->IdleTime.QuadPart;
217 gCtx.ullLastCpuLoad_Kernel = pProcInfo->KernelTime.QuadPart;
218 gCtx.ullLastCpuLoad_User = pProcInfo->UserTime.QuadPart;
219
220 Sleep(250);
221
222 rc = gCtx.pfnNtQuerySystemInformation(SystemProcessorPerformanceInformation, pProcInfo, cbStruct, &cbReturned);
223 Assert(!rc);
224 }
225
226 uint64_t deltaIdle = (pProcInfo->IdleTime.QuadPart - gCtx.ullLastCpuLoad_Idle);
227 uint64_t deltaKernel = (pProcInfo->KernelTime.QuadPart - gCtx.ullLastCpuLoad_Kernel);
228 uint64_t deltaUser = (pProcInfo->UserTime.QuadPart - gCtx.ullLastCpuLoad_User);
229 deltaKernel -= deltaIdle; /* idle time is added to kernel time */
230 uint64_t ullTotalTime = deltaIdle + deltaKernel + deltaUser;
231
232 req.guestStats.u32CpuLoad_Idle = (uint32_t)(deltaIdle * 100 / ullTotalTime);
233 req.guestStats.u32CpuLoad_Kernel = (uint32_t)(deltaKernel* 100 / ullTotalTime);
234 req.guestStats.u32CpuLoad_User = (uint32_t)(deltaUser * 100 / ullTotalTime);
235
236 req.guestStats.u32StatCaps |= VBOX_GUEST_STAT_CPU_LOAD_IDLE | VBOX_GUEST_STAT_CPU_LOAD_KERNEL | VBOX_GUEST_STAT_CPU_LOAD_USER;
237
238 gCtx.ullLastCpuLoad_Idle = pProcInfo->IdleTime.QuadPart;
239 gCtx.ullLastCpuLoad_Kernel = pProcInfo->KernelTime.QuadPart;
240 gCtx.ullLastCpuLoad_User = pProcInfo->UserTime.QuadPart;
241 }
242
243 for (uint32_t i=0; i<systemInfo.dwNumberOfProcessors; i++)
244 {
245 req.guestStats.u32CpuId = i;
246
247 rc = VbglR3StatReport(&req);
248 if (RT_SUCCESS(rc))
249 VBoxServiceVerbose(3, "VBoxStatsReportStatistics: new statistics reported successfully!\n");
250 else
251 VBoxServiceVerbose(3, "VBoxStatsReportStatistics: DeviceIoControl (stats report) failed with %d\n", GetLastError());
252 }
253
254 free(pProcInfo);
255
256#elif defined(RT_OS_LINUX)
257 VMMDevReportGuestStats req;
258 RT_ZERO(req);
259 PRTSTREAM pStrm;
260 char szLine[256];
261 char *psz;
262
263 int rc = RTStrmOpen("/proc/meminfo", "r", &pStrm);
264 if (RT_SUCCESS(rc))
265 {
266 uint64_t u64Kb;
267 uint64_t u64Total = 0, u64Free = 0, u64Buffers = 0, u64Cached = 0, u64PagedTotal = 0;
268 for (;;)
269 {
270 rc = RTStrmGetLine(pStrm, szLine, sizeof(szLine));
271 if (RT_FAILURE(rc))
272 break;
273 if (strstr("MemTotal:", szLine) == szLine)
274 {
275 rc = RTStrToUInt64Ex(RTStrStripL(&szLine[9]), &psz, 0, &u64Kb);
276 if (RT_SUCCESS(rc))
277 u64Total = u64Kb * _1K;
278 }
279 else if (strstr("MemFree:", szLine) == szLine)
280 {
281 rc = RTStrToUInt64Ex(RTStrStripL(&szLine[8]), &psz, 0, &u64Kb);
282 if (RT_SUCCESS(rc))
283 u64Free = u64Kb * _1K;
284 }
285 else if (strstr("Buffers:", szLine) == szLine)
286 {
287 rc = RTStrToUInt64Ex(RTStrStripL(&szLine[8]), &psz, 0, &u64Kb);
288 if (RT_SUCCESS(rc))
289 u64Buffers = u64Kb * _1K;
290 }
291 else if (strstr("Cached:", szLine) == szLine)
292 {
293 rc = RTStrToUInt64Ex(RTStrStripL(&szLine[7]), &psz, 0, &u64Kb);
294 if (RT_SUCCESS(rc))
295 u64Cached = u64Kb * _1K;
296 }
297 else if (strstr("SwapTotal:", szLine) == szLine)
298 {
299 rc = RTStrToUInt64Ex(RTStrStripL(&szLine[10]), &psz, 0, &u64Kb);
300 if (RT_SUCCESS(rc))
301 u64PagedTotal = u64Kb * _1K;
302 }
303 }
304 req.guestStats.u32PhysMemTotal = u64Total / _4K;
305 req.guestStats.u32PhysMemAvail = (u64Free + u64Buffers + u64Cached) / _4K;
306 req.guestStats.u32MemSystemCache = (u64Buffers + u64Cached) / _4K;
307 req.guestStats.u32PageFileSize = u64PagedTotal / _4K;
308 RTStrmClose(pStrm);
309 }
310
311 req.guestStats.u32PhysMemBalloon = VBoxServiceBalloonQueryChunks() * (_1M/_4K);
312 req.guestStats.u32StatCaps = VBOX_GUEST_STAT_PHYS_MEM_TOTAL \
313 | VBOX_GUEST_STAT_PHYS_MEM_AVAIL \
314 | VBOX_GUEST_STAT_PHYS_MEM_BALLOON \
315 | VBOX_GUEST_STAT_PAGE_FILE_SIZE;
316
317 rc = RTStrmOpen("/proc/stat", "r", &pStrm);
318 if (RT_SUCCESS(rc))
319 {
320 for (;;)
321 {
322 uint64_t u64CpuId, u64User = 0, u64Nice = 0, u64System = 0, u64Idle = 0;
323 rc = RTStrmGetLine(pStrm, szLine, sizeof(szLine));
324 if (RT_FAILURE(rc))
325 break;
326 if ( strstr("cpu", szLine) == szLine
327 && strlen(szLine) > 3
328 && RT_C_IS_DIGIT(szLine[3]))
329 {
330 rc = RTStrToUInt64Ex(&szLine[3], &psz, 0, &u64CpuId);
331 if (RT_SUCCESS(rc))
332 rc = RTStrToUInt64Ex(RTStrStripL(psz), &psz, 0, &u64User);
333 if (RT_SUCCESS(rc))
334 rc = RTStrToUInt64Ex(RTStrStripL(psz), &psz, 0, &u64Nice);
335 if (RT_SUCCESS(rc))
336 rc = RTStrToUInt64Ex(RTStrStripL(psz), &psz, 0, &u64System);
337 if (RT_SUCCESS(rc))
338 rc = RTStrToUInt64Ex(RTStrStripL(psz), &psz, 0, &u64Idle);
339 uint64_t u64All = u64Idle + u64System + u64User + u64Nice;
340 req.guestStats.u32CpuId = u64CpuId;
341 req.guestStats.u32CpuLoad_Idle = (uint32_t)(u64Idle * 100 / u64All);
342 req.guestStats.u32CpuLoad_Kernel = (uint32_t)(u64System * 100 / u64All);
343 req.guestStats.u32CpuLoad_User = (uint32_t)((u64User + u64Nice) * 100 / u64All);
344 req.guestStats.u32StatCaps |= VBOX_GUEST_STAT_CPU_LOAD_IDLE \
345 | VBOX_GUEST_STAT_CPU_LOAD_KERNEL \
346 | VBOX_GUEST_STAT_CPU_LOAD_USER;
347 rc = VbglR3StatReport(&req);
348 if (RT_SUCCESS(rc))
349 VBoxServiceVerbose(3, "VBoxStatsReportStatistics: new statistics reported successfully!\n");
350 else
351 VBoxServiceVerbose(3, "VBoxStatsReportStatistics: stats report failed with rc=%Rrc\n", rc);
352 }
353 }
354 RTStrmClose(pStrm);
355 }
356
357#else
358 /* todo: implement for other platforms. */
359
360#endif
361}
362
363/** @copydoc VBOXSERVICE::pfnWorker */
364DECLCALLBACK(int) VBoxServiceVMStatsWorker(bool volatile *pfShutdown)
365{
366 int rc = VINF_SUCCESS;
367
368 /* Start monitoring of the stat event change event. */
369 rc = VbglR3CtlFilterMask(VMMDEV_EVENT_STATISTICS_INTERVAL_CHANGE_REQUEST, 0);
370 if (RT_FAILURE(rc))
371 {
372 VBoxServiceVerbose(3, "VBoxServiceVMStatsWorker: VbglR3CtlFilterMask failed with %d\n", rc);
373 return rc;
374 }
375
376 /*
377 * Tell the control thread that it can continue
378 * spawning services.
379 */
380 RTThreadUserSignal(RTThreadSelf());
381
382 /*
383 * Now enter the loop retrieving runtime data continuously.
384 */
385 for (;;)
386 {
387 uint32_t fEvents = 0;
388 RTMSINTERVAL cWaitMillies;
389
390 /* Check if an update interval change is pending. */
391 rc = VbglR3WaitEvent(VMMDEV_EVENT_STATISTICS_INTERVAL_CHANGE_REQUEST, 0 /* no wait */, &fEvents);
392 if ( RT_SUCCESS(rc)
393 && (fEvents & VMMDEV_EVENT_STATISTICS_INTERVAL_CHANGE_REQUEST))
394 {
395 VbglR3StatQueryInterval(&gCtx.cMsStatInterval);
396 }
397
398 if (gCtx.cMsStatInterval)
399 {
400 VBoxServiceVMStatsReport();
401 cWaitMillies = gCtx.cMsStatInterval;
402 }
403 else
404 cWaitMillies = 3000;
405
406 /*
407 * Block for a while.
408 *
409 * The event semaphore takes care of ignoring interruptions and it
410 * allows us to implement service wakeup later.
411 */
412 if (*pfShutdown)
413 break;
414 int rc2 = RTSemEventMultiWait(g_VMStatEvent, cWaitMillies);
415 if (*pfShutdown)
416 break;
417 if (rc2 != VERR_TIMEOUT && RT_FAILURE(rc2))
418 {
419 VBoxServiceError("RTSemEventMultiWait failed; rc2=%Rrc\n", rc2);
420 rc = rc2;
421 break;
422 }
423 }
424
425 /* Cancel monitoring of the stat event change event. */
426 rc = VbglR3CtlFilterMask(0, VMMDEV_EVENT_STATISTICS_INTERVAL_CHANGE_REQUEST);
427 if (RT_FAILURE(rc))
428 VBoxServiceVerbose(3, "VBoxServiceVMStatsWorker: VbglR3CtlFilterMask failed with %d\n", rc);
429
430 RTSemEventMultiDestroy(g_VMStatEvent);
431 g_VMStatEvent = NIL_RTSEMEVENTMULTI;
432
433 VBoxServiceVerbose(3, "VBoxStatsThread: finished statistics change request thread\n");
434 return 0;
435}
436
437
438/** @copydoc VBOXSERVICE::pfnTerm */
439static DECLCALLBACK(void) VBoxServiceVMStatsTerm(void)
440{
441 VBoxServiceVerbose(3, "VBoxServiceVMStatsTerm\n");
442 return;
443}
444
445
446/** @copydoc VBOXSERVICE::pfnStop */
447static DECLCALLBACK(void) VBoxServiceVMStatsStop(void)
448{
449 RTSemEventMultiSignal(g_VMStatEvent);
450}
451
452
453/**
454 * The 'vminfo' service description.
455 */
456VBOXSERVICE g_VMStatistics =
457{
458 /* pszName. */
459 "vmstats",
460 /* pszDescription. */
461 "Virtual Machine Statistics",
462 /* pszUsage. */
463 NULL,
464 /* pszOptions. */
465 NULL,
466 /* methods */
467 VBoxServiceVMStatsPreInit,
468 VBoxServiceVMStatsOption,
469 VBoxServiceVMStatsInit,
470 VBoxServiceVMStatsWorker,
471 VBoxServiceVMStatsStop,
472 VBoxServiceVMStatsTerm
473};
Note: See TracBrowser for help on using the repository browser.

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