Introduce QMP support for querying stats. Provide a framework for adding new stats and support for the following commands:
- query-stats Returns a list of all stats per target type (only VM and vCPU to start), with additional options for specifying stat names, vCPU qom paths, and providers. - query-stats-schemas Returns a list of stats included in each target type, with an option for specifying the provider. The framework provides a method to register callbacks for these QMP commands. The first use-case will be for fd-based KVM stats (in an upcoming patch). Examples (with fd-based KVM stats): - Query all VM stats: { "execute": "query-stats", "arguments" : { "target": "vm" } } { "return": { "vm": [ { "provider": "kvm", "stats": [ { "name": "max_mmu_page_hash_collisions", "value": 0 }, { "name": "max_mmu_rmap_size", "value": 0 }, { "name": "nx_lpage_splits", "value": 148 }, ... { "provider": "xyz", "stats": [ ... ... ] } } - Query all vCPU stats: { "execute": "query-stats", "arguments" : { "target": "vcpu" } } { "return": { "vcpus": [ { "path": "/machine/unattached/device[0]" "providers": [ { "provider": "kvm", "stats": [ { "name": "guest_mode", "value": 0 }, { "name": "directed_yield_successful", "value": 0 }, { "name": "directed_yield_attempted", "value": 106 }, ... { "provider": "xyz", "stats": [ ... ... { "path": "/machine/unattached/device[1]" "providers": [ { "provider": "kvm", "stats": [... ... } ] } } - Query 'exits' and 'l1d_flush' KVM stats, and 'somestat' from provider 'xyz' for vCPUs '/machine/unattached/device[2]' and '/machine/unattached/device[4]': { "execute": "query-stats", "arguments": { "target": "vcpu", "vcpus": [ "/machine/unattached/device[2]", "/machine/unattached/device[4]" ], "filters": [ { "provider": "kvm", "fields": [ "l1d_flush", "exits" ] }, { "provider": "xyz", "fields": [ "somestat" ] } ] } } { "return": { "vcpus": [ { "path": "/machine/unattached/device[2]" "providers": [ { "provider": "kvm", "stats": [ { "name": "l1d_flush", "value": 41213 }, { "name": "exits", "value": 74291 } ] }, { "provider": "xyz", "stats": [ ... ] } ] }, { "path": "/machine/unattached/device[4]" "providers": [ { "provider": "kvm", "stats": [ { "name": "l1d_flush", "value": 16132 }, { "name": "exits", "value": 57922 } ] }, { "provider": "xyz", "stats": [ ... ] } ] } ] } } - Query stats schemas: { "execute": "query-stats-schemas" } { "return": { "vcpu": [ { "provider": "kvm", "stats": [ { "name": "guest_mode", "unit": "none", "base": 10, "exponent": 0, "type": "instant" }, { "name": "directed_yield_successful", "unit": "none", "base": 10, "exponent": 0, "type": "cumulative" }, ... { "provider": "xyz", ... "vm": [ { "provider": "kvm", "stats": [ { "name": "max_mmu_page_hash_collisions", "unit": "none", "base": 10, "exponent": 0, "type": "peak" }, { "provider": "xyz", ... Signed-off-by: Mark Kanda <mark.ka...@oracle.com> --- include/monitor/stats.h | 51 ++++++++ monitor/qmp-cmds.c | 219 +++++++++++++++++++++++++++++++++ qapi/meson.build | 1 + qapi/qapi-schema.json | 1 + qapi/stats.json | 259 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 531 insertions(+) create mode 100644 include/monitor/stats.h create mode 100644 qapi/stats.json diff --git a/include/monitor/stats.h b/include/monitor/stats.h new file mode 100644 index 0000000000..172dc01a4d --- /dev/null +++ b/include/monitor/stats.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * This work is licensed under the terms of the GNU GPL, version 2. + * See the COPYING file in the top-level directory. + */ + +#ifndef STATS_H +#define STATS_H + +#include "qapi/qapi-types-stats.h" + +/* + * Add QMP stats callbacks to the stats_callbacks list. + * + * @provider: stats provider + * + * @stats_fn: routine to query stats: + * void (*stats_fn)(StatsResults *results, StatsFilter *filter, Error **errp) + * + * @schema_fn: routine to query stat schemas: + * void (*schemas_fn)(StatsSchemaResult *results, Error **errp) + */ +void add_stats_callbacks(StatsProvider provider, + void (*stats_fn)(StatsResults *, StatsFilter *, + Error **), + void (*schemas_fn)(StatsSchemaResults *, Error **)); + +/* + * Helper routines for adding stats entries to the results lists. + */ +void add_vm_stats_entry(StatsList *, StatsResults *, StatsProvider); +void add_vcpu_stats_entry(StatsList *, StatsResults *, StatsProvider, char *); +void add_vm_stats_schema(StatsSchemaValueList *, StatsSchemaResults *, + StatsProvider); +void add_vcpu_stats_schema(StatsSchemaValueList *, StatsSchemaResults *, + StatsProvider); + +/* + * True if a stat name and provider match a filter or if no corresponding + * filters are defined. False otherwise. + */ +bool stats_requested_name(const char *, StatsProvider, StatsFilter *); + +/* + * True if a vcpu qom path and provider match a filter or if no corresponding + * filters are defined. False otherwise. + */ +bool stats_requested_vcpu(const char *, StatsProvider, StatsFilter *); + +#endif /* STATS_H */ diff --git a/monitor/qmp-cmds.c b/monitor/qmp-cmds.c index db4d186448..07f1e683be 100644 --- a/monitor/qmp-cmds.c +++ b/monitor/qmp-cmds.c @@ -36,6 +36,7 @@ #include "qapi/qapi-commands-control.h" #include "qapi/qapi-commands-machine.h" #include "qapi/qapi-commands-misc.h" +#include "qapi/qapi-commands-stats.h" #include "qapi/qapi-commands-ui.h" #include "qapi/type-helpers.h" #include "qapi/qmp/qerror.h" @@ -44,6 +45,7 @@ #include "hw/acpi/acpi_dev_interface.h" #include "hw/intc/intc.h" #include "hw/rdma/rdma.h" +#include "monitor/stats.h" NameInfo *qmp_query_name(Error **errp) { @@ -448,3 +450,220 @@ HumanReadableText *qmp_x_query_irq(Error **errp) return human_readable_text_from_str(buf); } + +typedef struct StatsCallbacks { + StatsProvider provider; + void (*stats_cb)(StatsResults *, StatsFilter *, Error **); + void (*schemas_cb)(StatsSchemaResults *, Error **); + QTAILQ_ENTRY(StatsCallbacks) next; +} StatsCallbacks; + +static QTAILQ_HEAD(, StatsCallbacks) stats_callbacks = + QTAILQ_HEAD_INITIALIZER(stats_callbacks); + +void add_stats_callbacks(StatsProvider provider, + void (*stats_fn)(StatsResults *, StatsFilter*, + Error **), + void (*schemas_fn)(StatsSchemaResults *, Error **)) +{ + StatsCallbacks *entry = g_malloc0(sizeof(*entry)); + entry->provider = provider; + entry->stats_cb = stats_fn; + entry->schemas_cb = schemas_fn; + + QTAILQ_INSERT_TAIL(&stats_callbacks, entry, next); +} + +static StatsRequestList *stats_target_filter(StatsFilter *filter) +{ + switch (filter->target) { + case STATS_TARGET_VM: + if (!filter->u.vm.has_filters) { + return NULL; + } + return filter->u.vm.filters; + case STATS_TARGET_VCPU: + if (!filter->u.vcpu.has_filters) { + return NULL; + } + return filter->u.vcpu.filters; + break; + default: + return NULL; + } +} + +static bool stats_provider_match(StatsProvider provider, + StatsRequestList *request) +{ + return (!request->value->has_provider || + (request->value->provider == provider)); +} + +static bool stats_requested_provider(StatsProvider provider, + StatsFilter *filter) +{ + StatsRequestList *request = stats_target_filter(filter); + + if (!request) { + return true; + } + for (; request; request = request->next) { + if (stats_provider_match(provider, request)) { + return true; + } + } + return false; +} + +StatsResults *qmp_query_stats(StatsFilter *filter, Error **errp) +{ + StatsResults *stats_results = g_malloc0(sizeof(*stats_results)); + StatsCallbacks *entry; + + QTAILQ_FOREACH(entry, &stats_callbacks, next) { + if (stats_requested_provider(entry->provider, filter)) { + entry->stats_cb(stats_results, filter, errp); + } + } + + return stats_results; +} + +StatsSchemaResults *qmp_query_stats_schemas(bool has_provider, + StatsProvider provider, + Error **errp) +{ + StatsSchemaResults *stats_results = g_malloc0(sizeof(*stats_results)); + StatsCallbacks *entry; + + QTAILQ_FOREACH(entry, &stats_callbacks, next) { + if (has_provider && (provider != entry->provider)) { + continue; + } + entry->schemas_cb(stats_results, errp); + } + + return stats_results; +} + +void add_vm_stats_entry(StatsList *stats_list, StatsResults *stats_results, + StatsProvider provider) +{ + StatsResultsEntry *entry = g_malloc0(sizeof(*entry)); + entry->provider = provider; + entry->stats = stats_list; + + QAPI_LIST_PREPEND(stats_results->vm, entry); + stats_results->has_vm = true; +} + +void add_vcpu_stats_entry(StatsList *stats_list, StatsResults *stats_results, + StatsProvider provider, char *path) +{ + StatsResultsVCPUEntry *value; + StatsResultsEntry *entry; + StatsResultsVCPUEntryList **tailp, *tail; + + entry = g_malloc0(sizeof(*entry)); + entry->provider = provider; + entry->stats = stats_list; + + /* Find the vCPU entry and add to its list; else create it */ + tailp = &stats_results->vcpus; + + for (tail = *tailp; tail; tail = tail->next) { + if (g_str_equal(tail->value->path, path)) { + /* Add to the existing vCPU list */ + QAPI_LIST_PREPEND(tail->value->providers, entry); + return; + } + tailp = &tail->next; + } + + /* Create and populate a new vCPU entry */ + value = g_malloc0(sizeof(*value)); + value->path = g_strdup(path); + value->providers = g_malloc0(sizeof(*value->providers)); + value->providers->value = entry; + QAPI_LIST_APPEND(tailp, value); + stats_results->has_vcpus = true; +} + +void add_vm_stats_schema(StatsSchemaValueList *stats_list, + StatsSchemaResults *schema_results, + StatsProvider provider) +{ + StatsSchemaProvider *entry = g_malloc0(sizeof(*entry)); + + entry->provider = provider; + entry->stats = stats_list; + QAPI_LIST_PREPEND(schema_results->vm, entry); + schema_results->has_vm = true; +} + +void add_vcpu_stats_schema(StatsSchemaValueList *stats_list, + StatsSchemaResults *schema_results, + StatsProvider provider) +{ + StatsSchemaProvider *entry = g_malloc0(sizeof(*entry)); + + entry->provider = provider; + entry->stats = stats_list; + QAPI_LIST_PREPEND(schema_results->vcpu, entry); + schema_results->has_vcpu = true; +} + +static bool str_in_list(const char *name, strList *list) +{ + strList *str_list = NULL; + + for (str_list = list; str_list; str_list = str_list->next) { + if (g_str_equal(name, str_list->value)) { + return true; + } + } + return false; +} + +bool stats_requested_name(const char *name, StatsProvider provider, + StatsFilter *filter) +{ + StatsRequestList *request = stats_target_filter(filter); + + if (!request) { + return true; + } + for (; request; request = request->next) { + if (!stats_provider_match(provider, request)) { + continue; + } + if (!request->value->has_fields || + str_in_list(name, request->value->fields)) { + return true; + } + } + return false; +} + +bool stats_requested_vcpu(const char *path, StatsProvider provider, + StatsFilter *filter) +{ + StatsRequestList *request = stats_target_filter(filter); + + if (!request) { + return true; + } + if (!filter->u.vcpu.has_filters) { + return true; + } + if (filter->u.vcpu.has_vcpus && !str_in_list(path, filter->u.vcpu.vcpus)) { + return false; + } + for (; request; request = request->next) { + if (stats_provider_match(provider, request)) { + return true; + } + } + return false; +} diff --git a/qapi/meson.build b/qapi/meson.build index 656ef0e039..fd5c93d643 100644 --- a/qapi/meson.build +++ b/qapi/meson.build @@ -46,6 +46,7 @@ qapi_all_modules = [ 'replay', 'run-state', 'sockets', + 'stats', 'trace', 'transaction', 'yank', diff --git a/qapi/qapi-schema.json b/qapi/qapi-schema.json index 4912b9744e..92d7ecc52c 100644 --- a/qapi/qapi-schema.json +++ b/qapi/qapi-schema.json @@ -93,3 +93,4 @@ { 'include': 'audio.json' } { 'include': 'acpi.json' } { 'include': 'pci.json' } +{ 'include': 'stats.json' } diff --git a/qapi/stats.json b/qapi/stats.json new file mode 100644 index 0000000000..ae5dc3ee2c --- /dev/null +++ b/qapi/stats.json @@ -0,0 +1,259 @@ +# -*- Mode: Python -*- +# vim: filetype=python +# +# Copyright (c) 2022 Oracle and/or its affiliates. +# +# This work is licensed under the terms of the GNU GPL, version 2 or later. +# See the COPYING file in the top-level directory. +# +# SPDX-License-Identifier: GPL-2.0-or-later + +## +# = Stats +## + +## +# @StatsType: +# +# Enumeration of stats types +# @cumulative: stat is cumulative; value can only increase. +# @instant: stat is instantaneous; value can increase or decrease. +# @peak: stat is the peak value; value can only increase. +# @linear-hist: stat is a linear histogram. +# @log-hist: stat is a logarithmic histogram. +# +# Since: 7.0 +## +{ 'enum' : 'StatsType', + 'data' : [ 'cumulative', 'instant', 'peak', 'linear-hist', 'log-hist' ] } + +## +# @StatsUnit: +# +# Enumeration of stats units +# @bytes: stat reported in bytes. +# @seconds: stat reported in seconds. +# @cycles: stat reported in clock cycles. +# @none: no unit for this stat. +# +# Since: 7.0 +## +{ 'enum' : 'StatsUnit', + 'data' : [ 'bytes', 'seconds', 'cycles', 'none' ] } + +## +# @StatsBase: +# +# Enumeration of stats base +# @pow10: scale is based on power of 10. +# @pow2: scale is based on power of 2. +# +# Since: 7.0 +## +{ 'enum' : 'StatsBase', + 'data' : [ 'pow10', 'pow2' ] } + +## +# @StatsProvider: +# +# Enumeration of stats providers. +# +# Since: 7.0 +## +{ 'enum': 'StatsProvider', + 'data': [ ] } + +## +# @StatsTarget: +# +# Enumeration of stats targets. +# @vm: stat is per vm. +# @vcpu: stat is per vcpu. +# +# Since: 7.0 +## +{ 'enum': 'StatsTarget', + 'data': [ 'vm', 'vcpu' ] } + +## +# @StatsRequest: +# +# Stats filter element. +# @provider: stat provider. +# @fields: list of stat names. +# +# Since: 7.0 +## +{ 'struct': 'StatsRequest', + 'data': { '*provider': 'StatsProvider', + '*fields': [ 'str' ] } } + +## +# @StatsRequestArray: +# +# @filters: filters for this request. +## +{ 'struct': 'StatsRequestArray', + 'data': { '*filters': [ 'StatsRequest' ] } } + +## +# @StatsVCPURequestArray: +# +# @vcpus: list of qom paths. +## +{ 'struct': 'StatsVCPURequestArray', + 'base': 'StatsRequestArray', + 'data': { '*vcpus': [ 'str' ] } } + +## +# @StatsFilter: +# +# Target specific filter. +# +# Since: 7.0 +## +{ 'union': 'StatsFilter', + 'base': { 'target': 'StatsTarget' }, + 'discriminator': 'target', + 'data': { 'vcpu': 'StatsVCPURequestArray', + 'vm': 'StatsRequestArray' } } + +## +# @StatsValueArray: +# +# @values: uint64 list. +# +# Since: 7.0 +## +{ 'struct': 'StatsValueArray', + 'data': { 'values' : [ 'uint64' ] } } + +## +# @StatsValue: +# +# @scalar: single uint64. +# @list: list of uint64. +# +# Since: 7.0 +## +{ 'alternate': 'StatsValue', + 'data': { 'scalar': 'uint64', + 'list': 'StatsValueArray' } } + +## +# @Stats: +# +# @name: name of stat. +# @value: stat value. +# +# Since: 7.0 +## +{ 'struct': 'Stats', + 'data': { 'name': 'str', + 'value' : 'StatsValue' } } + +## +# @StatsResultsEntry: +# +# @provider: stat provider. +# @stats: list of stats. +# +# Since: 7.0 +## +{ 'struct': 'StatsResultsEntry', + 'data': { 'provider': 'StatsProvider', + 'stats': [ 'Stats' ] } } + +## +# @StatsResultsVCPUEntry: +# +# @path: vCPU qom path. +# @providers: per provider results. +# +# Since: 7.0 +## +{ 'struct': 'StatsResultsVCPUEntry', + 'data': { 'path': 'str', + 'providers': [ 'StatsResultsEntry' ] } } + +## +# @StatsResults: +# +# Target specific results. +# +# Since: 7.0 +## +{ 'struct': 'StatsResults', + 'data': { + '*vcpus': [ 'StatsResultsVCPUEntry' ], + '*vm': [ 'StatsResultsEntry' ] } } + +## +# @query-stats: +# +# data: @StatsFilter. +# Returns: @StatsResults. +# +# Since: 7.0 +## +{ 'command': 'query-stats', + 'data': 'StatsFilter', + 'boxed': true, + 'returns': 'StatsResults' } + +## +# @StatsSchemaValue: +# +# Individual stat schema. +# @name: stat name. +# @type: @StatType. +# @unit: @StatUnit. +# @base: @StatBase. +# @exponent: Used together with @base. +# @bucket-size: Used with linear-hist to report bucket size +# +# Since: 7.0 +## +{ 'struct': 'StatsSchemaValue', + 'data': { 'name': 'str', + 'type': 'StatsType', + 'unit': 'StatsUnit', + 'base': 'StatsBase', + 'exponent': 'int16', + '*bucket-size': 'uint32' } } + +## +# @StatsSchemaProvider: +# +# @provider: @StatsProvider. +# @stats: list of stats. +# +# Since: 7.0 +## +{ 'struct': 'StatsSchemaProvider', + 'data': { 'provider': 'StatsProvider', + 'stats': [ 'StatsSchemaValue' ] } } + +## +# @StatsSchemaResults: +# +# @vm: vm stats schemas. +# @vcpu: vcpu stats schemas. +# +# Since: 7.0 +## +{ 'struct': 'StatsSchemaResults', + 'data': { '*vm': [ 'StatsSchemaProvider' ], + '*vcpu': [ 'StatsSchemaProvider' ] } } + +## +# @query-stats-schemas: +# +# Query Stats schemas. +# Returns @StatsSchemaResult. +# +# Since: 7.0 +## +{ 'command': 'query-stats-schemas', + 'data': { '*provider': 'StatsProvider' }, + 'returns': 'StatsSchemaResults' } -- 2.27.0