diff --git a/include/monitor/monitor.h b/include/monitor/monitor.h
index 12d395d62d..14d3432ade 100644
--- a/include/monitor/monitor.h
+++ b/include/monitor/monitor.h
@@ -56,4 +56,31 @@ void monitor_register_hmp(const char *name, bool info,
 void monitor_register_hmp_info_hrt(const char *name,
                                    HumanReadableText *(*handler)(Error **errp));
 
+/*
+ * Add qmp stats callbacks to the stats_callbacks list.
+ *
+ * @name: name of stats callbacks
+ * @stats_fn: routine to query stats - with options for name and type:
+ *    StatsList *(*stats_fn)(StatsList *list_tail, bool has_name,
+ *        const char *name, bool has_type, const char *type, Error **errp)
+ *
+ * @schema_fn: routine to query stat schemas - with an option for type:
+ *    StatsSchemaList *(*schemas_fn)(StatsSchemaList *list tail, bool has_type,
+ *                                   const char *type, Error **errp)
+ *
+ * @instance_fn: routine to query stat instances:
+ *     StatsInstanceList *(*instances_fn)(StatsInstanceList *list_tail,
+ *                                        Error **errp)
+ */
+void add_stats_callbacks(const char *name,
+                         StatsList *(*stats_fn)(StatsList *,
+                                                bool, const char *,
+                                                bool, const char *,
+                                                Error **),
+                         StatsSchemaList *(*schemas_fn)(StatsSchemaList *,
+                                                        bool, const char *,
+                                                        Error **),
+                         StatsInstanceList *(*instances_fn)(StatsInstanceList *,
+                                                            Error **));
+
 #endif /* MONITOR_H */
diff --git a/monitor/qmp-cmds.c b/monitor/qmp-cmds.c
index 343353e27a..c7bdff1e1c 100644
--- a/monitor/qmp-cmds.c
+++ b/monitor/qmp-cmds.c
@@ -466,3 +466,74 @@ HumanReadableText *qmp_x_query_irq(Error **errp)
 
     return human_readable_text_from_str(buf);
 }
+
+typedef struct StatsCallbacks {
+    char *name;
+    StatsList *(*stats_cb)(StatsList *, bool, const char *, bool,
+                           const char *, Error **);
+    StatsSchemaList *(*schemas_cb)(StatsSchemaList *, bool, const char *,
+                                   Error **);
+    StatsInstanceList *(*instances_cb)(StatsInstanceList *, Error **);
+    QTAILQ_ENTRY(StatsCallbacks) next;
+} StatsCallbacks;
+
+static QTAILQ_HEAD(, StatsCallbacks) stats_callbacks =
+    QTAILQ_HEAD_INITIALIZER(stats_callbacks);
+
+void add_stats_callbacks(const char *name,
+                         StatsList *(*stats_fn)(StatsList *,
+                                                bool, const char *,
+                                                bool, const char *,
+                                                Error **),
+                         StatsSchemaList *(*schemas_fn)(StatsSchemaList *,
+                                                        bool, const char *,
+                                                        Error **),
+                         StatsInstanceList *(*instances_fn)(StatsInstanceList *,
+                                                            Error **))
+{
+    StatsCallbacks *entry = g_malloc0(sizeof(*entry));
+    entry->name = strdup(name);
+    entry->stats_cb = stats_fn;
+    entry->schemas_cb = schemas_fn;
+    entry->instances_cb = instances_fn;
+
+    QTAILQ_INSERT_TAIL(&stats_callbacks, entry, next);
+}
+
+StatsList *qmp_query_stats(bool has_name, const char *name, bool has_type,
+                           const char *type, Error **errp) {
+    StatsList *list_tail = NULL;
+    StatsCallbacks *entry;
+
+    QTAILQ_FOREACH(entry, &stats_callbacks, next) {
+        list_tail = entry->stats_cb(list_tail, has_name, name,
+                                    has_type, type, errp);
+    }
+
+    return list_tail;
+}
+
+StatsSchemaList *qmp_query_stats_schemas(bool has_type, const char *type,
+                                         Error **errp)
+{
+    StatsSchemaList *list_tail = NULL;
+    StatsCallbacks *entry;
+
+    QTAILQ_FOREACH(entry, &stats_callbacks, next) {
+        list_tail = entry->schemas_cb(list_tail, has_type, type, errp);
+    }
+
+    return list_tail;
+}
+
+StatsInstanceList *qmp_query_stats_instances(Error **errp)
+{
+    StatsInstanceList *list_tail = NULL;
+    StatsCallbacks *entry;
+
+    QTAILQ_FOREACH(entry, &stats_callbacks, next) {
+        list_tail = entry->instances_cb(list_tail, errp);
+    }
+
+    return list_tail;
+}
diff --git a/qapi/misc.json b/qapi/misc.json
index 358548abe1..a0a07ef0b1 100644
--- a/qapi/misc.json
+++ b/qapi/misc.json
@@ -527,3 +527,145 @@
  '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' ] }
