[Patch 5/6] statistics infrastructure

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This patch adds statistics infrastructure as common code.

Signed-off-by: Martin Peschke <[email protected]>
---

 MAINTAINERS                |    7
 arch/s390/Kconfig          |    6
 arch/s390/oprofile/Kconfig |    5
 include/linux/statistic.h  |  348 ++++++++++
 lib/Kconfig.statistic      |   11
 lib/Makefile               |    2
 lib/statistic.c            | 1459 +++++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 1833 insertions(+), 5 deletions(-)

diff -Nurp a/include/linux/statistic.h b/include/linux/statistic.h
--- a/include/linux/statistic.h	1970-01-01 01:00:00.000000000 +0100
+++ b/include/linux/statistic.h	2006-05-19 16:23:07.000000000 +0200
@@ -0,0 +1,348 @@
+/*
+ * include/linux/statistic.h
+ *
+ * Statistics facility
+ *
+ * (C) Copyright IBM Corp. 2005, 2006
+ *
+ * Author(s): Martin Peschke <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef STATISTIC_H
+#define STATISTIC_H
+
+#include <linux/fs.h>
+#include <linux/types.h>
+#include <linux/percpu.h>
+
+#define STATISTIC_ROOT_DIR	"statistics"
+
+#define STATISTIC_FILENAME_DATA	"data"
+#define STATISTIC_FILENAME_DEF	"definition"
+
+#define STATISTIC_NEED_BARRIER	1
+
+struct statistic;
+
+enum statistic_state {
+	STATISTIC_STATE_INVALID,
+	STATISTIC_STATE_UNCONFIGURED,
+	STATISTIC_STATE_RELEASED,
+	STATISTIC_STATE_OFF,
+	STATISTIC_STATE_ON
+};
+
+enum statistic_type {
+	STATISTIC_TYPE_COUNTER_INC,
+	STATISTIC_TYPE_COUNTER_PROD,
+	STATISTIC_TYPE_UTIL,
+	STATISTIC_TYPE_HISTOGRAM_LIN,
+	STATISTIC_TYPE_HISTOGRAM_LOG2,
+	STATISTIC_TYPE_SPARSE,
+	STATISTIC_TYPE_NONE
+};
+
+#define STATISTIC_FLAGS_NOINCR	0x01
+
+/**
+ * struct statistic_info - description of a class of statistics
+ * @name: pointer to name name string
+ * @x_unit: pointer to string describing unit of X of (X, Y) data pair
+ * @y_unit: pointer to string describing unit of Y of (X, Y) data pair
+ * @flags: only flag so far (distinction of incremental and other statistic)
+ * @defaults: pointer to string describing defaults setting for attributes
+ *
+ * Exploiters must setup an array of struct statistic_info for a
+ * corresponding array of struct statistic, which are then pointed to
+ * by struct statistic_interface.
+ *
+ * Struct statistic_info and all members and addressed strings must stay for
+ * the lifetime of corresponding statistics created with statistic_create().
+ *
+ * Except for the name string, all other members may be left blank.
+ * It would be nice of exploiters to fill it out completely, though.
+ */
+struct statistic_info {
+/* public: */
+	char *name;
+	char *x_unit;
+	char *y_unit;
+	int  flags;
+	char *defaults;
+};
+
+/**
+ * struct statistic_interface - collection of statistics for an entity
+ * @stat: a struct statistic array
+ * @info: a struct statistic_info array describing the struct statistic array
+ * @number: number of entries in both arrays
+ * @pull: an optional function called when user reads data from file
+ * @pull_private: optional data pointer passed to pull function
+ *
+ * Exploiters must setup a struct statistic_interface prior to calling
+ * statistic_create().
+ */
+struct statistic_interface {
+/* private: */
+	struct list_head	 list;
+	struct dentry		*debugfs_dir;
+	struct dentry		*data_file;
+	struct dentry		*def_file;
+/* public: */
+	struct statistic	*stat;
+	struct statistic_info	*info;
+	int			 number;
+	int			(*pull)(void*);
+	void			*pull_private;
+};
+
+struct sgrb_seg {
+	struct list_head list;
+	char *address;
+	int offset;
+	int size;
+};
+
+struct statistic_file_private {
+	struct list_head read_seg_lh;
+	struct list_head write_seg_lh;
+	size_t write_seg_total_size;
+};
+
+struct statistic_merge_private {
+	struct statistic *stat;
+	spinlock_t lock;
+	void *dst;
+};
+
+/**
+ * struct statistic_discipline - description of a data processing mode
+ * @parse: parses additional attributes specific to this mode (if any)
+ * @alloc: allocates a data area (mandatory, default routine available)
+ * @free: frees a data area (optional, kfree() is used otherwise)
+ * @reset: discards content of a data area (mandatory)
+ * @merge: merges content of a data area into another data area (mandatory)
+ * @fdata: prints content of a data area into buffer (mandatory)
+ * @fdef: prints additional attributes specific to this mode (if any)
+ * @add: updates a data area for a statistic fed incremental data (mandatory)
+ * @set: updates a data area for a statistic fed total numbers (mandatory)
+ * @name: pointer to name string (mandatory)
+ * @size: base size for a data area (passed to alloc function)
+ *
+ * Struct statistic_discipline describes a statistic infrastructure internal
+ * programming interface. Another data processing mode can be added by
+ * implementing these routines and appending an entry to the
+ * statistic_discs array.
+ *
+ * "Data area" in te above description usually means a chunk of memory,
+ * may it be allocated for data gathering per CPU, or be shared by all
+ * CPUs, or used for other purposes, like merging per-CPU data when
+ * users read data from files. Implementers of data processing modes
+ * don't need to worry about the designation of a particular chunk of memory.
+ * A data area of a data processing mode always has to look the same.
+ */
+struct statistic_discipline {
+	int (*parse)(struct statistic *, struct statistic_info *, int, char *);
+	void* (*alloc)(struct statistic *, size_t, gfp_t, int);
+	void (*free)(struct statistic *, void *);
+	void (*reset)(struct statistic *, void *);
+	void (*merge)(struct statistic *, void *, void*);
+	int (*fdata)(struct statistic *, const char *,
+		     struct statistic_file_private *, void *);
+	int (*fdef)(struct statistic *, char *);
+	void (*add)(struct statistic *, int, s64, u64);
+	void (*set)(struct statistic *, s64, u64);
+	char *name;
+	size_t size;
+};
+
+struct statistic_entry_util {
+	u32 res;
+	u32 num;	/* FIXME: better 64 bit; do_div can't deal with it) */
+	s64 acc;
+	s64 min;
+	s64 max;
+};
+
+struct statistic_entry_sparse {
+	struct list_head list;
+	s64 value;
+	u64 hits;
+};
+
+struct statistic_sparse_list {
+	struct list_head entry_lh;
+	u32 entries;
+	u32 entries_max;
+	u64 hits_missed;
+};
+
+/**
+ * struct statistic - any data required for gathering data for a statistic
+ */
+struct statistic {
+/* private: */
+	enum statistic_state	 state;
+	enum statistic_type	 type;
+	struct percpu_data	*pdata;
+	void			(*add)(struct statistic *, int, s64, u64);
+	u64			 started;
+	u64			 stopped;
+	u64			 age;
+	union {
+		struct {
+			s64 range_min;
+			u32 last_index;
+			u32 base_interval;
+		} histogram;
+		struct {
+			u32 entries_max;
+		} sparse;
+	} u;
+};
+
+#ifdef CONFIG_STATISTICS
+
+extern int statistic_create(struct statistic_interface *, const char *);
+extern int statistic_remove(struct statistic_interface *);
+
+/**
+ * statistic_add - update statistic with incremental data in (X, Y) pair
+ * @stat: struct statistic array
+ * @i: index of statistic to be updated
+ * @value: X
+ * @incr: Y
+ *
+ * The actual processing of the (X, Y) data pair is determined by the current
+ * the definition applied to the statistic. See Documentation/statistics.txt.
+ *
+ * This variant takes care of protecting per-cpu data. It is preferred whenever
+ * exploiters don't update several statistics of the same entity in one go.
+ */
+static inline void statistic_add(struct statistic *stat, int i,
+				 s64 value, u64 incr)
+{
+	unsigned long flags;
+	local_irq_save(flags);
+	if (stat[i].state == STATISTIC_STATE_ON)
+		stat[i].add(&stat[i], smp_processor_id(), value, incr);
+	local_irq_restore(flags);
+}
+
+/**
+ * statistic_add_nolock - update statistic with incremental data in (X, Y) pair
+ * @stat: struct statistic array
+ * @i: index of statistic to be updated
+ * @value: X
+ * @incr: Y
+ *
+ * The actual processing of the (X, Y) data pair is determined by the current
+ * definition applied to the statistic. See Documentation/statistics.txt.
+ *
+ * This variant leaves protecting per-cpu data to exploiters. It is preferred
+ * whenever exploiters update several statistics of the same entity in one go.
+ */
+static inline void statistic_add_nolock(struct statistic *stat, int i,
+					s64 value, u64 incr)
+{
+	if (stat[i].state == STATISTIC_STATE_ON)
+		stat[i].add(&stat[i], smp_processor_id(), value, incr);
+}
+
+/**
+ * statistic_inc - update statistic with incremental data in (X, 1) pair
+ * @stat: struct statistic array
+ * @i: index of statistic to be updated
+ * @value: X
+ *
+ * The actual processing of the (X, Y) data pair is determined by the current
+ * definition applied to the statistic. See Documentation/statistics.txt.
+ *
+ * This variant takes care of protecting per-cpu data. It is preferred whenever
+ * exploiters don't update several statistics of the same entity in one go.
+ */
+static inline void statistic_inc(struct statistic *stat, int i, s64 value)
+{
+	unsigned long flags;
+	local_irq_save(flags);
+	if (stat[i].state == STATISTIC_STATE_ON)
+		stat[i].add(&stat[i], smp_processor_id(), value, 1);
+	local_irq_restore(flags);
+}
+
+/**
+ * statistic_inc_nolock - update statistic with incremental data in (X, 1) pair
+ * @stat: struct statistic array
+ * @i: index of statistic to be updated
+ * @value: X
+ *
+ * The actual processing of the (X, Y) data pair is determined by the current
+ * definition applied to the statistic. See Documentation/statistics.txt.
+ *
+ * This variant leaves protecting per-cpu data to exploiters. It is preferred
+ * whenever exploiters update several statistics of the same entity in one go.
+ */
+static inline void statistic_inc_nolock(struct statistic *stat, int i,
+					s64 value)
+{
+	if (stat[i].state == STATISTIC_STATE_ON)
+		stat[i].add(&stat[i], smp_processor_id(), value, 1);
+}
+
+extern void statistic_set(struct statistic *, int, s64, u64);
+
+#else /* CONFIG_STATISTICS */
+
+static inline int statistic_create(struct statistic_interface *interface,
+				   const char *name)
+{
+	return 0;
+}
+
+static inline int statistic_remove(
+				struct statistic_interface *interface_ptr)
+{
+	return 0;
+}
+
+static inline void statistic_add(struct statistic *stat, int i,
+				 s64 value, u64 incr)
+{
+}
+
+static inline void statistic_add_nolock(struct statistic *stat, int i,
+					s64 value, u64 incr)
+{
+}
+
+static inline void statistic_inc(struct statistic *stat, int i, s64 value)
+{
+}
+
+static inline void statistic_inc_nolock(struct statistic *stat, int i,
+					s64 value)
+{
+}
+
+static inline void statistic_set(struct statistic *stat, int i,
+				 s64 value, u64 total)
+{
+}
+
+#endif /* CONFIG_STATISTICS */
+
+#endif /* STATISTIC_H */
diff -Nurp a/lib/statistic.c b/lib/statistic.c
--- a/lib/statistic.c	1970-01-01 01:00:00.000000000 +0100
+++ b/lib/statistic.c	2006-05-19 16:22:55.000000000 +0200
@@ -0,0 +1,1459 @@
+/*
+ *  lib/statistic.c
+ *    statistics facility
+ *
+ *    Copyright (C) 2005, 2006
+ *		IBM Deutschland Entwicklung GmbH,
+ *		IBM Corporation
+ *
+ *    Author(s): Martin Peschke ([email protected]),
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ *    another bunch of ideas being pondered:
+ *	- define a set of agreed names or a naming scheme for
+ *	  consistency and comparability across exploiters;
+ *	  this entails an agreement about granularities
+ *	  as well (e.g. separate statistic for read/write/no-data commands);
+ *	  a common set of unit strings would be nice then, too, of course
+ *	  (e.g. "seconds", "milliseconds", "microseconds", ...)
+ *	- perf. opt. of array: table lookup of values, binary search for values
+ *	- another statistic disclipline based on some sort of tree, but
+ *	  similar in semantics to list discipline (for high-perf. histograms of
+ *	  discrete values)
+ *	- allow for more than a single "view" on data at the same time by
+ *	  providing the capability to attach several (a list of) "definitions"
+ *	  to a struct statistic
+ *	  (e.g. show histogram of requests sizes and history of megabytes/sec.
+ *	  at the same time)
+ *	- multi-dimensional statistic (combination of two or more
+ *	  characteristics/discriminators); worth the effort??
+ *	  (e.g. a matrix of occurences for latencies of requests of
+ *	  particular sizes)
+ *
+ *	FIXME:
+ *	- statistics file access when statistics are being removed
+ */
+
+#include <linux/fs.h>
+#include <linux/debugfs.h>
+#include <linux/module.h>
+#include <linux/list.h>
+#include <linux/parser.h>
+#include <linux/time.h>
+#include <linux/sched.h>
+#include <linux/cpu.h>
+#include <linux/percpu.h>
+#include <linux/mutex.h>
+#include <linux/statistic.h>
+
+#include <asm/bug.h>
+#include <asm/uaccess.h>
+
+static struct statistic_discipline statistic_discs[];
+
+static inline int statistic_initialise(struct statistic *stat)
+{
+	stat->type = STATISTIC_TYPE_NONE;
+	stat->state = STATISTIC_STATE_UNCONFIGURED;
+	return 0;
+}
+
+static inline int statistic_uninitialise(struct statistic *stat)
+{
+	stat->state = STATISTIC_STATE_INVALID;
+	return 0;
+}
+
+static inline int statistic_define(struct statistic *stat)
+{
+	if (stat->type == STATISTIC_TYPE_NONE)
+		return -EINVAL;
+	stat->state = STATISTIC_STATE_RELEASED;
+	return 0;
+}
+
+static inline void statistic_reset_ptr(struct statistic *stat, void *ptr)
+{
+	struct statistic_discipline *disc = &statistic_discs[stat->type];
+	if (ptr)
+		disc->reset(stat, ptr);
+}
+
+static inline void statistic_move_ptr(struct statistic *stat, void *src)
+{
+	struct statistic_discipline *disc = &statistic_discs[stat->type];
+	unsigned long flags;
+	local_irq_save(flags);
+	disc->merge(stat, stat->pdata->ptrs[smp_processor_id()], src);
+	local_irq_restore(flags);
+}
+
+static inline void statistic_free_ptr(struct statistic *stat, void *ptr)
+{
+	struct statistic_discipline *disc = &statistic_discs[stat->type];
+	if (ptr) {
+		if (unlikely(disc->free))
+			disc->free(stat, ptr);
+		kfree(ptr);
+	}
+}
+
+static int statistic_free(struct statistic *stat, struct statistic_info *info)
+{
+	int cpu;
+	stat->state = STATISTIC_STATE_RELEASED;
+	if (unlikely(info->flags & STATISTIC_FLAGS_NOINCR)) {
+		statistic_free_ptr(stat, stat->pdata);
+		stat->pdata = NULL;
+		return 0;
+	}
+	for_each_cpu(cpu) {
+		statistic_free_ptr(stat, stat->pdata->ptrs[cpu]);
+		stat->pdata->ptrs[cpu] = NULL;
+	}
+	kfree(stat->pdata);
+	stat->pdata = NULL;
+	return 0;
+}
+
+static void * statistic_alloc_generic(struct statistic *stat, size_t size,
+				      gfp_t flags, int node)
+{
+	return kmalloc_node(size, flags, node);
+}
+
+static void * statistic_alloc_ptr(struct statistic *stat, gfp_t flags, int node)
+{
+	struct statistic_discipline *disc = &statistic_discs[stat->type];
+	void *buf = disc->alloc(stat, disc->size, flags, node);
+	if (likely(buf))
+		statistic_reset_ptr(stat, buf);
+	return buf;
+}
+
+static int statistic_alloc(struct statistic *stat,
+			   struct statistic_info *info)
+{
+	int cpu;
+	stat->age = sched_clock();
+	if (unlikely(info->flags & STATISTIC_FLAGS_NOINCR)) {
+		stat->pdata = statistic_alloc_ptr(stat, GFP_KERNEL, -1);
+		if (unlikely(!stat->pdata))
+			return -ENOMEM;
+		stat->state = STATISTIC_STATE_OFF;
+		return 0;
+	}
+	stat->pdata = kzalloc(sizeof(struct percpu_data), GFP_KERNEL);
+	if (unlikely(!stat->pdata))
+		return -ENOMEM;
+	for_each_online_cpu(cpu) {
+		stat->pdata->ptrs[cpu] = statistic_alloc_ptr(stat, GFP_KERNEL,
+							     cpu_to_node(cpu));
+		if (unlikely(!stat->pdata->ptrs[cpu])) {
+			statistic_free(stat, info);
+			return -ENOMEM;
+		}
+	}
+	stat->state = STATISTIC_STATE_OFF;
+	return 0;
+}
+
+static inline int statistic_start(struct statistic *stat)
+{
+	stat->started = sched_clock();
+	stat->state = STATISTIC_STATE_ON;
+	return 0;
+}
+
+static void _statistic_barrier(void *unused)
+{
+}
+
+static inline int statistic_stop(struct statistic *stat)
+{
+	stat->stopped = sched_clock();
+	stat->state = STATISTIC_STATE_OFF;
+	/* ensures that all CPUs have ceased updating statistics */
+	smp_mb();
+	on_each_cpu(_statistic_barrier, NULL, 0, 1);
+	return 0;
+}
+
+static int statistic_transition(struct statistic *stat,
+				struct statistic_info *info,
+				enum statistic_state requested_state)
+{
+	int z = (requested_state < stat->state ? 1 : 0);
+	int retval = -EINVAL;
+
+	while (stat->state != requested_state) {
+		switch (stat->state) {
+		case STATISTIC_STATE_INVALID:
+			retval = ( z ? -EINVAL : statistic_initialise(stat) );
+			break;
+		case STATISTIC_STATE_UNCONFIGURED:
+			retval = ( z ? statistic_uninitialise(stat)
+				     : statistic_define(stat) );
+			break;
+		case STATISTIC_STATE_RELEASED:
+			retval = ( z ? statistic_initialise(stat)
+				     : statistic_alloc(stat, info) );
+			break;
+		case STATISTIC_STATE_OFF:
+			retval = ( z ? statistic_free(stat, info)
+				     : statistic_start(stat) );
+			break;
+		case STATISTIC_STATE_ON:
+			retval = ( z ? statistic_stop(stat) : -EINVAL );
+			break;
+		}
+		if (unlikely(retval))
+			return retval;
+	}
+	return 0;
+}
+
+static int statistic_reset(struct statistic *stat, struct statistic_info *info)
+{
+	enum statistic_state prev_state = stat->state;
+	int cpu;
+
+	if (unlikely(stat->state < STATISTIC_STATE_OFF))
+		return 0;
+	statistic_transition(stat, info, STATISTIC_STATE_OFF);
+	if (unlikely(info->flags & STATISTIC_FLAGS_NOINCR))
+		statistic_reset_ptr(stat, stat->pdata);
+	else
+		for_each_cpu(cpu)
+			statistic_reset_ptr(stat, stat->pdata->ptrs[cpu]);
+	stat->age = sched_clock();
+	statistic_transition(stat, info, prev_state);
+	return 0;
+}
+
+static void statistic_merge(void *__mpriv)
+{
+	struct statistic_merge_private *mpriv = __mpriv;
+	struct statistic *stat = mpriv->stat;
+	struct statistic_discipline *disc = &statistic_discs[stat->type];
+	spin_lock(&mpriv->lock);
+	disc->merge(stat, mpriv->dst, stat->pdata->ptrs[smp_processor_id()]);
+	spin_unlock(&mpriv->lock);
+}
+
+/**
+ * statistic_set - set statistic using total numbers in (X, Y) data pair
+ * @stat: struct statistic array
+ * @i: index of statistic to be updated
+ * @value: X
+ * @total: Y
+ *
+ * The actual processing of the (X, Y) data pair is determined by the current
+ * definition applied to the statistic. See Documentation/statistics.txt.
+ *
+ * There is no distinction between a concurrency protected and unprotected
+ * statistic_set() flavour needed. statistic_set() may only
+ * be called when we pull statistic updates from exploiters. The statistics
+ * infrastructure guarantees serialisation for that. Exploiters must not
+ * intermix statistic_set() and statistic_add/inc() anyway. That is why,
+ * concurrent updates won't happen and there is no additional protection
+ * required for statistics fed through statistic_set().
+ */
+void statistic_set(struct statistic *stat, int i, s64 value, u64 total)
+{
+	struct statistic_discipline *disc = &statistic_discs[stat[i].type];
+	if (stat[i].state == STATISTIC_STATE_ON)
+		disc->set(&stat[i], value, total);
+}
+
+static struct sgrb_seg * sgrb_seg_find(struct list_head *lh, int size)
+{
+	struct sgrb_seg *seg;
+
+	/* only the last buffer, if any, may have spare bytes */
+	list_for_each_entry_reverse(seg, lh, list) {
+		if (likely((PAGE_SIZE - seg->offset) >= size))
+			return seg;
+		break;
+	}
+	seg = kzalloc(sizeof(struct sgrb_seg), GFP_KERNEL);
+	if (unlikely(!seg))
+		return NULL;
+	seg->size = PAGE_SIZE;
+	seg->address = (void*)__get_free_page(GFP_KERNEL);
+	if (unlikely(!seg->address)) {
+		kfree(seg);
+		return NULL;
+	}
+	list_add_tail(&seg->list, lh);
+	return seg;
+}
+
+static void sgrb_seg_release_all(struct list_head *lh)
+{
+	struct sgrb_seg *seg, *tmp;
+
+	list_for_each_entry_safe(seg, tmp, lh, list) {
+		list_del(&seg->list);
+		free_page((unsigned long)seg->address);
+		kfree(seg);
+	}
+}
+
+static char * statistic_state_strings[] = {
+	"undefined(BUG)",
+	"unconfigured",
+	"released",
+	"off",
+	"on",
+};
+
+static int statistic_fdef(struct statistic_interface *interface, int i,
+			  struct statistic_file_private *private)
+{
+	struct statistic *stat = &interface->stat[i];
+	struct statistic_info *info = &interface->info[i];
+	struct statistic_discipline *disc = &statistic_discs[stat->type];
+	struct sgrb_seg *seg;
+	char t0[TIMESTAMP_SIZE], t1[TIMESTAMP_SIZE], t2[TIMESTAMP_SIZE];
+
+	seg = sgrb_seg_find(&private->read_seg_lh, 512);
+	if (unlikely(!seg))
+		return -ENOMEM;
+
+	seg->offset += sprintf(seg->address + seg->offset,
+			       "name=%s state=%s units=%s/%s",
+			       info->name, statistic_state_strings[stat->state],
+			       info->x_unit, info->y_unit);
+	if (stat->state == STATISTIC_STATE_UNCONFIGURED) {
+		seg->offset += sprintf(seg->address + seg->offset, "\n");
+		return 0;
+	}
+
+	seg->offset += sprintf(seg->address + seg->offset, " type=%s",
+			       disc->name);
+	if (disc->fdef)
+		seg->offset += disc->fdef(stat, seg->address + seg->offset);
+	if (stat->state == STATISTIC_STATE_RELEASED) {
+		seg->offset += sprintf(seg->address + seg->offset, "\n");
+		return 0;
+	}
+
+	nsec_to_timestamp(t0, stat->age);
+	nsec_to_timestamp(t1, stat->started);
+	nsec_to_timestamp(t2, stat->stopped);
+	seg->offset += sprintf(seg->address + seg->offset,
+			       " data=%s started=%s stopped=%s\n", t0, t1, t2);
+	return 0;
+}
+
+static inline int statistic_fdata(struct statistic_interface *interface, int i,
+				  struct statistic_file_private *fpriv)
+{
+	struct statistic *stat = &interface->stat[i];
+	struct statistic_info *info = &interface->info[i];
+	struct statistic_discipline *disc = &statistic_discs[stat->type];
+	struct statistic_merge_private mpriv;
+	int retval;
+
+	if (unlikely(stat->state < STATISTIC_STATE_OFF))
+		return 0;
+	if (unlikely(info->flags & STATISTIC_FLAGS_NOINCR))
+		return disc->fdata(stat, info->name, fpriv, stat->pdata);
+	mpriv.dst = statistic_alloc_ptr(stat, GFP_KERNEL, -1);
+	if (unlikely(!mpriv.dst))
+		return -ENOMEM;
+	spin_lock_init(&mpriv.lock);
+	mpriv.stat = stat;
+	on_each_cpu(statistic_merge, &mpriv, 0, 1);
+	retval = disc->fdata(stat, info->name, fpriv, mpriv.dst);
+	statistic_free_ptr(stat, mpriv.dst);
+	return retval;
+}
+
+/* cpu hotplug handling for per-cpu data */
+
+static inline int _statistic_hotcpu(struct statistic_interface *interface,
+				    int i, unsigned long action, int cpu)
+{
+	struct statistic *stat = &interface->stat[i];
+	struct statistic_info *info = &interface->info[i];
+
+	if (unlikely(info->flags & STATISTIC_FLAGS_NOINCR))
+		return 0;
+	if (stat->state < STATISTIC_STATE_OFF)
+		return 0;
+	switch (action) {
+	case CPU_UP_PREPARE:
+		stat->pdata->ptrs[cpu] = statistic_alloc_ptr(stat, GFP_ATOMIC,
+							     cpu_to_node(cpu));
+		break;
+	case CPU_UP_CANCELED:
+	case CPU_DEAD:
+		statistic_move_ptr(stat, stat->pdata->ptrs[cpu]);
+		statistic_free_ptr(stat, stat->pdata->ptrs[cpu]);
+		stat->pdata->ptrs[cpu] = NULL;
+		break;
+	}
+	return 0;
+}
+
+static struct list_head statistic_list;
+static struct mutex statistic_list_mutex;
+
+static int __cpuinit statistic_hotcpu(struct notifier_block *notifier,
+				      unsigned long action, void *__cpu)
+{
+	int cpu = (unsigned long)__cpu, i;
+	struct statistic_interface *interface;
+
+	mutex_lock(&statistic_list_mutex);
+	list_for_each_entry(interface, &statistic_list, list)
+		for (i = 0; i < interface->number; i++)
+			_statistic_hotcpu(interface, i, action, cpu);
+	mutex_unlock(&statistic_list_mutex);
+	return NOTIFY_OK;
+}
+
+static struct notifier_block statistic_hotcpu_notifier =
+{
+	.notifier_call = statistic_hotcpu,
+};
+
+/* module startup / removal */
+
+static struct dentry *statistic_root_dir;
+
+int __init statistic_init(void)
+{
+	statistic_root_dir = debugfs_create_dir(STATISTIC_ROOT_DIR, NULL);
+	if (unlikely(!statistic_root_dir))
+		return -ENOMEM;
+	INIT_LIST_HEAD(&statistic_list);
+	mutex_init(&statistic_list_mutex);
+	register_cpu_notifier(&statistic_hotcpu_notifier);
+	return 0;
+}
+
+void __exit statistic_exit(void)
+{
+	unregister_cpu_notifier(&statistic_hotcpu_notifier);
+	debugfs_remove(statistic_root_dir);
+}
+
+/* parser used for configuring statistics */
+
+static int statistic_parse_single(struct statistic *stat,
+				  struct statistic_info *info,
+				  char *def, int type)
+{
+	struct statistic_discipline *disc = &statistic_discs[type];
+	int prev_state = stat->state, retval = 0;
+	char *copy;
+
+	if (disc->parse) {
+		copy = kstrdup(def, GFP_KERNEL);
+		if (unlikely(!copy))
+			return -ENOMEM;
+		retval = disc->parse(stat, info, type, copy);
+		kfree(copy);
+	} else if (type != stat->type)
+		statistic_transition(stat, info, STATISTIC_STATE_UNCONFIGURED);
+	if (!retval) {
+		stat->type = type;
+		stat->add = disc->add;
+	}
+	statistic_transition(stat, info,
+			     max(prev_state, STATISTIC_STATE_RELEASED));
+	return retval;
+}
+
+static match_table_t statistic_match_type = {
+	{1, "type=%s"},
+	{9, NULL}
+};
+
+static int statistic_parse_match(struct statistic *stat,
+				 struct statistic_info *info, char *def)
+{
+	int type, len;
+	char *p, *copy, *twisted;
+	substring_t args[MAX_OPT_ARGS];
+	struct statistic_discipline *disc;
+
+	if (!def)
+		def = info->defaults;
+	twisted = copy = kstrdup(def, GFP_KERNEL);
+	if (unlikely(!copy))
+		return -ENOMEM;
+	while ((p = strsep(&twisted, " ")) != NULL) {
+		if (!*p)
+			continue;
+		if (match_token(p, statistic_match_type, args) != 1)
+			continue;
+		len = (args[0].to - args[0].from) + 1;
+		for (type = 0; type < STATISTIC_TYPE_NONE; type++) {
+			disc = &statistic_discs[type];
+			if (unlikely(strncmp(disc->name, args[0].from, len)))
+				continue;
+			kfree(copy);
+			return statistic_parse_single(stat, info, def, type);
+		}
+	}
+	kfree(copy);
+	if (unlikely(stat->type == STATISTIC_TYPE_NONE))
+		return -EINVAL;
+	return statistic_parse_single(stat, info, def, stat->type);
+}
+
+static match_table_t statistic_match_common = {
+	{STATISTIC_STATE_UNCONFIGURED, "state=unconfigured"},
+	{STATISTIC_STATE_RELEASED, "state=released"},
+	{STATISTIC_STATE_OFF, "state=off"},
+	{STATISTIC_STATE_ON, "state=on"},
+	{1001, "name=%s"},
+	{1002, "data=reset"},
+	{1003, "defaults"},
+	{9999, NULL}
+};
+
+static void statistic_parse_line(struct statistic_interface *interface,
+				 char *def)
+{
+	char *p, *copy, *twisted, *name = NULL;
+	substring_t args[MAX_OPT_ARGS];
+	int token, reset = 0, defaults = 0, i;
+	int state = STATISTIC_STATE_INVALID;
+	struct statistic *stat = interface->stat;
+	struct statistic_info *info = interface->info;
+
+	if (unlikely(!def))
+		return;
+	twisted = copy = kstrdup(def, GFP_KERNEL);
+	if (unlikely(!copy))
+		return;
+
+	while ((p = strsep(&twisted, " ")) != NULL) {
+		if (!*p)
+			continue;
+		token = match_token(p, statistic_match_common, args);
+		switch (token) {
+		case STATISTIC_STATE_UNCONFIGURED:
+		case STATISTIC_STATE_RELEASED:
+		case STATISTIC_STATE_OFF:
+		case STATISTIC_STATE_ON:
+			state = token;
+			break;
+		case 1001:
+			if (likely(!name))
+				name = match_strdup(&args[0]);
+			break;
+		case 1002:
+			reset = 1;
+			break;
+		case 1003:
+			defaults = 1;
+			break;
+		}
+	}
+	for (i = 0; i < interface->number; i++, stat++, info++) {
+		if (!name || (name && !strcmp(name, info->name))) {
+			if (defaults)
+				statistic_parse_match(stat, info, NULL);
+			if (name)
+				statistic_parse_match(stat, info, def);
+			if (state != STATISTIC_STATE_INVALID)
+				statistic_transition(stat, info, state);
+			if (reset)
+				statistic_reset(stat, info);
+		}
+	}
+	kfree(copy);
+	kfree(name);
+}
+
+static void statistic_parse(struct statistic_interface *interface,
+			    struct list_head *line_lh, size_t line_size)
+{
+	struct sgrb_seg *seg, *tmp;
+	char *buf;
+	int offset = 0;
+
+	if (unlikely(!line_size))
+		return;
+	buf = kmalloc(line_size + 2, GFP_KERNEL);
+	if (unlikely(!buf))
+		return;
+	buf[line_size] = ' ';
+	buf[line_size + 1] = '\0';
+	list_for_each_entry_safe(seg, tmp, line_lh, list) {
+		memcpy(buf + offset, seg->address, seg->size);
+		offset += seg->size;
+		list_del(&seg->list);
+		kfree(seg);
+	}
+	statistic_parse_line(interface, buf);
+	kfree(buf);
+}
+
+/* sequential files comprising user interface */
+
+static int statistic_generic_open(struct inode *inode,
+		struct file *file, struct statistic_interface **interface,
+		struct statistic_file_private **private)
+{
+	*interface = inode->u.generic_ip;
+	BUG_ON(!interface);
+	*private = kzalloc(sizeof(struct statistic_file_private), GFP_KERNEL);
+	if (unlikely(!*private))
+		return -ENOMEM;
+	INIT_LIST_HEAD(&(*private)->read_seg_lh);
+	INIT_LIST_HEAD(&(*private)->write_seg_lh);
+	file->private_data = *private;
+	return 0;
+}
+
+static int statistic_generic_close(struct inode *inode, struct file *file)
+{
+	struct statistic_file_private *private = file->private_data;
+	BUG_ON(!private);
+	sgrb_seg_release_all(&private->read_seg_lh);
+	sgrb_seg_release_all(&private->write_seg_lh);
+	kfree(private);
+	return 0;
+}
+
+static ssize_t statistic_generic_read(struct file *file,
+				char __user *buf, size_t len, loff_t *offset)
+{
+	struct statistic_file_private *private = file->private_data;
+	struct sgrb_seg *seg;
+	size_t seg_offset, seg_residual, seg_transfer;
+	size_t transfered = 0;
+	loff_t pos = 0;
+
+	BUG_ON(!private);
+	list_for_each_entry(seg, &private->read_seg_lh, list) {
+		if (unlikely(!len))
+			break;
+		if (*offset >= pos && *offset <= (pos + seg->offset)) {
+			seg_offset = *offset - pos;
+			seg_residual = seg->offset - seg_offset;
+			seg_transfer = min(len, seg_residual);
+			if (unlikely(copy_to_user(buf + transfered,
+						  seg->address + seg_offset,
+						  seg_transfer)))
+				return -EFAULT;
+			transfered += seg_transfer;
+			*offset += seg_transfer;
+			pos += seg_transfer + seg_offset;
+			len -= seg_transfer;
+		} else
+			pos += seg->offset;
+	}
+	return transfered;
+}
+
+static ssize_t statistic_generic_write(struct file *file,
+			const char __user *buf, size_t len, loff_t *offset)
+{
+	struct statistic_file_private *private = file->private_data;
+	struct sgrb_seg *seg;
+	size_t seg_residual, seg_transfer;
+	size_t transfered = 0;
+
+	BUG_ON(!private);
+	if (unlikely(*offset != private->write_seg_total_size))
+		return -EPIPE;
+	while (len) {
+		seg = sgrb_seg_find(&private->write_seg_lh, 1);
+		if (unlikely(!seg))
+			return -ENOMEM;
+		seg_residual = seg->size - seg->offset;
+		seg_transfer = min(len, seg_residual);
+		if (unlikely(copy_from_user(seg->address + seg->offset,
+					    buf + transfered, seg_transfer)))
+			return -EFAULT;
+		private->write_seg_total_size += seg_transfer;
+		seg->offset += seg_transfer;
+		transfered += seg_transfer;
+		*offset += seg_transfer;
+		len -= seg_transfer;
+	}
+	return transfered;
+}
+
+static int statistic_def_close(struct inode *inode, struct file *file)
+{
+	struct statistic_interface *interface = inode->u.generic_ip;
+	struct statistic_file_private *private = file->private_data;
+	struct sgrb_seg *seg, *seg_nl;
+	int offset;
+	struct list_head line_lh;
+	char *nl;
+	size_t line_size = 0;
+
+	INIT_LIST_HEAD(&line_lh);
+	list_for_each_entry(seg, &private->write_seg_lh, list) {
+		for (offset = 0; offset < seg->offset; offset += seg_nl->size) {
+			seg_nl = kmalloc(sizeof(struct sgrb_seg), GFP_KERNEL);
+			if (unlikely(!seg_nl))
+				/*
+				 * FIXME:
+				 * Should we omit other new settings because we
+				 * could not process this line of definitions?
+				 */
+				continue;
+			seg_nl->address = seg->address + offset;
+			nl = strnchr(seg_nl->address,
+				     seg->offset - offset, '\n');
+			if (nl) {
+				seg_nl->offset = nl - seg_nl->address;
+				if (seg_nl->offset)
+					seg_nl->offset--;
+			} else
+				seg_nl->offset = seg->offset - offset;
+			seg_nl->size = seg_nl->offset + 1;
+			line_size += seg_nl->size;
+			list_add_tail(&seg_nl->list, &line_lh);
+			if (nl) {
+				statistic_parse(interface, &line_lh, line_size);
+				line_size = 0;
+			}
+		}
+	}
+	if (!list_empty(&line_lh))
+		statistic_parse(interface, &line_lh, line_size);
+	return statistic_generic_close(inode, file);
+}
+
+static int statistic_def_open(struct inode *inode, struct file *file)
+{
+	struct statistic_interface *interface;
+	struct statistic_file_private *private;
+	int retval = 0;
+	int i;
+
+	retval = statistic_generic_open(inode, file, &interface, &private);
+	if (unlikely(retval))
+		return retval;
+	for (i = 0; i < interface->number; i++) {
+		retval = statistic_fdef(interface, i, private);
+		if (unlikely(retval)) {
+			statistic_def_close(inode, file);
+			break;
+		}
+	}
+	return retval;
+}
+
+static int statistic_data_open(struct inode *inode, struct file *file)
+{
+	struct statistic_interface *interface;
+	struct statistic_file_private *private;
+	int retval = 0;
+	int i;
+
+	retval = statistic_generic_open(inode, file, &interface, &private);
+	if (unlikely(retval))
+		return retval;
+	if (interface->pull)
+		interface->pull(interface->pull_private);
+	for (i = 0; i < interface->number; i++) {
+		retval = statistic_fdata(interface, i, private);
+		if (unlikely(retval)) {
+			statistic_generic_close(inode, file);
+			break;
+		}
+	}
+	return retval;
+}
+
+static struct file_operations statistic_def_fops = {
+	.owner		= THIS_MODULE,
+	.read		= statistic_generic_read,
+	.write		= statistic_generic_write,
+	.open		= statistic_def_open,
+	.release	= statistic_def_close,
+};
+
+static struct file_operations statistic_data_fops = {
+	.owner		= THIS_MODULE,
+	.read		= statistic_generic_read,
+	.open		= statistic_data_open,
+	.release	= statistic_generic_close,
+};
+
+/**
+ * statistic_create - setup statistics and create debugfs files
+ * @interface: struct statistic_interface provided by exploiter
+ * @name: name of debugfs directory to be created
+ *
+ * Creates a debugfs directory in "statistics" as well as the "data" and
+ * "definition" files. Then we attach setup statistics according to the
+ * definition provided by exploiter through struct statistic_interface.
+ *
+ * struct statistic_interface must have been set up prior to calling this.
+ *
+ * On success, 0 is returned.
+ *
+ * If some required memory could not be allocated, or the creation
+ * of debugfs entries failed, this routine fails, and -ENOMEM is returned.
+ */
+int statistic_create(struct statistic_interface *interface, const char *name)
+{
+	struct statistic *stat = interface->stat;
+	struct statistic_info *info = interface->info;
+	int i;
+
+	BUG_ON(!stat || !info || !interface->number);
+
+	interface->debugfs_dir =
+		debugfs_create_dir(name, statistic_root_dir);
+	if (unlikely(!interface->debugfs_dir))
+		return -ENOMEM;
+
+	interface->data_file = debugfs_create_file(
+		STATISTIC_FILENAME_DATA, S_IFREG | S_IRUSR,
+		interface->debugfs_dir, (void*)interface, &statistic_data_fops);
+	if (unlikely(!interface->data_file)) {
+		debugfs_remove(interface->debugfs_dir);
+		return -ENOMEM;
+	}
+
+	interface->def_file = debugfs_create_file(
+		STATISTIC_FILENAME_DEF, S_IFREG | S_IRUSR | S_IWUSR,
+		interface->debugfs_dir, (void*)interface, &statistic_def_fops);
+	if (unlikely(!interface->def_file)) {
+		debugfs_remove(interface->data_file);
+		debugfs_remove(interface->debugfs_dir);
+		return -ENOMEM;
+	}
+
+	for (i = 0; i < interface->number; i++, stat++, info++) {
+		statistic_transition(stat, info, STATISTIC_STATE_UNCONFIGURED);
+		statistic_parse_match(stat, info, NULL);
+	}
+
+	mutex_lock(&statistic_list_mutex);
+	list_add(&interface->list, &statistic_list);
+	mutex_unlock(&statistic_list_mutex);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(statistic_create);
+
+/**
+ * statistic_remove - remove unused statistics
+ * @interface: struct statistic_interface to clean up
+ *
+ * Remove a debugfs directory in "statistics" along with its "data" and
+ * "definition" files. Removing this user interface also causes the removal
+ * of all statistics attached to the interface.
+ *
+ * The exploiter must have ceased reporting statistic data.
+ *
+ * Returns -EINVAL for attempted double removal, 0 otherwise.
+ */
+int statistic_remove(struct statistic_interface *interface)
+{
+	struct statistic *stat = interface->stat;
+	struct statistic_info *info = interface->info;
+	int i;
+
+	if (unlikely(!interface->debugfs_dir))
+		return -EINVAL;
+	mutex_lock(&statistic_list_mutex);
+	list_del(&interface->list);
+	mutex_unlock(&statistic_list_mutex);
+	for (i = 0; i < interface->number; i++, stat++, info++)
+		statistic_transition(stat, info, STATISTIC_STATE_INVALID);
+	debugfs_remove(interface->data_file);
+	debugfs_remove(interface->def_file);
+	debugfs_remove(interface->debugfs_dir);
+	interface->debugfs_dir = NULL;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(statistic_remove);
+
+/* code concerned with single value statistics */
+
+static void statistic_reset_counter(struct statistic *stat, void *ptr)
+{
+	*(u64*)ptr = 0;
+}
+
+static void statistic_add_counter_inc(struct statistic *stat, int cpu,
+				     s64 value, u64 incr)
+{
+	*(u64*)stat->pdata->ptrs[cpu] += incr;
+}
+
+static void statistic_add_counter_prod(struct statistic *stat, int cpu,
+				       s64 value, u64 incr)
+{
+	if (unlikely(value < 0))
+		value = -value;
+	*(u64*)stat->pdata->ptrs[cpu] += value * incr;
+}
+
+static void statistic_set_counter_inc(struct statistic *stat,
+				      s64 value, u64 total)
+{
+	*(u64*)stat->pdata = total;
+}
+
+static void statistic_set_counter_prod(struct statistic *stat,
+				       s64 value, u64 total)
+{
+	if (unlikely(value < 0))
+		value = -value;
+	*(u64*)stat->pdata = value * total;
+}
+
+static void statistic_merge_counter(struct statistic *stat,
+				    void *dst, void *src)
+{
+	*(u64*)dst += *(u64*)src;
+}
+
+static int statistic_fdata_counter(struct statistic *stat, const char *name,
+				   struct statistic_file_private *fpriv,
+				   void *data)
+{
+	struct sgrb_seg *seg;
+	seg = sgrb_seg_find(&fpriv->read_seg_lh, 128);
+	if (unlikely(!seg))
+		return -ENOMEM;
+	seg->offset += sprintf(seg->address + seg->offset, "%s %Lu\n",
+			       name, *(unsigned long long *)data);
+	return 0;
+}
+
+/* code concerned with utilisation indicator statistic */
+
+static void statistic_reset_util(struct statistic *stat, void *ptr)
+{
+	struct statistic_entry_util *util = ptr;
+	util->num = 0;
+	util->acc = 0;
+	util->min = (~0ULL >> 1) - 1;
+	util->max = -(~0ULL >> 1) + 1;
+}
+
+static void statistic_add_util(struct statistic *stat, int cpu,
+			       s64 value, u64 incr)
+{
+	struct statistic_entry_util *util = stat->pdata->ptrs[cpu];
+	util->num += incr;
+	util->acc += value * incr;
+	if (unlikely(value < util->min))
+		util->min = value;
+	if (unlikely(value > util->max))
+		util->max = value;
+}
+
+static void statistic_set_util(struct statistic *stat, s64 value, u64 total)
+{
+	struct statistic_entry_util *util;
+	util = (struct statistic_entry_util *) stat->pdata;
+	util->num = total;
+	util->acc = value * total;
+	if (unlikely(value < util->min))
+		util->min = value;
+	if (unlikely(value > util->max))
+		util->max = value;
+}
+
+static void statistic_merge_util(struct statistic *stat, void *_dst, void *_src)
+{
+	struct statistic_entry_util *dst = _dst, *src = _src;
+	dst->num += src->num;
+	dst->acc += src->acc;
+	if (unlikely(src->min < dst->min))
+		dst->min = src->min;
+	if (unlikely(src->max > dst->max))
+		dst->max = src->max;
+}
+
+static int statistic_fdata_util(struct statistic *stat, const char *name,
+				struct statistic_file_private *fpriv,
+				void *data)
+{
+	struct sgrb_seg *seg;
+	struct statistic_entry_util *util = data;
+	unsigned long long whole = 0;
+	signed long long min = 0, max = 0, decimal = 0, last_digit;
+
+	seg = sgrb_seg_find(&fpriv->read_seg_lh, 128);
+	if (unlikely(!seg))
+		return -ENOMEM;
+	if (likely(util->num)) {
+		whole = util->acc;
+		do_div(whole, util->num);
+		decimal = util->acc * 10000;
+		do_div(decimal, util->num);
+		decimal -= whole * 10000;
+		if (decimal < 0)
+			decimal = -decimal;
+		last_digit = decimal;
+		do_div(last_digit, 10);
+		last_digit = decimal - last_digit * 10;
+		if (last_digit >= 5)
+			decimal += 10;
+		do_div(decimal, 10);
+		min = util->min;
+		max = util->max;
+	}
+	seg->offset += sprintf(seg->address + seg->offset,
+			       "%s %Lu %Ld %Ld.%03lld %Ld\n", name,
+			       (unsigned long long)util->num,
+			       (signed long long)min, whole, decimal,
+			       (signed long long)max);
+	return 0;
+}
+
+/* code concerned with histogram statistics */
+
+static void * statistic_alloc_histogram(struct statistic *stat, size_t size,
+					gfp_t flags, int node)
+{
+	return kmalloc_node(size * (stat->u.histogram.last_index + 1),
+			    flags, node);
+}
+
+static inline s64 statistic_histogram_calc_value_lin(struct statistic *stat,
+						     int i)
+{
+	return stat->u.histogram.range_min +
+		stat->u.histogram.base_interval * i;
+}
+
+static inline s64 statistic_histogram_calc_value_log2(struct statistic *stat,
+						      int i)
+{
+	return stat->u.histogram.range_min +
+		(i ? (stat->u.histogram.base_interval << (i - 1)) : 0);
+}
+
+static inline s64 statistic_histogram_calc_value(struct statistic *stat, int i)
+{
+	if (stat->type == STATISTIC_TYPE_HISTOGRAM_LIN)
+		return statistic_histogram_calc_value_lin(stat, i);
+	else
+		return statistic_histogram_calc_value_log2(stat, i);
+}
+
+static inline int statistic_histogram_calc_index_lin(struct statistic *stat,
+						 s64 value)
+{
+	unsigned long long i = value - stat->u.histogram.range_min;
+	do_div(i, stat->u.histogram.base_interval);
+	return i;
+}
+
+static inline int statistic_histogram_calc_index_log2(struct statistic *stat,
+						      s64 value)
+{
+	unsigned long long i;
+	for (i = 0;
+	     i < stat->u.histogram.last_index &&
+	     value > statistic_histogram_calc_value_log2(stat, i);
+	     i++);
+	return i;
+}
+
+static inline int statistic_histogram_calc_index(struct statistic *stat,
+						 s64 value)
+{
+	if (stat->type == STATISTIC_TYPE_HISTOGRAM_LIN)
+		return statistic_histogram_calc_index_lin(stat, value);
+	else
+		return statistic_histogram_calc_index_log2(stat, value);
+}
+
+static void statistic_reset_histogram(struct statistic *stat, void *ptr)
+{
+	memset(ptr, 0, (stat->u.histogram.last_index + 1) * sizeof(u64));
+}
+
+static void statistic_add_histogram_lin(struct statistic *stat, int cpu,
+					s64 value, u64 incr)
+{
+	int i = statistic_histogram_calc_index_lin(stat, value);
+	((u64*)stat->pdata->ptrs[cpu])[i] += incr;
+}
+
+static void statistic_add_histogram_log2(struct statistic *stat, int cpu,
+					 s64 value, u64 incr)
+{
+	int i = statistic_histogram_calc_index_log2(stat, value);
+	((u64*)stat->pdata->ptrs[cpu])[i] += incr;
+}
+
+static void statistic_set_histogram_lin(struct statistic *stat,
+					s64 value, u64 total)
+{
+	int i = statistic_histogram_calc_index_lin(stat, value);
+	((u64*)stat->pdata)[i] = total;
+}
+
+static void statistic_set_histogram_log2(struct statistic *stat,
+					 s64 value, u64 total)
+{
+	int i = statistic_histogram_calc_index_log2(stat, value);
+	((u64*)stat->pdata)[i] = total;
+}
+
+static void statistic_merge_histogram(struct statistic *stat,
+				      void *_dst, void *_src)
+{
+	u64 *dst = _dst, *src = _src;
+	int i;
+	for (i = 0; i <= stat->u.histogram.last_index; i++)
+		dst[i] += src[i];
+}
+
+static inline int statistic_fdata_histogram_line(const char *name,
+					struct statistic_file_private *private,
+					const char *prefix, s64 bound, u64 hits)
+{
+	struct sgrb_seg *seg;
+	seg = sgrb_seg_find(&private->read_seg_lh, 256);
+	if (unlikely(!seg))
+		return -ENOMEM;
+	seg->offset += sprintf(seg->address + seg->offset, "%s %s%Ld %Lu\n",
+			       name, prefix, (signed long long)bound,
+			       (unsigned long long)hits);
+	return 0;
+}
+
+static int statistic_fdata_histogram(struct statistic *stat, const char *name,
+				     struct statistic_file_private *fpriv,
+				     void *data)
+{
+	int i, retval;
+	s64 bound = 0;
+	for (i = 0; i < (stat->u.histogram.last_index); i++) {
+		bound = statistic_histogram_calc_value(stat, i);
+		retval = statistic_fdata_histogram_line(name, fpriv, "<=",
+							bound, ((u64*)data)[i]);
+		if (unlikely(retval))
+			return retval;
+	}
+	return statistic_fdata_histogram_line(name, fpriv, ">",
+					      bound, ((u64*)data)[i]);
+}
+
+static int statistic_fdef_histogram(struct statistic *stat, char *line)
+{
+	return sprintf(line, " range_min=%Li entries=%Li base_interval=%Lu",
+		       (signed long long)stat->u.histogram.range_min,
+		       (unsigned long long)(stat->u.histogram.last_index + 1),
+		       (unsigned long long)stat->u.histogram.base_interval);
+}
+
+static match_table_t statistic_match_histogram = {
+	{1, "entries=%u"},
+	{2, "base_interval=%s"},
+	{3, "range_min=%s"},
+	{9, NULL}
+};
+
+static int statistic_parse_histogram(struct statistic *stat,
+				     struct statistic_info *info,
+				     int type, char *def)
+{
+	char *p;
+	substring_t args[MAX_OPT_ARGS];
+	int token, got_entries = 0, got_interval = 0, got_range = 0;
+	u32 entries, base_interval;
+	s64 range_min;
+
+	while ((p = strsep(&def, " ")) != NULL) {
+		if (!*p)
+			continue;
+		token = match_token(p, statistic_match_histogram, args);
+		switch (token) {
+		case 1:
+			match_int(&args[0], &entries);
+			got_entries = 1;
+			break;
+		case 2:
+			match_int(&args[0], &base_interval);
+			got_interval = 1;
+			break;
+		case 3:
+			match_s64(&args[0], &range_min, 0);
+			got_range = 1;
+			break;
+		}
+	}
+	if (unlikely(type != stat->type &&
+		     !(got_entries && got_interval && got_range)))
+		return -EINVAL;
+	statistic_transition(stat, info, STATISTIC_STATE_UNCONFIGURED);
+	if (got_entries)
+		stat->u.histogram.last_index = entries - 1;
+	if (got_interval)
+		stat->u.histogram.base_interval = base_interval;
+	if (got_range)
+		stat->u.histogram.range_min = range_min;
+	return 0;
+}
+
+/* code concerned with histograms (discrete value) statistics */
+
+static void * statistic_alloc_sparse(struct statistic *stat, size_t size,
+				     gfp_t flags, int node)
+{
+	struct statistic_sparse_list *slist = kmalloc_node(size, flags, node);
+	INIT_LIST_HEAD(&slist->entry_lh);
+	slist->entries_max = stat->u.sparse.entries_max;
+	return slist;
+}
+
+static void statistic_free_sparse(struct statistic *stat, void *ptr)
+{
+	struct statistic_entry_sparse *entry, *tmp;
+	struct statistic_sparse_list *slist = ptr;
+	list_for_each_entry_safe(entry, tmp, &slist->entry_lh, list) {
+		list_del(&entry->list);
+		kfree(entry);
+	}
+	slist->hits_missed = 0;
+	slist->entries = 0;
+}
+
+static inline void statistic_add_sparse_sort(struct list_head *head,
+					struct statistic_entry_sparse *entry)
+{
+	struct statistic_entry_sparse *sort =
+		list_prepare_entry(entry, head, list);
+
+	list_for_each_entry_continue_reverse(sort, head, list)
+		if (likely(sort->hits >= entry->hits))
+			break;
+	if (unlikely(sort->list.next != &entry->list &&
+		     (&sort->list == head || sort->hits >= entry->hits)))
+		list_move(&entry->list, &sort->list);
+}
+
+static inline int statistic_add_sparse_new(struct statistic_sparse_list *slist,
+					   s64 value, u64 incr)
+{
+	struct statistic_entry_sparse *entry;
+
+	if (unlikely(slist->entries == slist->entries_max))
+		return -ENOMEM;
+	entry = kmalloc(sizeof(struct statistic_entry_sparse), GFP_ATOMIC);
+	if (unlikely(!entry))
+		return -ENOMEM;
+	entry->value = value;
+	entry->hits = incr;
+	slist->entries++;
+	list_add_tail(&entry->list, &slist->entry_lh);
+	return 0;
+}
+
+static inline void _statistic_add_sparse(struct statistic_sparse_list *slist,
+					 s64 value, u64 incr)
+{
+	struct list_head *head = &slist->entry_lh;
+	struct statistic_entry_sparse *entry;
+
+	list_for_each_entry(entry, head, list) {
+		if (likely(entry->value == value)) {
+			entry->hits += incr;
+			statistic_add_sparse_sort(head, entry);
+			return;
+		}
+	}
+	if (unlikely(statistic_add_sparse_new(slist, value, incr)))
+		slist->hits_missed += incr;
+}
+
+static void statistic_add_sparse(struct statistic *stat, int cpu,
+				 s64 value, u64 incr)
+{
+	struct statistic_sparse_list *slist = stat->pdata->ptrs[cpu];
+	_statistic_add_sparse(slist, value, incr);
+}
+
+static void statistic_set_sparse(struct statistic *stat, s64 value, u64 total)
+{
+	struct statistic_sparse_list *slist = (struct statistic_sparse_list *)
+						stat->pdata;
+	struct list_head *head = &slist->entry_lh;
+	struct statistic_entry_sparse *entry;
+
+	list_for_each_entry(entry, head, list) {
+		if (likely(entry->value == value)) {
+			entry->hits = total;
+			statistic_add_sparse_sort(head, entry);
+			return;
+		}
+	}
+	if (unlikely(statistic_add_sparse_new(slist, value, total)))
+		slist->hits_missed += total;
+}
+
+static void statistic_merge_sparse(struct statistic *stat,
+				   void *_dst, void *_src)
+{
+	struct statistic_sparse_list *dst = _dst, *src = _src;
+	struct statistic_entry_sparse *entry;
+	dst->hits_missed += src->hits_missed;
+	list_for_each_entry(entry, &src->entry_lh, list)
+		_statistic_add_sparse(dst, entry->value, entry->hits);
+}
+
+static int statistic_fdata_sparse(struct statistic *stat, const char *name,
+				  struct statistic_file_private *fpriv,
+				  void *data)
+{
+	struct sgrb_seg *seg;
+	struct statistic_sparse_list *slist = data;
+	struct statistic_entry_sparse *entry;
+
+	seg = sgrb_seg_find(&fpriv->read_seg_lh, 256);
+	if (unlikely(!seg))
+		return -ENOMEM;
+	seg->offset += sprintf(seg->address + seg->offset, "%s missed 0x%Lu\n",
+			       name, (unsigned long long)slist->hits_missed);
+	list_for_each_entry(entry, &slist->entry_lh, list) {
+		seg = sgrb_seg_find(&fpriv->read_seg_lh, 256);
+		if (unlikely(!seg))
+			return -ENOMEM;
+		seg->offset += sprintf(seg->address + seg->offset,
+				       "%s 0x%Lx %Lu\n", name,
+				       (signed long long)entry->value,
+				       (unsigned long long)entry->hits);
+	}
+	return 0;
+}
+
+static int statistic_fdef_sparse(struct statistic *stat, char *line)
+{
+	return sprintf(line, " entries=%u", stat->u.sparse.entries_max);
+}
+
+static match_table_t statistic_match_sparse = {
+	{1, "entries=%u"},
+	{9, NULL}
+};
+
+static int statistic_parse_sparse(struct statistic *stat,
+				  struct statistic_info *info,
+				  int type, char *def)
+{
+	char *p;
+	substring_t args[MAX_OPT_ARGS];
+
+	while ((p = strsep(&def, " ")) != NULL) {
+		if (!*p)
+			continue;
+		if (match_token(p, statistic_match_sparse, args) == 1) {
+			statistic_transition(stat, info,
+					     STATISTIC_STATE_UNCONFIGURED);
+			match_int(&args[0], &stat->u.sparse.entries_max);
+			return 0;
+		}
+	}
+	return -EINVAL;
+}
+
+/* code mostly concerned with managing statistics */
+
+static struct statistic_discipline statistic_discs[] = {
+	{ /* STATISTIC_TYPE_COUNTER_INC */
+	  NULL,
+	  statistic_alloc_generic,
+	  NULL,
+	  statistic_reset_counter,
+	  statistic_merge_counter,
+	  statistic_fdata_counter,
+	  NULL,
+	  statistic_add_counter_inc,
+	  statistic_set_counter_inc,
+	  "counter_inc", sizeof(u64)
+	},
+	{ /* STATISTIC_TYPE_COUNTER_PROD */
+	  NULL,
+	  statistic_alloc_generic,
+	  NULL,
+	  statistic_reset_counter,
+	  statistic_merge_counter,
+	  statistic_fdata_counter,
+	  NULL,
+	  statistic_add_counter_prod,
+	  statistic_set_counter_prod,
+	  "counter_prod", sizeof(u64)
+	},
+	{ /* STATISTIC_TYPE_UTIL */
+	  NULL,
+	  statistic_alloc_generic,
+	  NULL,
+	  statistic_reset_util,
+	  statistic_merge_util,
+	  statistic_fdata_util,
+	  NULL,
+	  statistic_add_util,
+	  statistic_set_util,
+	  "utilisation", sizeof(struct statistic_entry_util)
+	},
+	{ /* STATISTIC_TYPE_HISTOGRAM_LIN */
+	  statistic_parse_histogram,
+	  statistic_alloc_histogram,
+	  NULL,
+	  statistic_reset_histogram,
+	  statistic_merge_histogram,
+	  statistic_fdata_histogram,
+	  statistic_fdef_histogram,
+	  statistic_add_histogram_lin,
+	  statistic_set_histogram_lin,
+	  "histogram_lin", sizeof(u64)
+	},
+	{ /* STATISTIC_TYPE_HISTOGRAM_LOG2 */
+	  statistic_parse_histogram,
+	  statistic_alloc_histogram,
+	  NULL,
+	  statistic_reset_histogram,
+	  statistic_merge_histogram,
+	  statistic_fdata_histogram,
+	  statistic_fdef_histogram,
+	  statistic_add_histogram_log2,
+	  statistic_set_histogram_log2,
+	  "histogram_log2", sizeof(u64)
+	},
+	{ /* STATISTIC_TYPE_SPARSE */
+	  statistic_parse_sparse,
+	  statistic_alloc_sparse,
+	  statistic_free_sparse,
+	  statistic_free_sparse,	/* reset equals free */
+	  statistic_merge_sparse,
+	  statistic_fdata_sparse,
+	  statistic_fdef_sparse,
+	  statistic_add_sparse,
+	  statistic_set_sparse,
+	  "sparse", sizeof(struct statistic_sparse_list)
+	},
+	{ /* STATISTIC_TYPE_NONE */ }
+};
+
+postcore_initcall(statistic_init);
+module_exit(statistic_exit);
+
+MODULE_LICENSE("GPL");
diff -Nurp a/lib/Makefile b/lib/Makefile
--- a/lib/Makefile	2006-05-19 15:44:27.000000000 +0200
+++ b/lib/Makefile	2006-05-19 16:02:23.000000000 +0200
@@ -47,6 +47,8 @@ obj-$(CONFIG_TEXTSEARCH_KMP) += ts_kmp.o
 obj-$(CONFIG_TEXTSEARCH_BM) += ts_bm.o
 obj-$(CONFIG_TEXTSEARCH_FSM) += ts_fsm.o
 
+obj-$(CONFIG_STATISTICS) += statistic.o
+
 obj-$(CONFIG_SWIOTLB) += swiotlb.o
 
 hostprogs-y	:= gen_crc32table
diff -Nurp a/arch/s390/Kconfig b/arch/s390/Kconfig
--- a/arch/s390/Kconfig	2006-05-19 15:44:22.000000000 +0200
+++ b/arch/s390/Kconfig	2006-05-19 16:02:23.000000000 +0200
@@ -474,8 +474,14 @@ source "drivers/net/Kconfig"
 
 source "fs/Kconfig"
 
+menu "Instrumentation Support"
+
 source "arch/s390/oprofile/Kconfig"
 
+source "lib/Kconfig.statistic"
+
+endmenu
+
 source "arch/s390/Kconfig.debug"
 
 source "security/Kconfig"
diff -Nurp a/lib/Kconfig.statistic b/lib/Kconfig.statistic
--- a/lib/Kconfig.statistic	1970-01-01 01:00:00.000000000 +0100
+++ b/lib/Kconfig.statistic	2006-05-19 16:02:23.000000000 +0200
@@ -0,0 +1,11 @@
+config STATISTICS
+	bool "Statistics infrastructure"
+	depends on DEBUG_FS
+	help
+	  The statistics infrastructure provides a debug-fs based user interface
+	  for statistics of kernel components, that is, usually device drivers.
+	  Statistics are available for components that have been instrumented to
+	  feed data into the statistics infrastructure.
+	  This feature is useful for performance measurements or performance
+	  debugging.
+	  If in doubt, say "N".
diff -Nurp a/arch/s390/oprofile/Kconfig b/arch/s390/oprofile/Kconfig
--- a/arch/s390/oprofile/Kconfig	2006-03-20 06:53:29.000000000 +0100
+++ b/arch/s390/oprofile/Kconfig	2006-05-19 16:02:23.000000000 +0200
@@ -1,6 +1,3 @@
-
-menu "Profiling support"
-
 config PROFILING
 	bool "Profiling support"
 	help
@@ -18,5 +15,3 @@ config OPROFILE
 
 	  If unsure, say N.
 
-endmenu
-
diff -Nurp a/MAINTAINERS b/MAINTAINERS
--- a/MAINTAINERS	2006-05-19 15:44:32.000000000 +0200
+++ b/MAINTAINERS	2006-05-19 16:02:23.000000000 +0200
@@ -2633,6 +2633,13 @@ STARMODE RADIO IP (STRIP) PROTOCOL DRIVE
 W:	http://mosquitonet.Stanford.EDU/strip.html
 S:	Unsupported ?
 
+STATISTICS INFRASTRUCTURE
+P:	Martin Peschke
+M:	[email protected]
+M:	[email protected]
+W:	http://www.ibm.com/developerworks/linux/linux390/
+S:	Supported
+
 STRADIS MPEG-2 DECODER DRIVER
 P:	Nathan Laredo
 M:	[email protected]


-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

[Index of Archives]     [Kernel Newbies]     [Netfilter]     [Bugtraq]     [Photo]     [Stuff]     [Gimp]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Video 4 Linux]     [Linux for the blind]     [Linux Resources]
  Powered by Linux