VBoxBalloonCtrl.cpp revision c29bcf19a2399d4aec000b6aa566583203d78978
/* $Id$ */
/** @file
* VBoxBalloonCtrl - VirtualBox Ballooning Control Service.
*/
/*
* Copyright (C) 2011 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#ifndef VBOX_ONLY_DOCS
#endif /* !VBOX_ONLY_DOCS */
#include <package-generated.h>
#include <iprt/buildconfig.h>
#include <iprt/critsect.h>
#include <iprt/initterm.h>
#include <iprt/semaphore.h>
#include <map>
#include <string>
#include <signal.h>
#include "VBoxBalloonCtrl.h"
using namespace com;
/* When defined, use a global performance collector instead
* of a per-machine based one. */
#define VBOX_BALLOONCTRL_GLOBAL_PERFCOL
/** The semaphore we're blocking on. */
/** The critical section for keep our stuff in sync. */
static RTCRITSECT g_MapCritSect;
/** Set by the signal handler. */
static volatile bool g_fCanceled = false;
static bool g_fVerbose = false;
#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined (RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
/** Run in background. */
static bool g_fDaemonize = false;
#endif
/**
* RTGetOpt-IDs for the command line.
*/
{
GETOPTDEF_BALLOONCTRL_INC = 1000,
};
/**
* Command line arguments.
*/
static const RTGETOPTDEF g_aOptions[] = {
#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined (RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
#endif
/** For displayHelp(). */
/** Sets g_ulTimeoutMS. */
/** Sets g_ulMemoryBalloonIncrementMB. */
/** Sets g_ulMemoryBalloonDecrementMB. */
};
unsigned long g_ulMemoryBalloonIncrementMB = 256;
unsigned long g_ulMemoryBalloonDecrementMB = 128;
unsigned long g_ulLowerMemoryLimitMB = 64;
/** Global weak references (for event handlers). */
#endif
/** A machine's internal entry. */
typedef struct VBOXBALLOONCTRL_MACHINE
{
#ifndef VBOX_BALLOONCTRL_GLOBAL_PERFCOL
#endif
/* Prototypes. */
void serviceLog(const char *pszFormat, ...);
#ifdef RT_OS_WINDOWS
/* Required for ATL. */
static CComModule _Module;
#endif
/**
* Handler for global events.
*/
class VirtualBoxEventListener
{
public:
{
}
virtual ~VirtualBoxEventListener()
{
}
{
return S_OK;
}
void uninit()
{
}
{
switch (aType)
{
{
{
int rc;
if (fRegistered)
{
break;
}
else
}
break;
}
{
{
break;
}
{
}
break;
}
default:
/* Not handled event, just skip it. */
break;
}
return S_OK;
}
private:
};
/**
* Signal handler that sets g_fGuestCtrlCanceled.
*
* This can be executed on any thread in the process, on Windows it may even be
* a thread dedicated to delivering this signal. Do not doing anything
* unnecessary here.
*/
static void signalHandler(int iSignal)
{
ASMAtomicWriteBool(&g_fCanceled, true);
{
if (RT_FAILURE(rc))
serviceLog("Error: RTSemEventMultiSignal failed with rc=%Rrc\n");
}
}
/**
* Installs a custom signal handler to get notified
* whenever the user wants to intercept the program.
*/
static void signalHandlerInstall()
{
#ifdef SIGBREAK
#endif
}
/**
* Uninstalls a previously installed signal handler.
*/
static void signalHandlerUninstall()
{
#ifdef SIGBREAK
#endif
}
long getlBalloonDelta(unsigned long ulCurrentDesktopBalloonSize, unsigned long ulDesktopFreeMemory, unsigned long ulMaxBalloonSize)
{
return (ulMaxBalloonSize - ulCurrentDesktopBalloonSize);
long lBalloonDelta = 0;
{
/* Guest is running low on memory, we need to
* deflate the balloon. */
/* Ensure that the delta will not return a negative
* balloon size. */
if ((long)ulCurrentDesktopBalloonSize + lBalloonDelta < 0)
lBalloonDelta = 0;
}
else if (ulMaxBalloonSize > ulCurrentDesktopBalloonSize)
{
/* We want to inflate the balloon if we have room. */
{
}
}
return lBalloonDelta;
}
/**
* Indicates whether a VM is up and running (regardless of its running
* state, could be paused as well).
*
* @return bool Flag indicating whether the VM is running or not.
* @param enmState The VM's machine state to judge whether it's running or not.
*/
{
switch (enmState)
{
case MachineState_Running:
case MachineState_Teleporting:
case MachineState_Paused:
return true;
default:
break;
}
return false;
}
{
if (RT_SUCCESS(rc))
{
do
{
m.machine = rptrMachine;
/*
* Setup metrics.
*/
5 /* 5 seconds */,
1 /* One sample is enough */,
#else
5 /* 5 seconds */,
1 /* One sample is enough */,
#endif
/*
* Add machine to map.
*/
} while (0);
}
return rc;
}
{
if (RT_SUCCESS(rc))
{
{
do
{
/*
* Remove machine from map.
*/
} while (0);
}
else
{
AssertMsgFailed(("Removing non-existent machine \"%s\"!\n",
}
}
return rc;
}
{
if (RT_SUCCESS(rc))
{
{
{
serviceLogVerbose(("Updating machine \"%s\" to state \"%ld\"\n",
}
}
if (RT_SUCCESS(rc))
}
return rc;
}
{
/* Input. */
/* Output. */
/* Query current memory free. */
#else
#endif
}
/* Does not do locking! */
{
/* Is ballooning necessary? Only do if VM is running! */
&& !machineIsRunning(machineState))
{
return VINF_SUCCESS; /* Skip ballooning. */
}
/*
* Get metrics collected at this point.
*/
if (RT_SUCCESS(vrc))
lMemFree /= 1024;
lBalloonCur /= 1024;
if (RT_SUCCESS(vrc))
{
strValue.asOutParam());
serviceLog("Warning: Unable to get balloon size for machine \"%s\", setting to 64 MB",
else
/* Calculate current balloon delta. */
serviceLogVerbose(("%s: Current balloon: %ld, Maximum ballon: %ld, Free memory: %ld\n",
if (lDelta) /* Only do ballooning if there's really smth. to change ... */
{
Assert(lBalloonCur > 0);
serviceLog("%s: %s balloon by %ld to %ld ...\n",
/* Open a session for the VM. */
do
{
/* Get the associated console. */
else
serviceLog("Error: Unable to set new balloon size %ld for machine \"%s\"",
} while (0);
/* Unlock the machine again. */
}
}
else
serviceLog("Error: Unable to retrieve metrics for machine \"%s\"",
return VINF_SUCCESS;
}
void vmListDestroy()
{
if (RT_SUCCESS(rc))
{
{
#ifndef VBOX_BALLOONCTRL_GLOBAL_PERFCOL
#endif
it++;
}
}
}
int vmListBuild()
{
if (RT_SUCCESS(rc))
{
/*
* Get the list of all _running_ VMs
*/
{
/*
* Iterate through the collection
*/
{
if (machines[i])
{
{
if (RT_FAILURE(rc))
break;
}
}
}
}
if (RT_SUCCESS(rc))
}
return rc;
}
int balloonCtrlCheck()
{
if (RT_SUCCESS(rc))
{
{
if (RT_FAILURE(rc))
break;
it++;
}
if (RT_SUCCESS(rc))
}
return rc;
}
{
do
{
/* Initialize global weak references. */
g_pVirtualBox = a->virtualBox;
g_pSession = a->session;
/*
* Setup the global event listener.
*/
CHECK_ERROR_BREAK(es, RegisterListener(vboxListener, ComSafeArrayAsInParam(eventTypes), true /* Active listener */));
/*
* Setup metrics.
*/
#endif
/*
* Install signal handlers.
*/
#ifdef SIGBREAK
#endif
/*
* Build up initial VM list.
*/
vrc = vmListBuild();
for (;;)
{
/*
* Do the actual work.
*/
vrc = balloonCtrlCheck();
if (RT_FAILURE(vrc))
{
break;
}
/*
* Process pending events, then wait for new ones. Note, this
* processes NULL events signalling event loop termination.
*/
if (g_fCanceled)
{
serviceLog("Signal catched, exiting ...\n");
break;
}
{
break;
}
}
#ifdef SIGBREAK
#endif
/* VirtualBox callback unregistration. */
if (vboxListener)
{
}
#endif
if (RT_FAILURE(vrc))
} while (0);
}
void serviceLog(const char *pszFormat, ...)
{
}
{
/* Some introductory information. */
static RTTIMESPEC s_TimeSpec;
char szTmp[256];
if (enmPhase == RTLOGPHASE_BEGIN)
switch (enmPhase)
{
case RTLOGPHASE_BEGIN:
{
"VirtualBox Ballooning Control Service %s r%u %s (%s %s) release log\n"
#ifdef VBOX_BLEEDING_EDGE
#endif
"Log opened %s\n",
/* the package type is interesting for Linux distributions */
char szExecName[RTPATH_MAX];
"Executable: %s\n"
"Process ID: %u\n"
"Package type: %s"
#ifdef VBOX_OSE
" (OSE)"
#endif
"\n",
RTProcSelf(),
break;
}
case RTLOGPHASE_PREROTATE:
break;
case RTLOGPHASE_POSTROTATE:
break;
case RTLOGPHASE_END:
break;
default:
/* nothing */;
}
}
void displayHelp()
{
RTStrmPrintf(g_pStdErr, "\nUsage: VBoxBalloonCtrl [options]\n\nSupported options (default values in brackets):\n");
for (unsigned i = 0;
i < RT_ELEMENTS(g_aOptions);
++i)
{
{
str += ", -";
}
str += ":";
const char *pcszDescr = "";
switch (g_aOptions[i].iShort)
{
case 'h':
pcszDescr = "Print this help message and exit.";
break;
case 'i': /* Interval. */
pcszDescr = "Sets the check interval in ms (30 seconds).";
break;
#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined (RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
case 'b':
pcszDescr = "Run in background (daemon mode).";
break;
#endif
pcszDescr = "Sets the ballooning increment in MB (256 MB).";
break;
pcszDescr = "Sets the ballooning decrement in MB (128 MB).";
break;
pcszDescr = "Sets the ballooning lower limit in MB (64 MB).";
break;
}
}
RTStrmPrintf(g_pStdErr, "\nUse environment variable VBOXBALLOONCTRL_RELEASE_LOG for logging options.\n"
"Set \"VBoxInternal/Guest/BalloonSizeMax\" for a per-VM maximum ballooning size.\n");
}
{
/*
* Before we do anything, init the runtime without loading
* the support driver.
*/
if (RT_FAILURE(rc))
return RTMsgInitFailure(rc);
"All rights reserved.\n\n");
/*
* Parse the global options
*/
int c;
const char *pszLogFile = NULL;
const char *pszPidFile = NULL;
{
switch (c)
{
case 'h':
displayHelp();
return 0;
case 'i': /* Interval. */
break;
case 'v':
g_fVerbose = true;
break;
#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined (RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
case 'b':
g_fDaemonize = true;
break;
#endif
case 'V':
return 0;
break;
break;
break;
default:
return rc;
}
}
/* create release logger */
static const char * const s_apszGroups[] = VBOX_LOGGROUP_NAMES;
#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
#endif
if (RT_SUCCESS(rc))
{
/* register this logger as the release logger */
/* Explicitly flush the log in case of VBOXWEBSRV_RELEASE_LOG=buffered. */
}
else
#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined (RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
if (g_fDaemonize)
{
/* prepare release logging */
char szLogFile[RTPATH_MAX];
if (RT_FAILURE(rc))
if (RT_FAILURE(rc))
if (RT_FAILURE(rc))
/* create release logger */
static const char * const s_apszGroupsFile[] = VBOX_LOGGROUP_NAMES;
#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
#endif
if (RT_SUCCESS(vrc))
{
/* register this logger as the release logger */
/* Explicitly flush the log in case of VBOXBALLOONCTRL_RELEASE_LOG=buffered. */
}
else
return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to open release log (%s, %Rrc)", szErrorFile, vrc);
}
#endif
#ifndef VBOX_ONLY_DOCS
/*
* Initialize COM.
*/
using namespace com;
do
{
RTMsgError("Failed to create the VirtualBox object!");
else
{
RTMsgError("Failed to create a session object!");
}
{
{
RTMsgError("Most likely, the VirtualBox COM server is not running or failed to start.");
}
else
break;
}
} while (0);
return rcExit;
#else /* VBOX_ONLY_DOCS */
return RTEXITCODE_SUCCESS;
#endif /* VBOX_ONLY_DOCS */
}