mirror of
https://github.com/Motorhead1991/qemu.git
synced 2025-08-06 01:03:55 -06:00
qga-win: implement a 'guest-get-load' command
Windows has no native equivalent API, but it would be possible to simulate it as illustrated here (BSD-3-Clause): https://github.com/giampaolo/psutil/pull/1485 Reviewed-by: Daniel P. Berrangé <berrange@redhat.com> Tested-by: Dehan Meng <demeng@redhat.com> Reviewed-by: Yan Vugenfirer <yvugenfi@redhat.com> Signed-off-by: Konstantin Kostiuk <kkostiuk@redhat.com>
This commit is contained in:
parent
d9ce74873a
commit
6f0273d6ae
5 changed files with 205 additions and 3 deletions
|
@ -27,6 +27,7 @@
|
|||
#include <lm.h>
|
||||
#include <wtsapi32.h>
|
||||
#include <wininet.h>
|
||||
#include <pdh.h>
|
||||
|
||||
#include "guest-agent-core.h"
|
||||
#include "vss-win32.h"
|
||||
|
@ -119,6 +120,28 @@ static OpenFlags guest_file_open_modes[] = {
|
|||
{"a+b", FILE_GENERIC_APPEND | GENERIC_READ, OPEN_ALWAYS }
|
||||
};
|
||||
|
||||
/*
|
||||
* We use an exponentially weighted moving average, just like Unix systems do
|
||||
* https://en.wikipedia.org/wiki/Load_(computing)#Unix-style_load_calculation
|
||||
*
|
||||
* These constants serve as the damping factor and are calculated with
|
||||
* 1 / exp(sampling interval in seconds / window size in seconds)
|
||||
*
|
||||
* This formula comes from linux's include/linux/sched/loadavg.h
|
||||
* https://github.com/torvalds/linux/blob/345671ea0f9258f410eb057b9ced9cefbbe5dc78/include/linux/sched/loadavg.h#L20-L23
|
||||
*/
|
||||
#define LOADAVG_FACTOR_1F 0.9200444146293232478931553241
|
||||
#define LOADAVG_FACTOR_5F 0.9834714538216174894737477501
|
||||
#define LOADAVG_FACTOR_15F 0.9944598480048967508795473394
|
||||
/*
|
||||
* The time interval in seconds between taking load counts, same as Linux
|
||||
*/
|
||||
#define LOADAVG_SAMPLING_INTERVAL 5
|
||||
|
||||
double load_avg_1m;
|
||||
double load_avg_5m;
|
||||
double load_avg_15m;
|
||||
|
||||
#define debug_error(msg) do { \
|
||||
char *suffix = g_win32_error_message(GetLastError()); \
|
||||
g_debug("%s: %s", (msg), suffix); \
|
||||
|
@ -2444,3 +2467,128 @@ char *qga_get_host_name(Error **errp)
|
|||
|
||||
return g_utf16_to_utf8(tmp, size, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
|
||||
static VOID CALLBACK load_avg_callback(PVOID hCounter, BOOLEAN timedOut)
|
||||
{
|
||||
PDH_FMT_COUNTERVALUE displayValue;
|
||||
double currentLoad;
|
||||
PDH_STATUS err;
|
||||
|
||||
err = PdhGetFormattedCounterValue(
|
||||
(PDH_HCOUNTER)hCounter, PDH_FMT_DOUBLE, 0, &displayValue);
|
||||
/* Skip updating the load if we can't get the value successfully */
|
||||
if (err != ERROR_SUCCESS) {
|
||||
slog("PdhGetFormattedCounterValue failed to get load value with 0x%lx",
|
||||
err);
|
||||
return;
|
||||
}
|
||||
currentLoad = displayValue.doubleValue;
|
||||
|
||||
load_avg_1m = load_avg_1m * LOADAVG_FACTOR_1F + currentLoad * \
|
||||
(1.0 - LOADAVG_FACTOR_1F);
|
||||
load_avg_5m = load_avg_5m * LOADAVG_FACTOR_5F + currentLoad * \
|
||||
(1.0 - LOADAVG_FACTOR_5F);
|
||||
load_avg_15m = load_avg_15m * LOADAVG_FACTOR_15F + currentLoad * \
|
||||
(1.0 - LOADAVG_FACTOR_15F);
|
||||
}
|
||||
|
||||
static BOOL init_load_avg_counter(Error **errp)
|
||||
{
|
||||
CONST WCHAR *szCounterPath = L"\\System\\Processor Queue Length";
|
||||
PDH_STATUS status;
|
||||
BOOL ret;
|
||||
HQUERY hQuery;
|
||||
HCOUNTER hCounter;
|
||||
HANDLE event;
|
||||
HANDLE waitHandle;
|
||||
|
||||
status = PdhOpenQueryW(NULL, 0, &hQuery);
|
||||
if (status != ERROR_SUCCESS) {
|
||||
/*
|
||||
* If the function fails, the return value is a system error code or
|
||||
* a PDH error code. error_setg_win32 cant translate PDH error code
|
||||
* properly, so just report it as is.
|
||||
*/
|
||||
error_setg_win32(errp, (DWORD)status,
|
||||
"PdhOpenQueryW failed with 0x%lx", status);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
status = PdhAddEnglishCounterW(hQuery, szCounterPath, 0, &hCounter);
|
||||
if (status != ERROR_SUCCESS) {
|
||||
error_setg_win32(errp, (DWORD)status,
|
||||
"PdhAddEnglishCounterW failed with 0x%lx. Performance counters may be disabled.",
|
||||
status);
|
||||
PdhCloseQuery(hQuery);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
event = CreateEventW(NULL, FALSE, FALSE, L"LoadUpdateEvent");
|
||||
if (event == NULL) {
|
||||
error_setg_win32(errp, GetLastError(), "Create LoadUpdateEvent failed");
|
||||
PdhCloseQuery(hQuery);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
status = PdhCollectQueryDataEx(hQuery, LOADAVG_SAMPLING_INTERVAL, event);
|
||||
if (status != ERROR_SUCCESS) {
|
||||
error_setg_win32(errp, (DWORD)status,
|
||||
"PdhCollectQueryDataEx failed with 0x%lx", status);
|
||||
CloseHandle(event);
|
||||
PdhCloseQuery(hQuery);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
ret = RegisterWaitForSingleObject(
|
||||
&waitHandle,
|
||||
event,
|
||||
(WAITORTIMERCALLBACK)load_avg_callback,
|
||||
(PVOID)hCounter,
|
||||
INFINITE,
|
||||
WT_EXECUTEDEFAULT);
|
||||
|
||||
if (ret == 0) {
|
||||
error_setg_win32(errp, GetLastError(),
|
||||
"RegisterWaitForSingleObject failed");
|
||||
CloseHandle(event);
|
||||
PdhCloseQuery(hQuery);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
ga_set_load_avg_wait_handle(ga_state, waitHandle);
|
||||
ga_set_load_avg_event(ga_state, event);
|
||||
ga_set_load_avg_pdh_query(ga_state, hQuery);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
GuestLoadAverage *qmp_guest_get_load(Error **errp)
|
||||
{
|
||||
/*
|
||||
* The load average logic calls PerformaceCounterAPI, which can result
|
||||
* in a performance penalty. This avoids running the load average logic
|
||||
* until a management application actually requests it. The load average
|
||||
* will not initially be very accurate, but assuming that any interested
|
||||
* management application will request it repeatedly throughout the lifetime
|
||||
* of the VM, this seems like a good mitigation.
|
||||
*/
|
||||
if (ga_get_load_avg_pdh_query(ga_state) == NULL) {
|
||||
/* set initial values */
|
||||
load_avg_1m = 0;
|
||||
load_avg_5m = 0;
|
||||
load_avg_15m = 0;
|
||||
|
||||
if (init_load_avg_counter(errp) == false) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
GuestLoadAverage *ret = NULL;
|
||||
|
||||
ret = g_new0(GuestLoadAverage, 1);
|
||||
ret->load1m = load_avg_1m;
|
||||
ret->load5m = load_avg_5m;
|
||||
ret->load15m = load_avg_15m;
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,10 @@
|
|||
#ifndef GUEST_AGENT_CORE_H
|
||||
#define GUEST_AGENT_CORE_H
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <pdh.h>
|
||||
#endif
|
||||
|
||||
#include "qapi/qmp-registry.h"
|
||||
#include "qga-qapi-types.h"
|
||||
|
||||
|
@ -41,6 +45,12 @@ void ga_set_response_delimited(GAState *s);
|
|||
bool ga_is_frozen(GAState *s);
|
||||
void ga_set_frozen(GAState *s);
|
||||
void ga_unset_frozen(GAState *s);
|
||||
#ifdef _WIN32
|
||||
void ga_set_load_avg_event(GAState *s, HANDLE event);
|
||||
void ga_set_load_avg_wait_handle(GAState *s, HANDLE wait_handle);
|
||||
void ga_set_load_avg_pdh_query(GAState *s, HQUERY query);
|
||||
HQUERY ga_get_load_avg_pdh_query(GAState *s);
|
||||
#endif
|
||||
const char *ga_fsfreeze_hook(GAState *s);
|
||||
int64_t ga_get_fd_handle(GAState *s, Error **errp);
|
||||
int ga_parse_whence(GuestFileWhence *whence, Error **errp);
|
||||
|
|
39
qga/main.c
39
qga/main.c
|
@ -33,6 +33,7 @@
|
|||
#include "qemu-version.h"
|
||||
#ifdef _WIN32
|
||||
#include <dbt.h>
|
||||
#include <pdh.h>
|
||||
#include "qga/service-win32.h"
|
||||
#include "qga/vss-win32.h"
|
||||
#endif
|
||||
|
@ -105,6 +106,9 @@ struct GAState {
|
|||
GAService service;
|
||||
HANDLE wakeup_event;
|
||||
HANDLE event_log;
|
||||
HANDLE load_avg_wait_handle;
|
||||
HANDLE load_avg_event;
|
||||
HQUERY load_avg_pdh_query;
|
||||
#endif
|
||||
bool delimit_response;
|
||||
bool frozen;
|
||||
|
@ -582,6 +586,25 @@ const char *ga_fsfreeze_hook(GAState *s)
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
void ga_set_load_avg_wait_handle(GAState *s, HANDLE wait_handle)
|
||||
{
|
||||
s->load_avg_wait_handle = wait_handle;
|
||||
}
|
||||
void ga_set_load_avg_event(GAState *s, HANDLE event)
|
||||
{
|
||||
s->load_avg_event = event;
|
||||
}
|
||||
void ga_set_load_avg_pdh_query(GAState *s, HQUERY query)
|
||||
{
|
||||
s->load_avg_pdh_query = query;
|
||||
}
|
||||
HQUERY ga_get_load_avg_pdh_query(GAState *s)
|
||||
{
|
||||
return s->load_avg_pdh_query;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void become_daemon(const char *pidfile)
|
||||
{
|
||||
#ifndef _WIN32
|
||||
|
@ -1402,6 +1425,10 @@ static GAState *initialize_agent(GAConfig *config, int socket_activation)
|
|||
g_debug("Guest agent version %s started", QEMU_FULL_VERSION);
|
||||
|
||||
#ifdef _WIN32
|
||||
s->load_avg_wait_handle = INVALID_HANDLE_VALUE;
|
||||
s->load_avg_event = INVALID_HANDLE_VALUE;
|
||||
s->load_avg_pdh_query = NULL;
|
||||
|
||||
s->event_log = RegisterEventSource(NULL, "qemu-ga");
|
||||
if (!s->event_log) {
|
||||
g_autofree gchar *errmsg = g_win32_error_message(GetLastError());
|
||||
|
@ -1506,6 +1533,18 @@ static void cleanup_agent(GAState *s)
|
|||
#ifdef _WIN32
|
||||
CloseHandle(s->wakeup_event);
|
||||
CloseHandle(s->event_log);
|
||||
|
||||
if (s->load_avg_wait_handle != INVALID_HANDLE_VALUE) {
|
||||
UnregisterWait(s->load_avg_wait_handle);
|
||||
}
|
||||
|
||||
if (s->load_avg_event != INVALID_HANDLE_VALUE) {
|
||||
CloseHandle(s->load_avg_event);
|
||||
}
|
||||
|
||||
if (s->load_avg_pdh_query) {
|
||||
PdhCloseQuery(s->load_avg_pdh_query);
|
||||
}
|
||||
#endif
|
||||
if (s->command_state) {
|
||||
ga_command_state_cleanup_all(s->command_state);
|
||||
|
|
|
@ -95,7 +95,7 @@ gen_tlb = []
|
|||
qga_libs = []
|
||||
if host_os == 'windows'
|
||||
qga_libs += ['-lws2_32', '-lwinmm', '-lpowrprof', '-lwtsapi32', '-lwininet', '-liphlpapi', '-lnetapi32',
|
||||
'-lsetupapi', '-lcfgmgr32', '-luserenv']
|
||||
'-lsetupapi', '-lcfgmgr32', '-luserenv', '-lpdh' ]
|
||||
if have_qga_vss
|
||||
qga_libs += ['-lole32', '-loleaut32', '-lshlwapi', '-lstdc++', '-Wl,--enable-stdcall-fixup']
|
||||
subdir('vss-win32')
|
||||
|
|
|
@ -1880,7 +1880,7 @@
|
|||
'load5m': 'number',
|
||||
'load15m': 'number'
|
||||
},
|
||||
'if': 'CONFIG_GETLOADAVG'
|
||||
'if': { 'any': ['CONFIG_WIN32', 'CONFIG_GETLOADAVG'] }
|
||||
}
|
||||
|
||||
##
|
||||
|
@ -1888,13 +1888,18 @@
|
|||
#
|
||||
# Retrieve CPU process load information
|
||||
#
|
||||
# .. note:: Windows does not have load average API, so QGA emulates it by
|
||||
# calculating the average CPU usage in the last 1, 5, 15 minutes
|
||||
# similar as Linux does this.
|
||||
# Calculation starts from the first time this command is called.
|
||||
#
|
||||
# Returns: load information
|
||||
#
|
||||
# Since: 10.0
|
||||
##
|
||||
{ 'command': 'guest-get-load',
|
||||
'returns': 'GuestLoadAverage',
|
||||
'if': 'CONFIG_GETLOADAVG'
|
||||
'if': { 'any': ['CONFIG_WIN32', 'CONFIG_GETLOADAVG'] }
|
||||
}
|
||||
|
||||
##
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue