From 433815f5bdf71b5b2c47d9f1104816b95b551c49 Mon Sep 17 00:00:00 2001 From: Mark Kanda Date: Tue, 26 Apr 2022 12:17:35 +0200 Subject: hmp: add basic "info stats" implementation Add an HMP command to retrieve statistics collected at run-time. The command will retrieve and print either all VM-level statistics, or all vCPU-level statistics for the currently selected CPU. Reviewed-by: Dr. David Alan Gilbert Signed-off-by: Paolo Bonzini --- monitor/hmp-cmds.c | 190 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) (limited to 'monitor/hmp-cmds.c') diff --git a/monitor/hmp-cmds.c b/monitor/hmp-cmds.c index 622c783c32..04d5ee8fb7 100644 --- a/monitor/hmp-cmds.c +++ b/monitor/hmp-cmds.c @@ -40,6 +40,7 @@ #include "qapi/qapi-commands-pci.h" #include "qapi/qapi-commands-rocker.h" #include "qapi/qapi-commands-run-state.h" +#include "qapi/qapi-commands-stats.h" #include "qapi/qapi-commands-tpm.h" #include "qapi/qapi-commands-ui.h" #include "qapi/qapi-visit-net.h" @@ -52,6 +53,7 @@ #include "ui/console.h" #include "qemu/cutils.h" #include "qemu/error-report.h" +#include "hw/core/cpu.h" #include "hw/intc/intc.h" #include "migration/snapshot.h" #include "migration/misc.h" @@ -2239,3 +2241,191 @@ void hmp_info_memory_size_summary(Monitor *mon, const QDict *qdict) } hmp_handle_error(mon, err); } + +static void print_stats_schema_value(Monitor *mon, StatsSchemaValue *value) +{ + const char *unit = NULL; + monitor_printf(mon, " %s (%s%s", value->name, StatsType_str(value->type), + value->has_unit || value->exponent ? ", " : ""); + + if (value->has_unit) { + if (value->unit == STATS_UNIT_SECONDS) { + unit = "s"; + } else if (value->unit == STATS_UNIT_BYTES) { + unit = "B"; + } + } + + if (unit && value->base == 10 && + value->exponent >= -18 && value->exponent <= 18 && + value->exponent % 3 == 0) { + monitor_printf(mon, "%s", si_prefix(value->exponent)); + } else if (unit && value->base == 2 && + value->exponent >= 0 && value->exponent <= 60 && + value->exponent % 10 == 0) { + + monitor_printf(mon, "%s", iec_binary_prefix(value->exponent)); + } else if (value->exponent) { + /* Use exponential notation and write the unit's English name */ + monitor_printf(mon, "* %d^%d%s", + value->base, value->exponent, + value->has_unit ? " " : ""); + unit = NULL; + } + + if (value->has_unit) { + monitor_printf(mon, "%s", unit ? unit : StatsUnit_str(value->unit)); + } + + /* Print bucket size for linear histograms */ + if (value->type == STATS_TYPE_LINEAR_HISTOGRAM && value->has_bucket_size) { + monitor_printf(mon, ", bucket size=%d", value->bucket_size); + } + monitor_printf(mon, ")"); +} + +static StatsSchemaValueList *find_schema_value_list( + StatsSchemaList *list, StatsProvider provider, + StatsTarget target) +{ + StatsSchemaList *node; + + for (node = list; node; node = node->next) { + if (node->value->provider == provider && + node->value->target == target) { + return node->value->stats; + } + } + return NULL; +} + +static void print_stats_results(Monitor *mon, StatsTarget target, + StatsResult *result, + StatsSchemaList *schema) +{ + /* Find provider schema */ + StatsSchemaValueList *schema_value_list = + find_schema_value_list(schema, result->provider, target); + StatsList *stats_list; + + if (!schema_value_list) { + monitor_printf(mon, "failed to find schema list for %s\n", + StatsProvider_str(result->provider)); + return; + } + + monitor_printf(mon, "provider: %s\n", + StatsProvider_str(result->provider)); + + for (stats_list = result->stats; stats_list; + stats_list = stats_list->next, + schema_value_list = schema_value_list->next) { + + Stats *stats = stats_list->value; + StatsValue *stats_value = stats->value; + StatsSchemaValue *schema_value = schema_value_list->value; + + /* Find schema entry */ + while (!g_str_equal(stats->name, schema_value->name)) { + if (!schema_value_list->next) { + monitor_printf(mon, "failed to find schema entry for %s\n", + stats->name); + return; + } + schema_value_list = schema_value_list->next; + schema_value = schema_value_list->value; + } + + print_stats_schema_value(mon, schema_value); + + if (stats_value->type == QTYPE_QNUM) { + monitor_printf(mon, ": %" PRId64 "\n", stats_value->u.scalar); + } else if (stats_value->type == QTYPE_QLIST) { + uint64List *list; + int i; + + monitor_printf(mon, ": "); + for (list = stats_value->u.list, i = 1; + list; + list = list->next, i++) { + monitor_printf(mon, "[%d]=%" PRId64 " ", i, list->value); + } + monitor_printf(mon, "\n"); + } + } +} + +/* Create the StatsFilter that is needed for an "info stats" invocation. */ +static StatsFilter *stats_filter(StatsTarget target, int cpu_index) +{ + StatsFilter *filter = g_malloc0(sizeof(*filter)); + + filter->target = target; + switch (target) { + case STATS_TARGET_VM: + break; + case STATS_TARGET_VCPU: + { + strList *vcpu_list = NULL; + CPUState *cpu = qemu_get_cpu(cpu_index); + char *canonical_path = object_get_canonical_path(OBJECT(cpu)); + + QAPI_LIST_PREPEND(vcpu_list, canonical_path); + filter->u.vcpu.has_vcpus = true; + filter->u.vcpu.vcpus = vcpu_list; + break; + } + default: + break; + } + return filter; +} + +void hmp_info_stats(Monitor *mon, const QDict *qdict) +{ + const char *target_str = qdict_get_str(qdict, "target"); + StatsTarget target; + Error *err = NULL; + g_autoptr(StatsSchemaList) schema = NULL; + g_autoptr(StatsResultList) stats = NULL; + g_autoptr(StatsFilter) filter = NULL; + StatsResultList *entry; + + target = qapi_enum_parse(&StatsTarget_lookup, target_str, -1, &err); + if (err) { + monitor_printf(mon, "invalid stats target %s\n", target_str); + goto exit_no_print; + } + + schema = qmp_query_stats_schemas(&err); + if (err) { + goto exit; + } + + switch (target) { + case STATS_TARGET_VM: + filter = stats_filter(target, -1); + break; + case STATS_TARGET_VCPU: {} + int cpu_index = monitor_get_cpu_index(mon); + filter = stats_filter(target, cpu_index); + break; + default: + abort(); + } + + stats = qmp_query_stats(filter, &err); + if (err) { + goto exit; + } + for (entry = stats; entry; entry = entry->next) { + print_stats_results(mon, target, entry->value, schema); + } + +exit: + if (err) { + monitor_printf(mon, "%s\n", error_get_pretty(err)); + } +exit_no_print: + error_free(err); +} -- cgit v1.2.3-55-g7522 From 068cc51d42f771d2a453d628c10e199e7d104edd Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Tue, 26 Apr 2022 11:49:33 +0200 Subject: qmp: add filtering of statistics by provider Allow retrieving the statistics from a specific provider only. This can be used in the future by HMP commands such as "info sync-profile" or "info profile". The next patch also adds filter-by-provider capabilities to the HMP equivalent of query-stats, "info stats". Example: { "execute": "query-stats", "arguments": { "target": "vm", "providers": [ { "provider": "kvm" } ] } } The QAPI is a bit more verbose than just a list of StatsProvider, so that it can be subsequently extended with filtering of statistics by name. If a provider is specified more than once in the filter, each request will be included separately in the output. Extracted from a patch by Mark Kanda. Reviewed-by: Markus Armbruster Reviewed-by: Dr. David Alan Gilbert Signed-off-by: Paolo Bonzini --- accel/kvm/kvm-all.c | 3 ++- include/monitor/stats.h | 4 +++- monitor/hmp-cmds.c | 2 +- monitor/qmp-cmds.c | 41 ++++++++++++++++++++++++++++++++--------- qapi/stats.json | 19 +++++++++++++++++-- 5 files changed, 55 insertions(+), 14 deletions(-) (limited to 'monitor/hmp-cmds.c') diff --git a/accel/kvm/kvm-all.c b/accel/kvm/kvm-all.c index 547de842fd..2e819beaeb 100644 --- a/accel/kvm/kvm-all.c +++ b/accel/kvm/kvm-all.c @@ -2644,7 +2644,8 @@ static int kvm_init(MachineState *ms) } if (kvm_check_extension(kvm_state, KVM_CAP_BINARY_STATS_FD)) { - add_stats_callbacks(query_stats_cb, query_stats_schemas_cb); + add_stats_callbacks(STATS_PROVIDER_KVM, query_stats_cb, + query_stats_schemas_cb); } return 0; diff --git a/include/monitor/stats.h b/include/monitor/stats.h index 8c50feeaa9..80a523dd29 100644 --- a/include/monitor/stats.h +++ b/include/monitor/stats.h @@ -17,10 +17,12 @@ typedef void SchemaRetrieveFunc(StatsSchemaList **result, Error **errp); /* * Register callbacks for the QMP query-stats command. * + * @provider: stats provider checked against QMP command arguments * @stats_fn: routine to query stats: * @schema_fn: routine to query stat schemas: */ -void add_stats_callbacks(StatRetrieveFunc *stats_fn, +void add_stats_callbacks(StatsProvider provider, + StatRetrieveFunc *stats_fn, SchemaRetrieveFunc *schemas_fn); /* diff --git a/monitor/hmp-cmds.c b/monitor/hmp-cmds.c index 04d5ee8fb7..9180cf1841 100644 --- a/monitor/hmp-cmds.c +++ b/monitor/hmp-cmds.c @@ -2397,7 +2397,7 @@ void hmp_info_stats(Monitor *mon, const QDict *qdict) goto exit_no_print; } - schema = qmp_query_stats_schemas(&err); + schema = qmp_query_stats_schemas(false, STATS_PROVIDER__MAX, &err); if (err) { goto exit; } diff --git a/monitor/qmp-cmds.c b/monitor/qmp-cmds.c index 5f8f1e620b..e49ab345d7 100644 --- a/monitor/qmp-cmds.c +++ b/monitor/qmp-cmds.c @@ -445,6 +445,7 @@ HumanReadableText *qmp_x_query_irq(Error **errp) } typedef struct StatsCallbacks { + StatsProvider provider; StatRetrieveFunc *stats_cb; SchemaRetrieveFunc *schemas_cb; QTAILQ_ENTRY(StatsCallbacks) next; @@ -453,10 +454,12 @@ typedef struct StatsCallbacks { static QTAILQ_HEAD(, StatsCallbacks) stats_callbacks = QTAILQ_HEAD_INITIALIZER(stats_callbacks); -void add_stats_callbacks(StatRetrieveFunc *stats_fn, +void add_stats_callbacks(StatsProvider provider, + StatRetrieveFunc *stats_fn, SchemaRetrieveFunc *schemas_fn) { StatsCallbacks *entry = g_new(StatsCallbacks, 1); + entry->provider = provider; entry->stats_cb = stats_fn; entry->schemas_cb = schemas_fn; @@ -465,12 +468,18 @@ void add_stats_callbacks(StatRetrieveFunc *stats_fn, static bool invoke_stats_cb(StatsCallbacks *entry, StatsResultList **stats_results, - StatsFilter *filter, + StatsFilter *filter, StatsRequest *request, Error **errp) { strList *targets = NULL; ERRP_GUARD(); + if (request) { + if (request->provider != entry->provider) { + return true; + } + } + switch (filter->target) { case STATS_TARGET_VM: break; @@ -500,27 +509,41 @@ StatsResultList *qmp_query_stats(StatsFilter *filter, Error **errp) { StatsResultList *stats_results = NULL; StatsCallbacks *entry; + StatsRequestList *request; QTAILQ_FOREACH(entry, &stats_callbacks, next) { - if (!invoke_stats_cb(entry, &stats_results, filter, errp)) { - break; + if (filter->has_providers) { + for (request = filter->providers; request; request = request->next) { + if (!invoke_stats_cb(entry, &stats_results, filter, + request->value, errp)) { + break; + } + } + } else { + if (!invoke_stats_cb(entry, &stats_results, filter, NULL, errp)) { + break; + } } } return stats_results; } -StatsSchemaList *qmp_query_stats_schemas(Error **errp) +StatsSchemaList *qmp_query_stats_schemas(bool has_provider, + StatsProvider provider, + Error **errp) { StatsSchemaList *stats_results = NULL; StatsCallbacks *entry; ERRP_GUARD(); QTAILQ_FOREACH(entry, &stats_callbacks, next) { - entry->schemas_cb(&stats_results, errp); - if (*errp) { - qapi_free_StatsSchemaList(stats_results); - return NULL; + if (!has_provider || provider == entry->provider) { + entry->schemas_cb(&stats_results, errp); + if (*errp) { + qapi_free_StatsSchemaList(stats_results); + return NULL; + } } } diff --git a/qapi/stats.json b/qapi/stats.json index 8c9abb57f1..503918ea4c 100644 --- a/qapi/stats.json +++ b/qapi/stats.json @@ -69,6 +69,18 @@ { 'enum': 'StatsTarget', 'data': [ 'vm', 'vcpu' ] } +## +# @StatsRequest: +# +# Indicates a set of statistics that should be returned by query-stats. +# +# @provider: provider for which to return statistics. +# +# Since: 7.1 +## +{ 'struct': 'StatsRequest', + 'data': { 'provider': 'StatsProvider' } } + ## # @StatsVCPUFilter: # @@ -86,11 +98,14 @@ # request statistics and optionally the required subset of information for # that target: # - which vCPUs to request statistics for +# - which providers to request statistics from # # Since: 7.1 ## { 'union': 'StatsFilter', - 'base': { 'target': 'StatsTarget' }, + 'base': { + 'target': 'StatsTarget', + '*providers': [ 'StatsRequest' ] }, 'discriminator': 'target', 'data': { 'vcpu': 'StatsVCPUFilter' } } @@ -226,5 +241,5 @@ # Since: 7.1 ## { 'command': 'query-stats-schemas', - 'data': { }, + 'data': { '*provider': 'StatsProvider' }, 'returns': [ 'StatsSchema' ] } -- cgit v1.2.3-55-g7522 From 7716417eac82b319b204f29224ffe1a6d2c0668a Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Tue, 26 Apr 2022 13:58:59 +0200 Subject: hmp: add filtering of statistics by provider Allow the user to request statistics for a single provider of interest. Extracted from a patch by Mark Kanda. Reviewed-by: Dr. David Alan Gilbert Signed-off-by: Paolo Bonzini --- hmp-commands-info.hx | 7 ++++--- monitor/hmp-cmds.c | 39 ++++++++++++++++++++++++++++++++------- 2 files changed, 36 insertions(+), 10 deletions(-) (limited to 'monitor/hmp-cmds.c') diff --git a/hmp-commands-info.hx b/hmp-commands-info.hx index 28757768f7..a67040443b 100644 --- a/hmp-commands-info.hx +++ b/hmp-commands-info.hx @@ -897,9 +897,10 @@ ERST { .name = "stats", - .args_type = "target:s", - .params = "target", - .help = "show statistics; target is either vm or vcpu", + .args_type = "target:s,provider:s?", + .params = "target [provider]", + .help = "show statistics for the given target (vm or vcpu); optionally filter by " + "provider", .cmd = hmp_info_stats, }, diff --git a/monitor/hmp-cmds.c b/monitor/hmp-cmds.c index 9180cf1841..9278439533 100644 --- a/monitor/hmp-cmds.c +++ b/monitor/hmp-cmds.c @@ -2300,6 +2300,7 @@ static StatsSchemaValueList *find_schema_value_list( } static void print_stats_results(Monitor *mon, StatsTarget target, + bool show_provider, StatsResult *result, StatsSchemaList *schema) { @@ -2314,8 +2315,10 @@ static void print_stats_results(Monitor *mon, StatsTarget target, return; } - monitor_printf(mon, "provider: %s\n", - StatsProvider_str(result->provider)); + if (show_provider) { + monitor_printf(mon, "provider: %s\n", + StatsProvider_str(result->provider)); + } for (stats_list = result->stats; stats_list; stats_list = stats_list->next, @@ -2356,7 +2359,8 @@ static void print_stats_results(Monitor *mon, StatsTarget target, } /* Create the StatsFilter that is needed for an "info stats" invocation. */ -static StatsFilter *stats_filter(StatsTarget target, int cpu_index) +static StatsFilter *stats_filter(StatsTarget target, int cpu_index, + StatsProvider provider) { StatsFilter *filter = g_malloc0(sizeof(*filter)); @@ -2378,12 +2382,25 @@ static StatsFilter *stats_filter(StatsTarget target, int cpu_index) default: break; } + + if (provider == STATS_PROVIDER__MAX) { + return filter; + } + + /* "info stats" can only query either one or all the providers. */ + filter->has_providers = true; + filter->providers = g_new0(StatsRequestList, 1); + filter->providers->value = g_new0(StatsRequest, 1); + filter->providers->value->provider = provider; return filter; } void hmp_info_stats(Monitor *mon, const QDict *qdict) { const char *target_str = qdict_get_str(qdict, "target"); + const char *provider_str = qdict_get_try_str(qdict, "provider"); + + StatsProvider provider = STATS_PROVIDER__MAX; StatsTarget target; Error *err = NULL; g_autoptr(StatsSchemaList) schema = NULL; @@ -2396,19 +2413,27 @@ void hmp_info_stats(Monitor *mon, const QDict *qdict) monitor_printf(mon, "invalid stats target %s\n", target_str); goto exit_no_print; } + if (provider_str) { + provider = qapi_enum_parse(&StatsProvider_lookup, provider_str, -1, &err); + if (err) { + monitor_printf(mon, "invalid stats provider %s\n", provider_str); + goto exit_no_print; + } + } - schema = qmp_query_stats_schemas(false, STATS_PROVIDER__MAX, &err); + schema = qmp_query_stats_schemas(provider_str ? true : false, + provider, &err); if (err) { goto exit; } switch (target) { case STATS_TARGET_VM: - filter = stats_filter(target, -1); + filter = stats_filter(target, -1, provider); break; case STATS_TARGET_VCPU: {} int cpu_index = monitor_get_cpu_index(mon); - filter = stats_filter(target, cpu_index); + filter = stats_filter(target, cpu_index, provider); break; default: abort(); @@ -2419,7 +2444,7 @@ void hmp_info_stats(Monitor *mon, const QDict *qdict) goto exit; } for (entry = stats; entry; entry = entry->next) { - print_stats_results(mon, target, entry->value, schema); + print_stats_results(mon, target, provider_str == NULL, entry->value, schema); } exit: -- cgit v1.2.3-55-g7522 From 39cd0c7f12883ea99c3b321d836cb63b67352621 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Tue, 26 Apr 2022 14:09:31 +0200 Subject: hmp: add filtering of statistics by name Allow the user to request only a specific subset of statistics. This can be useful when working on a feature or optimization that is known to affect that statistic. Example: (qemu) info stats vcpu halt_poll_fail_ns provider: kvm halt_poll_fail_ns (cumulative, ns): 0 In case multiple providers have the same statistic, the provider can be specified too: (qemu) info stats vcpu halt_poll_fail_ns kvm provider: kvm halt_poll_fail_ns (cumulative, ns): 0 Extracted from a patch by Mark Kanda. Reviewed-by: Dr. David Alan Gilbert Signed-off-by: Paolo Bonzini --- hmp-commands-info.hx | 8 ++++---- monitor/hmp-cmds.c | 35 ++++++++++++++++++++++++++--------- 2 files changed, 30 insertions(+), 13 deletions(-) (limited to 'monitor/hmp-cmds.c') diff --git a/hmp-commands-info.hx b/hmp-commands-info.hx index a67040443b..3ffa24bd67 100644 --- a/hmp-commands-info.hx +++ b/hmp-commands-info.hx @@ -897,10 +897,10 @@ ERST { .name = "stats", - .args_type = "target:s,provider:s?", - .params = "target [provider]", - .help = "show statistics for the given target (vm or vcpu); optionally filter by " - "provider", + .args_type = "target:s,names:s?,provider:s?", + .params = "target [names] [provider]", + .help = "show statistics for the given target (vm or vcpu); optionally filter by" + "name (comma-separated list, or * for all) and provider", .cmd = hmp_info_stats, }, diff --git a/monitor/hmp-cmds.c b/monitor/hmp-cmds.c index 9278439533..47a27326ee 100644 --- a/monitor/hmp-cmds.c +++ b/monitor/hmp-cmds.c @@ -2359,10 +2359,12 @@ static void print_stats_results(Monitor *mon, StatsTarget target, } /* Create the StatsFilter that is needed for an "info stats" invocation. */ -static StatsFilter *stats_filter(StatsTarget target, int cpu_index, - StatsProvider provider) +static StatsFilter *stats_filter(StatsTarget target, const char *names, + int cpu_index, StatsProvider provider) { StatsFilter *filter = g_malloc0(sizeof(*filter)); + StatsProvider provider_idx; + StatsRequestList *request_list = NULL; filter->target = target; switch (target) { @@ -2383,15 +2385,29 @@ static StatsFilter *stats_filter(StatsTarget target, int cpu_index, break; } - if (provider == STATS_PROVIDER__MAX) { + if (!names && provider == STATS_PROVIDER__MAX) { return filter; } - /* "info stats" can only query either one or all the providers. */ + /* + * "info stats" can only query either one or all the providers. Querying + * by name, but not by provider, requires the creation of one filter per + * provider. + */ + for (provider_idx = 0; provider_idx < STATS_PROVIDER__MAX; provider_idx++) { + if (provider == STATS_PROVIDER__MAX || provider == provider_idx) { + StatsRequest *request = g_new0(StatsRequest, 1); + request->provider = provider_idx; + if (names && !g_str_equal(names, "*")) { + request->has_names = true; + request->names = strList_from_comma_list(names); + } + QAPI_LIST_PREPEND(request_list, request); + } + } + filter->has_providers = true; - filter->providers = g_new0(StatsRequestList, 1); - filter->providers->value = g_new0(StatsRequest, 1); - filter->providers->value->provider = provider; + filter->providers = request_list; return filter; } @@ -2399,6 +2415,7 @@ void hmp_info_stats(Monitor *mon, const QDict *qdict) { const char *target_str = qdict_get_str(qdict, "target"); const char *provider_str = qdict_get_try_str(qdict, "provider"); + const char *names = qdict_get_try_str(qdict, "names"); StatsProvider provider = STATS_PROVIDER__MAX; StatsTarget target; @@ -2429,11 +2446,11 @@ void hmp_info_stats(Monitor *mon, const QDict *qdict) switch (target) { case STATS_TARGET_VM: - filter = stats_filter(target, -1, provider); + filter = stats_filter(target, names, -1, provider); break; case STATS_TARGET_VCPU: {} int cpu_index = monitor_get_cpu_index(mon); - filter = stats_filter(target, cpu_index, provider); + filter = stats_filter(target, names, cpu_index, provider); break; default: abort(); -- cgit v1.2.3-55-g7522