VBoxManageMetrics.cpp revision 561574402775590253d11504354bfe21d80e4858
/* $Id$ */
/** @file
* VBoxManage - The 'metrics' command.
*/
/*
* Copyright (C) 2006-2010 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* you can redistribute it and/or modify it under the terms of the GNU
* 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.
*/
#ifndef VBOX_ONLY_DOCS
/*******************************************************************************
* Header Files *
*******************************************************************************/
#include <VBox/com/com.h>
#include <VBox/com/array.h>
#include <VBox/com/ErrorInfo.h>
#include <VBox/com/errorprint.h>
#include <VBox/com/VirtualBox.h>
#include <iprt/asm.h>
#include <iprt/stream.h>
#include <iprt/string.h>
#include <iprt/time.h>
#include <iprt/thread.h>
#include <VBox/log.h>
#include "VBoxManage.h"
using namespace com;
// funcs
///////////////////////////////////////////////////////////////////////////////
static bool isLastSlash(const char *str)
{
char c;
while ((c = *str++))
{
if (c == ',')
break;
if (c == '/')
return false;
}
return true;
}
static char *toBaseMetricNames(const char *metricList)
{
char *newList = (char*)RTMemAlloc(strlen(metricList) + 1);
if (newList)
{
int cSlashes = 0;
bool fSkip = false;
const char *src = metricList;
char c, *dst = newList;
while ((c = *src++))
if (c == ':')
fSkip = true;
else if (c == '/' && ++cSlashes >= 2 && isLastSlash(src))
fSkip = true;
else if (c == ',')
{
fSkip = false;
cSlashes = 0;
*dst++ = c;
}
else
if (!fSkip)
*dst++ = c;
*dst = 0;
}
return newList;
}
static int parseFilterParameters(int argc, char *argv[],
ComPtr<IVirtualBox> aVirtualBox,
ComSafeArrayOut(BSTR, outMetrics),
ComSafeArrayOut(BSTR, outBaseMetrics),
ComSafeArrayOut(IUnknown *, outObjects))
{
HRESULT rc = S_OK;
com::SafeArray<BSTR> retMetrics(1);
com::SafeArray<BSTR> retBaseMetrics(1);
com::SafeIfaceArray <IUnknown> retObjects;
Bstr metricNames, baseNames;
/* Metric list */
if (argc > 1)
{
metricNames = argv[1];
char *tmp = toBaseMetricNames(argv[1]);
if (!tmp)
return VERR_NO_MEMORY;
baseNames = tmp;
RTMemFree(tmp);
}
else
{
metricNames = L"*";
baseNames = L"*";
}
metricNames.cloneTo(&retMetrics[0]);
baseNames.cloneTo(&retBaseMetrics[0]);
/* Object name */
if (argc > 0 && strcmp(argv[0], "*"))
{
if (!strcmp(argv[0], "host"))
{
ComPtr<IHost> host;
CHECK_ERROR(aVirtualBox, COMGETTER(Host)(host.asOutParam()));
retObjects.reset(1);
host.queryInterfaceTo(&retObjects[0]);
}
else
{
ComPtr <IMachine> machine;
rc = aVirtualBox->FindMachine(Bstr(argv[0]).raw(),
machine.asOutParam());
if (SUCCEEDED (rc))
{
retObjects.reset(1);
machine.queryInterfaceTo(&retObjects[0]);
}
else
{
errorArgument("Invalid machine name: '%s'", argv[0]);
return rc;
}
}
}
retMetrics.detachTo(ComSafeArrayOutArg(outMetrics));
retBaseMetrics.detachTo(ComSafeArrayOutArg(outBaseMetrics));
retObjects.detachTo(ComSafeArrayOutArg(outObjects));
return rc;
}
static Bstr getObjectName(ComPtr<IVirtualBox> aVirtualBox,
ComPtr<IUnknown> aObject)
{
HRESULT rc;
ComPtr<IHost> host = aObject;
if (!host.isNull())
return Bstr("host");
ComPtr<IMachine> machine = aObject;
if (!machine.isNull())
{
Bstr name;
CHECK_ERROR(machine, COMGETTER(Name)(name.asOutParam()));
if (SUCCEEDED(rc))
return name;
}
return Bstr("unknown");
}
static void listAffectedMetrics(ComPtr<IVirtualBox> aVirtualBox,
ComSafeArrayIn(IPerformanceMetric*, aMetrics))
{
HRESULT rc;
com::SafeIfaceArray<IPerformanceMetric> metrics(ComSafeArrayInArg(aMetrics));
if (metrics.size())
{
ComPtr<IUnknown> object;
Bstr metricName;
RTPrintf("The following metrics were modified:\n\n"
"Object Metric\n"
"---------- --------------------\n");
for (size_t i = 0; i < metrics.size(); i++)
{
CHECK_ERROR(metrics[i], COMGETTER(Object)(object.asOutParam()));
CHECK_ERROR(metrics[i], COMGETTER(MetricName)(metricName.asOutParam()));
RTPrintf("%-10ls %-20ls\n",
getObjectName(aVirtualBox, object).raw(), metricName.raw());
}
RTPrintf("\n");
}
else
{
RTMsgError("No metrics match the specified filter!");
}
}
/**
* list
*/
static int handleMetricsList(int argc, char *argv[],
ComPtr<IVirtualBox> aVirtualBox,
ComPtr<IPerformanceCollector> performanceCollector)
{
HRESULT rc;
com::SafeArray<BSTR> metrics;
com::SafeArray<BSTR> baseMetrics;
com::SafeIfaceArray<IUnknown> objects;
rc = parseFilterParameters(argc - 1, &argv[1], aVirtualBox,
ComSafeArrayAsOutParam(metrics),
ComSafeArrayAsOutParam(baseMetrics),
ComSafeArrayAsOutParam(objects));
if (FAILED(rc))
return 1;
com::SafeIfaceArray<IPerformanceMetric> metricInfo;
CHECK_ERROR(performanceCollector,
GetMetrics(ComSafeArrayAsInParam(metrics),
ComSafeArrayAsInParam(objects),
ComSafeArrayAsOutParam(metricInfo)));
ComPtr<IUnknown> object;
Bstr metricName, unit, description;
ULONG period, count;
LONG minimum, maximum;
RTPrintf(
"Object Metric Unit Minimum Maximum Period Count Description\n"
"---------- -------------------- ---- ---------- ---------- ---------- ---------- -----------\n");
for (size_t i = 0; i < metricInfo.size(); i++)
{
CHECK_ERROR(metricInfo[i], COMGETTER(Object)(object.asOutParam()));
CHECK_ERROR(metricInfo[i], COMGETTER(MetricName)(metricName.asOutParam()));
CHECK_ERROR(metricInfo[i], COMGETTER(Period)(&period));
CHECK_ERROR(metricInfo[i], COMGETTER(Count)(&count));
CHECK_ERROR(metricInfo[i], COMGETTER(MinimumValue)(&minimum));
CHECK_ERROR(metricInfo[i], COMGETTER(MaximumValue)(&maximum));
CHECK_ERROR(metricInfo[i], COMGETTER(Unit)(unit.asOutParam()));
CHECK_ERROR(metricInfo[i], COMGETTER(Description)(description.asOutParam()));
RTPrintf("%-10ls %-20ls %-4ls %10d %10d %10u %10u %ls\n",
getObjectName(aVirtualBox, object).raw(), metricName.raw(), unit.raw(),
minimum, maximum, period, count, description.raw());
}
return 0;
}
/**
* Metrics setup
*/
static int handleMetricsSetup(int argc, char *argv[],
ComPtr<IVirtualBox> aVirtualBox,
ComPtr<IPerformanceCollector> performanceCollector)
{
HRESULT rc;
com::SafeArray<BSTR> metrics;
com::SafeArray<BSTR> baseMetrics;
com::SafeIfaceArray<IUnknown> objects;
uint32_t period = 1, samples = 1;
bool listMatches = false;
int i;
for (i = 1; i < argc; i++)
{
if ( !strcmp(argv[i], "--period")
|| !strcmp(argv[i], "-period"))
{
if (argc <= i + 1)
return errorArgument("Missing argument to '%s'", argv[i]);
if ( VINF_SUCCESS != RTStrToUInt32Full(argv[++i], 10, &period)
|| !period)
return errorArgument("Invalid value for 'period' parameter: '%s'", argv[i]);
}
else if ( !strcmp(argv[i], "--samples")
|| !strcmp(argv[i], "-samples"))
{
if (argc <= i + 1)
return errorArgument("Missing argument to '%s'", argv[i]);
if ( VINF_SUCCESS != RTStrToUInt32Full(argv[++i], 10, &samples)
|| !samples)
return errorArgument("Invalid value for 'samples' parameter: '%s'", argv[i]);
}
else if ( !strcmp(argv[i], "--list")
|| !strcmp(argv[i], "-list"))
listMatches = true;
else
break; /* The rest of params should define the filter */
}
rc = parseFilterParameters(argc - i, &argv[i], aVirtualBox,
ComSafeArrayAsOutParam(metrics),
ComSafeArrayAsOutParam(baseMetrics),
ComSafeArrayAsOutParam(objects));
if (FAILED(rc))
return 1;
com::SafeIfaceArray<IPerformanceMetric> affectedMetrics;
CHECK_ERROR(performanceCollector,
SetupMetrics(ComSafeArrayAsInParam(metrics),
ComSafeArrayAsInParam(objects), period, samples,
ComSafeArrayAsOutParam(affectedMetrics)));
if (FAILED(rc))
return 2;
if (listMatches)
listAffectedMetrics(aVirtualBox,
ComSafeArrayAsInParam(affectedMetrics));
return 0;
}
/**
* metrics query
*/
static int handleMetricsQuery(int argc, char *argv[],
ComPtr<IVirtualBox> aVirtualBox,
ComPtr<IPerformanceCollector> performanceCollector)
{
HRESULT rc;
com::SafeArray<BSTR> metrics;
com::SafeArray<BSTR> baseMetrics;
com::SafeIfaceArray<IUnknown> objects;
rc = parseFilterParameters(argc - 1, &argv[1], aVirtualBox,
ComSafeArrayAsOutParam(metrics),
ComSafeArrayAsOutParam(baseMetrics),
ComSafeArrayAsOutParam(objects));
if (FAILED(rc))
return 1;
com::SafeArray<BSTR> retNames;
com::SafeIfaceArray<IUnknown> retObjects;
com::SafeArray<BSTR> retUnits;
com::SafeArray<ULONG> retScales;
com::SafeArray<ULONG> retSequenceNumbers;
com::SafeArray<ULONG> retIndices;
com::SafeArray<ULONG> retLengths;
com::SafeArray<LONG> retData;
CHECK_ERROR (performanceCollector, QueryMetricsData(ComSafeArrayAsInParam(metrics),
ComSafeArrayAsInParam(objects),
ComSafeArrayAsOutParam(retNames),
ComSafeArrayAsOutParam(retObjects),
ComSafeArrayAsOutParam(retUnits),
ComSafeArrayAsOutParam(retScales),
ComSafeArrayAsOutParam(retSequenceNumbers),
ComSafeArrayAsOutParam(retIndices),
ComSafeArrayAsOutParam(retLengths),
ComSafeArrayAsOutParam(retData)) );
RTPrintf("Object Metric Values\n"
"---------- -------------------- --------------------------------------------\n");
for (unsigned i = 0; i < retNames.size(); i++)
{
Bstr metricUnit(retUnits[i]);
Bstr metricName(retNames[i]);
RTPrintf("%-10ls %-20ls ", getObjectName(aVirtualBox, retObjects[i]).raw(), metricName.raw());
const char *separator = "";
for (unsigned j = 0; j < retLengths[i]; j++)
{
if (retScales[i] == 1)
RTPrintf("%s%d %ls", separator, retData[retIndices[i] + j], metricUnit.raw());
else
RTPrintf("%s%d.%02d%ls", separator, retData[retIndices[i] + j] / retScales[i],
(retData[retIndices[i] + j] * 100 / retScales[i]) % 100, metricUnit.raw());
separator = ", ";
}
RTPrintf("\n");
}
return 0;
}
static void getTimestamp(char *pts, size_t tsSize)
{
*pts = 0;
AssertReturnVoid(tsSize >= 13); /* 3+3+3+3+1 */
RTTIMESPEC TimeSpec;
RTTIME Time;
RTTimeExplode(&Time, RTTimeNow(&TimeSpec));
pts += RTStrFormatNumber(pts, Time.u8Hour, 10, 2, 0, RTSTR_F_ZEROPAD);
*pts++ = ':';
pts += RTStrFormatNumber(pts, Time.u8Minute, 10, 2, 0, RTSTR_F_ZEROPAD);
*pts++ = ':';
pts += RTStrFormatNumber(pts, Time.u8Second, 10, 2, 0, RTSTR_F_ZEROPAD);
*pts++ = '.';
pts += RTStrFormatNumber(pts, Time.u32Nanosecond / 1000000, 10, 3, 0, RTSTR_F_ZEROPAD);
*pts = 0;
}
/** Used by the handleMetricsCollect loop. */
static bool volatile g_fKeepGoing = true;
#ifdef RT_OS_WINDOWS
/**
* Handler routine for catching Ctrl-C, Ctrl-Break and closing of
* the console.
*
* @returns true if handled, false if not handled.
* @param dwCtrlType The type of control signal.
*
* @remarks This is called on a new thread.
*/
static BOOL WINAPI ctrlHandler(DWORD dwCtrlType)
{
switch (dwCtrlType)
{
/* Ctrl-C or Ctrl-Break or Close */
case CTRL_C_EVENT:
case CTRL_BREAK_EVENT:
case CTRL_CLOSE_EVENT:
/* Let's shut down gracefully. */
ASMAtomicWriteBool(&g_fKeepGoing, false);
return TRUE;
}
/* Don't care about the rest -- let it die a horrible death. */
return FALSE;
}
#endif /* RT_OS_WINDOWS */
/**
* collect
*/
static int handleMetricsCollect(int argc, char *argv[],
ComPtr<IVirtualBox> aVirtualBox,
ComPtr<IPerformanceCollector> performanceCollector)
{
HRESULT rc;
com::SafeArray<BSTR> metrics;
com::SafeArray<BSTR> baseMetrics;
com::SafeIfaceArray<IUnknown> objects;
uint32_t period = 1, samples = 1;
bool isDetached = false, listMatches = false;
int i;
for (i = 1; i < argc; i++)
{
if ( !strcmp(argv[i], "--period")
|| !strcmp(argv[i], "-period"))
{
if (argc <= i + 1)
return errorArgument("Missing argument to '%s'", argv[i]);
if ( VINF_SUCCESS != RTStrToUInt32Full(argv[++i], 10, &period)
|| !period)
return errorArgument("Invalid value for 'period' parameter: '%s'", argv[i]);
}
else if ( !strcmp(argv[i], "--samples")
|| !strcmp(argv[i], "-samples"))
{
if (argc <= i + 1)
return errorArgument("Missing argument to '%s'", argv[i]);
if ( VINF_SUCCESS != RTStrToUInt32Full(argv[++i], 10, &samples)
|| !samples)
return errorArgument("Invalid value for 'samples' parameter: '%s'", argv[i]);
}
else if ( !strcmp(argv[i], "--list")
|| !strcmp(argv[i], "-list"))
listMatches = true;
else if ( !strcmp(argv[i], "--detach")
|| !strcmp(argv[i], "-detach"))
isDetached = true;
else
break; /* The rest of params should define the filter */
}
rc = parseFilterParameters(argc - i, &argv[i], aVirtualBox,
ComSafeArrayAsOutParam(metrics),
ComSafeArrayAsOutParam(baseMetrics),
ComSafeArrayAsOutParam(objects));
if (FAILED(rc))
return 1;
com::SafeIfaceArray<IPerformanceMetric> affectedMetrics;
CHECK_ERROR(performanceCollector,
SetupMetrics(ComSafeArrayAsInParam(baseMetrics),
ComSafeArrayAsInParam(objects), period, samples,
ComSafeArrayAsOutParam(affectedMetrics)));
if (FAILED(rc))
return 2;
if (listMatches)
listAffectedMetrics(aVirtualBox,
ComSafeArrayAsInParam(affectedMetrics));
if (!affectedMetrics.size())
return 1;
if (isDetached)
{
RTMsgWarning("The background process holding collected metrics will shutdown\n"
"in few seconds, discarding all collected data and parameters.");
return 0;
}
#ifdef RT_OS_WINDOWS
SetConsoleCtrlHandler(ctrlHandler, true);
#endif /* RT_OS_WINDOWS */
RTPrintf("Time stamp Object Metric Value\n");
while (g_fKeepGoing)
{
RTPrintf("------------ ---------- -------------------- --------------------\n");
RTThreadSleep(period * 1000); // Sleep for 'period' seconds
char ts[15];
getTimestamp(ts, sizeof(ts));
com::SafeArray<BSTR> retNames;
com::SafeIfaceArray<IUnknown> retObjects;
com::SafeArray<BSTR> retUnits;
com::SafeArray<ULONG> retScales;
com::SafeArray<ULONG> retSequenceNumbers;
com::SafeArray<ULONG> retIndices;
com::SafeArray<ULONG> retLengths;
com::SafeArray<LONG> retData;
CHECK_ERROR (performanceCollector, QueryMetricsData(ComSafeArrayAsInParam(metrics),
ComSafeArrayAsInParam(objects),
ComSafeArrayAsOutParam(retNames),
ComSafeArrayAsOutParam(retObjects),
ComSafeArrayAsOutParam(retUnits),
ComSafeArrayAsOutParam(retScales),
ComSafeArrayAsOutParam(retSequenceNumbers),
ComSafeArrayAsOutParam(retIndices),
ComSafeArrayAsOutParam(retLengths),
ComSafeArrayAsOutParam(retData)) );
for (unsigned j = 0; j < retNames.size(); j++)
{
Bstr metricUnit(retUnits[j]);
Bstr metricName(retNames[j]);
RTPrintf("%-12s %-10ls %-20ls ", ts, getObjectName(aVirtualBox, retObjects[j]).raw(), metricName.raw());
const char *separator = "";
for (unsigned k = 0; k < retLengths[j]; k++)
{
if (retScales[j] == 1)
RTPrintf("%s%d %ls", separator, retData[retIndices[j] + k], metricUnit.raw());
else
RTPrintf("%s%d.%02d%ls", separator, retData[retIndices[j] + k] / retScales[j],
(retData[retIndices[j] + k] * 100 / retScales[j]) % 100, metricUnit.raw());
separator = ", ";
}
RTPrintf("\n");
}
RTStrmFlush(g_pStdOut);
}
#ifdef RT_OS_WINDOWS
SetConsoleCtrlHandler(ctrlHandler, false);
#endif /* RT_OS_WINDOWS */
return 0;
}
/**
* Enable metrics
*/
static int handleMetricsEnable(int argc, char *argv[],
ComPtr<IVirtualBox> aVirtualBox,
ComPtr<IPerformanceCollector> performanceCollector)
{
HRESULT rc;
com::SafeArray<BSTR> metrics;
com::SafeArray<BSTR> baseMetrics;
com::SafeIfaceArray<IUnknown> objects;
bool listMatches = false;
int i;
for (i = 1; i < argc; i++)
{
if ( !strcmp(argv[i], "--list")
|| !strcmp(argv[i], "-list"))
listMatches = true;
else
break; /* The rest of params should define the filter */
}
rc = parseFilterParameters(argc - i, &argv[i], aVirtualBox,
ComSafeArrayAsOutParam(metrics),
ComSafeArrayAsOutParam(baseMetrics),
ComSafeArrayAsOutParam(objects));
if (FAILED(rc))
return 1;
com::SafeIfaceArray<IPerformanceMetric> affectedMetrics;
CHECK_ERROR(performanceCollector,
EnableMetrics(ComSafeArrayAsInParam(metrics),
ComSafeArrayAsInParam(objects),
ComSafeArrayAsOutParam(affectedMetrics)));
if (FAILED(rc))
return 2;
if (listMatches)
listAffectedMetrics(aVirtualBox,
ComSafeArrayAsInParam(affectedMetrics));
return 0;
}
/**
* Disable metrics
*/
static int handleMetricsDisable(int argc, char *argv[],
ComPtr<IVirtualBox> aVirtualBox,
ComPtr<IPerformanceCollector> performanceCollector)
{
HRESULT rc;
com::SafeArray<BSTR> metrics;
com::SafeArray<BSTR> baseMetrics;
com::SafeIfaceArray<IUnknown> objects;
bool listMatches = false;
int i;
for (i = 1; i < argc; i++)
{
if ( !strcmp(argv[i], "--list")
|| !strcmp(argv[i], "-list"))
listMatches = true;
else
break; /* The rest of params should define the filter */
}
rc = parseFilterParameters(argc - i, &argv[i], aVirtualBox,
ComSafeArrayAsOutParam(metrics),
ComSafeArrayAsOutParam(baseMetrics),
ComSafeArrayAsOutParam(objects));
if (FAILED(rc))
return 1;
com::SafeIfaceArray<IPerformanceMetric> affectedMetrics;
CHECK_ERROR(performanceCollector,
DisableMetrics(ComSafeArrayAsInParam(metrics),
ComSafeArrayAsInParam(objects),
ComSafeArrayAsOutParam(affectedMetrics)));
if (FAILED(rc))
return 2;
if (listMatches)
listAffectedMetrics(aVirtualBox,
ComSafeArrayAsInParam(affectedMetrics));
return 0;
}
int handleMetrics(HandlerArg *a)
{
int rc;
/* at least one option: subcommand name */
if (a->argc < 1)
return errorSyntax(USAGE_METRICS, "Subcommand missing");
ComPtr<IPerformanceCollector> performanceCollector;
CHECK_ERROR(a->virtualBox, COMGETTER(PerformanceCollector)(performanceCollector.asOutParam()));
if (!strcmp(a->argv[0], "list"))
rc = handleMetricsList(a->argc, a->argv, a->virtualBox, performanceCollector);
else if (!strcmp(a->argv[0], "setup"))
rc = handleMetricsSetup(a->argc, a->argv, a->virtualBox, performanceCollector);
else if (!strcmp(a->argv[0], "query"))
rc = handleMetricsQuery(a->argc, a->argv, a->virtualBox, performanceCollector);
else if (!strcmp(a->argv[0], "collect"))
rc = handleMetricsCollect(a->argc, a->argv, a->virtualBox, performanceCollector);
else if (!strcmp(a->argv[0], "enable"))
rc = handleMetricsEnable(a->argc, a->argv, a->virtualBox, performanceCollector);
else if (!strcmp(a->argv[0], "disable"))
rc = handleMetricsDisable(a->argc, a->argv, a->virtualBox, performanceCollector);
else
return errorSyntax(USAGE_METRICS, "Invalid subcommand '%s'", a->argv[0]);
return rc;
}
#endif /* VBOX_ONLY_DOCS */