kvm.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Emanuele Giuseppe Esposito <eesposit@redhat.com>
To: kvm@vger.kernel.org
Cc: linux-fsdevel@vger.kernel.org, mst@redhat.com,
	borntraeger@de.ibm.com, Paolo Bonzini <pbonzini@redhat.com>,
	Emanuele Giuseppe Esposito <eesposit@redhat.com>
Subject: [RFC PATCH 2/5] statsfs API: create, add and remove statsfs sources and values
Date: Mon, 27 Apr 2020 16:18:13 +0200	[thread overview]
Message-ID: <20200427141816.16703-3-eesposit@redhat.com> (raw)
In-Reply-To: <20200427141816.16703-1-eesposit@redhat.com>

Introduction to the statsfs API, that allows to easily create, add
and remove statsfs 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 statsfs 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>
---
 fs/Kconfig              |   7 +
 fs/Makefile             |   1 +
 fs/statsfs/Makefile     |   4 +
 fs/statsfs/internal.h   |  20 ++
 fs/statsfs/statsfs.c    | 618 ++++++++++++++++++++++++++++++++++++++++
 include/linux/statsfs.h | 222 +++++++++++++++
 6 files changed, 872 insertions(+)
 create mode 100644 fs/statsfs/Makefile
 create mode 100644 fs/statsfs/internal.h
 create mode 100644 fs/statsfs/statsfs.c
 create mode 100644 include/linux/statsfs.h

diff --git a/fs/Kconfig b/fs/Kconfig
index f08fbbfafd9a..824fcf86d12b 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -328,4 +328,11 @@ source "fs/unicode/Kconfig"
 config IO_WQ
 	bool
 
+config STATS_FS
+	bool "Statistics Filesystem"
+	default y
+	help
+	  statsfs 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..6942070f54b2 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)		+= statsfs/
 obj-$(CONFIG_TRACING)		+= tracefs/
 obj-$(CONFIG_OCFS2_FS)		+= ocfs2/
 obj-$(CONFIG_BTRFS_FS)		+= btrfs/
