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 for now), with additional options for specifying stat names, VCPU qom paths, and stat provider. - query-stats-schemas Returns a list of stats included in each schema type, with an option for specifying the stat provider. The framework provides a method to register callbacks for these QMP commands. The first usecase will be for fd-based KVM stats (in an upcoming patch). Examples (with fd-based KVM stats): - Display all VM stats: { "execute": "query-stats", "arguments" : { "target": "vm" } } { "return": { "list": [ { "provider": "kvm", "stats": [ { "name": "max_mmu_page_hash_collisions", "value": 0 }, { "name": "max_mmu_rmap_size", "value": 0 }, { "name": "nx_lpage_splits", "value": 131 }, ... ] } { "provider": "provider XYZ", ... ], "target": "vm" } } - Display all VCPU stats: { "execute": "query-stats", "arguments" : { "target": "vcpu" } } { "return": { "list": [ { "list": [ { "provider": "kvm", "stats": [ { "name": "guest_mode", "value": 0 }, { "name": "directed_yield_successful", "value": 0 }, { "name": "directed_yield_attempted", "value": 76 }, ... ] } { "provider": "provider XYZ", ... ], "path": "/machine/unattached/device[0]" }, { "list": [ { "provider": "kvm", "stats": [ { "name": "guest_mode", "value": 0 }, { "name": "directed_yield_successful", "value": 0 }, { "name": "directed_yield_attempted", "value": 51 }, ... } ], "target": "vcpu" } } - Display 'exits' and 'l1d_flush' KVM stats for VCPUs at '/machine/unattached/device[2]' and '/machine/unattached/device[4]': { "execute": "query-stats", "arguments" : { "target": "vcpu", "fields": [ "exits", "l1d_flush" ], "paths": [ "/machine/unattached/device[2]", "/machine/unattached/device[4]" ] "provider": "kvm" } } { "return": { "list": [ { "list": [ { "provider": "kvm", "stats": [ { "name": "l1d_flush", "value": 14690 }, { "name": "exits", "value": 50898 } ] } ], "path": "/machine/unattached/device[2]" }, { "list": [ { "provider": "kvm", "stats": [ { "name": "l1d_flush", "value": 24902 }, { "name": "exits", "value": 74374 } ] } ], "path": "/machine/unattached/device[4]" } ], "target": "vcpu" } } - 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": "provider XYZ", ... "vm": [ { "provider": "kvm", "stats": [ { "name": "max_mmu_page_hash_collisions", "unit": "none", "base": 10, "exponent": 0, "type": "peak" }, "provider": "provider XYZ", ... Signed-off-by: Mark Kanda <mark.ka...@oracle.com> --- include/monitor/stats.h | 36 ++++++ monitor/qmp-cmds.c | 183 +++++++++++++++++++++++++++++ qapi/misc.json | 253 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 472 insertions(+) create mode 100644 include/monitor/stats.h diff --git a/include/monitor/stats.h b/include/monitor/stats.h new file mode 100644 index 0000000000..d4b57322eb --- /dev/null +++ b/include/monitor/stats.h @@ -0,0 +1,36 @@ +/* + * 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 + +/* + * 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)(StatsSchemaResult *, Error **)); + +/* Stats helpers routines */ +StatsResultsEntry *add_vm_stats_entry(StatsResults *, StatsProvider); +StatsResultsEntry *add_vcpu_stats_entry(StatsResults *, StatsProvider, char *); +StatsSchemaProvider *add_vm_stats_schema(StatsSchemaResult *, StatsProvider); +StatsSchemaProvider *add_vcpu_stats_schema(StatsSchemaResult *, StatsProvider); + +bool stat_name_filter(StatsFilter *, StatsTarget, char *); +bool stat_cpu_filter(StatsFilter *, char *); + +#endif /* STATS_H */ diff --git a/monitor/qmp-cmds.c b/monitor/qmp-cmds.c index db4d186448..bd562edfb8 100644 --- a/monitor/qmp-cmds.c +++ b/monitor/qmp-cmds.c @@ -18,6 +18,7 @@ #include "qemu/cutils.h" #include "qemu/option.h" #include "monitor/monitor.h" +#include "monitor/stats.h" #include "sysemu/sysemu.h" #include "qemu/config-file.h" #include "qemu/uuid.h" @@ -448,3 +449,185 @@ 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)(StatsSchemaResult *, 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)(StatsSchemaResult *, 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); +} + +StatsResults *qmp_query_stats(StatsFilter *filter, Error **errp) +{ + StatsResults *stats_results = g_malloc0(sizeof(*stats_results)); + StatsCallbacks *entry; + StatsProvider provider = STATS_PROVIDER__MAX; + + if (filter->target == STATS_TARGET_VM && + filter->u.vm.has_provider) { + provider = filter->u.vm.provider; + } else if (filter->target == STATS_TARGET_VCPU && + filter->u.vcpu.has_provider) { + provider = filter->u.vcpu.provider; + } + + QTAILQ_FOREACH(entry, &stats_callbacks, next) { + if (provider != STATS_PROVIDER__MAX && (provider != entry->provider)) { + continue; + } + entry->stats_cb(stats_results, filter, errp); + } + + return stats_results; +} + +StatsSchemaResult *qmp_query_stats_schemas(bool has_provider, + StatsProvider provider, + Error **errp) +{ + StatsSchemaResult *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; +} + +StatsResultsEntry *add_vm_stats_entry(StatsResults *stats_results, + StatsProvider provider) +{ + StatsResultsEntry *entry = g_malloc0(sizeof(*entry)); + stats_results->target = STATS_TARGET_VM; + entry->provider = provider; + QAPI_LIST_PREPEND(stats_results->u.vm.list, entry); + + return entry; +} + +StatsResultsEntry *add_vcpu_stats_entry(StatsResults *stats_results, + StatsProvider provider, + char *path) +{ + StatsResultsEntry *entry = g_malloc0(sizeof(*entry)); + stats_results->target = STATS_TARGET_VCPU; + entry->provider = provider; + + /* + * Find the corresponding vCPU entry and add to its list. Else, create it. + */ + VCPUResultsEntryList **tailp = &stats_results->u.vcpu.list; + VCPUResultsEntryList *tail; + + 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->list, entry); + goto done; + } + tailp = &tail->next; + } + /* Create and populate a new entry for the vCPU */ + VCPUResultsEntry *value = g_malloc0(sizeof(*value)); + value->path = g_strdup(path); + value->list = g_malloc0(sizeof(*value->list)); + value->list->value = entry; + QAPI_LIST_APPEND(tailp, value); + +done: + return entry; +} + +StatsSchemaProvider *add_vm_stats_schema(StatsSchemaResult *schema_results, + StatsProvider provider) +{ + StatsSchemaProvider *entry = g_malloc0(sizeof(*entry)); + schema_results->has_vm = true; + entry->provider = provider; + QAPI_LIST_PREPEND(schema_results->vm, entry); + + return entry; +} + +StatsSchemaProvider *add_vcpu_stats_schema(StatsSchemaResult *schema_results, + StatsProvider provider) +{ + StatsSchemaProvider *entry = g_malloc0(sizeof(*entry)); + schema_results->has_vcpu = true; + entry->provider = provider; + QAPI_LIST_PREPEND(schema_results->vcpu, entry); + + return entry; +} + +/* True if stat doesn't match a requested name */ +bool stat_name_filter(StatsFilter *filter, StatsTarget type, char *name) +{ + strList *str_list = NULL; + + if (type == STATS_TARGET_VM) { + if (filter->target != STATS_TARGET_VM) { + return false; + } + if (!filter->u.vm.has_fields) { + return false; + } + str_list = filter->u.vm.fields; + } else if (type == STATS_TARGET_VCPU) { + if (filter->target != STATS_TARGET_VCPU) { + return false; + } + if (!filter->u.vcpu.has_fields) { + return false; + } + str_list = filter->u.vcpu.fields; + } + + for (; str_list; str_list = str_list->next) { + if (g_str_equal(name, str_list->value)) { + return false; + } + } + return true; +} + +/* True if cpu qom path doesn't match a requested path */ +bool stat_cpu_filter(StatsFilter *filter, char *path) +{ + strList *list; + + if (filter->target != STATS_TARGET_VCPU) { + return false; + } + + if (!filter->u.vcpu.has_paths) { + return false; + } + + for (list = filter->u.vcpu.paths; list; list = list->next) { + if (g_str_equal(list->value, path)) { + return false; + } + } + return true; +} diff --git a/qapi/misc.json b/qapi/misc.json index e8054f415b..8d326464f0 100644 --- a/qapi/misc.json +++ b/qapi/misc.json @@ -527,3 +527,256 @@ 'data': { '*option': 'str' }, 'returns': ['CommandLineOptionInfo'], 'allow-preconfig': true } + +## +# @StatType: +# +# Enumeration of stat 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. +# +# Since: 7.0 +## +{ 'enum' : 'StatType', + 'data' : [ 'cumulative', 'instant', 'peak', + 'linear-hist', 'log-hist', 'unknown' ] } + +## +# @StatUnit: +# +# Enumeration of stat 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' : 'StatUnit', + 'data' : [ 'none', 'bytes', 'seconds', 'cycles', 'unknown' ] } + +## +# @StatBase: +# +# Enumeration of stat base +# +# Since: 7.0 +## +{ 'enum' : 'StatBase', + 'data' : [ 'pow10', 'pow2', 'unknown' ] } + +## +# @StatsProvider: +# +# Enumeration of stat providers. +# +# Since: 7.0 +## +{ 'enum': 'StatsProvider', + 'data': [ ] } + +## +# @StatsTarget: +# +# Enumeration of stat targets. +# @vm: stat is per vm. +# @vcpu: stat is per vcpu. +# +# Since: 7.0 +## +{ 'enum': 'StatsTarget', + 'data': [ 'vm', 'vcpu' ] } + +## +# @StatsVCPURequest: +# +# vcpu specific filter element. +# @paths: list of qom paths. +# +# Since: 7.0 +## +{ 'struct': 'StatsVCPURequest', + 'base': 'StatsRequest', + 'data': { '*paths': [ 'str' ] } } + +## +# @StatsRequest: +# +# Stats filter element. +# @provider: stat provider. +# @fields: list of stat names. +# +# Since: 7.0 +## +{ 'struct': 'StatsRequest', + 'data': { '*provider': 'StatsProvider', + '*fields': [ 'str' ] } } + +## +# @StatsFilter: +# +# Target specific filter. +# +# Since: 7.0 +## +{ 'union': 'StatsFilter', + 'base': { 'target': 'StatsTarget' }, + 'discriminator': 'target', + 'data': { 'vcpu': 'StatsVCPURequest', + 'vm': 'StatsRequest' } } + +## +# @StatsValueArray: +# +# uint64 list for StatsValue. +# +# Since: 7.0 +## +{ 'struct': 'StatsValueArray', + 'data': { 'list' : [ 'uint64' ] } } + +## +# @StatsValue: +# +# @scalar: stat is single uint64. +# @list: stat is a 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' ] } } + +## +# @VCPUResultsEntry: +# +# @path: qom path. +# @list: per provider @StatsResultEntry list. +# +# Since: 7.0 +## +{ 'struct': 'VCPUResultsEntry', + 'data': { 'path': 'str', + 'list': [ 'StatsResultsEntry' ] } } + +## +# @VMStatsResults: +# +# Since: 7.0 +## +{ 'struct': 'VMStatsResults', + 'data': { 'list' : [ 'StatsResultsEntry' ] } } + +## +# @VCPUStatsResults: +# +# Since: 7.0 +## +{ 'struct': 'VCPUStatsResults', + 'data': { 'list': [ 'VCPUResultsEntry' ] } } + +## +# @StatsResults: +# +# Target specific results. +# +# Since: 7.0 +## +{ 'union': 'StatsResults', + 'base': { 'target': 'StatsTarget' }, + 'discriminator': 'target', + 'data': { 'vcpu': 'VCPUStatsResults', + 'vm': 'VMStatsResults' } } + +## +# @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. +# +# Since: 7.0 +## +{ 'struct': 'StatsSchemaValue', + 'data': { 'name': 'str', + 'type': 'StatType', + 'unit': 'StatUnit', + 'base': 'StatBase', + 'exponent': 'int16' } } + +## +# @StatsSchemaProvider: +# +# @provider: @StatsProvider. +# @stats: list of stats. +# +# Since: 7.0 +## +{ 'struct': 'StatsSchemaProvider', + 'data': { 'provider': 'StatsProvider', + 'stats': [ 'StatsSchemaValue' ] } } + +## +# @StatsSchemaResult: +# +# @vm: vm stats schemas. +# @vcpu: vcpu stats schemas. +# +# Since: 7.0 +## +{ 'struct': 'StatsSchemaResult', + 'data': { '*vm': [ 'StatsSchemaProvider' ], + '*vcpu': [ 'StatsSchemaProvider' ] } } + +## +# @query-stats-schemas: +# +# Query Stats schemas. +# Returns @StatsSchemaResult +# +# Since: 7.0 +## +{ 'command': 'query-stats-schemas', + 'data': { '*provider': 'StatsProvider' }, + 'returns': 'StatsSchemaResult' } -- 2.27.0