VirtualBox

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

Last change on this file since 36696 was 36696, checked in by vboxsync, 14 years ago

VBoxBalloonCtrl: Update.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 39.1 KB
Line 
1/* $Id: VBoxBalloonCtrl.cpp 36696 2011-04-18 08:18:22Z vboxsync $ */
2/** @file
3 * VBoxBalloonCtrl - VirtualBox Ballooning Control Service.
4 */
5
6/*
7 * Copyright (C) 2011 Oracle Corporation
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
18
19/*******************************************************************************
20* Header Files *
21*******************************************************************************/
22#ifndef VBOX_ONLY_DOCS
23# include <VBox/com/com.h>
24# include <VBox/com/string.h>
25# include <VBox/com/Guid.h>
26# include <VBox/com/array.h>
27# include <VBox/com/ErrorInfo.h>
28# include <VBox/com/errorprint.h>
29
30# include <VBox/com/EventQueue.h>
31# include <VBox/com/listeners.h>
32# include <VBox/com/VirtualBox.h>
33#endif /* !VBOX_ONLY_DOCS */
34
35#include <VBox/err.h>
36#include <VBox/log.h>
37#include <VBox/version.h>
38
39#include <package-generated.h>
40
41#include <iprt/asm.h>
42#include <iprt/buildconfig.h>
43#include <iprt/critsect.h>
44#include <iprt/getopt.h>
45#include <iprt/initterm.h>
46#include <iprt/path.h>
47#include <iprt/process.h>
48#include <iprt/semaphore.h>
49#include <iprt/stream.h>
50#include <iprt/string.h>
51#include <iprt/system.h>
52
53#include <map>
54#include <string>
55#include <signal.h>
56
57#include "VBoxBalloonCtrl.h"
58
59using namespace com;
60
61/* When defined, use a global performance collector instead
62 * of a per-machine based one. */
63#define VBOX_BALLOONCTRL_GLOBAL_PERFCOL
64
65/** The semaphore we're blocking on. */
66static RTSEMEVENTMULTI g_BalloonControlEvent = NIL_RTSEMEVENTMULTI;
67
68/** The critical section for keep our stuff in sync. */
69static RTCRITSECT g_MapCritSect;
70
71/** Set by the signal handler. */
72static volatile bool g_fCanceled = false;
73
74static uint32_t g_cHistory = 10; /* Enable log rotation, 10 files. */
75static uint32_t g_uHistoryFileTime = RT_SEC_1WEEK; /* Max 1 week per file. */
76static uint64_t g_uHistoryFileSize = 100 * _1M; /* Max 100MB per file. */
77
78static bool g_fVerbose = false;
79
80#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined (RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
81/** Run in background. */
82static bool g_fDaemonize = false;
83#endif
84
85/**
86 * RTGetOpt-IDs for the command line.
87 */
88enum GETOPTDEF_BALLOONCTRL
89{
90 GETOPTDEF_BALLOONCTRL_BALLOOINC = 1000,
91 GETOPTDEF_BALLOONCTRL_BALLOONDEC,
92 GETOPTDEF_BALLOONCTRL_BALLOONLOWERLIMIT,
93 GETOPTDEF_BALLOONCTRL_BALLOONMAX
94};
95
96/**
97 * Command line arguments.
98 */
99static const RTGETOPTDEF g_aOptions[] = {
100#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined (RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
101 { "--background", 'b', RTGETOPT_REQ_NOTHING },
102#endif
103 /** For displayHelp(). */
104 { "--help", 'h', RTGETOPT_REQ_NOTHING },
105 /** Sets g_ulTimeoutMS. */
106 { "--interval", 'i', RTGETOPT_REQ_INT32 },
107 /** Sets g_ulMemoryBalloonIncrementMB. */
108 { "--balloon-inc", GETOPTDEF_BALLOONCTRL_BALLOOINC, RTGETOPT_REQ_INT32 },
109 /** Sets g_ulMemoryBalloonDecrementMB. */
110 { "--balloon-dec", GETOPTDEF_BALLOONCTRL_BALLOONDEC, RTGETOPT_REQ_INT32 },
111 { "--balloon-lower-limit", GETOPTDEF_BALLOONCTRL_BALLOONLOWERLIMIT, RTGETOPT_REQ_INT32 },
112 /** Global max. balloon limit. */
113 { "--balloon-max", GETOPTDEF_BALLOONCTRL_BALLOONMAX, RTGETOPT_REQ_INT32 },
114 { "--verbose", 'v', RTGETOPT_REQ_NOTHING }
115};
116
117unsigned long g_ulTimeoutMS = 30 * 1000; /* Default is 30 seconds timeout. */
118unsigned long g_ulMemoryBalloonIncrementMB = 256;
119unsigned long g_ulMemoryBalloonDecrementMB = 128;
120/** Global balloon limit is 0, so disabled. Can be overridden by a per-VM
121 * "VBoxInternal/Guest/BalloonSizeMax" value. */
122unsigned long g_ulMemoryBalloonMaxMB = 0;
123unsigned long g_ulLowerMemoryLimitMB = 64;
124
125/** Global weak references (for event handlers). */
126static IVirtualBox *g_pVirtualBox = NULL;
127static ISession *g_pSession = NULL;
128static EventQueue *g_pEventQ = NULL;
129#ifdef VBOX_BALLOONCTRL_GLOBAL_PERFCOL
130static IPerformanceCollector *g_pPerfCollector = NULL;
131#endif
132
133/** A machine's internal entry. */
134typedef struct VBOXBALLOONCTRL_MACHINE
135{
136 ComPtr<IMachine> machine;
137 unsigned long ulBalloonSizeMax;
138#ifndef VBOX_BALLOONCTRL_GLOBAL_PERFCOL
139 ComPtr<IPerformanceCollector> collector;
140#endif
141} VBOXBALLOONCTRL_MACHINE, *PVBOXBALLOONCTRL_MACHINE;
142typedef std::map<Bstr, VBOXBALLOONCTRL_MACHINE> mapVM;
143typedef std::map<Bstr, VBOXBALLOONCTRL_MACHINE>::iterator mapVMIter;
144typedef std::map<Bstr, VBOXBALLOONCTRL_MACHINE>::const_iterator mapVMIterConst;
145mapVM g_mapVM;
146
147/* Prototypes. */
148#define serviceLogVerbose(a) if (g_fVerbose) { serviceLog a; }
149void serviceLog(const char *pszFormat, ...);
150
151bool machineIsRunning(MachineState_T enmState);
152mapVMIter machineGetByUUID(const Bstr &strUUID);
153int machineAdd(const ComPtr<IMachine> &rptrMachine);
154int machineUpdate(const ComPtr<IMachine> &rptrMachine, MachineState_T enmState);
155int machineUpdate(mapVMIter it, MachineState_T enmState);
156void machineRemove(mapVMIter it);
157
158unsigned long balloonGetMaxSize(const ComPtr<IMachine> &rptrMachine);
159bool balloonIsRequired(mapVMIter it);
160int balloonUpdate(mapVMIterConst it);
161
162#ifdef RT_OS_WINDOWS
163/* Required for ATL. */
164static CComModule _Module;
165#endif
166
167/**
168 * Handler for global events.
169 */
170class VirtualBoxEventListener
171{
172 public:
173 VirtualBoxEventListener()
174 {
175 }
176
177 virtual ~VirtualBoxEventListener()
178 {
179 }
180
181 HRESULT init()
182 {
183 return S_OK;
184 }
185
186 void uninit()
187 {
188 }
189
190 STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent *aEvent)
191 {
192 switch (aType)
193 {
194 case VBoxEventType_OnMachineRegistered:
195 {
196 ComPtr<IMachineRegisteredEvent> pEvent = aEvent;
197 Assert(pEvent);
198
199 Bstr uuid;
200 BOOL fRegistered;
201 HRESULT hr = pEvent->COMGETTER(Registered)(&fRegistered);
202 if (SUCCEEDED(hr))
203 hr = pEvent->COMGETTER(MachineId)(uuid.asOutParam());
204
205 if (SUCCEEDED(hr))
206 {
207 int rc = RTCritSectEnter(&g_MapCritSect);
208 if (RT_SUCCESS(rc))
209 {
210 if (fRegistered)
211 {
212 ComPtr <IMachine> machine;
213 hr = g_pVirtualBox->FindMachine(uuid.raw(), machine.asOutParam());
214 if (SUCCEEDED(hr))
215 rc = machineAdd(machine);
216 else
217 rc = VERR_NOT_FOUND;
218 }
219 else
220 machineRemove(machineGetByUUID(uuid));
221 AssertRC(rc);
222
223 int rc2 = RTCritSectLeave(&g_MapCritSect);
224 if (RT_SUCCESS(rc))
225 rc = rc2;
226 }
227 }
228 break;
229 }
230
231 case VBoxEventType_OnMachineStateChanged:
232 {
233 ComPtr<IMachineStateChangedEvent> pEvent = aEvent;
234 Assert(pEvent);
235
236 MachineState_T machineState;
237 Bstr uuid;
238
239 HRESULT hr = pEvent->COMGETTER(State)(&machineState);
240 if (SUCCEEDED(hr))
241 hr = pEvent->COMGETTER(MachineId)(uuid.asOutParam());
242
243 if (SUCCEEDED(hr))
244 {
245 int rc = RTCritSectEnter(&g_MapCritSect);
246 if (RT_SUCCESS(rc))
247 {
248 mapVMIter it = machineGetByUUID(uuid);
249 if (it == g_mapVM.end())
250 {
251 /* Use the machine object to figure out if we
252 * need to do something. */
253 ComPtr <IMachine> machine;
254 hr = g_pVirtualBox->FindMachine(uuid.raw(), machine.asOutParam());
255 if (SUCCEEDED(hr))
256 rc = machineUpdate(machine, machineState);
257 }
258 else /* Update an existing machine. */
259 rc = machineUpdate(it, machineState);
260 AssertRC(rc);
261
262 int rc2 = RTCritSectLeave(&g_MapCritSect);
263 if (RT_SUCCESS(rc))
264 rc = rc2;
265 }
266 }
267 break;
268 }
269
270 default:
271 /* Not handled event, just skip it. */
272 break;
273 }
274
275 return S_OK;
276 }
277
278 private:
279};
280typedef ListenerImpl<VirtualBoxEventListener> VirtualBoxEventListenerImpl;
281VBOX_LISTENER_DECLARE(VirtualBoxEventListenerImpl)
282
283
284/**
285 * Signal handler that sets g_fGuestCtrlCanceled.
286 *
287 * This can be executed on any thread in the process, on Windows it may even be
288 * a thread dedicated to delivering this signal. Do not doing anything
289 * unnecessary here.
290 */
291static void signalHandler(int iSignal)
292{
293 NOREF(iSignal);
294 ASMAtomicWriteBool(&g_fCanceled, true);
295
296 if (g_BalloonControlEvent != NIL_RTSEMEVENTMULTI)
297 {
298 int rc = RTSemEventMultiSignal(g_BalloonControlEvent);
299 if (RT_FAILURE(rc))
300 serviceLog("Error: RTSemEventMultiSignal failed with rc=%Rrc\n");
301 }
302}
303
304/**
305 * Installs a custom signal handler to get notified
306 * whenever the user wants to intercept the program.
307 */
308static void signalHandlerInstall()
309{
310 signal(SIGINT, signalHandler);
311#ifdef SIGBREAK
312 signal(SIGBREAK, signalHandler);
313#endif
314}
315
316/**
317 * Uninstalls a previously installed signal handler.
318 */
319static void signalHandlerUninstall()
320{
321 signal(SIGINT, SIG_DFL);
322#ifdef SIGBREAK
323 signal(SIGBREAK, SIG_DFL);
324#endif
325}
326
327long getlBalloonDelta(unsigned long ulCurrentDesktopBalloonSize, unsigned long ulDesktopFreeMemory, unsigned long ulMaxBalloonSize)
328{
329 if (ulCurrentDesktopBalloonSize > ulMaxBalloonSize)
330 return (ulMaxBalloonSize - ulCurrentDesktopBalloonSize);
331
332 long lBalloonDelta = 0;
333 if (ulDesktopFreeMemory < g_ulLowerMemoryLimitMB)
334 {
335 /* Guest is running low on memory, we need to
336 * deflate the balloon. */
337 lBalloonDelta = (g_ulMemoryBalloonDecrementMB * -1);
338
339 /* Ensure that the delta will not return a negative
340 * balloon size. */
341 if ((long)ulCurrentDesktopBalloonSize + lBalloonDelta < 0)
342 lBalloonDelta = 0;
343 }
344 else if (ulMaxBalloonSize > ulCurrentDesktopBalloonSize)
345 {
346 /* We want to inflate the balloon if we have room. */
347 long lIncrement = g_ulMemoryBalloonIncrementMB;
348 while (lIncrement >= 16 && (ulDesktopFreeMemory - lIncrement) < g_ulLowerMemoryLimitMB)
349 {
350 lIncrement = (lIncrement / 2);
351 }
352
353 if ((ulDesktopFreeMemory - lIncrement) > g_ulLowerMemoryLimitMB)
354 lBalloonDelta = lIncrement;
355 }
356 if (ulCurrentDesktopBalloonSize + lBalloonDelta > ulMaxBalloonSize)
357 lBalloonDelta = (ulMaxBalloonSize - ulCurrentDesktopBalloonSize);
358 return lBalloonDelta;
359}
360
361/**
362 * Indicates whether a VM is up and running (regardless of its running
363 * state, could be paused as well).
364 *
365 * @return bool Flag indicating whether the VM is running or not.
366 * @param enmState The VM's machine state to judge whether it's running or not.
367 */
368bool machineIsRunning(MachineState_T enmState)
369{
370 switch (enmState)
371 {
372 case MachineState_Running:
373 case MachineState_Teleporting:
374 case MachineState_LiveSnapshotting:
375 case MachineState_Paused:
376 case MachineState_TeleportingPausedVM:
377 return true;
378 default:
379 break;
380 }
381 return false;
382}
383
384mapVMIter machineGetByUUID(const Bstr &strUUID)
385{
386 return g_mapVM.find(strUUID);
387}
388
389int machineAdd(const ComPtr<IMachine> &rptrMachine)
390{
391 HRESULT rc;
392
393 do
394 {
395 VBOXBALLOONCTRL_MACHINE m;
396 m.machine = rptrMachine;
397
398 /*
399 * Setup metrics.
400 */
401 com::SafeArray<BSTR> metricNames(1);
402 com::SafeIfaceArray<IUnknown> metricObjects(1);
403 com::SafeIfaceArray<IPerformanceMetric> metricAffected;
404
405 Bstr strMetricNames(L"Guest/RAM*");
406 strMetricNames.cloneTo(&metricNames[0]);
407
408 m.machine.queryInterfaceTo(&metricObjects[0]);
409
410#ifdef VBOX_BALLOONCTRL_GLOBAL_PERFCOL
411 CHECK_ERROR_BREAK(g_pPerfCollector, SetupMetrics(ComSafeArrayAsInParam(metricNames),
412 ComSafeArrayAsInParam(metricObjects),
413 5 /* 5 seconds */,
414 1 /* One sample is enough */,
415 ComSafeArrayAsOutParam(metricAffected)));
416#else
417 CHECK_ERROR_BREAK(g_pVirtualBox, COMGETTER(PerformanceCollector)(m.collector.asOutParam()));
418 CHECK_ERROR_BREAK(m.collector, SetupMetrics(ComSafeArrayAsInParam(metricNames),
419 ComSafeArrayAsInParam(metricObjects),
420 5 /* 5 seconds */,
421 1 /* One sample is enough */,
422 ComSafeArrayAsOutParam(metricAffected)));
423#endif
424 /*
425 * Add machine to map.
426 */
427 Bstr strUUID;
428 CHECK_ERROR_BREAK(rptrMachine, COMGETTER(Id)(strUUID.asOutParam()));
429
430 if (!metricAffected.size())
431 serviceLogVerbose(("%s: No metrics available yet!\n", Utf8Str(strUUID).c_str()));
432
433 mapVMIter it = g_mapVM.find(strUUID);
434 Assert(it == g_mapVM.end());
435
436 g_mapVM.insert(std::make_pair(strUUID, m));
437
438 serviceLogVerbose(("Added machine \"%s\"\n", Utf8Str(strUUID).c_str()));
439
440 } while (0);
441
442 return SUCCEEDED(rc) ? VINF_SUCCESS : VERR_COM_IPRT_ERROR; /* @todo Find a better error! */
443}
444
445void machineRemove(mapVMIter it)
446{
447 if (it != g_mapVM.end())
448 {
449 /* Must log before erasing the iterator because of the UUID ref! */
450 serviceLogVerbose(("Removing machine \"%s\"\n", Utf8Str(it->first).c_str()));
451
452 /*
453 * Remove machine from map.
454 */
455 g_mapVM.erase(it);
456 }
457}
458
459int machineUpdate(const ComPtr<IMachine> &rptrMachine, MachineState_T enmState)
460{
461 if ( !balloonGetMaxSize(rptrMachine)
462 || !machineIsRunning(enmState))
463 {
464 return VINF_SUCCESS; /* Machine is not required to be added. */
465 }
466 return machineAdd(rptrMachine);
467}
468
469int machineUpdate(mapVMIter it, MachineState_T enmState)
470{
471 Assert(it != g_mapVM.end());
472
473 if ( !balloonIsRequired(it)
474 || !machineIsRunning(enmState))
475 {
476 machineRemove(it);
477 return VINF_SUCCESS;
478 }
479
480 return balloonUpdate(it);
481}
482
483int getMetric(mapVMIterConst it, const Bstr& strName, LONG *pulData)
484{
485 AssertPtrReturn(pulData, VERR_INVALID_PARAMETER);
486
487 /* Input. */
488 com::SafeArray<BSTR> metricNames(1);
489 com::SafeIfaceArray<IUnknown> metricObjects(1);
490 it->second.machine.queryInterfaceTo(&metricObjects[0]);
491
492 /* Output. */
493 com::SafeArray<BSTR> retNames;
494 com::SafeIfaceArray<IUnknown> retObjects;
495 com::SafeArray<BSTR> retUnits;
496 com::SafeArray<ULONG> retScales;
497 com::SafeArray<ULONG> retSequenceNumbers;
498 com::SafeArray<ULONG> retIndices;
499 com::SafeArray<ULONG> retLengths;
500 com::SafeArray<LONG> retData;
501
502 /* Query current memory free. */
503 strName.cloneTo(&metricNames[0]);
504#ifdef VBOX_BALLOONCTRL_GLOBAL_PERFCOL
505 HRESULT hrc = g_pPerfCollector->QueryMetricsData(
506#else
507 HRESULT hrc = it->second.collector->QueryMetricsData(
508#endif
509 ComSafeArrayAsInParam(metricNames),
510 ComSafeArrayAsInParam(metricObjects),
511 ComSafeArrayAsOutParam(retNames),
512 ComSafeArrayAsOutParam(retObjects),
513 ComSafeArrayAsOutParam(retUnits),
514 ComSafeArrayAsOutParam(retScales),
515 ComSafeArrayAsOutParam(retSequenceNumbers),
516 ComSafeArrayAsOutParam(retIndices),
517 ComSafeArrayAsOutParam(retLengths),
518 ComSafeArrayAsOutParam(retData));
519#if 0
520 /* Useful for metrics debugging. */
521 for (unsigned j = 0; j < retNames.size(); j++)
522 {
523 Bstr metricUnit(retUnits[j]);
524 Bstr metricName(retNames[j]);
525 RTPrintf("%-20ls ", metricName.raw());
526 const char *separator = "";
527 for (unsigned k = 0; k < retLengths[j]; k++)
528 {
529 if (retScales[j] == 1)
530 RTPrintf("%s%d %ls", separator, retData[retIndices[j] + k], metricUnit.raw());
531 else
532 RTPrintf("%s%d.%02d%ls", separator, retData[retIndices[j] + k] / retScales[j],
533 (retData[retIndices[j] + k] * 100 / retScales[j]) % 100, metricUnit.raw());
534 separator = ", ";
535 }
536 RTPrintf("\n");
537 }
538#endif
539
540 if (SUCCEEDED(hrc))
541 *pulData = retData.size() ? retData[retIndices[0]] : 0;
542
543 return SUCCEEDED(hrc) ? VINF_SUCCESS : VINF_NOT_SUPPORTED;
544}
545
546unsigned long balloonGetMaxSize(const ComPtr<IMachine> &rptrMachine)
547{
548 /*
549 * Try to retrieve the balloon maximum size via the following order:
550 * - command line parameter ("--balloon-max")
551 * - per-VM parameter ("VBoxInternal/Guest/BalloonSizeMax")
552 * - global parameter ("VBoxInternal/Guest/BalloonSizeMax")
553 */
554 unsigned long ulBalloonMax = g_ulMemoryBalloonMaxMB; /* Use global limit as default. */
555 if (!ulBalloonMax) /* Not set by command line? */
556 {
557 /* Try per-VM approach. */
558 Bstr strValue;
559 HRESULT rc = rptrMachine->GetExtraData(Bstr("VBoxInternal/Guest/BalloonSizeMax").raw(),
560 strValue.asOutParam());
561 if ( SUCCEEDED(rc)
562 && !strValue.isEmpty())
563 {
564 ulBalloonMax = Utf8Str(strValue).toUInt32();
565 }
566 }
567 if (!ulBalloonMax) /* Still not set by per-VM value? */
568 {
569 /* Try global approach. */
570 Bstr strValue;
571 HRESULT rc = g_pVirtualBox->GetExtraData(Bstr("VBoxInternal/Guest/BalloonSizeMax").raw(),
572 strValue.asOutParam());
573 if ( SUCCEEDED(rc)
574 && !strValue.isEmpty())
575 {
576 ulBalloonMax = Utf8Str(strValue).toUInt32();
577 }
578 }
579 return ulBalloonMax;
580}
581
582bool balloonIsRequired(mapVMIter it)
583{
584 /* Only do ballooning if we have a maximum balloon size set. */
585 it->second.ulBalloonSizeMax = balloonGetMaxSize(it->second.machine);
586
587 return it->second.ulBalloonSizeMax ? true : false;
588}
589
590/* Does the actual ballooning and assumes the machine is
591 * capable and ready for ballooning. */
592int balloonUpdate(mapVMIterConst it)
593{
594 /*
595 * Get metrics collected at this point.
596 */
597 LONG lMemFree, lBalloonCur;
598 int vrc = getMetric(it, L"Guest/RAM/Usage/Free", &lMemFree);
599 if (RT_SUCCESS(vrc))
600 vrc = getMetric(it, L"Guest/RAM/Usage/Balloon", &lBalloonCur);
601
602 if (RT_SUCCESS(vrc))
603 {
604 /* If guest statistics are not up and running yet, skip this iteration
605 * and try next time. */
606 if (lMemFree <= 0)
607 {
608#ifdef DEBUG
609 serviceLogVerbose(("%s: No metrics available yet!\n", Utf8Str(it->first).c_str()));
610#endif
611 return VINF_SUCCESS;
612 }
613
614 lMemFree /= 1024;
615 lBalloonCur /= 1024;
616
617 serviceLogVerbose(("%s: Balloon: %ld, Free mem: %ld, Max ballon: %ld\n",
618 Utf8Str(it->first).c_str(),
619 lBalloonCur, lMemFree, it->second.ulBalloonSizeMax));
620
621 /* Calculate current balloon delta. */
622 long lDelta = getlBalloonDelta(lBalloonCur, lMemFree, it->second.ulBalloonSizeMax);
623 if (lDelta) /* Only do ballooning if there's really smth. to change ... */
624 {
625 lBalloonCur = lBalloonCur + lDelta;
626 Assert(lBalloonCur > 0);
627
628 serviceLog("%s: %s balloon by %ld to %ld ...\n",
629 Utf8Str(it->first).c_str(),
630 lDelta > 0 ? "Inflating" : "Deflating", lDelta, lBalloonCur);
631
632 HRESULT rc;
633
634 /* Open a session for the VM. */
635 CHECK_ERROR(it->second.machine, LockMachine(g_pSession, LockType_Shared));
636
637 do
638 {
639 /* Get the associated console. */
640 ComPtr<IConsole> console;
641 CHECK_ERROR_BREAK(g_pSession, COMGETTER(Console)(console.asOutParam()));
642
643 ComPtr <IGuest> guest;
644 rc = console->COMGETTER(Guest)(guest.asOutParam());
645 if (SUCCEEDED(rc))
646 CHECK_ERROR_BREAK(guest, COMSETTER(MemoryBalloonSize)(lBalloonCur));
647 else
648 serviceLog("Error: Unable to set new balloon size %ld for machine \"%s\", rc=%Rhrc",
649 lBalloonCur, Utf8Str(it->first).c_str(), rc);
650 } while (0);
651
652 /* Unlock the machine again. */
653 g_pSession->UnlockMachine();
654 }
655 }
656 else
657 serviceLog("Error: Unable to retrieve metrics for machine \"%s\", rc=%Rrc",
658 Utf8Str(it->first).c_str(), vrc);
659 return vrc;
660}
661
662void vmListDestroy()
663{
664 serviceLogVerbose(("Destroying VM list ...\n"));
665
666 int rc = RTCritSectEnter(&g_MapCritSect);
667 if (RT_SUCCESS(rc))
668 {
669 mapVMIter it = g_mapVM.begin();
670 while (it != g_mapVM.end())
671 {
672#ifndef VBOX_BALLOONCTRL_GLOBAL_PERFCOL
673 it->second.collector.setNull();
674#endif
675 it->second.machine.setNull();
676 it++;
677 }
678
679 rc = RTCritSectLeave(&g_MapCritSect);
680 }
681 AssertRC(rc);
682}
683
684int vmListBuild()
685{
686 vmListDestroy();
687
688 serviceLogVerbose(("Building VM list ...\n"));
689
690 int rc = RTCritSectEnter(&g_MapCritSect);
691 if (RT_SUCCESS(rc))
692 {
693 /*
694 * Make sure the list is empty.
695 */
696 g_mapVM.clear();
697
698 /*
699 * Get the list of all _running_ VMs
700 */
701 com::SafeIfaceArray<IMachine> machines;
702 HRESULT hrc = g_pVirtualBox->COMGETTER(Machines)(ComSafeArrayAsOutParam(machines));
703 if (SUCCEEDED(hrc))
704 {
705 /*
706 * Iterate through the collection
707 */
708 for (size_t i = 0; i < machines.size(); ++i)
709 {
710 if (machines[i])
711 {
712 MachineState_T machineState;
713 hrc = machines[i]->COMGETTER(State)(&machineState);
714 if ( SUCCEEDED(hrc)
715 && machineIsRunning(machineState))
716 {
717 rc = machineAdd(machines[i]);
718 if (RT_FAILURE(rc))
719 break;
720 }
721 }
722 }
723 }
724
725 int rc2 = RTCritSectLeave(&g_MapCritSect);
726 if (RT_SUCCESS(rc))
727 rc = rc2;
728 }
729 return rc;
730}
731
732int balloonCtrlCheck()
733{
734 int rc = RTCritSectEnter(&g_MapCritSect);
735 if (RT_SUCCESS(rc))
736 {
737 mapVMIter it = g_mapVM.begin();
738 while (it != g_mapVM.end())
739 {
740 MachineState_T machineState;
741 HRESULT hrc = it->second.machine->COMGETTER(State)(&machineState);
742 if (SUCCEEDED(hrc))
743 {
744 rc = machineUpdate(it, machineState);
745 if (RT_FAILURE(rc))
746 break;
747 }
748 it++;
749 }
750
751 int rc2 = RTCritSectLeave(&g_MapCritSect);
752 if (RT_SUCCESS(rc))
753 rc = rc2;
754 }
755
756 return rc;
757}
758
759RTEXITCODE balloonCtrlMain(HandlerArg *a)
760{
761 HRESULT rc = S_OK;
762
763 do
764 {
765 /* Initialize global weak references. */
766 g_pVirtualBox = a->virtualBox;
767 g_pSession = a->session;
768 g_pEventQ = com::EventQueue::getMainEventQueue();
769
770 RTCritSectInit(&g_MapCritSect);
771
772 int vrc = RTSemEventMultiCreate(&g_BalloonControlEvent);
773 AssertRCReturn(vrc, RTEXITCODE_FAILURE);
774
775 /*
776 * Setup the global event listener.
777 */
778 ComPtr<IEventSource> es;
779 CHECK_ERROR_BREAK(a->virtualBox, COMGETTER(EventSource)(es.asOutParam()));
780
781 ComObjPtr<VirtualBoxEventListenerImpl> vboxListenerImpl;
782 vboxListenerImpl.createObject();
783 vboxListenerImpl->init(new VirtualBoxEventListener());
784
785 com::SafeArray <VBoxEventType_T> eventTypes(1);
786 eventTypes.push_back(VBoxEventType_OnMachineRegistered);
787 eventTypes.push_back(VBoxEventType_OnMachineStateChanged);
788
789 ComPtr<IEventListener> vboxListener;
790 vboxListener = vboxListenerImpl;
791
792 CHECK_ERROR_BREAK(es, RegisterListener(vboxListener, ComSafeArrayAsInParam(eventTypes), true /* Active listener */));
793
794 /*
795 * Setup metrics.
796 */
797#ifdef VBOX_BALLOONCTRL_GLOBAL_PERFCOL
798 ComPtr<IPerformanceCollector> perfCol;
799 CHECK_ERROR_BREAK(g_pVirtualBox, COMGETTER(PerformanceCollector)(perfCol.asOutParam()));
800 g_pPerfCollector = perfCol;
801#endif
802
803 /*
804 * Install signal handlers.
805 */
806 signal(SIGINT, signalHandler);
807 #ifdef SIGBREAK
808 signal(SIGBREAK, signalHandler);
809 #endif
810
811 /*
812 * Build up initial VM list.
813 */
814 vrc = vmListBuild();
815 if (FAILED(vrc)) break;
816
817 for (;;)
818 {
819 /*
820 * Do the actual work.
821 */
822 vrc = balloonCtrlCheck();
823 if (RT_FAILURE(vrc))
824 {
825 serviceLog("Error while doing ballooning control; rc=%Rrc\n", vrc);
826 break;
827 }
828
829 /*
830 * Process pending events, then wait for new ones. Note, this
831 * processes NULL events signalling event loop termination.
832 */
833 g_pEventQ->processEventQueue(500);
834
835 if (g_fCanceled)
836 {
837 serviceLog("Signal catched, exiting ...\n");
838 break;
839 }
840
841 vrc = RTSemEventMultiWait(g_BalloonControlEvent, g_ulTimeoutMS);
842 if (vrc != VERR_TIMEOUT && RT_FAILURE(vrc))
843 {
844 serviceLog("Error: RTSemEventMultiWait failed; rc=%Rrc\n", vrc);
845 break;
846 }
847 }
848
849 signal(SIGINT, SIG_DFL);
850 #ifdef SIGBREAK
851 signal(SIGBREAK, SIG_DFL);
852 #endif
853
854 /* VirtualBox callback unregistration. */
855 if (vboxListener)
856 {
857 if (!es.isNull())
858 CHECK_ERROR_BREAK(es, UnregisterListener(vboxListener));
859 vboxListener.setNull();
860 }
861
862 vmListDestroy();
863
864#ifdef VBOX_BALLOONCTRL_GLOBAL_PERFCOL
865 perfCol.setNull();
866#endif
867
868 RTCritSectDelete(&g_MapCritSect);
869 RTSemEventMultiDestroy(g_BalloonControlEvent);
870 g_BalloonControlEvent = NIL_RTSEMEVENTMULTI;
871
872 if (RT_FAILURE(vrc))
873 rc = VBOX_E_IPRT_ERROR;
874
875 } while (0);
876
877 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
878}
879
880void serviceLog(const char *pszFormat, ...)
881{
882 va_list args;
883 va_start(args, pszFormat);
884 char *psz = NULL;
885 RTStrAPrintfV(&psz, pszFormat, args);
886 va_end(args);
887
888 LogRel(("%s", psz));
889
890 RTStrFree(psz);
891}
892
893void logHeaderFooter(PRTLOGGER pLoggerRelease, RTLOGPHASE enmPhase, PFNRTLOGPHASEMSG pfnLog)
894{
895 /* Some introductory information. */
896 static RTTIMESPEC s_TimeSpec;
897 char szTmp[256];
898 if (enmPhase == RTLOGPHASE_BEGIN)
899 RTTimeNow(&s_TimeSpec);
900 RTTimeSpecToString(&s_TimeSpec, szTmp, sizeof(szTmp));
901
902 switch (enmPhase)
903 {
904 case RTLOGPHASE_BEGIN:
905 {
906 pfnLog(pLoggerRelease,
907 "VirtualBox Ballooning Control Service %s r%u %s (%s %s) release log\n"
908#ifdef VBOX_BLEEDING_EDGE
909 "EXPERIMENTAL build " VBOX_BLEEDING_EDGE "\n"
910#endif
911 "Log opened %s\n",
912 VBOX_VERSION_STRING, RTBldCfgRevision(), VBOX_BUILD_TARGET,
913 __DATE__, __TIME__, szTmp);
914
915 int vrc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szTmp, sizeof(szTmp));
916 if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
917 pfnLog(pLoggerRelease, "OS Product: %s\n", szTmp);
918 vrc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szTmp, sizeof(szTmp));
919 if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
920 pfnLog(pLoggerRelease, "OS Release: %s\n", szTmp);
921 vrc = RTSystemQueryOSInfo(RTSYSOSINFO_VERSION, szTmp, sizeof(szTmp));
922 if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
923 pfnLog(pLoggerRelease, "OS Version: %s\n", szTmp);
924 if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
925 pfnLog(pLoggerRelease, "OS Service Pack: %s\n", szTmp);
926
927 /* the package type is interesting for Linux distributions */
928 char szExecName[RTPATH_MAX];
929 char *pszExecName = RTProcGetExecutablePath(szExecName, sizeof(szExecName));
930 pfnLog(pLoggerRelease,
931 "Executable: %s\n"
932 "Process ID: %u\n"
933 "Package type: %s"
934#ifdef VBOX_OSE
935 " (OSE)"
936#endif
937 "\n",
938 pszExecName ? pszExecName : "unknown",
939 RTProcSelf(),
940 VBOX_PACKAGE_STRING);
941 break;
942 }
943
944 case RTLOGPHASE_PREROTATE:
945 pfnLog(pLoggerRelease, "Log rotated - Log started %s\n", szTmp);
946 break;
947
948 case RTLOGPHASE_POSTROTATE:
949 pfnLog(pLoggerRelease, "Log continuation - Log started %s\n", szTmp);
950 break;
951
952 case RTLOGPHASE_END:
953 pfnLog(pLoggerRelease, "End of log file - Log started %s\n", szTmp);
954 break;
955
956 default:
957 /* nothing */;
958 }
959}
960
961void displayHelp()
962{
963 RTStrmPrintf(g_pStdErr, "\nUsage: VBoxBalloonCtrl [options]\n\nSupported options (default values in brackets):\n");
964 for (unsigned i = 0;
965 i < RT_ELEMENTS(g_aOptions);
966 ++i)
967 {
968 std::string str(g_aOptions[i].pszLong);
969 if (g_aOptions[i].iShort < 1000) /* Don't show short options which are defined by an ID! */
970 {
971 str += ", -";
972 str += g_aOptions[i].iShort;
973 }
974 str += ":";
975
976 const char *pcszDescr = "";
977
978 switch (g_aOptions[i].iShort)
979 {
980 case 'h':
981 pcszDescr = "Print this help message and exit.";
982 break;
983
984 case 'i': /* Interval. */
985 pcszDescr = "Sets the check interval in ms (30 seconds).";
986 break;
987
988#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined (RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
989 case 'b':
990 pcszDescr = "Run in background (daemon mode).";
991 break;
992#endif
993 case GETOPTDEF_BALLOONCTRL_BALLOOINC:
994 pcszDescr = "Sets the ballooning increment in MB (256 MB).";
995 break;
996
997 case GETOPTDEF_BALLOONCTRL_BALLOONDEC:
998 pcszDescr = "Sets the ballooning decrement in MB (128 MB).";
999 break;
1000
1001 case GETOPTDEF_BALLOONCTRL_BALLOONLOWERLIMIT:
1002 pcszDescr = "Sets the ballooning lower limit in MB (64 MB).";
1003 break;
1004
1005 case GETOPTDEF_BALLOONCTRL_BALLOONMAX:
1006 pcszDescr = "Sets the balloon maximum limit in MB (0 MB).";
1007 break;
1008 }
1009
1010 RTStrmPrintf(g_pStdErr, "%-23s%s\n", str.c_str(), pcszDescr);
1011 }
1012
1013 RTStrmPrintf(g_pStdErr, "\nUse environment variable VBOXBALLOONCTRL_RELEASE_LOG for logging options.\n"
1014 "Set \"VBoxInternal/Guest/BalloonSizeMax\" for a per-VM maximum ballooning size.\n");
1015}
1016
1017int main(int argc, char *argv[])
1018{
1019 /*
1020 * Before we do anything, init the runtime without loading
1021 * the support driver.
1022 */
1023 int rc = RTR3Init();
1024 if (RT_FAILURE(rc))
1025 return RTMsgInitFailure(rc);
1026
1027 RTPrintf(VBOX_PRODUCT " Balloon Control " VBOX_VERSION_STRING "\n"
1028 "(C) " VBOX_C_YEAR " " VBOX_VENDOR "\n"
1029 "All rights reserved.\n\n");
1030
1031 /*
1032 * Parse the global options
1033 */
1034 int c;
1035 const char *pszLogFile = NULL;
1036 const char *pszPidFile = NULL;
1037 RTGETOPTUNION ValueUnion;
1038 RTGETOPTSTATE GetState;
1039 RTGetOptInit(&GetState, argc, argv, g_aOptions, RT_ELEMENTS(g_aOptions), 1, 0 /*fFlags*/);
1040 while ((c = RTGetOpt(&GetState, &ValueUnion)))
1041 {
1042 switch (c)
1043 {
1044 case 'h':
1045 displayHelp();
1046 return 0;
1047
1048 case 'i': /* Interval. */
1049 g_ulTimeoutMS = ValueUnion.u32;
1050 break;
1051
1052 case 'v':
1053 g_fVerbose = true;
1054 break;
1055
1056#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined (RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
1057 case 'b':
1058 g_fDaemonize = true;
1059 break;
1060#endif
1061 case 'V':
1062 RTPrintf("%sr%s\n", RTBldCfgVersion(), RTBldCfgRevisionStr());
1063 return 0;
1064
1065 case GETOPTDEF_BALLOONCTRL_BALLOOINC:
1066 g_ulMemoryBalloonIncrementMB = ValueUnion.u32;
1067 break;
1068
1069 case GETOPTDEF_BALLOONCTRL_BALLOONDEC:
1070 g_ulMemoryBalloonDecrementMB = ValueUnion.u32;
1071 break;
1072
1073 case GETOPTDEF_BALLOONCTRL_BALLOONLOWERLIMIT:
1074 g_ulLowerMemoryLimitMB = ValueUnion.u32;
1075 break;
1076
1077 case GETOPTDEF_BALLOONCTRL_BALLOONMAX:
1078 g_ulMemoryBalloonMaxMB = ValueUnion.u32;
1079 break;
1080
1081 default:
1082 rc = RTGetOptPrintError(c, &ValueUnion);
1083 return rc;
1084 }
1085 }
1086
1087 /* create release logger */
1088 PRTLOGGER pLoggerRelease;
1089 static const char * const s_apszGroups[] = VBOX_LOGGROUP_NAMES;
1090 RTUINT fFlags = RTLOGFLAGS_PREFIX_THREAD | RTLOGFLAGS_PREFIX_TIME_PROG;
1091#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
1092 fFlags |= RTLOGFLAGS_USECRLF;
1093#endif
1094 char szError[RTPATH_MAX + 128] = "";
1095 rc = RTLogCreateEx(&pLoggerRelease, fFlags, "all",
1096 "VBOXBALLOONCTRL_RELEASE_LOG", RT_ELEMENTS(s_apszGroups), s_apszGroups, RTLOGDEST_STDOUT,
1097 logHeaderFooter, g_cHistory, g_uHistoryFileSize, g_uHistoryFileTime,
1098 szError, sizeof(szError), pszLogFile);
1099 if (RT_SUCCESS(rc))
1100 {
1101 /* register this logger as the release logger */
1102 RTLogRelSetDefaultInstance(pLoggerRelease);
1103
1104 /* Explicitly flush the log in case of VBOXWEBSRV_RELEASE_LOG=buffered. */
1105 RTLogFlush(pLoggerRelease);
1106 }
1107 else
1108 return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to open release log (%s, %Rrc)", szError, rc);
1109
1110#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined (RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
1111 if (g_fDaemonize)
1112 {
1113 /* prepare release logging */
1114 char szLogFile[RTPATH_MAX];
1115
1116 rc = com::GetVBoxUserHomeDirectory(szLogFile, sizeof(szLogFile));
1117 if (RT_FAILURE(rc))
1118 return RTMsgErrorExit(RTEXITCODE_FAILURE, "could not get base directory for logging: %Rrc", rc);
1119 rc = RTPathAppend(szLogFile, sizeof(szLogFile), "vboxballoonctrl.log");
1120 if (RT_FAILURE(rc))
1121 return RTMsgErrorExit(RTEXITCODE_FAILURE, "could not construct logging path: %Rrc", rc);
1122
1123 rc = RTProcDaemonizeUsingFork(false /* fNoChDir */, false /* fNoClose */, pszPidFile);
1124 if (RT_FAILURE(rc))
1125 return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to daemonize, rc=%Rrc. exiting.", rc);
1126
1127 /* create release logger */
1128 PRTLOGGER pLoggerReleaseFile;
1129 static const char * const s_apszGroupsFile[] = VBOX_LOGGROUP_NAMES;
1130 RTUINT fFlagsFile = RTLOGFLAGS_PREFIX_THREAD | RTLOGFLAGS_PREFIX_TIME_PROG;
1131#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
1132 fFlagsFile |= RTLOGFLAGS_USECRLF;
1133#endif
1134 char szErrorFile[RTPATH_MAX + 128] = "";
1135 int vrc = RTLogCreateEx(&pLoggerReleaseFile, fFlagsFile, "all",
1136 "VBOXBALLOONCTRL_RELEASE_LOG", RT_ELEMENTS(s_apszGroupsFile), s_apszGroupsFile, RTLOGDEST_FILE,
1137 logHeaderFooter, g_cHistory, g_uHistoryFileSize, g_uHistoryFileTime,
1138 szErrorFile, sizeof(szErrorFile), szLogFile);
1139 if (RT_SUCCESS(vrc))
1140 {
1141 /* register this logger as the release logger */
1142 RTLogRelSetDefaultInstance(pLoggerReleaseFile);
1143
1144 /* Explicitly flush the log in case of VBOXBALLOONCTRL_RELEASE_LOG=buffered. */
1145 RTLogFlush(pLoggerReleaseFile);
1146 }
1147 else
1148 return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to open release log (%s, %Rrc)", szErrorFile, vrc);
1149 }
1150#endif
1151
1152#ifndef VBOX_ONLY_DOCS
1153 /*
1154 * Initialize COM.
1155 */
1156 using namespace com;
1157 HRESULT hrc = com::Initialize();
1158 if (FAILED(hrc))
1159 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to initialize COM!");
1160
1161 RTEXITCODE rcExit = RTEXITCODE_FAILURE;
1162 do
1163 {
1164 ComPtr<IVirtualBox> virtualBox;
1165 ComPtr<ISession> session;
1166
1167 hrc = virtualBox.createLocalObject(CLSID_VirtualBox);
1168 if (FAILED(hrc))
1169 RTMsgError("Failed to create the VirtualBox object!");
1170 else
1171 {
1172 hrc = session.createInprocObject(CLSID_Session);
1173 if (FAILED(hrc))
1174 RTMsgError("Failed to create a session object!");
1175 }
1176 if (FAILED(hrc))
1177 {
1178 com::ErrorInfo info;
1179 if (!info.isFullAvailable() && !info.isBasicAvailable())
1180 {
1181 com::GluePrintRCMessage(hrc);
1182 RTMsgError("Most likely, the VirtualBox COM server is not running or failed to start.");
1183 }
1184 else
1185 com::GluePrintErrorInfo(info);
1186 break;
1187 }
1188
1189 HandlerArg handlerArg = { 0, NULL, virtualBox, session };
1190 handlerArg.argc = argc;
1191 handlerArg.argv = argv;
1192
1193 rcExit = balloonCtrlMain(&handlerArg);
1194
1195 EventQueue::getMainEventQueue()->processEventQueue(0);
1196
1197 session.setNull();
1198 virtualBox.setNull();
1199
1200 } while (0);
1201
1202 com::Shutdown();
1203
1204 return rcExit;
1205#else /* VBOX_ONLY_DOCS */
1206 return RTEXITCODE_SUCCESS;
1207#endif /* VBOX_ONLY_DOCS */
1208}
1209
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