diff --git a/fs/statsfs/Makefile b/fs/statsfs/Makefile
new file mode 100644
index 000000000000..d494a3f30ba5
--- /dev/null
+++ b/fs/statsfs/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-only
+statsfs-objs	:= statsfs.o
+
+obj-$(CONFIG_STATS_FS)	+= statsfs.o
diff --git a/fs/statsfs/internal.h b/fs/statsfs/internal.h
new file mode 100644
index 000000000000..f124683a2ded
--- /dev/null
+++ b/fs/statsfs/internal.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _STATSFS_INTERNAL_H_
+#define _STATSFS_INTERNAL_H_
+
+#include <linux/list.h>
+#include <linux/kref.h>
+#include <linux/rwsem.h>
+#include <linux/statsfs.h>
+
+/* values, grouped by base */
+struct statsfs_value_source {
+	void *base_addr;
+	bool files_created;
+	struct statsfs_value *values;
+	struct list_head list_element;
+};
+
+int statsfs_val_get_mode(struct statsfs_value *val);
+
+#endif /* _STATSFS_INTERNAL_H_ */
diff --git a/fs/statsfs/statsfs.c b/fs/statsfs/statsfs.c
new file mode 100644
index 000000000000..0ad1d985be46
--- /dev/null
+++ b/fs/statsfs/statsfs.c
@@ -0,0 +1,618 @@
+// 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/statsfs.h>
+
+#include "internal.h"
+
+struct statsfs_aggregate_value {
+	uint64_t sum, min, max;
+	uint32_t count, count_zero;
+};
+
+static int is_val_signed(struct statsfs_value *val)
+{
+	return val->type & STATSFS_SIGN;
+}
+
+int statsfs_val_get_mode(struct statsfs_value *val)
+{
+	return val->mode ? val->mode : 0644;
+}
+
+static struct statsfs_value *find_value(struct statsfs_value_source *src,
+					struct statsfs_value *val)
+{
+	struct statsfs_value *entry;
+
+	for (entry = src->values; entry->name; entry++) {
+		if (entry == val) {
+			WARN_ON(strcmp(entry->name, val->name) != 0);
+			return entry;
+		}
+	}
+	return NULL;
+}
+
+static struct statsfs_value *
+search_value_in_source(struct statsfs_source *src, struct statsfs_value *arg,
+		       struct statsfs_value_source **val_src)
+{
+	struct statsfs_value *entry;
+	struct statsfs_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 statsfs_value_source *create_value_source(void *base)
+{
+	struct statsfs_value_source *val_src;
+
+	val_src = kzalloc(sizeof(struct statsfs_value_source), GFP_KERNEL);
+	if (!val_src)
+		return ERR_PTR(-ENOMEM);
+
+	val_src->base_addr = base;
+	val_src->list_element =
+		(struct list_head)LIST_HEAD_INIT(val_src->list_element);
+
+	return val_src;
+}
+
+int statsfs_source_add_values(struct statsfs_source *source,
+			      struct statsfs_value *stat, void *ptr)
+{
+	struct statsfs_value_source *val_src;
+	struct statsfs_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 statsfs_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(statsfs_source_add_values);
+
+void statsfs_source_add_subordinate(struct statsfs_source *source,
+				    struct statsfs_source *sub)
+{
+	down_write(&source->rwsem);
+
+	statsfs_source_get(sub);
+	list_add(&sub->list_element, &source->subordinates_head);
+
+	up_write(&source->rwsem);
+}
+EXPORT_SYMBOL_GPL(statsfs_source_add_subordinate);
+
+/* Called with rwsem held for writing */
+static void
+statsfs_source_remove_subordinate_locked(struct statsfs_source *source,
+					 struct statsfs_source *sub)
+{
+	struct list_head *it, *safe;
+	struct statsfs_source *src_entry;
+
+	list_for_each_safe(it, safe, &source->subordinates_head) {
+		src_entry = list_entry(it, struct statsfs_source, list_element);
+		if (src_entry == sub) {
+			WARN_ON(strcmp(src_entry->name, sub->name) != 0);
+			list_del_init(&src_entry->list_element);
+			statsfs_source_put(src_entry);
+			return;
+		}
+	}
+}
+
+void statsfs_source_remove_subordinate(struct statsfs_source *source,
+				       struct statsfs_source *sub)
+{
+	down_write(&source->rwsem);
+	statsfs_source_remove_subordinate_locked(source, sub);
+	up_write(&source->rwsem);
+}
+EXPORT_SYMBOL_GPL(statsfs_source_remove_subordinate);
+
+/* Called with rwsem held for reading */
+static uint64_t get_simple_value(struct statsfs_value_source *src,
+				 struct statsfs_value *val)
+{
+	uint64_t value_found;
+	void *address;
+
+	address = src->base_addr + val->offset;
+
+	switch (val->type) {
+	case STATSFS_U8:
+		value_found = *((uint8_t *)address);
+		break;
+	case STATSFS_U8 | STATSFS_SIGN:
+		value_found = *((int8_t *)address);
+		break;
+	case STATSFS_U16:
+		value_found = *((uint16_t *)address);
+		break;
+	case STATSFS_U16 | STATSFS_SIGN:
+		value_found = *((int16_t *)address);
+		break;
+	case STATSFS_U32:
+		value_found = *((uint32_t *)address);
+		break;
+	case STATSFS_U32 | STATSFS_SIGN:
+		value_found = *((int32_t *)address);
+		break;
+	case STATSFS_U64:
+		value_found = *((uint64_t *)address);
+		break;
+	case STATSFS_U64 | STATSFS_SIGN:
+		value_found = *((int64_t *)address);
+		break;
+	case STATSFS_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 statsfs_value_source *src,
+			       struct statsfs_value *val)
+{
+	void *address;
+
+	address = src->base_addr + val->offset;
+
+	switch (val->type) {
+	case STATSFS_U8:
+		*((uint8_t *)address) = 0;
+		break;
+	case STATSFS_U8 | STATSFS_SIGN:
+		*((int8_t *)address) = 0;
+		break;
+	case STATSFS_U16:
+		*((uint16_t *)address) = 0;
+		break;
+	case STATSFS_U16 | STATSFS_SIGN:
+		*((int16_t *)address) = 0;
+		break;
+	case STATSFS_U32:
+		*((uint32_t *)address) = 0;
+		break;
+	case STATSFS_U32 | STATSFS_SIGN:
+		*((int32_t *)address) = 0;
+		break;
+	case STATSFS_U64:
+		*((uint64_t *)address) = 0;
+		break;
+	case STATSFS_U64 | STATSFS_SIGN:
+		*((int64_t *)address) = 0;
+		break;
+	case STATSFS_BOOL:
+		*((uint8_t *)address) = 0;
+		break;
+	default:
+		break;
+	}
+}
+
+/* Called with rwsem held for reading */
+static void search_all_simple_values(struct statsfs_source *src,
+				     struct statsfs_value_source *ref_src_entry,
+				     struct statsfs_value *val,
+				     struct statsfs_aggregate_value *agg)
+{
+	struct statsfs_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 statsfs_source *root,
+				     struct statsfs_value_source *ref_src_entry,
+				     struct statsfs_value *val,
+				     struct statsfs_aggregate_value *agg)
+{
+	struct statsfs_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 statsfs_aggregate_value *agg,
+				 struct statsfs_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 statsfs_aggregate_value *agg,
+			    struct statsfs_value *val, uint64_t *ret)
+{
+	int operation;
+
+	operation = val->aggr_kind | is_val_signed(val);
+
+	switch (operation) {
+	case STATSFS_AVG:
+		*ret = agg->count ? agg->sum / agg->count : 0;
+		break;
+	case STATSFS_AVG | STATSFS_SIGN:
+		*ret = agg->count ? ((int64_t)agg->sum) / agg->count : 0;
+		break;
+	case STATSFS_SUM:
+	case STATSFS_SUM | STATSFS_SIGN:
+		*ret = agg->sum;
+		break;
+	case STATSFS_MIN:
+	case STATSFS_MIN | STATSFS_SIGN:
+		*ret = agg->min;
+		break;
+	case STATSFS_MAX:
+	case STATSFS_MAX | STATSFS_SIGN:
+		*ret = agg->max;
+		break;
+	case STATSFS_COUNT_ZERO:
+	case STATSFS_COUNT_ZERO | STATSFS_SIGN:
+		*ret = agg->count_zero;
+		break;
+	default:
+		break;
+	}
+}
+
+/* Called with rwsem held for reading */
+static int statsfs_source_get_value_locked(struct statsfs_source *source,
+					   struct statsfs_value *arg,
+					   uint64_t *ret)
+{
+	struct statsfs_value_source *src_entry;
+	struct statsfs_value *found;
+	struct statsfs_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 "Statsfs: 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 statsfs_source_get_value(struct statsfs_source *source,
+			     struct statsfs_value *arg, uint64_t *ret)
+{
+	int retval;
+
+	down_read(&source->rwsem);
+	retval = statsfs_source_get_value_locked(source, arg, ret);
+	up_read(&source->rwsem);
+
+	return retval;
+}
+EXPORT_SYMBOL_GPL(statsfs_source_get_value);
+
+/* Called with rwsem held for reading */
+static void set_all_simple_values(struct statsfs_source *src,
+				  struct statsfs_value_source *ref_src_entry,
+				  struct statsfs_value *val)
+{
+	struct statsfs_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 statsfs_source *root,
+			       struct statsfs_value_source *ref_src_entry,
+			       struct statsfs_value *val)
+{
+	struct statsfs_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 statsfs_source_clear_locked(struct statsfs_source *source,
+				       struct statsfs_value *val)
+{
+	struct statsfs_value_source *src_entry;
+	struct statsfs_value *found;
+
+	if (!val)
+		return -ENOENT;
+
+	/* look in simple values */
+	found = search_value_in_source(source, val, &src_entry);
+
+	if (!found) {
+		printk(KERN_ERR "Statsfs: 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 statsfs_source_clear(struct statsfs_source *source,
+			 struct statsfs_value *val)
+{
+	int retval;
+
+	down_read(&source->rwsem);
+	retval = statsfs_source_clear_locked(source, val);
+	up_read(&source->rwsem);
+
+	return retval;
+}
+
+/* Called with rwsem held for reading */
+static struct statsfs_value *
+find_value_by_name(struct statsfs_value_source *src, char *val)
+{
+	struct statsfs_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 statsfs_value *
+search_in_source_by_name(struct statsfs_source *src, char *name)
+{
+	struct statsfs_value *entry;
+	struct statsfs_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 statsfs_source_get_value_by_name(struct statsfs_source *source, char *name,
+				     uint64_t *ret)
+{
+	struct statsfs_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 = statsfs_source_get_value_locked(source, val, ret);
+	up_read(&source->rwsem);
+
+	return retval;
+}
+EXPORT_SYMBOL_GPL(statsfs_source_get_value_by_name);
+
+void statsfs_source_get(struct statsfs_source *source)
+{
+	kref_get(&source->refcount);
+}
+EXPORT_SYMBOL_GPL(statsfs_source_get);
+
+void statsfs_source_revoke(struct statsfs_source *source)
+{
+	struct list_head *it, *safe;
+	struct statsfs_value_source *val_src_entry;
+
+	down_write(&source->rwsem);
+
+	list_for_each_safe(it, safe, &source->values_head) {
+		val_src_entry = list_entry(it, struct statsfs_value_source,
+					   list_element);
+		val_src_entry->base_addr = NULL;
+	}
+
+	up_write(&source->rwsem);
+}
+EXPORT_SYMBOL_GPL(statsfs_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 statsfs_source_destroy(struct kref *kref_source)
+{
+	struct statsfs_value_source *val_src_entry;
+	struct list_head *it, *safe;
+	struct statsfs_source *child, *source;
+
+	source = container_of(kref_source, struct statsfs_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 statsfs_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 statsfs_source, list_element);
+		statsfs_source_remove_subordinate_locked(source, child);
+	}
+
+
+	up_write(&source->rwsem);
+	kfree(source->name);
+	kfree(source);
+}
+
+void statsfs_source_put(struct statsfs_source *source)
+{
+	kref_put_rwsem(&source->refcount, statsfs_source_destroy,
+		       &source->rwsem);
+}
+EXPORT_SYMBOL_GPL(statsfs_source_put);
+
+struct statsfs_source *statsfs_source_create(const char *fmt, ...)
+{
+	va_list ap;
+	char buf[100];
+	struct statsfs_source *ret;
+	int char_needed;
+
+	va_start(ap, fmt);
+	char_needed = vsnprintf(buf, 100, fmt, ap);
+	va_end(ap);
+
+	ret = kzalloc(sizeof(struct statsfs_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(statsfs_source_create);
diff --git a/include/linux/statsfs.h b/include/linux/statsfs.h
new file mode 100644
index 000000000000..3f01f094946d
--- /dev/null
+++ b/include/linux/statsfs.h
@@ -0,0 +1,222 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ *  statsfs.h - a tiny little statistics file system
+ *
+ *  Copyright (C) 2020 Emanuele Giuseppe Esposito
+ *  Copyright (C) 2020 Redhat.
+ *
+ */
+
+#ifndef _STATSFS_H_
+#define _STATSFS_H_
+
+#include <linux/list.h>
+
+/* Used to distinguish signed types */
+#define STATSFS_SIGN 0x8000
+
+struct statsfs_source;
+
+enum stat_type {
+	STATSFS_U8 = 0,
+	STATSFS_U16 = 1,
+	STATSFS_U32 = 2,
+	STATSFS_U64 = 3,
+	STATSFS_BOOL = 4,
+	STATSFS_S8 = STATSFS_U8 | STATSFS_SIGN,
+	STATSFS_S16 = STATSFS_U16 | STATSFS_SIGN,
+	STATSFS_S32 = STATSFS_U32 | STATSFS_SIGN,
+	STATSFS_S64 = STATSFS_U64 | STATSFS_SIGN,
+};
+
+enum stat_aggr {
+	STATSFS_NONE = 0,
+	STATSFS_SUM,
+	STATSFS_MIN,
+	STATSFS_MAX,
+	STATSFS_COUNT_ZERO,
+	STATSFS_AVG,
+};
+
+struct statsfs_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 statsfs_source {
+	struct kref refcount;
+
+	char *name;
+
+	/* list of source statsfs_value_source*/
+	struct list_head values_head;
+
+	/* list of struct statsfs_source for subordinate sources */
+	struct list_head subordinates_head;
+
+	struct list_head list_element;
+
+	struct rw_semaphore rwsem;
+
+	struct dentry *source_dentry;
+};
+
+/**
+ * statsfs_source_create - create a statsfs_source
+ * Creates a statsfs_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 statsfs.
+ *
+ * Returns a pointer to a statsfs_source if it succeeds.
+ * This or one of the parents' pointer must be passed to the statsfs_put()
+ * function when the file is to be removed.  If an error occurs,
+ * ERR_PTR(-ERROR) will be returned.
+ */
+struct statsfs_source *statsfs_source_create(const char *fmt, ...);
+
+/**
+ * statsfs_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 statsfs_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 statsfs_source_add_values(struct statsfs_source *source,
+			      struct statsfs_value *val, void *base_ptr);
+
+/**
+ * statsfs_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 statsfs filesystem
+ * only if the parent has already a dentry (created with
+ * statsfs_source_register).
+ * This avoids the case where this function is called before register.
+ */
+void statsfs_source_add_subordinate(struct statsfs_source *parent,
+				    struct statsfs_source *child);
+
+/**
+ * statsfs_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 statsfs_put on the child.
+ */
+void statsfs_source_remove_subordinate(struct statsfs_source *parent,
+				       struct statsfs_source *child);
+
+/**
+ * statsfs_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 statsfs_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 != STATSFS_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 statsfs_source_get_value(struct statsfs_source *source,
+			     struct statsfs_value *val, uint64_t *ret);
+
+/**
+ * statsfs_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 statsfs_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, statsfs_source_get_value will be called with the found
+ * value, otherwise -ENOENT will be returned.
+ */
+int statsfs_source_get_value_by_name(struct statsfs_source *source, char *name,
+				     uint64_t *ret);
+
+/**
+ * statsfs_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 statsfs_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 != STATSFS_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 statsfs_source_clear(struct statsfs_source *source,
+			 struct statsfs_value *val);
+
+/**
+ * statsfs_source_revoke - disconnect the source from its backing data
+ * @source: a pointer to the source that will be revoked
+ *
+ * Ensure that statsfs will not access the data that were passed to
+ * statsfs_source_add_value for this source.
+ *
+ * Because open files increase the reference count for a statsfs_source,
+ * the source can end up living longer than the data that provides the
+ * values for the source.  Calling statsfs_source_revoke just before the
+ * backing data is freed avoids accesses to freed data structures.  The
+ * sources will return 0.
+ */
+void statsfs_source_revoke(struct statsfs_source *source);
+
+/**
+ * statsfs_source_get - increases refcount of source
+ * @source: a pointer to the source whose refcount will be increased
+ */
+void statsfs_source_get(struct statsfs_source *source);
+
+/**
+ * statsfs_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
+ * statsfs_source_remove_subordinate to the child and then deleting
+ * its own files and allocations.
+ */
+void statsfs_source_put(struct statsfs_source *source);
+
+/**
+ * statsfs_initialized - returns true if statsfs fs has been registered
+ */
+bool statsfs_initialized(void);
+
+#endif
-- 
2.25.2


  parent reply	other threads:[~2020-04-27 14:18 UTC|newest]

Thread overview: 17+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-04-27 14:18 [RFC PATCH 0/5] Statsfs: a new ram-based file sytem for Linux kernel statistics Emanuele Giuseppe Esposito
2020-04-27 14:18 ` [RFC PATCH 1/5] refcount, kref: add dec-and-test wrappers for rw_semaphores Emanuele Giuseppe Esposito
2020-04-27 14:18 ` Emanuele Giuseppe Esposito [this message]
2020-04-27 15:47   ` [RFC PATCH 2/5] statsfs API: create, add and remove statsfs sources and values Matthew Wilcox
2020-04-27 16:48     ` Emanuele Giuseppe Esposito
2020-04-29  9:49     ` [RFC PATCH 2/5] statsfs API: create, add and remove statsfs Emanuele Giuseppe Esposito
2020-04-27 21:53   ` [RFC PATCH 2/5] statsfs API: create, add and remove statsfs sources and values Andreas Dilger
2020-04-29 10:55     ` Emanuele Giuseppe Esposito
2020-04-28 17:47   ` Randy Dunlap
2020-04-29 10:34     ` Paolo Bonzini
2020-04-27 14:18 ` [RFC PATCH 3/5] kunit: tests for statsfs API Emanuele Giuseppe Esposito
2020-04-28 17:50   ` Randy Dunlap
2020-04-27 14:18 ` [RFC PATCH 4/5] statsfs fs: virtual fs to show stats to the end-user Emanuele Giuseppe Esposito
2020-04-27 14:18 ` [RFC PATCH 5/5] kvm_main: replace debugfs with statsfs Emanuele Giuseppe Esposito
2020-04-28 17:56   ` Randy Dunlap
2020-04-29 10:34     ` Emanuele Giuseppe Esposito
2020-04-29 10:35       ` Paolo Bonzini

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20200427141816.16703-3-eesposit@redhat.com \
    --to=eesposit@redhat.com \
    --cc=borntraeger@de.ibm.com \
    --cc=kvm@vger.kernel.org \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=mst@redhat.com \
    --cc=pbonzini@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).