* [PATCH v2 2/5] stats_fs API: create, add and remove stats_fs sources and values
2020-05-04 11:03 [PATCH v2 0/5] Statsfs: a new ram-based file sytem for Linux kernel statistics Emanuele Giuseppe Esposito
2020-05-04 11:03 ` [PATCH v2 1/5] refcount, kref: add dec-and-test wrappers for rw_semaphores Emanuele Giuseppe Esposito
@ 2020-05-04 11:03 ` Emanuele Giuseppe Esposito
2020-05-04 22:11 ` Randy Dunlap
2020-05-04 11:03 ` [PATCH v2 3/5] kunit: tests for stats_fs API Emanuele Giuseppe Esposito
` (4 subsequent siblings)
6 siblings, 1 reply; 21+ messages in thread
From: Emanuele Giuseppe Esposito @ 2020-05-04 11:03 UTC (permalink / raw)
To: kvm
Cc: Emanuele Giuseppe Esposito, linux-s390, David Hildenbrand,
Cornelia Huck, Emanuele Giuseppe Esposito, linux-kernel, kvm-ppc,
linux-mips, Christian Borntraeger, Alexander Viro, linux-fsdevel,
Paolo Bonzini, Vitaly Kuznetsov, linuxppc-dev, Jim Mattson
Introduction to the stats_fs API, that allows to easily create, add
and remove stats_fs sources and values. The API allows to easily building
the statistics directory tree to automatically gather them for the linux
kernel. The main functionalities are: create a source, add child
sources/values/aggregates, register it to the root source (that on
the virtual fs would be /sys/kernel/statsfs), ad perform a search for
a value/aggregate.
This allows creating any kind of source tree, making it more flexible
also to future readjustments.
The API representation is only logical and will be backed up
by a virtual file system in patch 4.
Its usage will be shared between the stats_fs file system
and the end-users like kvm, the former calling it when it needs to
display and clear statistics, the latter to add values and sources.
Signed-off-by: Emanuele Giuseppe Esposito <eesposit@redhat.com>
---
MAINTAINERS | 7 +
fs/Kconfig | 6 +
fs/Makefile | 1 +
fs/stats_fs/Makefile | 4 +
fs/stats_fs/internal.h | 20 ++
fs/stats_fs/stats_fs.c | 610 +++++++++++++++++++++++++++++++++++++++
include/linux/stats_fs.h | 289 +++++++++++++++++++
7 files changed, 937 insertions(+)
create mode 100644 fs/stats_fs/Makefile
create mode 100644 fs/stats_fs/internal.h
create mode 100644 fs/stats_fs/stats_fs.c
create mode 100644 include/linux/stats_fs.h
diff --git a/MAINTAINERS b/MAINTAINERS
index b816a453b10e..a8403d07cee5 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5229,6 +5229,13 @@ F: include/linux/debugfs.h
F: include/linux/kobj*
F: lib/kobj*
+STATS_FS
+M: Paolo Bonzini <pbonzini@redhat.com>
+R: Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com>
+S: Supported
+F: include/linux/stats_fs.h
+F: fs/stats_fs
+
DRIVERS FOR ADAPTIVE VOLTAGE SCALING (AVS)
M: Kevin Hilman <khilman@kernel.org>
M: Nishanth Menon <nm@ti.com>
diff --git a/fs/Kconfig b/fs/Kconfig
index f08fbbfafd9a..1b0de0f19e96 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -328,4 +328,10 @@ source "fs/unicode/Kconfig"
config IO_WQ
bool
+config STATS_FS
+ bool "Statistics Filesystem"
+ help
+ stats_fs is a virtual file system that provides counters and
+ other statistics about the running kernel.
+
endmenu
diff --git a/fs/Makefile b/fs/Makefile
index 2ce5112b02c8..91558eca0cf7 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -125,6 +125,7 @@ obj-$(CONFIG_BEFS_FS) += befs/
obj-$(CONFIG_HOSTFS) += hostfs/
obj-$(CONFIG_CACHEFILES) += cachefiles/
obj-$(CONFIG_DEBUG_FS) += debugfs/
+obj-$(CONFIG_STATS_FS) += stats_fs/
obj-$(CONFIG_TRACING) += tracefs/
obj-$(CONFIG_OCFS2_FS) += ocfs2/
obj-$(CONFIG_BTRFS_FS) += btrfs/
diff --git a/fs/stats_fs/Makefile b/fs/stats_fs/Makefile
new file mode 100644
index 000000000000..94fe52d590d5
--- /dev/null
+++ b/fs/stats_fs/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-only
+stats_fs-objs := stats_fs.o
+
+obj-$(CONFIG_STATS_FS) += stats_fs.o
diff --git a/fs/stats_fs/internal.h b/fs/stats_fs/internal.h
new file mode 100644
index 000000000000..ddf262a60736
--- /dev/null
+++ b/fs/stats_fs/internal.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _STATS_FS_INTERNAL_H_
+#define _STATS_FS_INTERNAL_H_
+
+#include <linux/list.h>
+#include <linux/kref.h>
+#include <linux/rwsem.h>
+#include <linux/stats_fs.h>
+
+/* values, grouped by base */
+struct stats_fs_value_source {
+ void *base_addr;
+ bool files_created;
+ struct stats_fs_value *values;
+ struct list_head list_element;
+};
+
+int stats_fs_val_get_mode(struct stats_fs_value *val);
+
+#endif /* _STATS_FS_INTERNAL_H_ */
diff --git a/fs/stats_fs/stats_fs.c b/fs/stats_fs/stats_fs.c
new file mode 100644
index 000000000000..b63de12769e2
--- /dev/null
+++ b/fs/stats_fs/stats_fs.c
@@ -0,0 +1,610 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/rwsem.h>
+#include <linux/list.h>
+#include <linux/kref.h>
+#include <linux/limits.h>
+#include <linux/stats_fs.h>
+
+#include "internal.h"
+
+struct stats_fs_aggregate_value {
+ uint64_t sum, min, max;
+ uint32_t count, count_zero;
+};
+
+static int is_val_signed(struct stats_fs_value *val)
+{
+ return val->type & STATS_FS_SIGN;
+}
+
+int stats_fs_val_get_mode(struct stats_fs_value *val)
+{
+ return val->mode ? val->mode : 0644;
+}
+
+static struct stats_fs_value *find_value(struct stats_fs_value_source *src,
+ struct stats_fs_value *val)
+{
+ struct stats_fs_value *entry;
+
+ for (entry = src->values; entry->name; entry++) {
+ if (entry == val)
+ return entry;
+ }
+ return NULL;
+}
+
+static struct stats_fs_value *
+search_value_in_source(struct stats_fs_source *src, struct stats_fs_value *arg,
+ struct stats_fs_value_source **val_src)
+{
+ struct stats_fs_value *entry;
+ struct stats_fs_value_source *src_entry;
+
+ list_for_each_entry (src_entry, &src->values_head, list_element) {
+ entry = find_value(src_entry, arg);
+ if (entry) {
+ *val_src = src_entry;
+ return entry;
+ }
+ }
+
+ return NULL;
+}
+
+/* Called with rwsem held for writing */
+static struct stats_fs_value_source *create_value_source(void *base)
+{
+ struct stats_fs_value_source *val_src;
+
+ val_src = kzalloc(sizeof(struct stats_fs_value_source), GFP_KERNEL);
+ if (!val_src)
+ return ERR_PTR(-ENOMEM);
+
+ val_src->base_addr = base;
+ INIT_LIST_HEAD(&val_src->list_element);
+
+ return val_src;
+}
+
+int stats_fs_source_add_values(struct stats_fs_source *source,
+ struct stats_fs_value *stat, void *ptr)
+{
+ struct stats_fs_value_source *val_src;
+ struct stats_fs_value_source *entry;
+
+ down_write(&source->rwsem);
+
+ list_for_each_entry (entry, &source->values_head, list_element) {
+ if (entry->base_addr == ptr && entry->values == stat) {
+ up_write(&source->rwsem);
+ return -EEXIST;
+ }
+ }
+
+ val_src = create_value_source(ptr);
+ val_src->values = (struct stats_fs_value *)stat;
+
+ /* add the val_src to the source list */
+ list_add(&val_src->list_element, &source->values_head);
+
+ up_write(&source->rwsem);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(stats_fs_source_add_values);
+
+void stats_fs_source_add_subordinate(struct stats_fs_source *source,
+ struct stats_fs_source *sub)
+{
+ down_write(&source->rwsem);
+
+ stats_fs_source_get(sub);
+ list_add(&sub->list_element, &source->subordinates_head);
+
+ up_write(&source->rwsem);
+}
+EXPORT_SYMBOL_GPL(stats_fs_source_add_subordinate);
+
+/* Called with rwsem held for writing */
+static void
+stats_fs_source_remove_subordinate_locked(struct stats_fs_source *source,
+ struct stats_fs_source *sub)
+{
+ struct stats_fs_source *src_entry;
+
+ list_for_each_entry (src_entry, &source->subordinates_head,
+ list_element) {
+ if (src_entry == sub) {
+ list_del_init(&src_entry->list_element);
+ stats_fs_source_put(src_entry);
+ return;
+ }
+ }
+}
+
+void stats_fs_source_remove_subordinate(struct stats_fs_source *source,
+ struct stats_fs_source *sub)
+{
+ down_write(&source->rwsem);
+ stats_fs_source_remove_subordinate_locked(source, sub);
+ up_write(&source->rwsem);
+}
+EXPORT_SYMBOL_GPL(stats_fs_source_remove_subordinate);
+
+/* Called with rwsem held for reading */
+static uint64_t get_simple_value(struct stats_fs_value_source *src,
+ struct stats_fs_value *val)
+{
+ uint64_t value_found;
+ void *address;
+
+ address = src->base_addr + val->offset;
+
+ switch (val->type) {
+ case STATS_FS_U8:
+ value_found = *((uint8_t *)address);
+ break;
+ case STATS_FS_U8 | STATS_FS_SIGN:
+ value_found = *((int8_t *)address);
+ break;
+ case STATS_FS_U16:
+ value_found = *((uint16_t *)address);
+ break;
+ case STATS_FS_U16 | STATS_FS_SIGN:
+ value_found = *((int16_t *)address);
+ break;
+ case STATS_FS_U32:
+ value_found = *((uint32_t *)address);
+ break;
+ case STATS_FS_U32 | STATS_FS_SIGN:
+ value_found = *((int32_t *)address);
+ break;
+ case STATS_FS_U64:
+ value_found = *((uint64_t *)address);
+ break;
+ case STATS_FS_U64 | STATS_FS_SIGN:
+ value_found = *((int64_t *)address);
+ break;
+ case STATS_FS_BOOL:
+ value_found = *((uint8_t *)address);
+ break;
+ default:
+ value_found = 0;
+ break;
+ }
+
+ return value_found;
+}
+
+/* Called with rwsem held for reading */
+static void clear_simple_value(struct stats_fs_value_source *src,
+ struct stats_fs_value *val)
+{
+ void *address;
+
+ address = src->base_addr + val->offset;
+
+ switch (val->type) {
+ case STATS_FS_U8:
+ *((uint8_t *)address) = 0;
+ break;
+ case STATS_FS_U8 | STATS_FS_SIGN:
+ *((int8_t *)address) = 0;
+ break;
+ case STATS_FS_U16:
+ *((uint16_t *)address) = 0;
+ break;
+ case STATS_FS_U16 | STATS_FS_SIGN:
+ *((int16_t *)address) = 0;
+ break;
+ case STATS_FS_U32:
+ *((uint32_t *)address) = 0;
+ break;
+ case STATS_FS_U32 | STATS_FS_SIGN:
+ *((int32_t *)address) = 0;
+ break;
+ case STATS_FS_U64:
+ *((uint64_t *)address) = 0;
+ break;
+ case STATS_FS_U64 | STATS_FS_SIGN:
+ *((int64_t *)address) = 0;
+ break;
+ case STATS_FS_BOOL:
+ *((uint8_t *)address) = 0;
+ break;
+ default:
+ break;
+ }
+}
+
+/* Called with rwsem held for reading */
+static void
+search_all_simple_values(struct stats_fs_source *src,
+ struct stats_fs_value_source *ref_src_entry,
+ struct stats_fs_value *val,
+ struct stats_fs_aggregate_value *agg)
+{
+ struct stats_fs_value_source *src_entry;
+ uint64_t value_found;
+
+ list_for_each_entry (src_entry, &src->values_head, list_element) {
+ /* skip aggregates */
+ if (src_entry->base_addr == NULL)
+ continue;
+
+ /* useless to search here */
+ if (src_entry->values != ref_src_entry->values)
+ continue;
+
+ /* must be here */
+ value_found = get_simple_value(src_entry, val);
+ agg->sum += value_found;
+ agg->count++;
+ agg->count_zero += (value_found == 0);
+
+ if (is_val_signed(val)) {
+ agg->max = (((int64_t)value_found) >=
+ ((int64_t)agg->max)) ?
+ value_found :
+ agg->max;
+ agg->min = (((int64_t)value_found) <=
+ ((int64_t)agg->min)) ?
+ value_found :
+ agg->min;
+ } else {
+ agg->max = (value_found >= agg->max) ? value_found :
+ agg->max;
+ agg->min = (value_found <= agg->min) ? value_found :
+ agg->min;
+ }
+ }
+}
+
+/* Called with rwsem held for reading */
+static void
+do_recursive_aggregation(struct stats_fs_source *root,
+ struct stats_fs_value_source *ref_src_entry,
+ struct stats_fs_value *val,
+ struct stats_fs_aggregate_value *agg)
+{
+ struct stats_fs_source *subordinate;
+
+ /* search all simple values in this folder */
+ search_all_simple_values(root, ref_src_entry, val, agg);
+
+ /* recursively search in all subfolders */
+ list_for_each_entry (subordinate, &root->subordinates_head,
+ list_element) {
+ down_read(&subordinate->rwsem);
+ do_recursive_aggregation(subordinate, ref_src_entry, val, agg);
+ up_read(&subordinate->rwsem);
+ }
+}
+
+/* Called with rwsem held for reading */
+static void init_aggregate_value(struct stats_fs_aggregate_value *agg,
+ struct stats_fs_value *val)
+{
+ agg->count = agg->count_zero = agg->sum = 0;
+ if (is_val_signed(val)) {
+ agg->max = S64_MIN;
+ agg->min = S64_MAX;
+ } else {
+ agg->max = 0;
+ agg->min = U64_MAX;
+ }
+}
+
+/* Called with rwsem held for reading */
+static void store_final_value(struct stats_fs_aggregate_value *agg,
+ struct stats_fs_value *val, uint64_t *ret)
+{
+ int operation;
+
+ operation = val->aggr_kind | is_val_signed(val);
+
+ switch (operation) {
+ case STATS_FS_AVG:
+ *ret = agg->count ? agg->sum / agg->count : 0;
+ break;
+ case STATS_FS_AVG | STATS_FS_SIGN:
+ *ret = agg->count ? ((int64_t)agg->sum) / agg->count : 0;
+ break;
+ case STATS_FS_SUM:
+ case STATS_FS_SUM | STATS_FS_SIGN:
+ *ret = agg->sum;
+ break;
+ case STATS_FS_MIN:
+ case STATS_FS_MIN | STATS_FS_SIGN:
+ *ret = agg->min;
+ break;
+ case STATS_FS_MAX:
+ case STATS_FS_MAX | STATS_FS_SIGN:
+ *ret = agg->max;
+ break;
+ case STATS_FS_COUNT_ZERO:
+ case STATS_FS_COUNT_ZERO | STATS_FS_SIGN:
+ *ret = agg->count_zero;
+ break;
+ default:
+ break;
+ }
+}
+
+/* Called with rwsem held for reading */
+static int stats_fs_source_get_value_locked(struct stats_fs_source *source,
+ struct stats_fs_value *arg,
+ uint64_t *ret)
+{
+ struct stats_fs_value_source *src_entry;
+ struct stats_fs_value *found;
+ struct stats_fs_aggregate_value aggr;
+
+ *ret = 0;
+
+ if (!arg)
+ return -ENOENT;
+
+ /* look in simple values */
+ found = search_value_in_source(source, arg, &src_entry);
+
+ if (!found) {
+ printk(KERN_ERR "Stats_fs: Value in source \"%s\" not found!\n",
+ source->name);
+ return -ENOENT;
+ }
+
+ if (src_entry->base_addr != NULL) {
+ *ret = get_simple_value(src_entry, found);
+ return 0;
+ }
+
+ /* look in aggregates */
+ init_aggregate_value(&aggr, found);
+ do_recursive_aggregation(source, src_entry, found, &aggr);
+ store_final_value(&aggr, found, ret);
+
+ return 0;
+}
+
+int stats_fs_source_get_value(struct stats_fs_source *source,
+ struct stats_fs_value *arg, uint64_t *ret)
+{
+ int retval;
+
+ down_read(&source->rwsem);
+ retval = stats_fs_source_get_value_locked(source, arg, ret);
+ up_read(&source->rwsem);
+
+ return retval;
+}
+EXPORT_SYMBOL_GPL(stats_fs_source_get_value);
+
+/* Called with rwsem held for reading */
+static void set_all_simple_values(struct stats_fs_source *src,
+ struct stats_fs_value_source *ref_src_entry,
+ struct stats_fs_value *val)
+{
+ struct stats_fs_value_source *src_entry;
+
+ list_for_each_entry (src_entry, &src->values_head, list_element) {
+ /* skip aggregates */
+ if (src_entry->base_addr == NULL)
+ continue;
+
+ /* wrong to search here */
+ if (src_entry->values != ref_src_entry->values)
+ continue;
+
+ if (src_entry->base_addr &&
+ src_entry->values == ref_src_entry->values)
+ clear_simple_value(src_entry, val);
+ }
+}
+
+/* Called with rwsem held for reading */
+static void do_recursive_clean(struct stats_fs_source *root,
+ struct stats_fs_value_source *ref_src_entry,
+ struct stats_fs_value *val)
+{
+ struct stats_fs_source *subordinate;
+
+ /* search all simple values in this folder */
+ set_all_simple_values(root, ref_src_entry, val);
+
+ /* recursively search in all subfolders */
+ list_for_each_entry (subordinate, &root->subordinates_head,
+ list_element) {
+ down_read(&subordinate->rwsem);
+ do_recursive_clean(subordinate, ref_src_entry, val);
+ up_read(&subordinate->rwsem);
+ }
+}
+
+/* Called with rwsem held for reading */
+static int stats_fs_source_clear_locked(struct stats_fs_source *source,
+ struct stats_fs_value *val)
+{
+ struct stats_fs_value_source *src_entry;
+ struct stats_fs_value *found;
+
+ if (!val)
+ return -ENOENT;
+
+ /* look in simple values */
+ found = search_value_in_source(source, val, &src_entry);
+
+ if (!found) {
+ printk(KERN_ERR "Stats_fs: Value in source \"%s\" not found!\n",
+ source->name);
+ return -ENOENT;
+ }
+
+ if (src_entry->base_addr != NULL) {
+ clear_simple_value(src_entry, found);
+ return 0;
+ }
+
+ /* look in aggregates */
+ do_recursive_clean(source, src_entry, found);
+
+ return 0;
+}
+
+int stats_fs_source_clear(struct stats_fs_source *source,
+ struct stats_fs_value *val)
+{
+ int retval;
+
+ down_read(&source->rwsem);
+ retval = stats_fs_source_clear_locked(source, val);
+ up_read(&source->rwsem);
+
+ return retval;
+}
+
+/* Called with rwsem held for reading */
+static struct stats_fs_value *
+find_value_by_name(struct stats_fs_value_source *src, char *val)
+{
+ struct stats_fs_value *entry;
+
+ for (entry = src->values; entry->name; entry++)
+ if (!strcmp(entry->name, val))
+ return entry;
+
+ return NULL;
+}
+
+/* Called with rwsem held for reading */
+static struct stats_fs_value *
+search_in_source_by_name(struct stats_fs_source *src, char *name)
+{
+ struct stats_fs_value *entry;
+ struct stats_fs_value_source *src_entry;
+
+ list_for_each_entry (src_entry, &src->values_head, list_element) {
+ entry = find_value_by_name(src_entry, name);
+ if (entry)
+ return entry;
+ }
+
+ return NULL;
+}
+
+int stats_fs_source_get_value_by_name(struct stats_fs_source *source,
+ char *name, uint64_t *ret)
+{
+ struct stats_fs_value *val;
+ int retval;
+
+ down_read(&source->rwsem);
+ val = search_in_source_by_name(source, name);
+
+ if (!val) {
+ *ret = 0;
+ up_read(&source->rwsem);
+ return -ENOENT;
+ }
+
+ retval = stats_fs_source_get_value_locked(source, val, ret);
+ up_read(&source->rwsem);
+
+ return retval;
+}
+EXPORT_SYMBOL_GPL(stats_fs_source_get_value_by_name);
+
+void stats_fs_source_get(struct stats_fs_source *source)
+{
+ kref_get(&source->refcount);
+}
+EXPORT_SYMBOL_GPL(stats_fs_source_get);
+
+void stats_fs_source_revoke(struct stats_fs_source *source)
+{
+ struct stats_fs_value_source *val_src_entry;
+
+ down_write(&source->rwsem);
+
+ list_for_each_entry (val_src_entry, &source->values_head, list_element)
+ val_src_entry->base_addr = NULL;
+
+ up_write(&source->rwsem);
+}
+EXPORT_SYMBOL_GPL(stats_fs_source_revoke);
+
+/* Called with rwsem held for writing
+ *
+ * The refcount is 0 and the lock was taken before refcount
+ * went from 1 to 0
+ */
+static void stats_fs_source_destroy(struct kref *kref_source)
+{
+ struct stats_fs_value_source *val_src_entry;
+ struct list_head *it, *safe;
+ struct stats_fs_source *child, *source;
+
+ source = container_of(kref_source, struct stats_fs_source, refcount);
+
+ /* iterate through the values and delete them */
+ list_for_each_safe (it, safe, &source->values_head) {
+ val_src_entry = list_entry(it, struct stats_fs_value_source,
+ list_element);
+ kfree(val_src_entry);
+ }
+
+ /* iterate through the subordinates and delete them */
+ list_for_each_safe (it, safe, &source->subordinates_head) {
+ child = list_entry(it, struct stats_fs_source, list_element);
+ stats_fs_source_remove_subordinate_locked(source, child);
+ }
+
+ up_write(&source->rwsem);
+ kfree(source->name);
+ kfree(source);
+}
+
+void stats_fs_source_put(struct stats_fs_source *source)
+{
+ kref_put_rwsem(&source->refcount, stats_fs_source_destroy,
+ &source->rwsem);
+}
+EXPORT_SYMBOL_GPL(stats_fs_source_put);
+
+struct stats_fs_source *stats_fs_source_create(const char *fmt, ...)
+{
+ va_list ap;
+ char buf[100];
+ struct stats_fs_source *ret;
+ int char_needed;
+
+ va_start(ap, fmt);
+ char_needed = vsnprintf(buf, 100, fmt, ap);
+ va_end(ap);
+
+ ret = kzalloc(sizeof(struct stats_fs_source), GFP_KERNEL);
+ if (!ret)
+ return ERR_PTR(-ENOMEM);
+
+ ret->name = kstrdup(buf, GFP_KERNEL);
+ if (!ret->name) {
+ kfree(ret);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ kref_init(&ret->refcount);
+ init_rwsem(&ret->rwsem);
+
+ INIT_LIST_HEAD(&ret->values_head);
+ INIT_LIST_HEAD(&ret->subordinates_head);
+ INIT_LIST_HEAD(&ret->list_element);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(stats_fs_source_create);
diff --git a/include/linux/stats_fs.h b/include/linux/stats_fs.h
new file mode 100644
index 000000000000..dc2d2e11f5ea
--- /dev/null
+++ b/include/linux/stats_fs.h
@@ -0,0 +1,289 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * stats_fs.h - a tiny little statistics file system
+ *
+ * Copyright (C) 2020 Emanuele Giuseppe Esposito
+ * Copyright (C) 2020 Redhat.
+ *
+ */
+
+#ifndef _STATS_FS_H_
+#define _STATS_FS_H_
+
+#include <linux/list.h>
+
+/* Used to distinguish signed types */
+#define STATS_FS_SIGN 0x8000
+
+enum stat_type {
+ STATS_FS_U8 = 0,
+ STATS_FS_U16 = 1,
+ STATS_FS_U32 = 2,
+ STATS_FS_U64 = 3,
+ STATS_FS_BOOL = 4,
+ STATS_FS_S8 = STATS_FS_U8 | STATS_FS_SIGN,
+ STATS_FS_S16 = STATS_FS_U16 | STATS_FS_SIGN,
+ STATS_FS_S32 = STATS_FS_U32 | STATS_FS_SIGN,
+ STATS_FS_S64 = STATS_FS_U64 | STATS_FS_SIGN,
+};
+
+enum stat_aggr {
+ STATS_FS_NONE = 0,
+ STATS_FS_SUM,
+ STATS_FS_MIN,
+ STATS_FS_MAX,
+ STATS_FS_COUNT_ZERO,
+ STATS_FS_AVG,
+};
+
+struct stats_fs_value {
+ /* Name of the stat */
+ char *name;
+
+ /* Offset from base address to field containing the value */
+ int offset;
+
+ /* Type of the stat BOOL,U64,... */
+ enum stat_type type;
+
+ /* Aggregate type: MIN, MAX, SUM,... */
+ enum stat_aggr aggr_kind;
+
+ /* File mode */
+ uint16_t mode;
+};
+
+struct stats_fs_source {
+ struct kref refcount;
+
+ char *name;
+
+ /* list of source stats_fs_value_source*/
+ struct list_head values_head;
+
+ /* list of struct stats_fs_source for subordinate sources */
+ struct list_head subordinates_head;
+
+ struct list_head list_element;
+
+ struct rw_semaphore rwsem;
+
+ struct dentry *source_dentry;
+};
+
+#if defined(CONFIG_STATS_FS)
+
+/**
+ * stats_fs_source_create - create a stats_fs_source
+ * Creates a stats_fs_source with the given name. This
+ * does not mean it will be backed by the filesystem yet, it will only
+ * be visible to the user once one of its parents (or itself) are
+ * registered in stats_fs.
+ *
+ * Returns a pointer to a stats_fs_source if it succeeds.
+ * This or one of the parents' pointer must be passed to the stats_fs_put()
+ * function when the file is to be removed. If an error occurs,
+ * ERR_PTR(-ERROR) will be returned.
+ */
+struct stats_fs_source *stats_fs_source_create(const char *fmt, ...);
+
+/**
+ * stats_fs_source_add_values - adds values to the given source
+ * @source: a pointer to the source that will receive the values
+ * @val: a pointer to the NULL terminated stats_fs_value array to add
+ * @base_ptr: a pointer to the base pointer used by these values
+ *
+ * In addition to adding values to the source, also create the
+ * files in the filesystem if the source already is backed up by a directory.
+ *
+ * Returns 0 it succeeds. If the value are already in the
+ * source and have the same base_ptr, -EEXIST is returned.
+ */
+int stats_fs_source_add_values(struct stats_fs_source *source,
+ struct stats_fs_value *val, void *base_ptr);
+
+/**
+ * stats_fs_source_add_subordinate - adds a child to the given source
+ * @parent: a pointer to the parent source
+ * @child: a pointer to child source to add
+ *
+ * Recursively create all files in the stats_fs filesystem
+ * only if the parent has already a dentry (created with
+ * stats_fs_source_register).
+ * This avoids the case where this function is called before register.
+ */
+void stats_fs_source_add_subordinate(struct stats_fs_source *parent,
+ struct stats_fs_source *child);
+
+/**
+ * stats_fs_source_remove_subordinate - removes a child from the given source
+ * @parent: a pointer to the parent source
+ * @child: a pointer to child source to remove
+ *
+ * Look if there is such child in the parent. If so,
+ * it will remove all its files and call stats_fs_put on the child.
+ */
+void stats_fs_source_remove_subordinate(struct stats_fs_source *parent,
+ struct stats_fs_source *child);
+
+/**
+ * stats_fs_source_get_value - search a value in the source (and
+ * subordinates)
+ * @source: a pointer to the source that will be searched
+ * @val: a pointer to the stats_fs_value to search
+ * @ret: a pointer to the uint64_t that will hold the found value
+ *
+ * Look up in the source if a value with same value pointer
+ * exists.
+ * If not, it will return -ENOENT. If it exists and it's a simple value
+ * (not an aggregate), the value that it points to will be returned.
+ * If it exists and it's an aggregate (aggr_type != STATS_FS_NONE), all
+ * subordinates will be recursively searched and every simple value match
+ * will be used to aggregate the final result. For example if it's a sum,
+ * all suboordinates having the same value will be sum together.
+ *
+ * This function will return 0 it succeeds.
+ */
+int stats_fs_source_get_value(struct stats_fs_source *source,
+ struct stats_fs_value *val, uint64_t *ret);
+
+/**
+ * stats_fs_source_get_value_by_name - search a value in the source (and
+ * subordinates)
+ * @source: a pointer to the source that will be searched
+ * @name: a pointer to the string representing the value to search
+ * (for example "exits")
+ * @ret: a pointer to the uint64_t that will hold the found value
+ *
+ * Same as stats_fs_source_get_value, but initially the name is used
+ * to search in the given source if there is a value with a matching
+ * name. If so, stats_fs_source_get_value will be called with the found
+ * value, otherwise -ENOENT will be returned.
+ */
+int stats_fs_source_get_value_by_name(struct stats_fs_source *source,
+ char *name, uint64_t *ret);
+
+/**
+ * stats_fs_source_clear - search and clears a value in the source (and
+ * subordinates)
+ * @source: a pointer to the source that will be searched
+ * @val: a pointer to the stats_fs_value to search
+ *
+ * Look up in the source if a value with same value pointer
+ * exists.
+ * If not, it will return -ENOENT. If it exists and it's a simple value
+ * (not an aggregate), the value that it points to will be set to 0.
+ * If it exists and it's an aggregate (aggr_type != STATS_FS_NONE), all
+ * subordinates will be recursively searched and every simple value match
+ * will be set to 0.
+ *
+ * This function will return 0 it succeeds.
+ */
+int stats_fs_source_clear(struct stats_fs_source *source,
+ struct stats_fs_value *val);
+
+/**
+ * stats_fs_source_revoke - disconnect the source from its backing data
+ * @source: a pointer to the source that will be revoked
+ *
+ * Ensure that stats_fs will not access the data that were passed to
+ * stats_fs_source_add_value for this source.
+ *
+ * Because open files increase the reference count for a stats_fs_source,
+ * the source can end up living longer than the data that provides the
+ * values for the source. Calling stats_fs_source_revoke just before the
+ * backing data is freed avoids accesses to freed data structures. The
+ * sources will return 0.
+ */
+void stats_fs_source_revoke(struct stats_fs_source *source);
+
+/**
+ * stats_fs_source_get - increases refcount of source
+ * @source: a pointer to the source whose refcount will be increased
+ */
+void stats_fs_source_get(struct stats_fs_source *source);
+
+/**
+ * stats_fs_source_put - decreases refcount of source and deletes if needed
+ * @source: a pointer to the source whose refcount will be decreased
+ *
+ * If refcount arrives to zero, take care of deleting
+ * and free the source resources and files, by firstly recursively calling
+ * stats_fs_source_remove_subordinate to the child and then deleting
+ * its own files and allocations.
+ */
+void stats_fs_source_put(struct stats_fs_source *source);
+
+/**
+ * stats_fs_initialized - returns true if stats_fs fs has been registered
+ */
+bool stats_fs_initialized(void);
+
+#else
+
+#include <linux/err.h>
+
+/*
+ * We do not return NULL from these functions if CONFIG_STATS_FS is not enabled
+ * so users have a chance to detect if there was a real error or not. We don't
+ * want to duplicate the design decision mistakes of procfs and devfs again.
+ */
+
+static inline struct stats_fs_source *stats_fs_source_create(const char *fmt,
+ ...)
+{
+ return ERR_PTR(-ENODEV);
+}
+
+static inline int stats_fs_source_add_values(struct stats_fs_source *source,
+ struct stats_fs_value *val,
+ void *base_ptr)
+{
+ return -ENODEV;
+}
+
+static inline void
+stats_fs_source_add_subordinate(struct stats_fs_source *parent,
+ struct stats_fs_source *child)
+{ }
+
+static inline void
+stats_fs_source_remove_subordinate(struct stats_fs_source *parent,
+ struct stats_fs_source *child)
+{ }
+
+static inline int stats_fs_source_get_value(struct stats_fs_source *source,
+ struct stats_fs_value *val,
+ uint64_t *ret)
+{
+ return -ENODEV;
+}
+
+static inline int
+stats_fs_source_get_value_by_name(struct stats_fs_source *source, char *name,
+ uint64_t *ret)
+{
+ return -ENODEV;
+}
+
+static inline int stats_fs_source_clear(struct stats_fs_source *source,
+ struct stats_fs_value *val)
+{
+ return -ENODEV;
+}
+
+static inline void stats_fs_source_revoke(struct stats_fs_source *source)
+{ }
+
+static inline void stats_fs_source_get(struct stats_fs_source *source)
+{ }
+
+static inline void stats_fs_source_put(struct stats_fs_source *source)
+{ }
+
+static inline bool stats_fs_initialized(void)
+{ }
+
+#endif
+
+#endif
--
2.25.2
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v2 3/5] kunit: tests for stats_fs API
2020-05-04 11:03 [PATCH v2 0/5] Statsfs: a new ram-based file sytem for Linux kernel statistics Emanuele Giuseppe Esposito
2020-05-04 11:03 ` [PATCH v2 1/5] refcount, kref: add dec-and-test wrappers for rw_semaphores Emanuele Giuseppe Esposito
2020-05-04 11:03 ` [PATCH v2 2/5] stats_fs API: create, add and remove stats_fs sources and values Emanuele Giuseppe Esposito
@ 2020-05-04 11:03 ` Emanuele Giuseppe Esposito
2020-05-04 11:03 ` [PATCH v2 4/5] stats_fs fs: virtual fs to show stats to the end-user Emanuele Giuseppe Esposito
` (3 subsequent siblings)
6 siblings, 0 replies; 21+ messages in thread
From: Emanuele Giuseppe Esposito @ 2020-05-04 11:03 UTC (permalink / raw)
To: kvm
Cc: Emanuele Giuseppe Esposito, linux-s390, David Hildenbrand,
Cornelia Huck, Emanuele Giuseppe Esposito, linux-kernel, kvm-ppc,
linux-mips, Christian Borntraeger, Alexander Viro, linux-fsdevel,
Paolo Bonzini, Vitaly Kuznetsov, linuxppc-dev, Jim Mattson
Add kunit tests to extensively test the stats_fs API functionality.
In order to run them, the kernel .config must set CONFIG_KUNIT=y
and a new .kunitconfig file must be created with CONFIG_STATS_FS=y
and CONFIG_STATS_FS_TEST=y
Tests can be then started by running the following command from the root
directory of the linux kernel source tree:
./tools/testing/kunit/kunit.py run --timeout=30 --jobs=`nproc --all`
Signed-off-by: Emanuele Giuseppe Esposito <eesposit@redhat.com>
---
fs/Kconfig | 6 +
fs/stats_fs/Makefile | 2 +
fs/stats_fs/stats_fs-tests.c | 1088 ++++++++++++++++++++++++++++++++++
3 files changed, 1096 insertions(+)
create mode 100644 fs/stats_fs/stats_fs-tests.c
diff --git a/fs/Kconfig b/fs/Kconfig
index 1b0de0f19e96..0844e8defd22 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -334,4 +334,10 @@ config STATS_FS
stats_fs is a virtual file system that provides counters and
other statistics about the running kernel.
+config STATS_FS_TEST
+ bool "Tests for stats_fs"
+ depends on STATS_FS && KUNIT
+ help
+ tests for the stats_fs API.
+
endmenu
diff --git a/fs/stats_fs/Makefile b/fs/stats_fs/Makefile
index 94fe52d590d5..9db130fac6b6 100644
--- a/fs/stats_fs/Makefile
+++ b/fs/stats_fs/Makefile
@@ -1,4 +1,6 @@
# SPDX-License-Identifier: GPL-2.0-only
stats_fs-objs := stats_fs.o
+stats_fs-tests-objs := stats_fs-tests.o
obj-$(CONFIG_STATS_FS) += stats_fs.o
+obj-$(CONFIG_STATS_FS_TEST) += stats_fs-tests.o
diff --git a/fs/stats_fs/stats_fs-tests.c b/fs/stats_fs/stats_fs-tests.c
new file mode 100644
index 000000000000..46c3fb510ee9
--- /dev/null
+++ b/fs/stats_fs/stats_fs-tests.c
@@ -0,0 +1,1088 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/anon_inodes.h>
+#include <linux/spinlock.h>
+#include <linux/uaccess.h>
+#include <linux/rwsem.h>
+#include <linux/list.h>
+#include <linux/kref.h>
+
+#include <linux/limits.h>
+#include <linux/stats_fs.h>
+#include <kunit/test.h>
+#include "internal.h"
+
+#define STATS_FS_STAT(el, x, ...) \
+ { \
+ .name = #x, .offset = offsetof(struct container, el.x), \
+ ##__VA_ARGS__ \
+ }
+
+#define ARR_SIZE(el) ((int)(sizeof(el) / sizeof(struct stats_fs_value) - 1))
+
+struct test_values_struct {
+ uint64_t u64;
+ int32_t s32;
+ bool bo;
+ uint8_t u8;
+ int16_t s16;
+};
+
+struct container {
+ struct test_values_struct vals;
+};
+
+struct stats_fs_value test_values[6] = {
+ STATS_FS_STAT(vals, u64, .type = STATS_FS_U64,
+ .aggr_kind = STATS_FS_NONE, .mode = 0),
+ STATS_FS_STAT(vals, s32, .type = STATS_FS_S32,
+ .aggr_kind = STATS_FS_NONE, .mode = 0),
+ STATS_FS_STAT(vals, bo, .type = STATS_FS_BOOL,
+ .aggr_kind = STATS_FS_NONE, .mode = 0),
+ STATS_FS_STAT(vals, u8, .type = STATS_FS_U8, .aggr_kind = STATS_FS_NONE,
+ .mode = 0),
+ STATS_FS_STAT(vals, s16, .type = STATS_FS_S16,
+ .aggr_kind = STATS_FS_NONE, .mode = 0),
+ { NULL },
+};
+
+struct stats_fs_value test_aggr[4] = {
+ STATS_FS_STAT(vals, s32, .type = STATS_FS_S32,
+ .aggr_kind = STATS_FS_MIN, .mode = 0),
+ STATS_FS_STAT(vals, bo, .type = STATS_FS_BOOL,
+ .aggr_kind = STATS_FS_MAX, .mode = 0),
+ STATS_FS_STAT(vals, u64, .type = STATS_FS_U64,
+ .aggr_kind = STATS_FS_SUM, .mode = 0),
+ { NULL },
+};
+
+struct stats_fs_value test_same_name[3] = {
+ STATS_FS_STAT(vals, s32, .type = STATS_FS_S32,
+ .aggr_kind = STATS_FS_NONE, .mode = 0),
+ STATS_FS_STAT(vals, s32, .type = STATS_FS_S32,
+ .aggr_kind = STATS_FS_MIN, .mode = 0),
+ { NULL },
+};
+
+struct stats_fs_value test_all_aggr[6] = {
+ STATS_FS_STAT(vals, s32, .type = STATS_FS_S32,
+ .aggr_kind = STATS_FS_MIN, .mode = 0),
+ STATS_FS_STAT(vals, bo, .type = STATS_FS_BOOL,
+ .aggr_kind = STATS_FS_COUNT_ZERO, .mode = 0),
+ STATS_FS_STAT(vals, u64, .type = STATS_FS_U64,
+ .aggr_kind = STATS_FS_SUM, .mode = 0),
+ STATS_FS_STAT(vals, u8, .type = STATS_FS_U8, .aggr_kind = STATS_FS_AVG,
+ .mode = 0),
+ STATS_FS_STAT(vals, s16, .type = STATS_FS_S16,
+ .aggr_kind = STATS_FS_MAX, .mode = 0),
+ { NULL },
+};
+
+#define def_u64 ((uint64_t)64)
+
+#define def_val_s32 ((int32_t)S32_MIN)
+#define def_val_bool ((bool)true)
+#define def_val_u8 ((uint8_t)127)
+#define def_val_s16 ((int16_t)10000)
+
+#define def_val2_s32 ((int32_t)S16_MAX)
+#define def_val2_bool ((bool)false)
+#define def_val2_u8 ((uint8_t)255)
+#define def_val2_s16 ((int16_t)-20000)
+
+struct container cont = {
+ .vals = {
+ .u64 = def_u64,
+ .s32 = def_val_s32,
+ .bo = def_val_bool,
+ .u8 = def_val_u8,
+ .s16 = def_val_s16,
+ },
+};
+
+struct container cont2 = {
+ .vals = {
+ .u64 = def_u64,
+ .s32 = def_val2_s32,
+ .bo = def_val2_bool,
+ .u8 = def_val2_u8,
+ .s16 = def_val2_s16,
+ },
+};
+
+static void get_stats_at_addr(struct stats_fs_source *src, void *addr,
+ int *aggr, int *val, int use_addr)
+{
+ struct stats_fs_value *entry;
+ struct stats_fs_value_source *src_entry;
+ int counter_val = 0, counter_aggr = 0;
+
+ list_for_each_entry (src_entry, &src->values_head, list_element) {
+ if (use_addr && src_entry->base_addr != addr)
+ continue;
+
+ for (entry = src_entry->values; entry->name; entry++) {
+ if (entry->aggr_kind == STATS_FS_NONE)
+ counter_val++;
+ else
+ counter_aggr++;
+ }
+ }
+
+ if (aggr)
+ *aggr = counter_aggr;
+
+ if (val)
+ *val = counter_val;
+}
+
+int source_has_subsource(struct stats_fs_source *src,
+ struct stats_fs_source *sub)
+{
+ struct stats_fs_source *entry;
+
+ list_for_each_entry (entry, &src->subordinates_head, list_element) {
+ if (entry == sub)
+ return 1;
+ }
+ return 0;
+}
+
+int get_number_subsources(struct stats_fs_source *src)
+{
+ struct stats_fs_source *entry;
+ int counter = 0;
+
+ list_for_each_entry (entry, &src->subordinates_head, list_element) {
+ counter++;
+ }
+ return counter;
+}
+
+int get_number_values(struct stats_fs_source *src)
+{
+ int counter = 0;
+
+ get_stats_at_addr(src, NULL, NULL, &counter, 0);
+ return counter;
+}
+
+int get_total_number_values(struct stats_fs_source *src)
+{
+ struct stats_fs_source *sub_entry;
+ int counter = 0;
+
+ get_stats_at_addr(src, NULL, NULL, &counter, 0);
+
+ list_for_each_entry (sub_entry, &src->subordinates_head, list_element) {
+ counter += get_total_number_values(sub_entry);
+ }
+
+ return counter;
+}
+
+int get_number_aggregates(struct stats_fs_source *src)
+{
+ int counter = 0;
+
+ get_stats_at_addr(src, NULL, &counter, NULL, 1);
+ return counter;
+}
+
+int get_number_values_with_base(struct stats_fs_source *src, void *addr)
+{
+ int counter = 0;
+
+ get_stats_at_addr(src, addr, NULL, &counter, 1);
+ return counter;
+}
+
+int get_number_aggr_with_base(struct stats_fs_source *src, void *addr)
+{
+ int counter = 0;
+
+ get_stats_at_addr(src, addr, &counter, NULL, 1);
+ return counter;
+}
+
+static void test_empty_folder(struct kunit *test)
+{
+ struct stats_fs_source *src;
+
+ src = stats_fs_source_create("kvm_%d", 123);
+ KUNIT_EXPECT_EQ(test, strcmp(src->name, "kvm_123"), 0);
+ KUNIT_EXPECT_EQ(test, get_number_subsources(src), 0);
+ KUNIT_EXPECT_EQ(test, get_number_values(src), 0);
+ KUNIT_EXPECT_EQ(test, get_number_aggregates(src), 0);
+ stats_fs_source_put(src);
+}
+
+static void test_add_subfolder(struct kunit *test)
+{
+ struct stats_fs_source *src, *sub;
+
+ src = stats_fs_source_create("parent");
+ sub = stats_fs_source_create("child");
+ stats_fs_source_add_subordinate(src, sub);
+ KUNIT_EXPECT_EQ(test, source_has_subsource(src, sub), true);
+ KUNIT_EXPECT_EQ(test, get_number_subsources(src), 1);
+ KUNIT_EXPECT_EQ(test, get_number_values(src), 0);
+ KUNIT_EXPECT_EQ(test, get_number_aggregates(src), 0);
+ KUNIT_EXPECT_EQ(test, get_number_values(sub), 0);
+ KUNIT_EXPECT_EQ(test, get_number_aggregates(sub), 0);
+ KUNIT_EXPECT_EQ(test, get_total_number_values(src), 0);
+
+ stats_fs_source_put(sub);
+ sub = stats_fs_source_create("not a child");
+ KUNIT_EXPECT_EQ(test, source_has_subsource(src, sub), false);
+ KUNIT_EXPECT_EQ(test, get_number_subsources(src), 1);
+
+ stats_fs_source_put(sub);
+ stats_fs_source_put(src);
+}
+
+static void test_add_value(struct kunit *test)
+{
+ struct stats_fs_source *src;
+ int n;
+
+ src = stats_fs_source_create("parent");
+
+ // add values
+ n = stats_fs_source_add_values(src, test_values, &cont);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = get_number_values_with_base(src, &cont);
+ KUNIT_EXPECT_EQ(test, n, ARR_SIZE(test_values));
+
+ // add same values, nothing happens
+ n = stats_fs_source_add_values(src, test_values, &cont);
+ KUNIT_EXPECT_EQ(test, n, -EEXIST);
+ n = get_number_values_with_base(src, &cont);
+ KUNIT_EXPECT_EQ(test, n, ARR_SIZE(test_values));
+
+ // size is invaried
+ KUNIT_EXPECT_EQ(test, get_number_values(src), ARR_SIZE(test_values));
+
+ // no aggregates
+ n = get_number_aggr_with_base(src, &cont);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ KUNIT_EXPECT_EQ(test, get_number_values(src), ARR_SIZE(test_values));
+ KUNIT_EXPECT_EQ(test, get_number_aggregates(src), 0);
+
+ stats_fs_source_put(src);
+}
+
+static void test_add_value_in_subfolder(struct kunit *test)
+{
+ struct stats_fs_source *src, *sub, *sub_not;
+ int n;
+
+ src = stats_fs_source_create("parent");
+ sub = stats_fs_source_create("child");
+
+ // src -> sub
+ stats_fs_source_add_subordinate(src, sub);
+
+ // add values
+ n = stats_fs_source_add_values(sub, test_values, &cont);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = get_number_values_with_base(sub, &cont);
+ KUNIT_EXPECT_EQ(test, n, ARR_SIZE(test_values));
+ KUNIT_EXPECT_EQ(test, get_number_values(src), 0);
+ KUNIT_EXPECT_EQ(test, get_number_aggregates(src), 0);
+ KUNIT_EXPECT_EQ(test, get_total_number_values(src),
+ ARR_SIZE(test_values));
+
+ KUNIT_EXPECT_EQ(test, get_number_values(sub), ARR_SIZE(test_values));
+ // no values in sub
+ KUNIT_EXPECT_EQ(test, get_number_aggregates(sub), 0);
+
+ // different folder
+ sub_not = stats_fs_source_create("not a child");
+
+ // add values
+ n = stats_fs_source_add_values(sub_not, test_values, &cont);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = get_number_values_with_base(sub_not, &cont);
+ KUNIT_EXPECT_EQ(test, n, ARR_SIZE(test_values));
+ KUNIT_EXPECT_EQ(test, get_number_values(src), 0);
+ KUNIT_EXPECT_EQ(test, get_number_aggregates(src), 0);
+ KUNIT_EXPECT_EQ(test, get_total_number_values(src),
+ ARR_SIZE(test_values));
+
+ // remove sub, check values is 0
+ stats_fs_source_remove_subordinate(src, sub);
+ KUNIT_EXPECT_EQ(test, get_total_number_values(src), 0);
+
+ // re-add sub, check value are added
+ stats_fs_source_add_subordinate(src, sub);
+ KUNIT_EXPECT_EQ(test, get_total_number_values(src),
+ ARR_SIZE(test_values));
+
+ // add sub_not, check value are twice as many
+ stats_fs_source_add_subordinate(src, sub_not);
+ KUNIT_EXPECT_EQ(test, get_total_number_values(src),
+ ARR_SIZE(test_values) * 2);
+
+ KUNIT_EXPECT_EQ(test, get_number_values(sub_not),
+ ARR_SIZE(test_values));
+ KUNIT_EXPECT_EQ(test, get_number_aggregates(sub_not), 0);
+
+ stats_fs_source_put(sub);
+ stats_fs_source_put(sub_not);
+ stats_fs_source_put(src);
+}
+
+static void test_search_value(struct kunit *test)
+{
+ struct stats_fs_source *src;
+ uint64_t ret;
+ int n;
+
+ src = stats_fs_source_create("parent");
+
+ // add values
+ n = stats_fs_source_add_values(src, test_values, &cont);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = get_number_values_with_base(src, &cont);
+ KUNIT_EXPECT_EQ(test, n, ARR_SIZE(test_values));
+
+ // get u64
+ n = stats_fs_source_get_value_by_name(src, "u64", &ret);
+ KUNIT_EXPECT_EQ(test, ret, def_u64);
+ KUNIT_EXPECT_EQ(test, n, 0);
+
+ n = stats_fs_source_get_value_by_name(src, "s32", &ret);
+ KUNIT_EXPECT_EQ(test, ((int32_t)ret), def_val_s32);
+ KUNIT_EXPECT_EQ(test, n, 0);
+
+ n = stats_fs_source_get_value_by_name(src, "bo", &ret);
+ KUNIT_EXPECT_EQ(test, ((bool)ret), def_val_bool);
+ KUNIT_EXPECT_EQ(test, n, 0);
+
+ // get a non-added value
+ n = stats_fs_source_get_value_by_name(src, "does not exist", &ret);
+ KUNIT_EXPECT_EQ(test, ret, 0ull);
+ KUNIT_EXPECT_EQ(test, n, -ENOENT);
+
+ stats_fs_source_put(src);
+}
+
+static void test_search_value_in_subfolder(struct kunit *test)
+{
+ struct stats_fs_source *src, *sub;
+ uint64_t ret;
+ int n;
+
+ src = stats_fs_source_create("parent");
+ sub = stats_fs_source_create("child");
+
+ // src -> sub
+ stats_fs_source_add_subordinate(src, sub);
+
+ // add values to sub
+ n = stats_fs_source_add_values(sub, test_values, &cont);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = get_number_values_with_base(sub, &cont);
+ KUNIT_EXPECT_EQ(test, n, ARR_SIZE(test_values));
+
+ n = stats_fs_source_get_value_by_name(sub, "u64", &ret);
+ KUNIT_EXPECT_EQ(test, ret, def_u64);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = stats_fs_source_get_value_by_name(src, "u64", &ret);
+ KUNIT_EXPECT_EQ(test, ret, 0ull);
+ KUNIT_EXPECT_EQ(test, n, -ENOENT);
+
+ n = stats_fs_source_get_value_by_name(sub, "s32", &ret);
+ KUNIT_EXPECT_EQ(test, ((int32_t)ret), def_val_s32);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = stats_fs_source_get_value_by_name(src, "s32", &ret);
+ KUNIT_EXPECT_EQ(test, ret, 0ull);
+ KUNIT_EXPECT_EQ(test, n, -ENOENT);
+
+ n = stats_fs_source_get_value_by_name(sub, "bo", &ret);
+ KUNIT_EXPECT_EQ(test, ((bool)ret), def_val_bool);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = stats_fs_source_get_value_by_name(src, "bo", &ret);
+ KUNIT_EXPECT_EQ(test, ret, 0ull);
+ KUNIT_EXPECT_EQ(test, n, -ENOENT);
+
+ n = stats_fs_source_get_value_by_name(sub, "does not exist", &ret);
+ KUNIT_EXPECT_EQ(test, ret, 0ull);
+ KUNIT_EXPECT_EQ(test, n, -ENOENT);
+ n = stats_fs_source_get_value_by_name(src, "does not exist", &ret);
+ KUNIT_EXPECT_EQ(test, ret, 0ull);
+ KUNIT_EXPECT_EQ(test, n, -ENOENT);
+
+ stats_fs_source_put(sub);
+ stats_fs_source_put(src);
+}
+
+static void test_search_value_in_empty_folder(struct kunit *test)
+{
+ struct stats_fs_source *src;
+ uint64_t ret;
+ int n;
+
+ src = stats_fs_source_create("empty folder");
+ KUNIT_EXPECT_EQ(test, get_number_aggregates(src), 0);
+ KUNIT_EXPECT_EQ(test, get_number_subsources(src), 0);
+ KUNIT_EXPECT_EQ(test, get_number_values(src), 0);
+
+ n = stats_fs_source_get_value_by_name(src, "u64", &ret);
+ KUNIT_EXPECT_EQ(test, ret, 0ull);
+ KUNIT_EXPECT_EQ(test, n, -ENOENT);
+
+ n = stats_fs_source_get_value_by_name(src, "s32", &ret);
+ KUNIT_EXPECT_EQ(test, ret, 0ull);
+ KUNIT_EXPECT_EQ(test, n, -ENOENT);
+
+ n = stats_fs_source_get_value_by_name(src, "bo", &ret);
+ KUNIT_EXPECT_EQ(test, ret, 0ull);
+ KUNIT_EXPECT_EQ(test, n, -ENOENT);
+
+ n = stats_fs_source_get_value_by_name(src, "does not exist", &ret);
+ KUNIT_EXPECT_EQ(test, ret, 0ull);
+ KUNIT_EXPECT_EQ(test, n, -ENOENT);
+
+ stats_fs_source_put(src);
+}
+
+static void test_add_aggregate(struct kunit *test)
+{
+ struct stats_fs_source *src;
+ int n;
+
+ src = stats_fs_source_create("parent");
+
+ // add aggr to src, no values
+ n = stats_fs_source_add_values(src, test_aggr, NULL);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = get_number_values_with_base(src, NULL);
+ KUNIT_EXPECT_EQ(test, n, 0);
+
+ // count values
+ n = get_number_aggr_with_base(src, NULL);
+ KUNIT_EXPECT_EQ(test, n, ARR_SIZE(test_aggr));
+
+ // add same array again, should not be added
+ n = stats_fs_source_add_values(src, test_aggr, NULL);
+ KUNIT_EXPECT_EQ(test, n, -EEXIST);
+ n = get_number_aggr_with_base(src, NULL);
+ KUNIT_EXPECT_EQ(test, n, ARR_SIZE(test_aggr));
+
+ KUNIT_EXPECT_EQ(test, get_number_values(src), 0);
+ KUNIT_EXPECT_EQ(test, get_number_aggregates(src), ARR_SIZE(test_aggr));
+
+ stats_fs_source_put(src);
+}
+
+static void test_add_aggregate_in_subfolder(struct kunit *test)
+{
+ struct stats_fs_source *src, *sub, *sub_not;
+ int n;
+
+ src = stats_fs_source_create("parent");
+ sub = stats_fs_source_create("child");
+ // src->sub
+ stats_fs_source_add_subordinate(src, sub);
+
+ // add aggr to sub
+ n = stats_fs_source_add_values(sub, test_aggr, NULL);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = get_number_aggr_with_base(sub, NULL);
+ KUNIT_EXPECT_EQ(test, n, ARR_SIZE(test_aggr));
+ KUNIT_EXPECT_EQ(test, get_number_values(src), 0);
+ KUNIT_EXPECT_EQ(test, get_number_aggregates(src), 0);
+ KUNIT_EXPECT_EQ(test, get_total_number_values(src), 0);
+
+ KUNIT_EXPECT_EQ(test, get_number_values(sub), 0);
+ KUNIT_EXPECT_EQ(test, get_number_aggregates(sub), ARR_SIZE(test_aggr));
+
+ // not a child
+ sub_not = stats_fs_source_create("not a child");
+
+ // add aggr to "not a child"
+ n = stats_fs_source_add_values(sub_not, test_aggr, NULL);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = get_number_aggr_with_base(sub_not, NULL);
+ KUNIT_EXPECT_EQ(test, n, ARR_SIZE(test_aggr));
+ KUNIT_EXPECT_EQ(test, get_number_values(src), 0);
+ KUNIT_EXPECT_EQ(test, get_number_aggregates(src), 0);
+ KUNIT_EXPECT_EQ(test, get_total_number_values(src), 0);
+
+ // remove sub
+ stats_fs_source_remove_subordinate(src, sub);
+ KUNIT_EXPECT_EQ(test, get_total_number_values(src), 0);
+
+ // re-add both
+ stats_fs_source_add_subordinate(src, sub);
+ KUNIT_EXPECT_EQ(test, get_total_number_values(src), 0);
+ stats_fs_source_add_subordinate(src, sub_not);
+ KUNIT_EXPECT_EQ(test, get_total_number_values(src), 0);
+
+ KUNIT_EXPECT_EQ(test, get_number_values(sub_not), 0);
+ KUNIT_EXPECT_EQ(test, get_number_aggregates(sub_not),
+ ARR_SIZE(test_aggr));
+
+ stats_fs_source_put(sub);
+ stats_fs_source_put(sub_not);
+ stats_fs_source_put(src);
+}
+
+static void test_search_aggregate(struct kunit *test)
+{
+ struct stats_fs_source *src;
+ uint64_t ret;
+ int n;
+
+ src = stats_fs_source_create("parent");
+ n = stats_fs_source_add_values(src, test_aggr, NULL);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = get_number_aggr_with_base(src, NULL);
+ KUNIT_EXPECT_EQ(test, n, ARR_SIZE(test_aggr));
+ n = get_number_aggr_with_base(src, &cont);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = stats_fs_source_get_value_by_name(src, "u64", &ret);
+ KUNIT_EXPECT_EQ(test, ret, 0ull);
+ KUNIT_EXPECT_EQ(test, n, 0);
+
+ n = stats_fs_source_get_value_by_name(src, "s32", &ret);
+ KUNIT_EXPECT_EQ(test, (int64_t)ret, S64_MAX);
+ KUNIT_EXPECT_EQ(test, n, 0);
+
+ n = stats_fs_source_get_value_by_name(src, "bo", &ret);
+ KUNIT_EXPECT_EQ(test, ret, 0ull);
+ KUNIT_EXPECT_EQ(test, n, 0);
+
+ n = stats_fs_source_get_value_by_name(src, "does not exist", &ret);
+ KUNIT_EXPECT_EQ(test, ret, 0ull);
+ KUNIT_EXPECT_EQ(test, n, -ENOENT);
+ stats_fs_source_put(src);
+}
+
+static void test_search_aggregate_in_subfolder(struct kunit *test)
+{
+ struct stats_fs_source *src, *sub;
+ uint64_t ret;
+ int n;
+
+ src = stats_fs_source_create("parent");
+ sub = stats_fs_source_create("child");
+
+ stats_fs_source_add_subordinate(src, sub);
+
+ n = stats_fs_source_add_values(sub, test_aggr, NULL);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = get_number_aggr_with_base(sub, NULL);
+ KUNIT_EXPECT_EQ(test, n, ARR_SIZE(test_aggr));
+ n = get_number_aggr_with_base(sub, &cont);
+ KUNIT_EXPECT_EQ(test, n, 0);
+
+ // no u64 in test_aggr
+ n = stats_fs_source_get_value_by_name(sub, "u64", &ret);
+ KUNIT_EXPECT_EQ(test, ret, 0ull);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = stats_fs_source_get_value_by_name(src, "u64", &ret);
+ KUNIT_EXPECT_EQ(test, ret, 0ull);
+ KUNIT_EXPECT_EQ(test, n, -ENOENT);
+
+ n = stats_fs_source_get_value_by_name(sub, "s32", &ret);
+ KUNIT_EXPECT_EQ(test, (int64_t)ret, S64_MAX);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = stats_fs_source_get_value_by_name(src, "s32", &ret);
+ KUNIT_EXPECT_EQ(test, ret, 0ull);
+ KUNIT_EXPECT_EQ(test, n, -ENOENT);
+
+ n = stats_fs_source_get_value_by_name(sub, "bo", &ret);
+ KUNIT_EXPECT_EQ(test, ret, 0ull);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = stats_fs_source_get_value_by_name(src, "bo", &ret);
+ KUNIT_EXPECT_EQ(test, ret, 0ull);
+ KUNIT_EXPECT_EQ(test, n, -ENOENT);
+
+ n = stats_fs_source_get_value_by_name(sub, "does not exist", &ret);
+ KUNIT_EXPECT_EQ(test, ret, 0ull);
+ KUNIT_EXPECT_EQ(test, n, -ENOENT);
+ n = stats_fs_source_get_value_by_name(src, "does not exist", &ret);
+ KUNIT_EXPECT_EQ(test, ret, 0ull);
+ KUNIT_EXPECT_EQ(test, n, -ENOENT);
+
+ stats_fs_source_put(sub);
+ stats_fs_source_put(src);
+}
+
+void test_search_same(struct kunit *test)
+{
+ struct stats_fs_source *src;
+ uint64_t ret;
+ int n;
+
+ src = stats_fs_source_create("parent");
+ n = stats_fs_source_add_values(src, test_same_name, &cont);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = get_number_values_with_base(src, &cont);
+ KUNIT_EXPECT_EQ(test, n, 1);
+ n = get_number_aggr_with_base(src, &cont);
+ KUNIT_EXPECT_EQ(test, n, 1);
+
+ n = stats_fs_source_add_values(src, test_same_name, &cont);
+ KUNIT_EXPECT_EQ(test, n, -EEXIST);
+ n = get_number_values_with_base(src, &cont);
+ KUNIT_EXPECT_EQ(test, n, 1);
+ n = get_number_aggr_with_base(src, &cont);
+ KUNIT_EXPECT_EQ(test, n, 1);
+
+ // returns first the value
+ n = stats_fs_source_get_value_by_name(src, "s32", &ret);
+ KUNIT_EXPECT_EQ(test, ((int32_t)ret), def_val_s32);
+ KUNIT_EXPECT_EQ(test, n, 0);
+
+ stats_fs_source_put(src);
+}
+
+static void test_add_mixed(struct kunit *test)
+{
+ struct stats_fs_source *src;
+ int n;
+
+ src = stats_fs_source_create("parent");
+
+ n = stats_fs_source_add_values(src, test_aggr, NULL);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = get_number_values_with_base(src, NULL);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = stats_fs_source_add_values(src, test_values, &cont);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = get_number_aggr_with_base(src, NULL);
+ KUNIT_EXPECT_EQ(test, n, ARR_SIZE(test_aggr));
+
+ n = stats_fs_source_add_values(src, test_values, &cont);
+ KUNIT_EXPECT_EQ(test, n, -EEXIST);
+ n = get_number_values_with_base(src, &cont);
+ KUNIT_EXPECT_EQ(test, n, ARR_SIZE(test_values));
+ n = stats_fs_source_add_values(src, test_aggr, NULL);
+ KUNIT_EXPECT_EQ(test, n, -EEXIST);
+ n = get_number_aggr_with_base(src, NULL);
+ KUNIT_EXPECT_EQ(test, n, ARR_SIZE(test_aggr));
+
+ KUNIT_EXPECT_EQ(test, get_number_values(src), ARR_SIZE(test_values));
+ KUNIT_EXPECT_EQ(test, get_number_aggregates(src), ARR_SIZE(test_aggr));
+ stats_fs_source_put(src);
+}
+
+static void test_search_mixed(struct kunit *test)
+{
+ struct stats_fs_source *src, *sub;
+ uint64_t ret;
+ int n;
+
+ src = stats_fs_source_create("parent");
+ sub = stats_fs_source_create("child");
+ stats_fs_source_add_subordinate(src, sub);
+
+ // src has the aggregates, sub the values. Just search
+ n = stats_fs_source_add_values(sub, test_values, &cont);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = get_number_values_with_base(sub, &cont);
+ KUNIT_EXPECT_EQ(test, n, ARR_SIZE(test_values));
+ n = stats_fs_source_add_values(src, test_aggr, &cont);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = get_number_aggr_with_base(src, &cont);
+ KUNIT_EXPECT_EQ(test, n, ARR_SIZE(test_aggr));
+
+ // u64 is sum so again same value
+ n = stats_fs_source_get_value_by_name(sub, "u64", &ret);
+ KUNIT_EXPECT_EQ(test, ret, def_u64);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = stats_fs_source_get_value_by_name(src, "u64", &ret);
+ KUNIT_EXPECT_EQ(test, ret, def_u64);
+ KUNIT_EXPECT_EQ(test, n, 0);
+
+ // s32 is min so return the value also in the aggregate
+ n = stats_fs_source_get_value_by_name(sub, "s32", &ret);
+ KUNIT_EXPECT_EQ(test, ((int32_t)ret), def_val_s32);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = stats_fs_source_get_value_by_name(src, "s32", &ret);
+ KUNIT_EXPECT_EQ(test, ((int32_t)ret), def_val_s32);
+ KUNIT_EXPECT_EQ(test, n, 0);
+
+ // bo is max
+ n = stats_fs_source_get_value_by_name(sub, "bo", &ret);
+ KUNIT_EXPECT_EQ(test, (bool)ret, def_val_bool);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = stats_fs_source_get_value_by_name(src, "bo", &ret);
+ KUNIT_EXPECT_EQ(test, (bool)ret, def_val_bool);
+ KUNIT_EXPECT_EQ(test, n, 0);
+
+ n = stats_fs_source_get_value_by_name(sub, "does not exist", &ret);
+ KUNIT_EXPECT_EQ(test, ret, 0ull);
+ KUNIT_EXPECT_EQ(test, n, -ENOENT);
+ n = stats_fs_source_get_value_by_name(src, "does not exist", &ret);
+ KUNIT_EXPECT_EQ(test, ret, 0ull);
+ KUNIT_EXPECT_EQ(test, n, -ENOENT);
+
+ stats_fs_source_put(sub);
+ stats_fs_source_put(src);
+}
+
+static void test_all_aggregations_agg_val_val(struct kunit *test)
+{
+ struct stats_fs_source *src, *sub1, *sub2;
+ uint64_t ret;
+ int n;
+
+ src = stats_fs_source_create("parent");
+ sub1 = stats_fs_source_create("child1");
+ sub2 = stats_fs_source_create("child2");
+ stats_fs_source_add_subordinate(src, sub1);
+ stats_fs_source_add_subordinate(src, sub2);
+
+ n = stats_fs_source_add_values(sub1, test_all_aggr, &cont);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = get_number_aggr_with_base(sub1, &cont);
+ KUNIT_EXPECT_EQ(test, n, ARR_SIZE(test_all_aggr));
+ n = stats_fs_source_add_values(sub2, test_all_aggr, &cont2);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = get_number_aggr_with_base(sub2, &cont2);
+ KUNIT_EXPECT_EQ(test, n, ARR_SIZE(test_all_aggr));
+
+ n = stats_fs_source_add_values(src, test_all_aggr, NULL);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = get_number_aggr_with_base(src, NULL);
+ KUNIT_EXPECT_EQ(test, n, ARR_SIZE(test_all_aggr));
+
+ // sum
+ n = stats_fs_source_get_value_by_name(src, "u64", &ret);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ KUNIT_EXPECT_EQ(test, ret, def_u64 * 2);
+
+ // min
+ n = stats_fs_source_get_value_by_name(src, "s32", &ret);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ KUNIT_EXPECT_EQ(test, ((int32_t)ret), def_val_s32);
+
+ // count_0
+ n = stats_fs_source_get_value_by_name(src, "bo", &ret);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ KUNIT_EXPECT_EQ(test, ret, 1ull);
+
+ // avg
+ n = stats_fs_source_get_value_by_name(src, "u8", &ret);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ KUNIT_EXPECT_EQ(test, ret, 191ull);
+
+ // max
+ n = stats_fs_source_get_value_by_name(src, "s16", &ret);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ KUNIT_EXPECT_EQ(test, (int16_t)ret, def_val_s16);
+
+ stats_fs_source_put(sub1);
+ stats_fs_source_put(sub2);
+ stats_fs_source_put(src);
+}
+
+static void test_all_aggregations_val_agg_val(struct kunit *test)
+{
+ struct stats_fs_source *src, *sub1, *sub2;
+ uint64_t ret;
+ int n;
+
+ src = stats_fs_source_create("parent");
+ sub1 = stats_fs_source_create("child1");
+ sub2 = stats_fs_source_create("child2");
+ stats_fs_source_add_subordinate(src, sub1);
+ stats_fs_source_add_subordinate(src, sub2);
+
+ n = stats_fs_source_add_values(src, test_all_aggr, &cont);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = get_number_aggr_with_base(src, &cont);
+ KUNIT_EXPECT_EQ(test, n, ARR_SIZE(test_all_aggr));
+ n = stats_fs_source_add_values(sub2, test_all_aggr, &cont2);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = get_number_aggr_with_base(sub2, &cont2);
+ KUNIT_EXPECT_EQ(test, n, ARR_SIZE(test_all_aggr));
+
+ n = stats_fs_source_add_values(sub1, test_all_aggr, NULL);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = get_number_aggr_with_base(sub1, NULL);
+ KUNIT_EXPECT_EQ(test, n, ARR_SIZE(test_all_aggr));
+
+ n = stats_fs_source_get_value_by_name(src, "u64", &ret);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ KUNIT_EXPECT_EQ(test, ret, def_u64);
+ n = stats_fs_source_get_value_by_name(sub1, "u64", &ret);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ KUNIT_EXPECT_EQ(test, ret, 0ull);
+ n = stats_fs_source_get_value_by_name(sub2, "u64", &ret);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ KUNIT_EXPECT_EQ(test, ret, def_u64);
+
+ n = stats_fs_source_get_value_by_name(src, "s32", &ret);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ KUNIT_EXPECT_EQ(test, ((int32_t)ret), def_val_s32);
+ n = stats_fs_source_get_value_by_name(sub1, "s32", &ret);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ KUNIT_EXPECT_EQ(test, (int64_t)ret, S64_MAX); // MIN
+ n = stats_fs_source_get_value_by_name(sub2, "s32", &ret);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ KUNIT_EXPECT_EQ(test, ((int32_t)ret), def_val2_s32);
+
+ n = stats_fs_source_get_value_by_name(src, "bo", &ret);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ KUNIT_EXPECT_EQ(test, (bool)ret, def_val_bool);
+ n = stats_fs_source_get_value_by_name(sub1, "bo", &ret);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ KUNIT_EXPECT_EQ(test, ret, 0ull);
+ n = stats_fs_source_get_value_by_name(sub2, "bo", &ret);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ KUNIT_EXPECT_EQ(test, (bool)ret, def_val2_bool);
+
+ n = stats_fs_source_get_value_by_name(src, "u8", &ret);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ KUNIT_EXPECT_EQ(test, (uint8_t)ret, def_val_u8);
+ n = stats_fs_source_get_value_by_name(sub1, "u8", &ret);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ KUNIT_EXPECT_EQ(test, ret, 0ull);
+ n = stats_fs_source_get_value_by_name(sub2, "u8", &ret);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ KUNIT_EXPECT_EQ(test, (uint8_t)ret, def_val2_u8);
+
+ n = stats_fs_source_get_value_by_name(src, "s16", &ret);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ KUNIT_EXPECT_EQ(test, (int16_t)ret, def_val_s16);
+ n = stats_fs_source_get_value_by_name(sub1, "s16", &ret);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ KUNIT_EXPECT_EQ(test, (int64_t)ret, S64_MIN); // MAX
+ n = stats_fs_source_get_value_by_name(sub2, "s16", &ret);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ KUNIT_EXPECT_EQ(test, (int16_t)ret, def_val2_s16);
+
+ stats_fs_source_put(sub1);
+ stats_fs_source_put(sub2);
+ stats_fs_source_put(src);
+}
+
+static void test_all_aggregations_agg_val_val_sub(struct kunit *test)
+{
+ struct stats_fs_source *src, *sub1, *sub11;
+ uint64_t ret;
+ int n;
+
+ src = stats_fs_source_create("parent");
+ sub1 = stats_fs_source_create("child1");
+ sub11 = stats_fs_source_create("child11");
+ stats_fs_source_add_subordinate(src, sub1);
+ stats_fs_source_add_subordinate(sub1, sub11); // changes here!
+
+ n = stats_fs_source_add_values(sub1, test_values, &cont);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = get_number_values_with_base(sub1, &cont);
+ KUNIT_EXPECT_EQ(test, n, ARR_SIZE(test_values));
+ n = stats_fs_source_add_values(sub11, test_values, &cont2);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = get_number_values_with_base(sub11, &cont2);
+ KUNIT_EXPECT_EQ(test, n, ARR_SIZE(test_values));
+
+ KUNIT_EXPECT_EQ(test, get_total_number_values(src),
+ ARR_SIZE(test_values) * 2);
+
+ n = stats_fs_source_add_values(sub1, test_all_aggr, &cont);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = get_number_aggr_with_base(sub1, &cont);
+ KUNIT_EXPECT_EQ(test, n, ARR_SIZE(test_all_aggr));
+ n = stats_fs_source_add_values(sub11, test_all_aggr, &cont2);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = get_number_aggr_with_base(sub11, &cont2);
+ KUNIT_EXPECT_EQ(test, n, ARR_SIZE(test_all_aggr));
+
+ n = stats_fs_source_add_values(src, test_all_aggr, NULL);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = get_number_aggr_with_base(src, NULL);
+ KUNIT_EXPECT_EQ(test, n, ARR_SIZE(test_all_aggr));
+
+ // sum
+ n = stats_fs_source_get_value_by_name(src, "u64", &ret);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ KUNIT_EXPECT_EQ(test, ret, def_u64 * 2);
+
+ // min
+ n = stats_fs_source_get_value_by_name(src, "s32", &ret);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ KUNIT_EXPECT_EQ(test, ((int32_t)ret), def_val_s32);
+
+ // count_0
+ n = stats_fs_source_get_value_by_name(src, "bo", &ret);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ KUNIT_EXPECT_EQ(test, ret, 1ull);
+
+ // avg
+ n = stats_fs_source_get_value_by_name(src, "u8", &ret);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ KUNIT_EXPECT_EQ(test, ret, 191ull);
+
+ // max
+ n = stats_fs_source_get_value_by_name(src, "s16", &ret);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ KUNIT_EXPECT_EQ(test, (int16_t)ret, def_val_s16);
+
+ stats_fs_source_put(sub1);
+ stats_fs_source_put(sub11);
+ stats_fs_source_put(src);
+}
+
+static void test_all_aggregations_agg_no_val_sub(struct kunit *test)
+{
+ struct stats_fs_source *src, *sub1, *sub11;
+ uint64_t ret;
+ int n;
+
+ src = stats_fs_source_create("parent");
+ sub1 = stats_fs_source_create("child1");
+ sub11 = stats_fs_source_create("child11");
+ stats_fs_source_add_subordinate(src, sub1);
+ stats_fs_source_add_subordinate(sub1, sub11);
+
+ n = stats_fs_source_add_values(sub11, test_all_aggr, &cont2);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = get_number_aggr_with_base(sub11, &cont2);
+ KUNIT_EXPECT_EQ(test, n, ARR_SIZE(test_all_aggr));
+
+ KUNIT_EXPECT_EQ(test, get_total_number_values(src), 0);
+
+ n = stats_fs_source_add_values(src, test_all_aggr, NULL);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = get_number_aggr_with_base(src, NULL);
+ KUNIT_EXPECT_EQ(test, n, ARR_SIZE(test_all_aggr));
+
+ // sum
+ n = stats_fs_source_get_value_by_name(src, "u64", &ret);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ KUNIT_EXPECT_EQ(test, ret, def_u64);
+
+ // min
+ n = stats_fs_source_get_value_by_name(src, "s32", &ret);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ KUNIT_EXPECT_EQ(test, ((int32_t)ret), def_val2_s32);
+
+ // count_0
+ n = stats_fs_source_get_value_by_name(src, "bo", &ret);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ KUNIT_EXPECT_EQ(test, ret, 1ull);
+
+ // avg
+ n = stats_fs_source_get_value_by_name(src, "u8", &ret);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ KUNIT_EXPECT_EQ(test, (uint8_t)ret, def_val2_u8);
+
+ // max
+ n = stats_fs_source_get_value_by_name(src, "s16", &ret);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ KUNIT_EXPECT_EQ(test, (int16_t)ret, def_val2_s16);
+
+ stats_fs_source_put(sub1);
+ stats_fs_source_put(sub11);
+ stats_fs_source_put(src);
+}
+
+static void test_all_aggregations_agg_agg_val_sub(struct kunit *test)
+{
+ struct stats_fs_source *src, *sub1, *sub11, *sub12;
+ uint64_t ret;
+ int n;
+
+ src = stats_fs_source_create("parent");
+ sub1 = stats_fs_source_create("child1");
+ sub11 = stats_fs_source_create("child11");
+ sub12 = stats_fs_source_create("child12");
+ stats_fs_source_add_subordinate(src, sub1);
+ stats_fs_source_add_subordinate(sub1, sub11);
+ stats_fs_source_add_subordinate(sub1, sub12);
+
+ n = stats_fs_source_add_values(sub11, test_all_aggr, &cont2);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = get_number_aggr_with_base(sub11, &cont2);
+ KUNIT_EXPECT_EQ(test, n, ARR_SIZE(test_all_aggr));
+
+ n = stats_fs_source_add_values(sub12, test_all_aggr, &cont);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = get_number_aggr_with_base(sub12, &cont);
+ KUNIT_EXPECT_EQ(test, n, ARR_SIZE(test_all_aggr));
+
+ KUNIT_EXPECT_EQ(test, get_total_number_values(src), 0);
+
+ n = stats_fs_source_add_values(src, test_all_aggr, NULL);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = get_number_aggr_with_base(src, NULL);
+ KUNIT_EXPECT_EQ(test, n, ARR_SIZE(test_all_aggr));
+
+ n = stats_fs_source_add_values(sub1, test_all_aggr, NULL);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ n = get_number_aggr_with_base(sub1, NULL);
+ KUNIT_EXPECT_EQ(test, n, ARR_SIZE(test_all_aggr));
+
+ // sum
+ n = stats_fs_source_get_value_by_name(src, "u64", &ret);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ KUNIT_EXPECT_EQ(test, ret, def_u64 * 2);
+
+ // min
+ n = stats_fs_source_get_value_by_name(src, "s32", &ret);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ KUNIT_EXPECT_EQ(test, ((int32_t)ret), def_val_s32);
+
+ // count_0
+ n = stats_fs_source_get_value_by_name(src, "bo", &ret);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ KUNIT_EXPECT_EQ(test, ret, 1ull);
+
+ // avg
+ n = stats_fs_source_get_value_by_name(src, "u8", &ret);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ KUNIT_EXPECT_EQ(test, (uint8_t)ret,
+ (uint8_t)((def_val2_u8 + def_val_u8) / 2));
+
+ // max
+ n = stats_fs_source_get_value_by_name(src, "s16", &ret);
+ KUNIT_EXPECT_EQ(test, n, 0);
+ KUNIT_EXPECT_EQ(test, (int16_t)ret, def_val_s16);
+
+ stats_fs_source_put(sub1);
+ stats_fs_source_put(sub11);
+ stats_fs_source_put(sub12);
+ stats_fs_source_put(src);
+}
+
+static struct kunit_case stats_fs_test_cases[] = {
+ KUNIT_CASE(test_empty_folder),
+ KUNIT_CASE(test_add_subfolder),
+ KUNIT_CASE(test_add_value),
+ KUNIT_CASE(test_add_value_in_subfolder),
+ KUNIT_CASE(test_search_value),
+ KUNIT_CASE(test_search_value_in_subfolder),
+ KUNIT_CASE(test_search_value_in_empty_folder),
+ KUNIT_CASE(test_add_aggregate),
+ KUNIT_CASE(test_add_aggregate_in_subfolder),
+ KUNIT_CASE(test_search_aggregate),
+ KUNIT_CASE(test_search_aggregate_in_subfolder),
+ KUNIT_CASE(test_search_same),
+ KUNIT_CASE(test_add_mixed),
+ KUNIT_CASE(test_search_mixed),
+ KUNIT_CASE(test_all_aggregations_agg_val_val),
+ KUNIT_CASE(test_all_aggregations_val_agg_val),
+ KUNIT_CASE(test_all_aggregations_agg_val_val_sub),
+ KUNIT_CASE(test_all_aggregations_agg_no_val_sub),
+ KUNIT_CASE(test_all_aggregations_agg_agg_val_sub),
+ {}
+};
+
+static struct kunit_suite stats_fs_test_suite = {
+ .name = "stats_fs",
+ .test_cases = stats_fs_test_cases,
+};
+
+kunit_test_suite(stats_fs_test_suite);
--
2.25.2
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v2 4/5] stats_fs fs: virtual fs to show stats to the end-user
2020-05-04 11:03 [PATCH v2 0/5] Statsfs: a new ram-based file sytem for Linux kernel statistics Emanuele Giuseppe Esposito
` (2 preceding siblings ...)
2020-05-04 11:03 ` [PATCH v2 3/5] kunit: tests for stats_fs API Emanuele Giuseppe Esposito
@ 2020-05-04 11:03 ` Emanuele Giuseppe Esposito
2020-05-04 11:03 ` [PATCH v2 5/5] kvm_main: replace debugfs with stats_fs Emanuele Giuseppe Esposito
` (2 subsequent siblings)
6 siblings, 0 replies; 21+ messages in thread
From: Emanuele Giuseppe Esposito @ 2020-05-04 11:03 UTC (permalink / raw)
To: kvm
Cc: Emanuele Giuseppe Esposito, linux-s390, David Hildenbrand,
Cornelia Huck, Emanuele Giuseppe Esposito, linux-kernel, kvm-ppc,
linux-mips, Christian Borntraeger, Alexander Viro, linux-fsdevel,
Paolo Bonzini, Vitaly Kuznetsov, linuxppc-dev, Jim Mattson
Add virtual fs that maps stats_fs sources with directories, and values
(simple or aggregates) to files.
Every time a file is read/cleared, the fs internally invokes the stats_fs
API to get/set the requested value.
fs/stats_fs/inode.c is pretty much similar to what is done in
fs/debugfs/inode.c, with the exception that the API is only
composed by stats_fs_create_file, stats_fs_create_dir and stats_fs_remove.
Signed-off-by: Emanuele Giuseppe Esposito <eesposit@redhat.com>
---
fs/stats_fs/Makefile | 2 +-
fs/stats_fs/inode.c | 337 +++++++++++++++++++++++++++++++++++++
fs/stats_fs/internal.h | 15 ++
fs/stats_fs/stats_fs.c | 163 ++++++++++++++++++
include/linux/stats_fs.h | 15 ++
include/uapi/linux/magic.h | 1 +
tools/lib/api/fs/fs.c | 21 +++
7 files changed, 553 insertions(+), 1 deletion(-)
create mode 100644 fs/stats_fs/inode.c
diff --git a/fs/stats_fs/Makefile b/fs/stats_fs/Makefile
index 9db130fac6b6..ac12c27545f6 100644
--- a/fs/stats_fs/Makefile
+++ b/fs/stats_fs/Makefile
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-only
-stats_fs-objs := stats_fs.o
+stats_fs-objs := inode.o stats_fs.o
stats_fs-tests-objs := stats_fs-tests.o
obj-$(CONFIG_STATS_FS) += stats_fs.o
diff --git a/fs/stats_fs/inode.c b/fs/stats_fs/inode.c
new file mode 100644
index 000000000000..865ee91656ba
--- /dev/null
+++ b/fs/stats_fs/inode.c
@@ -0,0 +1,337 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * inode.c - part of stats_fs, a tiny little stats_fs file system
+ *
+ * Copyright (C) 2020 Emanuele Giuseppe Esposito <eesposit@redhat.com>
+ * Copyright (C) 2020 Redhat
+ */
+#define pr_fmt(fmt) "stats_fs: " fmt
+
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/mount.h>
+#include <linux/init.h>
+#include <linux/stats_fs.h>
+#include <linux/string.h>
+#include <linux/seq_file.h>
+#include <linux/parser.h>
+#include <linux/magic.h>
+#include <linux/slab.h>
+
+#include "internal.h"
+
+#define STATS_FS_DEFAULT_MODE 0700
+
+static struct simple_fs stats_fs;
+static bool stats_fs_registered;
+
+struct stats_fs_mount_opts {
+ kuid_t uid;
+ kgid_t gid;
+ umode_t mode;
+};
+
+enum {
+ Opt_uid,
+ Opt_gid,
+ Opt_mode,
+ Opt_err
+};
+
+static const match_table_t tokens = {
+ {Opt_uid, "uid=%u"},
+ {Opt_gid, "gid=%u"},
+ {Opt_mode, "mode=%o"},
+ {Opt_err, NULL}
+};
+
+struct stats_fs_fs_info {
+ struct stats_fs_mount_opts mount_opts;
+};
+
+static int stats_fs_parse_options(char *data, struct stats_fs_mount_opts *opts)
+{
+ substring_t args[MAX_OPT_ARGS];
+ int option;
+ int token;
+ kuid_t uid;
+ kgid_t gid;
+ char *p;
+
+ opts->mode = STATS_FS_DEFAULT_MODE;
+
+ while ((p = strsep(&data, ",")) != NULL) {
+ if (!*p)
+ continue;
+
+ token = match_token(p, tokens, args);
+ switch (token) {
+ case Opt_uid:
+ if (match_int(&args[0], &option))
+ return -EINVAL;
+ uid = make_kuid(current_user_ns(), option);
+ if (!uid_valid(uid))
+ return -EINVAL;
+ opts->uid = uid;
+ break;
+ case Opt_gid:
+ if (match_int(&args[0], &option))
+ return -EINVAL;
+ gid = make_kgid(current_user_ns(), option);
+ if (!gid_valid(gid))
+ return -EINVAL;
+ opts->gid = gid;
+ break;
+ case Opt_mode:
+ if (match_octal(&args[0], &option))
+ return -EINVAL;
+ opts->mode = option & S_IALLUGO;
+ break;
+ /*
+ * We might like to report bad mount options here;
+ * but traditionally stats_fs has ignored all mount options
+ */
+ }
+ }
+
+ return 0;
+}
+
+static int stats_fs_apply_options(struct super_block *sb)
+{
+ struct stats_fs_fs_info *fsi = sb->s_fs_info;
+ struct inode *inode = d_inode(sb->s_root);
+ struct stats_fs_mount_opts *opts = &fsi->mount_opts;
+
+ inode->i_mode &= ~S_IALLUGO;
+ inode->i_mode |= opts->mode;
+
+ inode->i_uid = opts->uid;
+ inode->i_gid = opts->gid;
+
+ return 0;
+}
+
+static int stats_fs_remount(struct super_block *sb, int *flags, char *data)
+{
+ int err;
+ struct stats_fs_fs_info *fsi = sb->s_fs_info;
+
+ sync_filesystem(sb);
+ err = stats_fs_parse_options(data, &fsi->mount_opts);
+ if (err)
+ goto fail;
+
+ stats_fs_apply_options(sb);
+
+fail:
+ return err;
+}
+
+static int stats_fs_show_options(struct seq_file *m, struct dentry *root)
+{
+ struct stats_fs_fs_info *fsi = root->d_sb->s_fs_info;
+ struct stats_fs_mount_opts *opts = &fsi->mount_opts;
+
+ if (!uid_eq(opts->uid, GLOBAL_ROOT_UID))
+ seq_printf(m, ",uid=%u",
+ from_kuid_munged(&init_user_ns, opts->uid));
+ if (!gid_eq(opts->gid, GLOBAL_ROOT_GID))
+ seq_printf(m, ",gid=%u",
+ from_kgid_munged(&init_user_ns, opts->gid));
+ if (opts->mode != STATS_FS_DEFAULT_MODE)
+ seq_printf(m, ",mode=%o", opts->mode);
+
+ return 0;
+}
+
+
+static void stats_fs_free_inode(struct inode *inode)
+{
+ kfree(inode->i_private);
+ free_inode_nonrcu(inode);
+}
+
+static const struct super_operations stats_fs_super_operations = {
+ .statfs = simple_statfs,
+ .remount_fs = stats_fs_remount,
+ .show_options = stats_fs_show_options,
+ .free_inode = stats_fs_free_inode,
+};
+
+static int stats_fs_fill_super(struct super_block *sb, void *data, int silent)
+{
+ static const struct tree_descr stats_fs_files[] = {{""}};
+ struct stats_fs_fs_info *fsi;
+ int err;
+
+ fsi = kzalloc(sizeof(struct stats_fs_fs_info), GFP_KERNEL);
+ sb->s_fs_info = fsi;
+ if (!fsi) {
+ err = -ENOMEM;
+ goto fail;
+ }
+
+ err = stats_fs_parse_options(data, &fsi->mount_opts);
+ if (err)
+ goto fail;
+
+ err = simple_fill_super(sb, STATSFS_MAGIC, stats_fs_files);
+ if (err)
+ goto fail;
+
+ sb->s_op = &stats_fs_super_operations;
+
+ stats_fs_apply_options(sb);
+
+ return 0;
+
+fail:
+ kfree(fsi);
+ sb->s_fs_info = NULL;
+ return err;
+}
+
+static struct dentry *stats_fs_mount(struct file_system_type *fs_type,
+ int flags, const char *dev_name,
+ void *data)
+{
+ return mount_single(fs_type, flags, data, stats_fs_fill_super);
+}
+
+static struct file_system_type stats_fs_fs_type = {
+ .owner = THIS_MODULE,
+ .name = "statsfs",
+ .mount = stats_fs_mount,
+ .kill_sb = kill_litter_super,
+};
+MODULE_ALIAS_FS("statsfs");
+
+
+/**
+ * stats_fs_create_file - create a file in the stats_fs filesystem
+ * @val: a pointer to a stats_fs_value containing all the infos of
+ * the file to create (name, permission)
+ * @src: a pointer to a stats_fs_source containing the dentry of where
+ * to add this file
+ *
+ * This function will return a pointer to a dentry if it succeeds. This
+ * pointer must be passed to the stats_fs_remove() function when the file is
+ * to be removed (no automatic cleanup happens if your module is unloaded,
+ * you are responsible here.) If an error occurs, ERR_PTR(-ERROR) will be
+ * returned.
+ *
+ * Val and src will be also inglobated in a ststsfs_data_inode struct
+ * that will be internally stored as inode->i_private and used in the
+ * get/set attribute functions (see stats_fs_ops in stats_fs.c).
+ */
+struct dentry *stats_fs_create_file(struct stats_fs_value *val, struct stats_fs_source *src)
+{
+ struct dentry *dentry;
+ struct inode *inode;
+ struct stats_fs_data_inode *val_inode;
+
+ val_inode = kzalloc(sizeof(struct stats_fs_data_inode), GFP_KERNEL);
+ if (!val_inode) {
+ printk(KERN_ERR
+ "Kzalloc failure in stats_fs_create_files (ENOMEM)\n");
+ return ERR_PTR(-ENOMEM);
+ }
+
+ val_inode->src = src;
+ val_inode->val = val;
+
+
+ dentry = simplefs_create_file(&stats_fs, &stats_fs_fs_type,
+ val->name, stats_fs_val_get_mode(val),
+ src->source_dentry, val_inode, &inode);
+ if (IS_ERR(dentry))
+ return dentry;
+
+ inode->i_fop = &stats_fs_ops;
+
+ return simplefs_finish_dentry(dentry, inode);
+}
+/**
+ * stats_fs_create_dir - create a directory in the stats_fs filesystem
+ * @name: a pointer to a string containing the name of the directory to
+ * create.
+ * @parent: a pointer to the parent dentry for this file. This should be a
+ * directory dentry if set. If this parameter is NULL, then the
+ * directory will be created in the root of the stats_fs filesystem.
+ *
+ * This function creates a directory in stats_fs with the given name.
+ *
+ * This function will return a pointer to a dentry if it succeeds. This
+ * pointer must be passed to the stats_fs_remove() function when the file is
+ * to be removed (no automatic cleanup happens if your module is unloaded,
+ * you are responsible here.) If an error occurs, ERR_PTR(-ERROR) will be
+ * returned.
+ */
+struct dentry *stats_fs_create_dir(const char *name, struct dentry *parent)
+{
+ struct dentry *dentry;
+ struct inode *inode;
+
+ dentry = simplefs_create_dir(&stats_fs, &stats_fs_fs_type,
+ name, 0755, parent, &inode);
+ if (IS_ERR(dentry))
+ return dentry;
+
+ inode->i_op = &simple_dir_inode_operations;
+ return simplefs_finish_dentry(dentry, inode);
+}
+
+static void remove_one(struct dentry *victim)
+{
+ simple_release_fs(&stats_fs);
+}
+
+/**
+ * stats_fs_remove - recursively removes a directory
+ * @dentry: a pointer to a the dentry of the directory to be removed. If this
+ * parameter is NULL or an error value, nothing will be done.
+ *
+ * This function recursively removes a directory tree in stats_fs that
+ * was previously created with a call to another stats_fs function
+ * (like stats_fs_create_file() or variants thereof.)
+ *
+ * This function is required to be called in order for the file to be
+ * removed, no automatic cleanup of files will happen when a module is
+ * removed, you are responsible here.
+ */
+void stats_fs_remove(struct dentry *dentry)
+{
+ if (IS_ERR_OR_NULL(dentry))
+ return;
+
+ simple_pin_fs(&stats_fs, &stats_fs_fs_type);
+ simple_recursive_removal(dentry, remove_one);
+ simple_release_fs(&stats_fs);
+}
+/**
+ * stats_fs_initialized - Tells whether stats_fs has been registered
+ */
+bool stats_fs_initialized(void)
+{
+ return stats_fs_registered;
+}
+EXPORT_SYMBOL_GPL(stats_fs_initialized);
+
+static int __init stats_fs_init(void)
+{
+ int retval;
+
+ retval = sysfs_create_mount_point(kernel_kobj, "statsfs");
+ if (retval)
+ return retval;
+
+ retval = register_filesystem(&stats_fs_fs_type);
+ if (retval)
+ sysfs_remove_mount_point(kernel_kobj, "statsfs");
+ else
+ stats_fs_registered = true;
+
+ return retval;
+}
+core_initcall(stats_fs_init);
diff --git a/fs/stats_fs/internal.h b/fs/stats_fs/internal.h
index ddf262a60736..1f7bb1da6c3c 100644
--- a/fs/stats_fs/internal.h
+++ b/fs/stats_fs/internal.h
@@ -15,6 +15,21 @@ struct stats_fs_value_source {
struct list_head list_element;
};
+struct stats_fs_data_inode {
+ struct stats_fs_source *src;
+ struct stats_fs_value *val;
+};
+
+extern const struct file_operations stats_fs_ops;
+
+struct dentry *stats_fs_create_file(struct stats_fs_value *val,
+ struct stats_fs_source *src);
+
+struct dentry *stats_fs_create_dir(const char *name, struct dentry *parent);
+
+void stats_fs_remove(struct dentry *dentry);
+#define stats_fs_remove_recursive stats_fs_remove
+
int stats_fs_val_get_mode(struct stats_fs_value *val);
#endif /* _STATS_FS_INTERNAL_H_ */
diff --git a/fs/stats_fs/stats_fs.c b/fs/stats_fs/stats_fs.c
index b63de12769e2..4ac6fe1ec62e 100644
--- a/fs/stats_fs/stats_fs.c
+++ b/fs/stats_fs/stats_fs.c
@@ -17,16 +17,114 @@ struct stats_fs_aggregate_value {
uint32_t count, count_zero;
};
+static void stats_fs_source_remove_files(struct stats_fs_source *src);
+
static int is_val_signed(struct stats_fs_value *val)
{
return val->type & STATS_FS_SIGN;
}
+static int stats_fs_attr_get(void *data, u64 *val)
+{
+ int r = -EFAULT;
+ struct stats_fs_data_inode *val_inode =
+ (struct stats_fs_data_inode *)data;
+
+ r = stats_fs_source_get_value(val_inode->src, val_inode->val, val);
+ return r;
+}
+
+static int stats_fs_attr_clear(void *data, u64 val)
+{
+ int r = -EFAULT;
+ struct stats_fs_data_inode *val_inode =
+ (struct stats_fs_data_inode *)data;
+
+ if (val)
+ return -EINVAL;
+
+ r = stats_fs_source_clear(val_inode->src, val_inode->val);
+ return r;
+}
+
int stats_fs_val_get_mode(struct stats_fs_value *val)
{
return val->mode ? val->mode : 0644;
}
+static int stats_fs_attr_data_open(struct inode *inode, struct file *file)
+{
+ struct stats_fs_data_inode *val_inode;
+ char *fmt;
+
+ val_inode = (struct stats_fs_data_inode *)inode->i_private;
+
+ /* Inodes hold a pointer to the source which is not included in the
+ * refcount, so they files be opened while destroy is running, but
+ * values are removed (base_addr = NULL) before the source is destroyed.
+ */
+ if (!kref_get_unless_zero(&val_inode->src->refcount))
+ return -ENOENT;
+
+ if (is_val_signed(val_inode->val))
+ fmt = "%lld\n";
+ else
+ fmt = "%llu\n";
+
+ if (simple_attr_open(inode, file, stats_fs_attr_get,
+ stats_fs_val_get_mode(val_inode->val) & 0222 ?
+ stats_fs_attr_clear :
+ NULL,
+ fmt)) {
+ stats_fs_source_put(val_inode->src);
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+static int stats_fs_attr_release(struct inode *inode, struct file *file)
+{
+ struct stats_fs_data_inode *val_inode;
+
+ val_inode = (struct stats_fs_data_inode *)inode->i_private;
+
+ simple_attr_release(inode, file);
+ stats_fs_source_put(val_inode->src);
+
+ return 0;
+}
+
+const struct file_operations stats_fs_ops = {
+ .owner = THIS_MODULE,
+ .open = stats_fs_attr_data_open,
+ .release = stats_fs_attr_release,
+ .read = simple_attr_read,
+ .write = simple_attr_write,
+ .llseek = no_llseek,
+};
+
+/* Called with rwsem held for writing */
+static void stats_fs_source_remove_files_locked(struct stats_fs_source *src)
+{
+ struct stats_fs_source *child;
+
+ if (src->source_dentry == NULL)
+ return;
+
+ list_for_each_entry (child, &src->subordinates_head, list_element)
+ stats_fs_source_remove_files(child);
+
+ stats_fs_remove_recursive(src->source_dentry);
+ src->source_dentry = NULL;
+}
+
+static void stats_fs_source_remove_files(struct stats_fs_source *src)
+{
+ down_write(&src->rwsem);
+ stats_fs_source_remove_files_locked(src);
+ up_write(&src->rwsem);
+}
+
static struct stats_fs_value *find_value(struct stats_fs_value_source *src,
struct stats_fs_value *val)
{
@@ -57,6 +155,62 @@ search_value_in_source(struct stats_fs_source *src, struct stats_fs_value *arg,
return NULL;
}
+/* Called with rwsem held for writing */
+static void stats_fs_create_files_locked(struct stats_fs_source *source)
+{
+ struct stats_fs_value_source *val_src;
+ struct stats_fs_value *val;
+
+ if (!source->source_dentry)
+ return;
+
+ list_for_each_entry (val_src, &source->values_head, list_element) {
+ if (val_src->files_created)
+ continue;
+
+ for (val = val_src->values; val->name; val++)
+ stats_fs_create_file(val, source);
+
+ val_src->files_created = true;
+ }
+}
+
+/* Called with rwsem held for writing */
+static void
+stats_fs_create_files_recursive_locked(struct stats_fs_source *source,
+ struct dentry *parent_dentry)
+{
+ struct stats_fs_source *child;
+
+ /* first check values in this folder, since it might be new */
+ if (!source->source_dentry) {
+ source->source_dentry =
+ stats_fs_create_dir(source->name, parent_dentry);
+ }
+
+ stats_fs_create_files_locked(source);
+
+ list_for_each_entry (child, &source->subordinates_head, list_element) {
+ if (child->source_dentry == NULL) {
+ /* assume that if child has a folder,
+ * also the sub-child have that.
+ */
+ down_write(&child->rwsem);
+ stats_fs_create_files_recursive_locked(
+ child, source->source_dentry);
+ up_write(&child->rwsem);
+ }
+ }
+}
+
+void stats_fs_source_register(struct stats_fs_source *source)
+{
+ down_write(&source->rwsem);
+ stats_fs_create_files_recursive_locked(source, NULL);
+ up_write(&source->rwsem);
+}
+EXPORT_SYMBOL_GPL(stats_fs_source_register);
+
/* Called with rwsem held for writing */
static struct stats_fs_value_source *create_value_source(void *base)
{
@@ -93,6 +247,9 @@ int stats_fs_source_add_values(struct stats_fs_source *source,
/* add the val_src to the source list */
list_add(&val_src->list_element, &source->values_head);
+ /* create child if it's the case */
+ stats_fs_create_files_locked(source);
+
up_write(&source->rwsem);
return 0;
@@ -106,6 +263,9 @@ void stats_fs_source_add_subordinate(struct stats_fs_source *source,
stats_fs_source_get(sub);
list_add(&sub->list_element, &source->subordinates_head);
+ if (source->source_dentry)
+ stats_fs_create_files_recursive_locked(sub,
+ source->source_dentry);
up_write(&source->rwsem);
}
@@ -122,6 +282,7 @@ stats_fs_source_remove_subordinate_locked(struct stats_fs_source *source,
list_element) {
if (src_entry == sub) {
list_del_init(&src_entry->list_element);
+ stats_fs_source_remove_files(src_entry);
stats_fs_source_put(src_entry);
return;
}
@@ -565,6 +726,8 @@ static void stats_fs_source_destroy(struct kref *kref_source)
stats_fs_source_remove_subordinate_locked(source, child);
}
+ stats_fs_source_remove_files_locked(source);
+
up_write(&source->rwsem);
kfree(source->name);
kfree(source);
diff --git a/include/linux/stats_fs.h b/include/linux/stats_fs.h
index dc2d2e11f5ea..b04c42d827cf 100644
--- a/include/linux/stats_fs.h
+++ b/include/linux/stats_fs.h
@@ -87,6 +87,18 @@ struct stats_fs_source {
*/
struct stats_fs_source *stats_fs_source_create(const char *fmt, ...);
+/**
+ * stats_fs_source_register - register a source in the stats_fs filesystem
+ * @source: a pointer to the source that will be registered
+ *
+ * Add the given folder as direct child of /sys/kernel/statsfs.
+ * It also starts to recursively search its own child and create all folders
+ * and files if they weren't already. All subsequent add_subordinate calls
+ * on the same source that is used in this function will create corresponding
+ * files and directories.
+ */
+void stats_fs_source_register(struct stats_fs_source *source);
+
/**
* stats_fs_source_add_values - adds values to the given source
* @source: a pointer to the source that will receive the values
@@ -235,6 +247,9 @@ static inline struct stats_fs_source *stats_fs_source_create(const char *fmt,
return ERR_PTR(-ENODEV);
}
+static inline void stats_fs_source_register(struct stats_fs_source *source)
+{ }
+
static inline int stats_fs_source_add_values(struct stats_fs_source *source,
struct stats_fs_value *val,
void *base_ptr)
diff --git a/include/uapi/linux/magic.h b/include/uapi/linux/magic.h
index d78064007b17..46c66ea3fc9e 100644
--- a/include/uapi/linux/magic.h
+++ b/include/uapi/linux/magic.h
@@ -10,6 +10,7 @@
#define CRAMFS_MAGIC 0x28cd3d45 /* some random number */
#define CRAMFS_MAGIC_WEND 0x453dcd28 /* magic number with the wrong endianess */
#define DEBUGFS_MAGIC 0x64626720
+#define STATSFS_MAGIC 0x73746174
#define SECURITYFS_MAGIC 0x73636673
#define SELINUX_MAGIC 0xf97cff8c
#define SMACK_MAGIC 0x43415d53 /* "SMAC" */
diff --git a/tools/lib/api/fs/fs.c b/tools/lib/api/fs/fs.c
index 027b18f7ed8c..6fe306206dfb 100644
--- a/tools/lib/api/fs/fs.c
+++ b/tools/lib/api/fs/fs.c
@@ -35,6 +35,10 @@
#define TRACEFS_MAGIC 0x74726163
#endif
+#ifndef STATSFS_MAGIC
+#define STATSFS_MAGIC 0x73746174
+#endif
+
#ifndef HUGETLBFS_MAGIC
#define HUGETLBFS_MAGIC 0x958458f6
#endif
@@ -76,6 +80,16 @@ static const char * const tracefs__known_mountpoints[] = {
0,
};
+#ifndef STATSFS_DEFAULT_PATH
+#define STATSFS_DEFAULT_PATH "/sys/kernel/statsfs"
+#endif
+
+static const char * const statsfs__known_mountpoints[] = {
+ STATSFS_DEFAULT_PATH,
+ "/statsfs",
+ 0,
+};
+
static const char * const hugetlbfs__known_mountpoints[] = {
0,
};
@@ -100,6 +114,7 @@ enum {
FS__TRACEFS = 3,
FS__HUGETLBFS = 4,
FS__BPF_FS = 5,
+ FS__STATSFS = 6,
};
#ifndef TRACEFS_MAGIC
@@ -127,6 +142,11 @@ static struct fs fs__entries[] = {
.mounts = tracefs__known_mountpoints,
.magic = TRACEFS_MAGIC,
},
+ [FS__STATSFS] = {
+ .name = "statsfs",
+ .mounts = statsfs__known_mountpoints,
+ .magic = STATSFS_MAGIC,
+ },
[FS__HUGETLBFS] = {
.name = "hugetlbfs",
.mounts = hugetlbfs__known_mountpoints,
@@ -297,6 +317,7 @@ FS(sysfs, FS__SYSFS);
FS(procfs, FS__PROCFS);
FS(debugfs, FS__DEBUGFS);
FS(tracefs, FS__TRACEFS);
+FS(statsfs, FS__STATSFS);
FS(hugetlbfs, FS__HUGETLBFS);
FS(bpf_fs, FS__BPF_FS);
--
2.25.2
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v2 5/5] kvm_main: replace debugfs with stats_fs
2020-05-04 11:03 [PATCH v2 0/5] Statsfs: a new ram-based file sytem for Linux kernel statistics Emanuele Giuseppe Esposito
` (3 preceding siblings ...)
2020-05-04 11:03 ` [PATCH v2 4/5] stats_fs fs: virtual fs to show stats to the end-user Emanuele Giuseppe Esposito
@ 2020-05-04 11:03 ` Emanuele Giuseppe Esposito
2020-05-04 21:37 ` [PATCH v2 0/5] Statsfs: a new ram-based file sytem for Linux kernel statistics David Rientjes
[not found] ` <CA+VK+GN=iDhDV2ZDJbBsxrjZ3Qoyotk_L0DvsbwDVvqrpFZ8fQ@mail.gmail.com>
6 siblings, 0 replies; 21+ messages in thread
From: Emanuele Giuseppe Esposito @ 2020-05-04 11:03 UTC (permalink / raw)
To: kvm
Cc: Emanuele Giuseppe Esposito, linux-s390, David Hildenbrand,
Cornelia Huck, Emanuele Giuseppe Esposito, linux-kernel, kvm-ppc,
linux-mips, Christian Borntraeger, Alexander Viro, linux-fsdevel,
Paolo Bonzini, Vitaly Kuznetsov, linuxppc-dev, Jim Mattson
Use stats_fs API instead of debugfs to create sources and add values.
This also requires to change all architecture files to replace the old
debugfs_entries with stats_fs_vcpu_entries and statsfs_vm_entries.
The files/folders name and organization is kept unchanged, and a symlink
in sys/kernel/debugfs/kvm is left for backward compatibility.
Signed-off-by: Emanuele Giuseppe Esposito <eesposit@redhat.com>
---
arch/arm64/kvm/Kconfig | 1 +
arch/arm64/kvm/guest.c | 2 +-
arch/mips/kvm/Kconfig | 1 +
arch/mips/kvm/mips.c | 2 +-
arch/powerpc/kvm/Kconfig | 1 +
arch/powerpc/kvm/book3s.c | 6 +-
arch/powerpc/kvm/booke.c | 8 +-
arch/s390/kvm/Kconfig | 1 +
arch/s390/kvm/kvm-s390.c | 16 +-
arch/x86/include/asm/kvm_host.h | 2 +-
arch/x86/kvm/Kconfig | 1 +
arch/x86/kvm/Makefile | 2 +-
arch/x86/kvm/debugfs.c | 64 -------
arch/x86/kvm/stats_fs.c | 56 ++++++
arch/x86/kvm/x86.c | 6 +-
include/linux/kvm_host.h | 39 +---
virt/kvm/arm/arm.c | 2 +-
virt/kvm/kvm_main.c | 314 ++++----------------------------
18 files changed, 142 insertions(+), 382 deletions(-)
delete mode 100644 arch/x86/kvm/debugfs.c
create mode 100644 arch/x86/kvm/stats_fs.c
diff --git a/arch/arm64/kvm/Kconfig b/arch/arm64/kvm/Kconfig
index 449386d76441..8c125387b673 100644
--- a/arch/arm64/kvm/Kconfig
+++ b/arch/arm64/kvm/Kconfig
@@ -23,6 +23,7 @@ config KVM
depends on OF
# for TASKSTATS/TASK_DELAY_ACCT:
depends on NET && MULTIUSER
+ imply STATS_FS
select MMU_NOTIFIER
select PREEMPT_NOTIFIERS
select HAVE_KVM_CPU_RELAX_INTERCEPT
diff --git a/arch/arm64/kvm/guest.c b/arch/arm64/kvm/guest.c
index 8417b200bec9..235ed44e4353 100644
--- a/arch/arm64/kvm/guest.c
+++ b/arch/arm64/kvm/guest.c
@@ -29,7 +29,7 @@
#include "trace.h"
-struct kvm_stats_debugfs_item debugfs_entries[] = {
+struct stats_fs_value stats_fs_vcpu_entries[] = {
VCPU_STAT("halt_successful_poll", halt_successful_poll),
VCPU_STAT("halt_attempted_poll", halt_attempted_poll),
VCPU_STAT("halt_poll_invalid", halt_poll_invalid),
diff --git a/arch/mips/kvm/Kconfig b/arch/mips/kvm/Kconfig
index b91d145aa2d5..19d14e979e5f 100644
--- a/arch/mips/kvm/Kconfig
+++ b/arch/mips/kvm/Kconfig
@@ -19,6 +19,7 @@ config KVM
tristate "Kernel-based Virtual Machine (KVM) support"
depends on HAVE_KVM
depends on MIPS_FP_SUPPORT
+ imply STATS_FS
select EXPORT_UASM
select PREEMPT_NOTIFIERS
select KVM_GENERIC_DIRTYLOG_READ_PROTECT
diff --git a/arch/mips/kvm/mips.c b/arch/mips/kvm/mips.c
index fdf1c14d9205..a47d21f35444 100644
--- a/arch/mips/kvm/mips.c
+++ b/arch/mips/kvm/mips.c
@@ -39,7 +39,7 @@
#define VECTORSPACING 0x100 /* for EI/VI mode */
#endif
-struct kvm_stats_debugfs_item debugfs_entries[] = {
+struct stats_fs_value stats_fs_vcpu_entries[] = {
VCPU_STAT("wait", wait_exits),
VCPU_STAT("cache", cache_exits),
VCPU_STAT("signal", signal_exits),
diff --git a/arch/powerpc/kvm/Kconfig b/arch/powerpc/kvm/Kconfig
index 12885eda324e..feb5e110ebb0 100644
--- a/arch/powerpc/kvm/Kconfig
+++ b/arch/powerpc/kvm/Kconfig
@@ -19,6 +19,7 @@ if VIRTUALIZATION
config KVM
bool
+ imply STATS_FS
select PREEMPT_NOTIFIERS
select HAVE_KVM_EVENTFD
select HAVE_KVM_VCPU_ASYNC_IOCTL
diff --git a/arch/powerpc/kvm/book3s.c b/arch/powerpc/kvm/book3s.c
index 37508a356f28..76222ab148da 100644
--- a/arch/powerpc/kvm/book3s.c
+++ b/arch/powerpc/kvm/book3s.c
@@ -38,7 +38,7 @@
/* #define EXIT_DEBUG */
-struct kvm_stats_debugfs_item debugfs_entries[] = {
+struct stats_fs_value stats_fs_vcpu_entries[] = {
VCPU_STAT("exits", sum_exits),
VCPU_STAT("mmio", mmio_exits),
VCPU_STAT("sig", signal_exits),
@@ -66,6 +66,10 @@ struct kvm_stats_debugfs_item debugfs_entries[] = {
VCPU_STAT("pthru_all", pthru_all),
VCPU_STAT("pthru_host", pthru_host),
VCPU_STAT("pthru_bad_aff", pthru_bad_aff),
+ { NULL }
+};
+
+struct stats_fs_value stats_fs_vm_entries[] = {
VM_STAT("largepages_2M", num_2M_pages, .mode = 0444),
VM_STAT("largepages_1G", num_1G_pages, .mode = 0444),
{ NULL }
diff --git a/arch/powerpc/kvm/booke.c b/arch/powerpc/kvm/booke.c
index c2984cb6dfa7..b14c07786cc8 100644
--- a/arch/powerpc/kvm/booke.c
+++ b/arch/powerpc/kvm/booke.c
@@ -35,7 +35,12 @@
unsigned long kvmppc_booke_handlers;
-struct kvm_stats_debugfs_item debugfs_entries[] = {
+struct stats_fs_value stats_fs_vm_entries[] = {
+ VM_STAT("remote_tlb_flush", remote_tlb_flush),
+ { NULL }
+};
+
+struct stats_fs_value stats_fs_vcpu_entries[] = {
VCPU_STAT("mmio", mmio_exits),
VCPU_STAT("sig", signal_exits),
VCPU_STAT("itlb_r", itlb_real_miss_exits),
@@ -54,7 +59,6 @@ struct kvm_stats_debugfs_item debugfs_entries[] = {
VCPU_STAT("halt_wakeup", halt_wakeup),
VCPU_STAT("doorbell", dbell_exits),
VCPU_STAT("guest doorbell", gdbell_exits),
- VM_STAT("remote_tlb_flush", remote_tlb_flush),
{ NULL }
};
diff --git a/arch/s390/kvm/Kconfig b/arch/s390/kvm/Kconfig
index def3b60f1fe8..4e912ffcde78 100644
--- a/arch/s390/kvm/Kconfig
+++ b/arch/s390/kvm/Kconfig
@@ -20,6 +20,7 @@ config KVM
def_tristate y
prompt "Kernel-based Virtual Machine (KVM) support"
depends on HAVE_KVM
+ imply STATS_FS
select PREEMPT_NOTIFIERS
select HAVE_KVM_CPU_RELAX_INTERCEPT
select HAVE_KVM_VCPU_ASYNC_IOCTL
diff --git a/arch/s390/kvm/kvm-s390.c b/arch/s390/kvm/kvm-s390.c
index dbeb7da07f18..f2f090b78529 100644
--- a/arch/s390/kvm/kvm-s390.c
+++ b/arch/s390/kvm/kvm-s390.c
@@ -57,7 +57,16 @@
#define VCPU_IRQS_MAX_BUF (sizeof(struct kvm_s390_irq) * \
(KVM_MAX_VCPUS + LOCAL_IRQS))
-struct kvm_stats_debugfs_item debugfs_entries[] = {
+struct stats_fs_value stats_fs_vm_entries[] = {
+ VM_STAT("inject_float_mchk", inject_float_mchk),
+ VM_STAT("inject_io", inject_io),
+ VM_STAT("inject_pfault_done", inject_pfault_done),
+ VM_STAT("inject_service_signal", inject_service_signal),
+ VM_STAT("inject_virtio", inject_virtio),
+ { NULL }
+};
+
+struct stats_fs_value stats_fs_vcpu_entries[] = {
VCPU_STAT("userspace_handled", exit_userspace),
VCPU_STAT("exit_null", exit_null),
VCPU_STAT("exit_validity", exit_validity),
@@ -95,18 +104,13 @@ struct kvm_stats_debugfs_item debugfs_entries[] = {
VCPU_STAT("inject_ckc", inject_ckc),
VCPU_STAT("inject_cputm", inject_cputm),
VCPU_STAT("inject_external_call", inject_external_call),
- VM_STAT("inject_float_mchk", inject_float_mchk),
VCPU_STAT("inject_emergency_signal", inject_emergency_signal),
- VM_STAT("inject_io", inject_io),
VCPU_STAT("inject_mchk", inject_mchk),
- VM_STAT("inject_pfault_done", inject_pfault_done),
VCPU_STAT("inject_program", inject_program),
VCPU_STAT("inject_restart", inject_restart),
- VM_STAT("inject_service_signal", inject_service_signal),
VCPU_STAT("inject_set_prefix", inject_set_prefix),
VCPU_STAT("inject_stop_signal", inject_stop_signal),
VCPU_STAT("inject_pfault_init", inject_pfault_init),
- VM_STAT("inject_virtio", inject_virtio),
VCPU_STAT("instruction_epsw", instruction_epsw),
VCPU_STAT("instruction_gs", instruction_gs),
VCPU_STAT("instruction_io_other", instruction_io_other),
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index 42a2d0d3984a..6a04f590963f 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -35,7 +35,7 @@
#include <asm/kvm_vcpu_regs.h>
#include <asm/hyperv-tlfs.h>
-#define __KVM_HAVE_ARCH_VCPU_DEBUGFS
+#define __KVM_HAVE_ARCH_VCPU_STATS_FS
#define KVM_MAX_VCPUS 288
#define KVM_SOFT_MAX_VCPUS 240
diff --git a/arch/x86/kvm/Kconfig b/arch/x86/kvm/Kconfig
index d8154e0684b6..834b8f4790a7 100644
--- a/arch/x86/kvm/Kconfig
+++ b/arch/x86/kvm/Kconfig
@@ -25,6 +25,7 @@ config KVM
# for TASKSTATS/TASK_DELAY_ACCT:
depends on NET && MULTIUSER
depends on X86_LOCAL_APIC
+ imply STATS_FS
select PREEMPT_NOTIFIERS
select MMU_NOTIFIER
select HAVE_KVM_IRQCHIP
diff --git a/arch/x86/kvm/Makefile b/arch/x86/kvm/Makefile
index a789759b7261..18285a382eba 100644
--- a/arch/x86/kvm/Makefile
+++ b/arch/x86/kvm/Makefile
@@ -11,7 +11,7 @@ kvm-$(CONFIG_KVM_ASYNC_PF) += $(KVM)/async_pf.o
kvm-y += x86.o emulate.o i8259.o irq.o lapic.o \
i8254.o ioapic.o irq_comm.o cpuid.o pmu.o mtrr.o \
- hyperv.o debugfs.o mmu/mmu.o mmu/page_track.o
+ hyperv.o stats_fs.o mmu/mmu.o mmu/page_track.o
kvm-intel-y += vmx/vmx.o vmx/vmenter.o vmx/pmu_intel.o vmx/vmcs12.o vmx/evmcs.o vmx/nested.o
kvm-amd-y += svm/svm.o svm/vmenter.o svm/pmu.o svm/nested.o svm/avic.o svm/sev.o
diff --git a/arch/x86/kvm/debugfs.c b/arch/x86/kvm/debugfs.c
deleted file mode 100644
index 018aebce33ff..000000000000
--- a/arch/x86/kvm/debugfs.c
+++ /dev/null
@@ -1,64 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-/*
- * Kernel-based Virtual Machine driver for Linux
- *
- * Copyright 2016 Red Hat, Inc. and/or its affiliates.
- */
-#include <linux/kvm_host.h>
-#include <linux/debugfs.h>
-#include "lapic.h"
-
-static int vcpu_get_timer_advance_ns(void *data, u64 *val)
-{
- struct kvm_vcpu *vcpu = (struct kvm_vcpu *) data;
- *val = vcpu->arch.apic->lapic_timer.timer_advance_ns;
- return 0;
-}
-
-DEFINE_SIMPLE_ATTRIBUTE(vcpu_timer_advance_ns_fops, vcpu_get_timer_advance_ns, NULL, "%llu\n");
-
-static int vcpu_get_tsc_offset(void *data, u64 *val)
-{
- struct kvm_vcpu *vcpu = (struct kvm_vcpu *) data;
- *val = vcpu->arch.tsc_offset;
- return 0;
-}
-
-DEFINE_SIMPLE_ATTRIBUTE(vcpu_tsc_offset_fops, vcpu_get_tsc_offset, NULL, "%lld\n");
-
-static int vcpu_get_tsc_scaling_ratio(void *data, u64 *val)
-{
- struct kvm_vcpu *vcpu = (struct kvm_vcpu *) data;
- *val = vcpu->arch.tsc_scaling_ratio;
- return 0;
-}
-
-DEFINE_SIMPLE_ATTRIBUTE(vcpu_tsc_scaling_fops, vcpu_get_tsc_scaling_ratio, NULL, "%llu\n");
-
-static int vcpu_get_tsc_scaling_frac_bits(void *data, u64 *val)
-{
- *val = kvm_tsc_scaling_ratio_frac_bits;
- return 0;
-}
-
-DEFINE_SIMPLE_ATTRIBUTE(vcpu_tsc_scaling_frac_fops, vcpu_get_tsc_scaling_frac_bits, NULL, "%llu\n");
-
-void kvm_arch_create_vcpu_debugfs(struct kvm_vcpu *vcpu)
-{
- debugfs_create_file("tsc-offset", 0444, vcpu->debugfs_dentry, vcpu,
- &vcpu_tsc_offset_fops);
-
- if (lapic_in_kernel(vcpu))
- debugfs_create_file("lapic_timer_advance_ns", 0444,
- vcpu->debugfs_dentry, vcpu,
- &vcpu_timer_advance_ns_fops);
-
- if (kvm_has_tsc_control) {
- debugfs_create_file("tsc-scaling-ratio", 0444,
- vcpu->debugfs_dentry, vcpu,
- &vcpu_tsc_scaling_fops);
- debugfs_create_file("tsc-scaling-ratio-frac-bits", 0444,
- vcpu->debugfs_dentry, vcpu,
- &vcpu_tsc_scaling_frac_fops);
- }
-}
diff --git a/arch/x86/kvm/stats_fs.c b/arch/x86/kvm/stats_fs.c
new file mode 100644
index 000000000000..f2ac6ed8b01b
--- /dev/null
+++ b/arch/x86/kvm/stats_fs.c
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Kernel-based Virtual Machine driver for Linux
+ *
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates.
+ */
+#include <linux/kvm_host.h>
+#include <linux/stats_fs.h>
+#include "lapic.h"
+
+#define VCPU_ARCH_STATS_FS(n, s, x, ...) \
+ { n, offsetof(struct s, x), .aggr_kind = STATS_FS_SUM, \
+ ##__VA_ARGS__ }
+
+struct stats_fs_value stats_fs_vcpu_tsc_offset[] = {
+ VCPU_ARCH_STATS_FS("tsc-offset", kvm_vcpu_arch, tsc_offset,
+ .type = STATS_FS_S64, .mode = 0444),
+ { NULL }
+};
+
+struct stats_fs_value stats_fs_vcpu_arch_lapic_timer[] = {
+ VCPU_ARCH_STATS_FS("lapic_timer_advance_ns", kvm_timer, timer_advance_ns,
+ .type = STATS_FS_U64, .mode = 0444),
+ { NULL }
+};
+
+struct stats_fs_value stats_fs_vcpu_arch_tsc_ratio[] = {
+ VCPU_ARCH_STATS_FS("tsc-scaling-ratio", kvm_vcpu_arch, tsc_scaling_ratio,
+ .type = STATS_FS_U64, .mode = 0444),
+ { NULL }
+};
+
+struct stats_fs_value stats_fs_vcpu_arch_tsc_frac[] = {
+ { "tsc-scaling-ratio-frac-bits", 0, .type = STATS_FS_U64, .mode = 0444 },
+ { NULL } /* base is &kvm_tsc_scaling_ratio_frac_bits */
+};
+
+void kvm_arch_create_vcpu_stats_fs(struct kvm_vcpu *vcpu)
+{
+ stats_fs_source_add_values(vcpu->stats_fs_src, stats_fs_vcpu_tsc_offset,
+ &vcpu->arch);
+
+ if (lapic_in_kernel(vcpu))
+ stats_fs_source_add_values(vcpu->stats_fs_src,
+ stats_fs_vcpu_arch_lapic_timer,
+ &vcpu->arch.apic->lapic_timer);
+
+ if (kvm_has_tsc_control) {
+ stats_fs_source_add_values(vcpu->stats_fs_src,
+ stats_fs_vcpu_arch_tsc_ratio,
+ &vcpu->arch);
+ stats_fs_source_add_values(vcpu->stats_fs_src,
+ stats_fs_vcpu_arch_tsc_frac,
+ &kvm_tsc_scaling_ratio_frac_bits);
+ }
+}
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 35723dafedeb..01fed7ac2e49 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -190,7 +190,7 @@ static u64 __read_mostly host_xss;
u64 __read_mostly supported_xss;
EXPORT_SYMBOL_GPL(supported_xss);
-struct kvm_stats_debugfs_item debugfs_entries[] = {
+struct stats_fs_value stats_fs_vcpu_entries[] = {
VCPU_STAT("pf_fixed", pf_fixed),
VCPU_STAT("pf_guest", pf_guest),
VCPU_STAT("tlb_flush", tlb_flush),
@@ -217,6 +217,10 @@ struct kvm_stats_debugfs_item debugfs_entries[] = {
VCPU_STAT("nmi_injections", nmi_injections),
VCPU_STAT("req_event", req_event),
VCPU_STAT("l1d_flush", l1d_flush),
+ { NULL }
+};
+
+struct stats_fs_value stats_fs_vm_entries[] = {
VM_STAT("mmu_shadow_zapped", mmu_shadow_zapped),
VM_STAT("mmu_pte_write", mmu_pte_write),
VM_STAT("mmu_pte_updated", mmu_pte_updated),
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index 3845f857ef7b..1282799aa46e 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -27,6 +27,7 @@
#include <linux/refcount.h>
#include <linux/nospec.h>
#include <asm/signal.h>
+#include <linux/stats_fs.h>
#include <linux/kvm.h>
#include <linux/kvm_para.h>
@@ -318,7 +319,7 @@ struct kvm_vcpu {
bool preempted;
bool ready;
struct kvm_vcpu_arch arch;
- struct dentry *debugfs_dentry;
+ struct stats_fs_source *stats_fs_src;
};
static inline int kvm_vcpu_exiting_guest_mode(struct kvm_vcpu *vcpu)
@@ -498,8 +499,7 @@ struct kvm {
long tlbs_dirty;
struct list_head devices;
u64 manual_dirty_log_protect;
- struct dentry *debugfs_dentry;
- struct kvm_stat_data **debugfs_stat_data;
+ struct stats_fs_source *stats_fs_src;
struct srcu_struct srcu;
struct srcu_struct irq_srcu;
pid_t userspace_pid;
@@ -880,8 +880,8 @@ int kvm_arch_vcpu_create(struct kvm_vcpu *vcpu);
void kvm_arch_vcpu_postcreate(struct kvm_vcpu *vcpu);
void kvm_arch_vcpu_destroy(struct kvm_vcpu *vcpu);
-#ifdef __KVM_HAVE_ARCH_VCPU_DEBUGFS
-void kvm_arch_create_vcpu_debugfs(struct kvm_vcpu *vcpu);
+#ifdef __KVM_HAVE_ARCH_VCPU_STATS_FS
+void kvm_arch_create_vcpu_stats_fs(struct kvm_vcpu *vcpu);
#endif
int kvm_arch_hardware_enable(void);
@@ -1110,33 +1110,14 @@ static inline bool kvm_is_error_gpa(struct kvm *kvm, gpa_t gpa)
return kvm_is_error_hva(hva);
}
-enum kvm_stat_kind {
- KVM_STAT_VM,
- KVM_STAT_VCPU,
-};
-
-struct kvm_stat_data {
- struct kvm *kvm;
- struct kvm_stats_debugfs_item *dbgfs_item;
-};
-
-struct kvm_stats_debugfs_item {
- const char *name;
- int offset;
- enum kvm_stat_kind kind;
- int mode;
-};
-
-#define KVM_DBGFS_GET_MODE(dbgfs_item) \
- ((dbgfs_item)->mode ? (dbgfs_item)->mode : 0644)
-
#define VM_STAT(n, x, ...) \
- { n, offsetof(struct kvm, stat.x), KVM_STAT_VM, ## __VA_ARGS__ }
+ { n, offsetof(struct kvm, stat.x), STATS_FS_U64, STATS_FS_SUM, ## __VA_ARGS__ }
#define VCPU_STAT(n, x, ...) \
- { n, offsetof(struct kvm_vcpu, stat.x), KVM_STAT_VCPU, ## __VA_ARGS__ }
+ { n, offsetof(struct kvm_vcpu, stat.x), STATS_FS_U64, STATS_FS_SUM, ## __VA_ARGS__ }
-extern struct kvm_stats_debugfs_item debugfs_entries[];
-extern struct dentry *kvm_debugfs_dir;
+extern struct stats_fs_value stats_fs_vcpu_entries[];
+extern struct stats_fs_value stats_fs_vm_entries[];
+extern struct stats_fs_source *kvm_stats_fs_dir;
#if defined(CONFIG_MMU_NOTIFIER) && defined(KVM_ARCH_WANT_MMU_NOTIFIER)
static inline int mmu_notifier_retry(struct kvm *kvm, unsigned long mmu_seq)
diff --git a/virt/kvm/arm/arm.c b/virt/kvm/arm/arm.c
index 48d0ec44ad77..4171f92fa473 100644
--- a/virt/kvm/arm/arm.c
+++ b/virt/kvm/arm/arm.c
@@ -140,7 +140,7 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type)
return ret;
}
-int kvm_arch_create_vcpu_debugfs(struct kvm_vcpu *vcpu)
+int kvm_arch_create_vcpu_stats_fs(struct kvm_vcpu *vcpu)
{
return 0;
}
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index 74bdb7bf3295..093150125bc2 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -25,6 +25,7 @@
#include <linux/vmalloc.h>
#include <linux/reboot.h>
#include <linux/debugfs.h>
+#include <linux/stats_fs.h>
#include <linux/highmem.h>
#include <linux/file.h>
#include <linux/syscore_ops.h>
@@ -109,11 +110,8 @@ static struct kmem_cache *kvm_vcpu_cache;
static __read_mostly struct preempt_ops kvm_preempt_ops;
static DEFINE_PER_CPU(struct kvm_vcpu *, kvm_running_vcpu);
-struct dentry *kvm_debugfs_dir;
-EXPORT_SYMBOL_GPL(kvm_debugfs_dir);
-
-static int kvm_debugfs_num_entries;
-static const struct file_operations stat_fops_per_vm;
+struct stats_fs_source *kvm_stats_fs_dir;
+EXPORT_SYMBOL_GPL(kvm_stats_fs_dir);
static long kvm_vcpu_ioctl(struct file *file, unsigned int ioctl,
unsigned long arg);
@@ -356,6 +354,8 @@ static void kvm_vcpu_init(struct kvm_vcpu *vcpu, struct kvm *kvm, unsigned id)
void kvm_vcpu_destroy(struct kvm_vcpu *vcpu)
{
+ stats_fs_source_revoke(vcpu->stats_fs_src);
+ stats_fs_source_put(vcpu->stats_fs_src);
kvm_arch_vcpu_destroy(vcpu);
/*
@@ -601,52 +601,27 @@ static void kvm_free_memslots(struct kvm *kvm, struct kvm_memslots *slots)
kvfree(slots);
}
-static void kvm_destroy_vm_debugfs(struct kvm *kvm)
+static void kvm_destroy_vm_stats_fs(struct kvm *kvm)
{
- int i;
-
- if (!kvm->debugfs_dentry)
- return;
-
- debugfs_remove_recursive(kvm->debugfs_dentry);
-
- if (kvm->debugfs_stat_data) {
- for (i = 0; i < kvm_debugfs_num_entries; i++)
- kfree(kvm->debugfs_stat_data[i]);
- kfree(kvm->debugfs_stat_data);
- }
+ stats_fs_source_remove_subordinate(kvm_stats_fs_dir, kvm->stats_fs_src);
+ stats_fs_source_revoke(kvm->stats_fs_src);
+ stats_fs_source_put(kvm->stats_fs_src);
}
-static int kvm_create_vm_debugfs(struct kvm *kvm, int fd)
+static int kvm_create_vm_stats_fs(struct kvm *kvm, int fd)
{
char dir_name[ITOA_MAX_LEN * 2];
- struct kvm_stat_data *stat_data;
- struct kvm_stats_debugfs_item *p;
- if (!debugfs_initialized())
+ if (!stats_fs_initialized())
return 0;
snprintf(dir_name, sizeof(dir_name), "%d-%d", task_pid_nr(current), fd);
- kvm->debugfs_dentry = debugfs_create_dir(dir_name, kvm_debugfs_dir);
+ kvm->stats_fs_src = stats_fs_source_create(dir_name);
+ stats_fs_source_add_subordinate(kvm_stats_fs_dir, kvm->stats_fs_src);
- kvm->debugfs_stat_data = kcalloc(kvm_debugfs_num_entries,
- sizeof(*kvm->debugfs_stat_data),
- GFP_KERNEL_ACCOUNT);
- if (!kvm->debugfs_stat_data)
- return -ENOMEM;
+ stats_fs_source_add_values(kvm->stats_fs_src, stats_fs_vm_entries, kvm);
- for (p = debugfs_entries; p->name; p++) {
- stat_data = kzalloc(sizeof(*stat_data), GFP_KERNEL_ACCOUNT);
- if (!stat_data)
- return -ENOMEM;
-
- stat_data->kvm = kvm;
- stat_data->dbgfs_item = p;
- kvm->debugfs_stat_data[p - debugfs_entries] = stat_data;
- debugfs_create_file(p->name, KVM_DBGFS_GET_MODE(p),
- kvm->debugfs_dentry, stat_data,
- &stat_fops_per_vm);
- }
+ stats_fs_source_add_values(kvm->stats_fs_src, stats_fs_vcpu_entries, NULL);
return 0;
}
@@ -783,7 +758,7 @@ static void kvm_destroy_vm(struct kvm *kvm)
struct mm_struct *mm = kvm->mm;
kvm_uevent_notify_change(KVM_EVENT_DESTROY_VM, kvm);
- kvm_destroy_vm_debugfs(kvm);
+ kvm_destroy_vm_stats_fs(kvm);
kvm_arch_sync_events(kvm);
mutex_lock(&kvm_lock);
list_del(&kvm->vm_list);
@@ -2946,7 +2921,6 @@ static int kvm_vcpu_release(struct inode *inode, struct file *filp)
{
struct kvm_vcpu *vcpu = filp->private_data;
- debugfs_remove_recursive(vcpu->debugfs_dentry);
kvm_put_kvm(vcpu->kvm);
return 0;
}
@@ -2970,19 +2944,22 @@ static int create_vcpu_fd(struct kvm_vcpu *vcpu)
return anon_inode_getfd(name, &kvm_vcpu_fops, vcpu, O_RDWR | O_CLOEXEC);
}
-static void kvm_create_vcpu_debugfs(struct kvm_vcpu *vcpu)
+static void kvm_create_vcpu_stats_fs(struct kvm_vcpu *vcpu)
{
-#ifdef __KVM_HAVE_ARCH_VCPU_DEBUGFS
char dir_name[ITOA_MAX_LEN * 2];
- if (!debugfs_initialized())
+ if (!stats_fs_initialized())
return;
snprintf(dir_name, sizeof(dir_name), "vcpu%d", vcpu->vcpu_id);
- vcpu->debugfs_dentry = debugfs_create_dir(dir_name,
- vcpu->kvm->debugfs_dentry);
- kvm_arch_create_vcpu_debugfs(vcpu);
+ vcpu->stats_fs_src = stats_fs_source_create(dir_name);
+ stats_fs_source_add_subordinate(vcpu->kvm->stats_fs_src, vcpu->stats_fs_src);
+
+ stats_fs_source_add_values(vcpu->stats_fs_src, stats_fs_vcpu_entries, vcpu);
+
+#ifdef __KVM_HAVE_ARCH_VCPU_STATS_FS
+ kvm_arch_create_vcpu_stats_fs(vcpu);
#endif
}
@@ -3031,8 +3008,6 @@ static int kvm_vm_ioctl_create_vcpu(struct kvm *kvm, u32 id)
if (r)
goto vcpu_free_run_page;
- kvm_create_vcpu_debugfs(vcpu);
-
mutex_lock(&kvm->lock);
if (kvm_get_vcpu_by_id(kvm, id)) {
r = -EEXIST;
@@ -3061,11 +3036,11 @@ static int kvm_vm_ioctl_create_vcpu(struct kvm *kvm, u32 id)
mutex_unlock(&kvm->lock);
kvm_arch_vcpu_postcreate(vcpu);
+ kvm_create_vcpu_stats_fs(vcpu);
return r;
unlock_vcpu_destroy:
mutex_unlock(&kvm->lock);
- debugfs_remove_recursive(vcpu->debugfs_dentry);
kvm_arch_vcpu_destroy(vcpu);
vcpu_free_run_page:
free_page((unsigned long)vcpu->run);
@@ -3839,7 +3814,7 @@ static int kvm_dev_ioctl_create_vm(unsigned long type)
* cases it will be called by the final fput(file) and will take
* care of doing kvm_put_kvm(kvm).
*/
- if (kvm_create_vm_debugfs(kvm, r) < 0) {
+ if (kvm_create_vm_stats_fs(kvm, r) < 0) {
put_unused_fd(r);
fput(file);
return -ENOMEM;
@@ -4295,214 +4270,6 @@ struct kvm_io_device *kvm_io_bus_get_dev(struct kvm *kvm, enum kvm_bus bus_idx,
}
EXPORT_SYMBOL_GPL(kvm_io_bus_get_dev);
-static int kvm_debugfs_open(struct inode *inode, struct file *file,
- int (*get)(void *, u64 *), int (*set)(void *, u64),
- const char *fmt)
-{
- struct kvm_stat_data *stat_data = (struct kvm_stat_data *)
- inode->i_private;
-
- /* The debugfs files are a reference to the kvm struct which
- * is still valid when kvm_destroy_vm is called.
- * To avoid the race between open and the removal of the debugfs
- * directory we test against the users count.
- */
- if (!refcount_inc_not_zero(&stat_data->kvm->users_count))
- return -ENOENT;
-
- if (simple_attr_open(inode, file, get,
- KVM_DBGFS_GET_MODE(stat_data->dbgfs_item) & 0222
- ? set : NULL,
- fmt)) {
- kvm_put_kvm(stat_data->kvm);
- return -ENOMEM;
- }
-
- return 0;
-}
-
-static int kvm_debugfs_release(struct inode *inode, struct file *file)
-{
- struct kvm_stat_data *stat_data = (struct kvm_stat_data *)
- inode->i_private;
-
- simple_attr_release(inode, file);
- kvm_put_kvm(stat_data->kvm);
-
- return 0;
-}
-
-static int kvm_get_stat_per_vm(struct kvm *kvm, size_t offset, u64 *val)
-{
- *val = *(ulong *)((void *)kvm + offset);
-
- return 0;
-}
-
-static int kvm_clear_stat_per_vm(struct kvm *kvm, size_t offset)
-{
- *(ulong *)((void *)kvm + offset) = 0;
-
- return 0;
-}
-
-static int kvm_get_stat_per_vcpu(struct kvm *kvm, size_t offset, u64 *val)
-{
- int i;
- struct kvm_vcpu *vcpu;
-
- *val = 0;
-
- kvm_for_each_vcpu(i, vcpu, kvm)
- *val += *(u64 *)((void *)vcpu + offset);
-
- return 0;
-}
-
-static int kvm_clear_stat_per_vcpu(struct kvm *kvm, size_t offset)
-{
- int i;
- struct kvm_vcpu *vcpu;
-
- kvm_for_each_vcpu(i, vcpu, kvm)
- *(u64 *)((void *)vcpu + offset) = 0;
-
- return 0;
-}
-
-static int kvm_stat_data_get(void *data, u64 *val)
-{
- int r = -EFAULT;
- struct kvm_stat_data *stat_data = (struct kvm_stat_data *)data;
-
- switch (stat_data->dbgfs_item->kind) {
- case KVM_STAT_VM:
- r = kvm_get_stat_per_vm(stat_data->kvm,
- stat_data->dbgfs_item->offset, val);
- break;
- case KVM_STAT_VCPU:
- r = kvm_get_stat_per_vcpu(stat_data->kvm,
- stat_data->dbgfs_item->offset, val);
- break;
- }
-
- return r;
-}
-
-static int kvm_stat_data_clear(void *data, u64 val)
-{
- int r = -EFAULT;
- struct kvm_stat_data *stat_data = (struct kvm_stat_data *)data;
-
- if (val)
- return -EINVAL;
-
- switch (stat_data->dbgfs_item->kind) {
- case KVM_STAT_VM:
- r = kvm_clear_stat_per_vm(stat_data->kvm,
- stat_data->dbgfs_item->offset);
- break;
- case KVM_STAT_VCPU:
- r = kvm_clear_stat_per_vcpu(stat_data->kvm,
- stat_data->dbgfs_item->offset);
- break;
- }
-
- return r;
-}
-
-static int kvm_stat_data_open(struct inode *inode, struct file *file)
-{
- __simple_attr_check_format("%llu\n", 0ull);
- return kvm_debugfs_open(inode, file, kvm_stat_data_get,
- kvm_stat_data_clear, "%llu\n");
-}
-
-static const struct file_operations stat_fops_per_vm = {
- .owner = THIS_MODULE,
- .open = kvm_stat_data_open,
- .release = kvm_debugfs_release,
- .read = simple_attr_read,
- .write = simple_attr_write,
- .llseek = no_llseek,
-};
-
-static int vm_stat_get(void *_offset, u64 *val)
-{
- unsigned offset = (long)_offset;
- struct kvm *kvm;
- u64 tmp_val;
-
- *val = 0;
- mutex_lock(&kvm_lock);
- list_for_each_entry(kvm, &vm_list, vm_list) {
- kvm_get_stat_per_vm(kvm, offset, &tmp_val);
- *val += tmp_val;
- }
- mutex_unlock(&kvm_lock);
- return 0;
-}
-
-static int vm_stat_clear(void *_offset, u64 val)
-{
- unsigned offset = (long)_offset;
- struct kvm *kvm;
-
- if (val)
- return -EINVAL;
-
- mutex_lock(&kvm_lock);
- list_for_each_entry(kvm, &vm_list, vm_list) {
- kvm_clear_stat_per_vm(kvm, offset);
- }
- mutex_unlock(&kvm_lock);
-
- return 0;
-}
-
-DEFINE_SIMPLE_ATTRIBUTE(vm_stat_fops, vm_stat_get, vm_stat_clear, "%llu\n");
-
-static int vcpu_stat_get(void *_offset, u64 *val)
-{
- unsigned offset = (long)_offset;
- struct kvm *kvm;
- u64 tmp_val;
-
- *val = 0;
- mutex_lock(&kvm_lock);
- list_for_each_entry(kvm, &vm_list, vm_list) {
- kvm_get_stat_per_vcpu(kvm, offset, &tmp_val);
- *val += tmp_val;
- }
- mutex_unlock(&kvm_lock);
- return 0;
-}
-
-static int vcpu_stat_clear(void *_offset, u64 val)
-{
- unsigned offset = (long)_offset;
- struct kvm *kvm;
-
- if (val)
- return -EINVAL;
-
- mutex_lock(&kvm_lock);
- list_for_each_entry(kvm, &vm_list, vm_list) {
- kvm_clear_stat_per_vcpu(kvm, offset);
- }
- mutex_unlock(&kvm_lock);
-
- return 0;
-}
-
-DEFINE_SIMPLE_ATTRIBUTE(vcpu_stat_fops, vcpu_stat_get, vcpu_stat_clear,
- "%llu\n");
-
-static const struct file_operations *stat_fops[] = {
- [KVM_STAT_VCPU] = &vcpu_stat_fops,
- [KVM_STAT_VM] = &vm_stat_fops,
-};
-
static void kvm_uevent_notify_change(unsigned int type, struct kvm *kvm)
{
struct kobj_uevent_env *env;
@@ -4537,34 +4304,32 @@ static void kvm_uevent_notify_change(unsigned int type, struct kvm *kvm)
}
add_uevent_var(env, "PID=%d", kvm->userspace_pid);
- if (!IS_ERR_OR_NULL(kvm->debugfs_dentry)) {
+ if (!IS_ERR_OR_NULL(kvm->stats_fs_src->source_dentry)) {
char *tmp, *p = kmalloc(PATH_MAX, GFP_KERNEL_ACCOUNT);
if (p) {
- tmp = dentry_path_raw(kvm->debugfs_dentry, p, PATH_MAX);
+ tmp = dentry_path_raw(kvm->stats_fs_src->source_dentry, p, PATH_MAX);
if (!IS_ERR(tmp))
add_uevent_var(env, "STATS_PATH=%s", tmp);
kfree(p);
}
}
+
/* no need for checks, since we are adding at most only 5 keys */
env->envp[env->envp_idx++] = NULL;
kobject_uevent_env(&kvm_dev.this_device->kobj, KOBJ_CHANGE, env->envp);
kfree(env);
}
-static void kvm_init_debug(void)
+static void kvm_init_stats_fs(void)
{
- struct kvm_stats_debugfs_item *p;
+ kvm_stats_fs_dir = stats_fs_source_create("kvm");
+ /* symlink to debugfs */
+ debugfs_create_symlink("kvm", NULL, "/sys/kernel/stats_fs/kvm");
+ stats_fs_source_register(kvm_stats_fs_dir);
- kvm_debugfs_dir = debugfs_create_dir("kvm", NULL);
-
- kvm_debugfs_num_entries = 0;
- for (p = debugfs_entries; p->name; ++p, kvm_debugfs_num_entries++) {
- debugfs_create_file(p->name, KVM_DBGFS_GET_MODE(p),
- kvm_debugfs_dir, (void *)(long)p->offset,
- stat_fops[p->kind]);
- }
+ stats_fs_source_add_values(kvm_stats_fs_dir, stats_fs_vcpu_entries, NULL);
+ stats_fs_source_add_values(kvm_stats_fs_dir, stats_fs_vm_entries, NULL);
}
static int kvm_suspend(void)
@@ -4738,7 +4503,7 @@ int kvm_init(void *opaque, unsigned vcpu_size, unsigned vcpu_align,
kvm_preempt_ops.sched_in = kvm_sched_in;
kvm_preempt_ops.sched_out = kvm_sched_out;
- kvm_init_debug();
+ kvm_init_stats_fs();
r = kvm_vfio_ops_init();
WARN_ON(r);
@@ -4767,7 +4532,8 @@ EXPORT_SYMBOL_GPL(kvm_init);
void kvm_exit(void)
{
- debugfs_remove_recursive(kvm_debugfs_dir);
+ stats_fs_source_revoke(kvm_stats_fs_dir);
+ stats_fs_source_put(kvm_stats_fs_dir);
misc_deregister(&kvm_dev);
kmem_cache_destroy(kvm_vcpu_cache);
kvm_async_pf_deinit();
--
2.25.2
^ permalink raw reply related [flat|nested] 21+ messages in thread