From: Mark Kanda <mark.kanda@oracle.com>
To: qemu-devel@nongnu.org
Cc: pbonzini@redhat.com, berrange@redhat.com, armbru@redhat.com
Subject: [PATCH v4 1/3] qmp: Support for querying stats
Date: Tue, 15 Feb 2022 09:04:31 -0600 [thread overview]
Message-ID: <20220215150433.2310711-2-mark.kanda@oracle.com> (raw)
In-Reply-To: <20220215150433.2310711-1-mark.kanda@oracle.com>
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.kanda@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
next prev parent reply other threads:[~2022-02-15 15:08 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-02-15 15:04 [PATCH v4 0/3] Support fd-based KVM stats Mark Kanda
2022-02-15 15:04 ` Mark Kanda [this message]
2022-03-11 13:06 ` [PATCH v4 1/3] qmp: Support for querying stats Markus Armbruster
2022-03-11 13:12 ` Daniel P. Berrangé
2022-03-14 17:28 ` Mark Kanda
2022-03-21 13:50 ` Markus Armbruster
2022-03-21 14:55 ` Paolo Bonzini
2022-03-21 15:18 ` Mark Kanda
2022-02-15 15:04 ` [PATCH v4 2/3] hmp: " Mark Kanda
2022-02-15 15:04 ` [PATCH v4 3/3] kvm: Support for querying fd-based stats Mark Kanda
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20220215150433.2310711-2-mark.kanda@oracle.com \
--to=mark.kanda@oracle.com \
--cc=armbru@redhat.com \
--cc=berrange@redhat.com \
--cc=pbonzini@redhat.com \
--cc=qemu-devel@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.