[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[PATCH v4 1/3] qmp: Support for querying stats
From: |
Mark Kanda |
Subject: |
[PATCH v4 1/3] qmp: Support for querying stats |
Date: |
Tue, 15 Feb 2022 09:04:31 -0600 |
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