+
+##
+# @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' : [ 'bytes', 'seconds', 'cycles', 'none' ] }
+
+##
+# @StatData:
+#
+# Individual stat
+# @name: Stat name
+# @type: @StatType
+# @unit: @StatUnit
+# @base: Exponent base (2 or 10)
+# @exponent: Used together with @base
+# @val: List of uint64 values
+#
+# Since: 7.0
+##
+{ 'struct': 'StatData',
+  'data': { 'name': 'str',
+            'type': 'StatType',
+            'unit': 'StatUnit',
+            'base': 'uint8',
+            'exponent': 'int16',
+            'val': [ 'uint64' ] } }
+
+##
+# @Stats:
+#
+# Stats per resource (e.g. vm or vcpu)
+# @name: Resource name
+# @stats: List of @StatData
+#
+# Since: 7.0
+##
+{ 'struct': 'Stats',
+  'data': {'name': 'str',
+           'type': 'StatSchemaType',
+           'stats': [ 'StatData' ] } }
+
+##
+# @query-stats:
+#
+# @name: Stat name (optional)
+# @type: Type name (optional)
+# Returns: List of @Stats
+#
+# Since: 7.0
+##
+{ 'command': 'query-stats',
+  'data': { '*name': 'str', '*type': 'str' },
+  'returns': [ 'Stats' ] }
+
+##
+# @StatSchemaType:
+#
+# Enumeration of stats schema types
+#
+# Since: 7.0
+##
+{ 'enum' : 'StatSchemaType',
+  'data' : [ ] }
+
+##
+# @StatSchemaEntry:
+#
+# Individual stat in a schema type
+#
+# Since: 7.0
+##
+{ 'struct': 'StatSchemaEntry',
+  'data': { 'name': 'str' } }
+
+##
+# @StatsSchema:
+#
+# Stats per @StatSchemaType
+# @type: @StatSchemaType
+# @stats: @StatCchemaName
+#
+# Since: 7.0
+##
+{ 'struct': 'StatsSchema',
+  'data': { 'type': 'StatSchemaType',
+            'stats': [ 'StatSchemaEntry' ] } }
+
+##
+# @query-stats-schemas:
+#
+# @type: type name (optional)
+# Returns: List of @StatsSchema
+#
+# Since: 7.0
+##
+{ 'command': 'query-stats-schemas',
+  'data': { '*type': 'str' },
+  'returns': [ 'StatsSchema' ] }
+
+##
+# @StatsInstance:
+#
+# @name: resource name
+# @type: @StatSchemaType
+#
+# Since: 7.0
+##
+{ 'struct': 'StatsInstance',
+  'data': { 'name': 'str',
+            'type': 'StatSchemaType' } }
+
+##
+# @query-stats-instances:
+#
+# Returns list of @StatsInstance
+#
+# Since: 7.0
+##
+{ 'command': 'query-stats-instances',
+  'returns': [ 'StatsInstance' ] }
