VirtualBox

source: vbox/trunk/src/VBox/Frontends/VBoxBalloonCtrl/VBoxModBallooning.cpp@ 41360

Last change on this file since 41360 was 41360, checked in by vboxsync, 13 years ago

VBoxBalloonCtrl: Update, renamed "--apimon-trigger-timeout" to "--apimon-response-timeout".

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 18.8 KB
Line 
1/* $Id: VBoxModBallooning.cpp 41360 2012-05-21 11:01:31Z vboxsync $ */
2/** @file
3 * VBoxModBallooning - Module for handling the automatic ballooning of VMs.
4 */
5
6/*
7 * Copyright (C) 2011-2012 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.215389.xyz. 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
18
19/*******************************************************************************
20* Header Files *
21*******************************************************************************/
22#ifndef VBOX_ONLY_DOCS
23# include <VBox/com/errorprint.h>
24#endif /* !VBOX_ONLY_DOCS */
25
26#include "VBoxWatchdogInternal.h"
27
28using namespace com;
29
30#define VBOX_MOD_BALLOONING_NAME "balloonctrl"
31
32/**
33 * The module's RTGetOpt-IDs for the command line.
34 */
35enum GETOPTDEF_BALLOONCTRL
36{
37 GETOPTDEF_BALLOONCTRL_BALLOOINC = 2000,
38 GETOPTDEF_BALLOONCTRL_BALLOONDEC,
39 GETOPTDEF_BALLOONCTRL_BALLOONLOWERLIMIT,
40 GETOPTDEF_BALLOONCTRL_BALLOONMAX,
41 GETOPTDEF_BALLOONCTRL_TIMEOUTMS,
42 GETOPTDEF_BALLOONCTRL_GROUPS
43};
44
45/**
46 * The module's command line arguments.
47 */
48static const RTGETOPTDEF g_aBalloonOpts[] = {
49 { "--balloon-dec", GETOPTDEF_BALLOONCTRL_BALLOONDEC, RTGETOPT_REQ_UINT32 },
50 { "--balloon-groups", GETOPTDEF_BALLOONCTRL_GROUPS, RTGETOPT_REQ_STRING },
51 { "--balloon-inc", GETOPTDEF_BALLOONCTRL_BALLOOINC, RTGETOPT_REQ_UINT32 },
52 { "--balloon-interval", GETOPTDEF_BALLOONCTRL_TIMEOUTMS, RTGETOPT_REQ_UINT32 },
53 { "--balloon-lower-limit", GETOPTDEF_BALLOONCTRL_BALLOONLOWERLIMIT, RTGETOPT_REQ_UINT32 },
54 { "--balloon-max", GETOPTDEF_BALLOONCTRL_BALLOONMAX, RTGETOPT_REQ_UINT32 }
55};
56
57static unsigned long g_ulMemoryBalloonTimeoutMS = 30 * 1000; /* Default is 30 seconds timeout. */
58static unsigned long g_ulMemoryBalloonIncrementMB = 256;
59static unsigned long g_ulMemoryBalloonDecrementMB = 128;
60/** Global balloon limit is 0, so disabled. Can be overridden by a per-VM
61 * "VBoxInternal/Guest/BalloonSizeMax" value. */
62static unsigned long g_ulMemoryBalloonMaxMB = 0;
63static unsigned long g_ulMemoryBalloonLowerLimitMB = 64;
64
65/** The ballooning module's payload. */
66typedef struct VBOXWATCHDOG_BALLOONCTRL_PAYLOAD
67{
68 /** The maximum ballooning size for the VM.
69 * Specify 0 for ballooning disabled. */
70 unsigned long ulBalloonSizeMax;
71} VBOXWATCHDOG_BALLOONCTRL_PAYLOAD, *PVBOXWATCHDOG_BALLOONCTRL_PAYLOAD;
72
73
74/**
75 * Retrieves the current delta value
76 *
77 * @return long Delta (MB) of the balloon to be deflated (<0) or inflated (>0).
78 * @param ulCurrentDesktopBalloonSize The balloon's current size.
79 * @param ulDesktopFreeMemory The VM's current free memory.
80 * @param ulMaxBalloonSize The maximum balloon size (MB) it can inflate to.
81 */
82static long balloonGetDelta(unsigned long ulCurrentDesktopBalloonSize,
83 unsigned long ulDesktopFreeMemory, unsigned long ulMaxBalloonSize)
84{
85 if (ulCurrentDesktopBalloonSize > ulMaxBalloonSize)
86 return (ulMaxBalloonSize - ulCurrentDesktopBalloonSize);
87
88 long lBalloonDelta = 0;
89 if (ulDesktopFreeMemory < g_ulMemoryBalloonLowerLimitMB)
90 {
91 /* Guest is running low on memory, we need to
92 * deflate the balloon. */
93 lBalloonDelta = (g_ulMemoryBalloonDecrementMB * -1);
94
95 /* Ensure that the delta will not return a negative
96 * balloon size. */
97 if ((long)ulCurrentDesktopBalloonSize + lBalloonDelta < 0)
98 lBalloonDelta = 0;
99 }
100 else if (ulMaxBalloonSize > ulCurrentDesktopBalloonSize)
101 {
102 /* We want to inflate the balloon if we have room. */
103 long lIncrement = g_ulMemoryBalloonIncrementMB;
104 while (lIncrement >= 16 && (ulDesktopFreeMemory - lIncrement) < g_ulMemoryBalloonLowerLimitMB)
105 {
106 lIncrement = (lIncrement / 2);
107 }
108
109 if ((ulDesktopFreeMemory - lIncrement) > g_ulMemoryBalloonLowerLimitMB)
110 lBalloonDelta = lIncrement;
111 }
112 if (ulCurrentDesktopBalloonSize + lBalloonDelta > ulMaxBalloonSize)
113 lBalloonDelta = (ulMaxBalloonSize - ulCurrentDesktopBalloonSize);
114 return lBalloonDelta;
115}
116
117/**
118 * Determines the maximum balloon size to set for the specified machine.
119 *
120 * @return unsigned long Balloon size (in MB) to set, 0 if no ballooning required.
121 * @param rptrMachine Pointer to interface of specified machine.
122 */
123static unsigned long balloonGetMaxSize(const ComPtr<IMachine> &rptrMachine)
124{
125 /*
126 * Try to retrieve the balloon maximum size via the following order:
127 * - command line parameter ("--balloon-max")
128 * Legacy (VBoxBalloonCtrl):
129 * - per-VM parameter ("VBoxInternal/Guest/BalloonSizeMax")
130 * Global:
131 * - global parameter ("VBoxInternal/Guest/BalloonSizeMax")
132 * New:
133 * - per-VM parameter ("VBoxInternal2/Watchdog/BalloonCtrl/BalloonSizeMax")
134 *
135 * By default (e.g. if none of above is set), ballooning is disabled.
136 */
137 unsigned long ulBalloonMax = g_ulMemoryBalloonMaxMB; /* Use global limit as default. */
138 if (!ulBalloonMax) /* Not set by command line? */
139 {
140 /* Try per-VM approach. */
141 Bstr strValue;
142 HRESULT rc = rptrMachine->GetExtraData(Bstr("VBoxInternal/Guest/BalloonSizeMax").raw(),
143 strValue.asOutParam());
144 if ( SUCCEEDED(rc)
145 && !strValue.isEmpty())
146 {
147 ulBalloonMax = Utf8Str(strValue).toUInt32();
148 }
149 }
150 if (!ulBalloonMax) /* Still not set by per-VM value? */
151 {
152 /* Try global approach. */
153 Bstr strValue;
154 HRESULT rc = g_pVirtualBox->GetExtraData(Bstr("VBoxInternal/Guest/BalloonSizeMax").raw(),
155 strValue.asOutParam());
156 if ( SUCCEEDED(rc)
157 && !strValue.isEmpty())
158 {
159 ulBalloonMax = Utf8Str(strValue).toUInt32();
160 }
161 }
162 if (!ulBalloonMax)
163 {
164 /** @todo ("VBoxInternal2/Watchdog/BalloonCtrl/BalloonSizeMax") */
165 }
166 return ulBalloonMax;
167}
168
169/**
170 * Indicates whether ballooning on the specified machine state is
171 * possible -- this only is true if the machine is up and running.
172 *
173 * @return bool Flag indicating whether the VM is running or not.
174 * @param enmState The VM's machine state to judge whether it's running or not.
175 */
176static bool balloonIsPossible(MachineState_T enmState)
177{
178 switch (enmState)
179 {
180 case MachineState_Running:
181#if 0
182 /* Not required for ballooning. */
183 case MachineState_Teleporting:
184 case MachineState_LiveSnapshotting:
185 case MachineState_Paused:
186 case MachineState_TeleportingPausedVM:
187#endif
188 return true;
189 default:
190 break;
191 }
192 return false;
193}
194
195/**
196 * Determines whether ballooning is required to the specified machine.
197 *
198 * @return bool True if ballooning is required, false if not.
199 * @param pMachine Machine to determine ballooning for.
200 */
201static bool balloonIsRequired(PVBOXWATCHDOG_MACHINE pMachine)
202{
203 AssertPtrReturn(pMachine, false);
204
205 /* Only do ballooning if we have a maximum balloon size set. */
206 PVBOXWATCHDOG_BALLOONCTRL_PAYLOAD pData = (PVBOXWATCHDOG_BALLOONCTRL_PAYLOAD)
207 payloadFrom(pMachine, VBOX_MOD_BALLOONING_NAME);
208 AssertPtr(pData);
209 pData->ulBalloonSizeMax = pMachine->machine.isNull()
210 ? 0 : balloonGetMaxSize(pMachine->machine);
211
212 /** @todo Add grouping as a criteria! */
213
214 return pData->ulBalloonSizeMax ? true : false;
215}
216
217int balloonMachineSetup(const Bstr& strUuid)
218{
219 int vrc = VINF_SUCCESS;
220
221 do
222 {
223 PVBOXWATCHDOG_MACHINE pMachine = getMachine(strUuid);
224 AssertPtrBreakStmt(pMachine, vrc=VERR_INVALID_PARAMETER);
225
226 ComPtr<IMachine> m = pMachine->machine;
227
228 /*
229 * Setup metrics required for ballooning.
230 */
231 com::SafeArray<BSTR> metricNames(1);
232 com::SafeIfaceArray<IUnknown> metricObjects(1);
233 com::SafeIfaceArray<IPerformanceMetric> metricAffected;
234
235 Bstr strMetricNames(L"Guest/RAM/Usage");
236 strMetricNames.cloneTo(&metricNames[0]);
237
238 HRESULT rc = m.queryInterfaceTo(&metricObjects[0]);
239
240#ifdef VBOX_WATCHDOG_GLOBAL_PERFCOL
241 CHECK_ERROR_BREAK(g_pPerfCollector, SetupMetrics(ComSafeArrayAsInParam(metricNames),
242 ComSafeArrayAsInParam(metricObjects),
243 5 /* 5 seconds */,
244 1 /* One sample is enough */,
245 ComSafeArrayAsOutParam(metricAffected)));
246#else
247 ComPtr<IPerformanceCollector> coll = pMachine->collector;
248
249 CHECK_ERROR_BREAK(g_pVirtualBox, COMGETTER(PerformanceCollector)(coll.asOutParam()));
250 CHECK_ERROR_BREAK(coll, SetupMetrics(ComSafeArrayAsInParam(metricNames),
251 ComSafeArrayAsInParam(metricObjects),
252 5 /* 5 seconds */,
253 1 /* One sample is enough */,
254 ComSafeArrayAsOutParam(metricAffected)));
255#endif
256 if (FAILED(rc))
257 vrc = VERR_COM_IPRT_ERROR; /* @todo Find better rc! */
258
259 } while (0);
260
261 return vrc;
262}
263
264/**
265 * Does the actual ballooning and assumes the machine is
266 * capable and ready for ballooning.
267 *
268 * @return IPRT status code.
269 * @param strUuid UUID of the specified machine.
270 * @param pMachine Pointer to the machine's internal structure.
271 */
272static int balloonMachineUpdate(const Bstr &strUuid, PVBOXWATCHDOG_MACHINE pMachine)
273{
274 AssertPtrReturn(pMachine, VERR_INVALID_POINTER);
275
276 /*
277 * Get metrics collected at this point.
278 */
279 LONG lMemFree, lBalloonCur;
280 int vrc = getMetric(pMachine, L"Guest/RAM/Usage/Free", &lMemFree);
281 if (RT_SUCCESS(vrc))
282 vrc = getMetric(pMachine, L"Guest/RAM/Usage/Balloon", &lBalloonCur);
283
284 if (RT_SUCCESS(vrc))
285 {
286 /* If guest statistics are not up and running yet, skip this iteration
287 * and try next time. */
288 if (lMemFree <= 0)
289 {
290#ifdef DEBUG
291 serviceLogVerbose(("%ls: No metrics available yet!\n", strUuid.raw()));
292#endif
293 return VINF_SUCCESS;
294 }
295
296 lMemFree /= 1024;
297 lBalloonCur /= 1024;
298
299 PVBOXWATCHDOG_BALLOONCTRL_PAYLOAD pData = (PVBOXWATCHDOG_BALLOONCTRL_PAYLOAD)
300 payloadFrom(pMachine, VBOX_MOD_BALLOONING_NAME);
301 AssertPtr(pData);
302
303 serviceLogVerbose(("%ls: Balloon: %ld, Free mem: %ld, Max ballon: %ld\n",
304 strUuid.raw(),
305 lBalloonCur, lMemFree, pData->ulBalloonSizeMax));
306
307 /* Calculate current balloon delta. */
308 long lDelta = balloonGetDelta(lBalloonCur, lMemFree, pData->ulBalloonSizeMax);
309 if (lDelta) /* Only do ballooning if there's really smth. to change ... */
310 {
311 lBalloonCur = lBalloonCur + lDelta;
312 Assert(lBalloonCur > 0);
313
314 serviceLog("%ls: %s balloon by %ld to %ld ...\n",
315 strUuid.raw(),
316 lDelta > 0 ? "Inflating" : "Deflating", lDelta, lBalloonCur);
317
318 if (!g_fDryrun)
319 {
320 /* Open a session for the VM. */
321 HRESULT rc;
322 CHECK_ERROR(pMachine->machine, LockMachine(g_pSession, LockType_Shared));
323
324 do
325 {
326 /* Get the associated console. */
327 ComPtr<IConsole> console;
328 CHECK_ERROR_BREAK(g_pSession, COMGETTER(Console)(console.asOutParam()));
329
330 ComPtr <IGuest> guest;
331 rc = console->COMGETTER(Guest)(guest.asOutParam());
332 if (SUCCEEDED(rc))
333 CHECK_ERROR_BREAK(guest, COMSETTER(MemoryBalloonSize)(lBalloonCur));
334 else
335 serviceLog("Error: Unable to set new balloon size %ld for machine \"%ls\", rc=%Rhrc",
336 lBalloonCur, strUuid.raw(), rc);
337 if (FAILED(rc))
338 vrc = VERR_COM_IPRT_ERROR;
339 } while (0);
340
341 /* Unlock the machine again. */
342 g_pSession->UnlockMachine();
343 }
344 }
345 }
346 else
347 serviceLog("Error: Unable to retrieve metrics for machine \"%ls\", rc=%Rrc",
348 strUuid.raw(), vrc);
349 return vrc;
350}
351
352/* Callbacks. */
353static DECLCALLBACK(int) VBoxModBallooningPreInit(void)
354{
355 return VINF_SUCCESS;
356}
357
358static DECLCALLBACK(int) VBoxModBallooningOption(int argc, char **argv)
359{
360 if (!argc) /* Take a shortcut. */
361 return -1;
362
363 AssertPtrReturn(argv, VERR_INVALID_PARAMETER);
364
365 RTGETOPTSTATE GetState;
366 int rc = RTGetOptInit(&GetState, argc, argv,
367 g_aBalloonOpts, RT_ELEMENTS(g_aBalloonOpts),
368 0 /* First */, 0 /*fFlags*/);
369 if (RT_FAILURE(rc))
370 return rc;
371
372 rc = 0; /* Set default parsing result to valid. */
373
374 int c;
375 RTGETOPTUNION ValueUnion;
376 while ((c = RTGetOpt(&GetState, &ValueUnion)))
377 {
378 switch (c)
379 {
380 case GETOPTDEF_BALLOONCTRL_BALLOONDEC:
381 g_ulMemoryBalloonDecrementMB = ValueUnion.u32;
382 break;
383
384 case GETOPTDEF_BALLOONCTRL_BALLOOINC:
385 g_ulMemoryBalloonIncrementMB = ValueUnion.u32;
386 break;
387
388 case GETOPTDEF_BALLOONCTRL_GROUPS:
389 /** @todo Add ballooning groups cmd line arg. */
390 break;
391
392 case GETOPTDEF_BALLOONCTRL_BALLOONLOWERLIMIT:
393 g_ulMemoryBalloonLowerLimitMB = ValueUnion.u32;
394 break;
395
396 case GETOPTDEF_BALLOONCTRL_BALLOONMAX:
397 g_ulMemoryBalloonMaxMB = ValueUnion.u32;
398 break;
399
400 /** @todo Add (legacy) "--inverval" setting! */
401 /** @todo This option is a common moudle option! Put
402 * this into a utility function! */
403 case GETOPTDEF_BALLOONCTRL_TIMEOUTMS:
404 g_ulMemoryBalloonTimeoutMS = ValueUnion.u32;
405 if (g_ulMemoryBalloonTimeoutMS < 500)
406 g_ulMemoryBalloonTimeoutMS = 500;
407 break;
408
409 default:
410 rc = -1; /* We don't handle this option, skip. */
411 break;
412 }
413 }
414
415 return rc;
416}
417
418static DECLCALLBACK(int) VBoxModBallooningInit(void)
419{
420 return VINF_SUCCESS; /* Nothing to do here right now. */
421}
422
423static DECLCALLBACK(int) VBoxModBallooningMain(void)
424{
425 static uint64_t uLast = UINT64_MAX;
426 uint64_t uNow = RTTimeProgramMilliTS() / g_ulMemoryBalloonTimeoutMS;
427 if (uLast == uNow)
428 return VINF_SUCCESS;
429 uLast = uNow;
430
431 int rc = VINF_SUCCESS;
432
433 /** @todo Provide API for enumerating/working w/ machines inside a module! */
434 mapVMIter it = g_mapVM.begin();
435 while (it != g_mapVM.end())
436 {
437 MachineState_T state = getMachineState(&it->second);
438
439 /* Our actual ballooning criteria. */
440 if ( balloonIsPossible(state)
441 && balloonIsRequired(&it->second))
442 {
443 rc = balloonMachineUpdate(it->first /* UUID */,
444 &it->second /* Machine */);
445 AssertRC(rc);
446 }
447 if (RT_FAILURE(rc))
448 break;
449
450 it++;
451 }
452
453 return rc;
454}
455
456static DECLCALLBACK(int) VBoxModBallooningStop(void)
457{
458 return VINF_SUCCESS;
459}
460
461static DECLCALLBACK(void) VBoxModBallooningTerm(void)
462{
463}
464
465static DECLCALLBACK(int) VBoxModBallooningOnMachineRegistered(const Bstr &strUuid)
466{
467 PVBOXWATCHDOG_MACHINE pMachine = getMachine(strUuid);
468 AssertPtrReturn(pMachine, VERR_INVALID_PARAMETER);
469
470 PVBOXWATCHDOG_BALLOONCTRL_PAYLOAD pData;
471 int rc = payloadAlloc(pMachine, VBOX_MOD_BALLOONING_NAME,
472 sizeof(VBOXWATCHDOG_BALLOONCTRL_PAYLOAD), (void**)&pData);
473 if (RT_SUCCESS(rc))
474 rc = balloonMachineUpdate(strUuid, pMachine);
475
476 return rc;
477}
478
479static DECLCALLBACK(int) VBoxModBallooningOnMachineUnregistered(const Bstr &strUuid)
480{
481 PVBOXWATCHDOG_MACHINE pMachine = getMachine(strUuid);
482 AssertPtrReturn(pMachine, VERR_INVALID_PARAMETER);
483
484 payloadFree(pMachine, VBOX_MOD_BALLOONING_NAME);
485
486 return VINF_SUCCESS;
487}
488
489static DECLCALLBACK(int) VBoxModBallooningOnMachineStateChanged(const Bstr &strUuid,
490 MachineState_T enmState)
491{
492 PVBOXWATCHDOG_MACHINE pMachine = getMachine(strUuid);
493 /* Note: The machine state will change to "setting up" when machine gets deleted,
494 * so pMachine might be NULL here. */
495 if (!pMachine)
496 return VINF_SUCCESS;
497
498 return balloonMachineUpdate(strUuid, pMachine);
499}
500
501static DECLCALLBACK(int) VBoxModBallooningOnServiceStateChanged(bool fAvailable)
502{
503 return VINF_SUCCESS;
504}
505
506/**
507 * The 'balloonctrl' module description.
508 */
509VBOXMODULE g_ModBallooning =
510{
511 /* pszName. */
512 VBOX_MOD_BALLOONING_NAME,
513 /* pszDescription. */
514 "Memory Ballooning Control",
515 /* pszDepends. */
516 NULL,
517 /* uPriority. */
518 0 /* Not used */,
519 /* pszUsage. */
520 " [--balloon-dec=<MB>] [--balloon-groups=<string>] [--balloon-inc=<MB>]\n"
521 " [--balloon-interval=<ms>] [--balloon-lower-limit=<MB>]\n"
522 " [--balloon-max=<MB>]\n",
523 /* pszOptions. */
524 "--balloon-dec Sets the ballooning decrement in MB (128 MB).\n"
525 "--balloon-groups Sets the VM groups for ballooning (all).\n"
526 "--balloon-inc Sets the ballooning increment in MB (256 MB).\n"
527 "--balloon-interval Sets the check interval in ms (30 seconds).\n"
528 "--balloon-lower-limit Sets the ballooning lower limit in MB (64 MB).\n"
529 "--balloon-max Sets the balloon maximum limit in MB (0 MB).\n"
530 " Specifying \"0\" means disabled ballooning.\n"
531#if 1
532 /* (Legacy) note. */
533 "Set \"VBoxInternal/Guest/BalloonSizeMax\" for a per-VM maximum ballooning size.\n"
534#endif
535 ,
536 /* methods. */
537 VBoxModBallooningPreInit,
538 VBoxModBallooningOption,
539 VBoxModBallooningInit,
540 VBoxModBallooningMain,
541 VBoxModBallooningStop,
542 VBoxModBallooningTerm,
543 /* callbacks. */
544 VBoxModBallooningOnMachineRegistered,
545 VBoxModBallooningOnMachineUnregistered,
546 VBoxModBallooningOnMachineStateChanged,
547 VBoxModBallooningOnServiceStateChanged
548};
549
Note: See TracBrowser for help on using the repository browser.

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