stats: Limit reporting by class id

This commit adds class_id fields to the rate_ctr and stat_item group
descriptions. The stats reporter code is extended to only process
groups whose class_id does not exceed a per reporter max_class level.

If the class_id is not set, the code assumes 'global' for groups with
idx == 0 and 'subscriber' otherwise.

The following vty command is added to config-stats:

  level (global|peer|subscriber)  Set the maximum group level

Sponsored-by: On-Waves ehf
diff --git a/include/osmocom/core/rate_ctr.h b/include/osmocom/core/rate_ctr.h
index f3c03de..03b1bfb 100644
--- a/include/osmocom/core/rate_ctr.h
+++ b/include/osmocom/core/rate_ctr.h
@@ -47,6 +47,8 @@
 	const char *group_name_prefix;
 	/*! \brief The human-readable description of the group */
 	const char *group_description;
+	/*! \brief The class to which this group belongs */
+	int class_id;
 	/*! \brief The number of counters in this group */
 	const unsigned int num_ctr;
 	/*! \brief Pointer to array of counter names */
diff --git a/include/osmocom/core/stat_item.h b/include/osmocom/core/stat_item.h
index 4047495..c2ad8cf 100644
--- a/include/osmocom/core/stat_item.h
+++ b/include/osmocom/core/stat_item.h
@@ -45,6 +45,8 @@
 	const char *group_name_prefix;
 	/*! \brief The human-readable description of the group */
 	const char *group_description;
+	/*! \brief The class to which this group belongs */
+	int class_id;
 	/*! \brief The number of values in this group */
 	const unsigned int num_items;
 	/*! \brief Pointer to array of value names */
diff --git a/include/osmocom/core/stats.h b/include/osmocom/core/stats.h
index 7b3d021..731fdb9 100644
--- a/include/osmocom/core/stats.h
+++ b/include/osmocom/core/stats.h
@@ -28,6 +28,13 @@
 struct rate_ctr_group;
 struct rate_ctr_desc;
 
+enum osmo_stats_class {
+	OSMO_STATS_CLASS_UNKNOWN,
+	OSMO_STATS_CLASS_GLOBAL,
+	OSMO_STATS_CLASS_PEER,
+	OSMO_STATS_CLASS_SUBSCRIBER,
+};
+
 enum osmo_stats_reporter_type {
 	OSMO_STATS_REPORTER_STATSD,
 	OSMO_STATS_REPORTER_LOG,
@@ -46,6 +53,7 @@
 	char *bind_addr_str;
 	int dest_port;
 	int mtu;
+	enum osmo_stats_class max_class;
 
 	/* state */
 	int running;
@@ -95,6 +103,8 @@
 int osmo_stats_reporter_set_remote_port(struct osmo_stats_reporter *srep, int port);
 int osmo_stats_reporter_set_local_addr(struct osmo_stats_reporter *srep, const char *addr);
 int osmo_stats_reporter_set_mtu(struct osmo_stats_reporter *srep, int mtu);
+int osmo_stats_reporter_set_max_class(struct osmo_stats_reporter *srep,
+	enum osmo_stats_class class_id);
 int osmo_stats_reporter_set_name_prefix(struct osmo_stats_reporter *srep, const char *prefix);
 int osmo_stats_reporter_enable(struct osmo_stats_reporter *srep);
 int osmo_stats_reporter_disable(struct osmo_stats_reporter *srep);
diff --git a/src/gb/gprs_bssgp.c b/src/gb/gprs_bssgp.c
index fe4fcca..e3e69c9 100644
--- a/src/gb/gprs_bssgp.c
+++ b/src/gb/gprs_bssgp.c
@@ -31,6 +31,7 @@
 #include <osmocom/gsm/tlv.h>
 #include <osmocom/core/talloc.h>
 #include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/stats.h>
 
 #include <osmocom/gprs/gprs_bssgp.h>
 #include <osmocom/gprs/gprs_ns.h>
@@ -54,6 +55,7 @@
 	.group_description = "BSSGP Peer Statistics",
 	.num_ctr = ARRAY_SIZE(bssgp_ctr_description),
 	.ctr_desc = bssgp_ctr_description,
+	.class_id = OSMO_STATS_CLASS_PEER,
 };
 
 LLIST_HEAD(bssgp_bvc_ctxts);
diff --git a/src/gb/gprs_ns.c b/src/gb/gprs_ns.c
index 5025d57..2b189cd 100644
--- a/src/gb/gprs_ns.c
+++ b/src/gb/gprs_ns.c
@@ -76,6 +76,7 @@
 #include <osmocom/core/select.h>
 #include <osmocom/core/rate_ctr.h>
 #include <osmocom/core/stat_item.h>
+#include <osmocom/core/stats.h>
 #include <osmocom/core/socket.h>
 #include <osmocom/core/signal.h>
 #include <osmocom/gprs/gprs_ns.h>
@@ -144,6 +145,7 @@
 	.group_description = "NSVC Peer Statistics",
 	.num_items = ARRAY_SIZE(nsvc_stat_description),
 	.item_desc = nsvc_stat_description,
+	.class_id = OSMO_STATS_CLASS_PEER,
 };
 
 #define CHECK_TX_RC(rc, nsvc) \
diff --git a/src/stats.c b/src/stats.c
index 4d5a1f5..bdb0fbe 100644
--- a/src/stats.c
+++ b/src/stats.c
@@ -254,6 +254,17 @@
 	return update_srep_config(srep);
 }
 
+int osmo_stats_reporter_set_max_class(struct osmo_stats_reporter *srep,
+	enum osmo_stats_class class_id)
+{
+	if (class_id == OSMO_STATS_CLASS_UNKNOWN)
+		return -EINVAL;
+
+	srep->max_class = class_id;
+
+	return 0;
+}
+
 int osmo_stats_set_interval(int interval)
 {
 	if (interval <= 0)
@@ -317,6 +328,16 @@
 	return rc;
 }
 
+static int osmo_stats_reporter_check_config(struct osmo_stats_reporter *srep,
+	unsigned int index, int class_id)
+{
+	if (class_id == OSMO_STATS_CLASS_UNKNOWN)
+		class_id = index != 0 ?
+			OSMO_STATS_CLASS_SUBSCRIBER : OSMO_STATS_CLASS_GLOBAL;
+
+	return class_id <= srep->max_class;
+}
+
 /*** log reporter ***/
 
 struct osmo_stats_reporter *osmo_stats_reporter_create_log(const char *name)
@@ -559,6 +580,10 @@
 		if (!srep->running)
 			continue;
 
+		if (!osmo_stats_reporter_check_config(srep,
+			       ctrg->idx, ctrg->desc->class_id))
+			return 0;
+
 		rc = osmo_stats_reporter_send_counter(srep, ctrg, desc,
 			ctr->current, delta);
 
@@ -601,6 +626,10 @@
 			if (!srep->running)
 				continue;
 
+			if (!osmo_stats_reporter_check_config(srep,
+					statg->idx, statg->desc->class_id))
+				return 0;
+
 			rc = osmo_stats_reporter_send_item(srep, statg,
 				item->desc, value);
 		}
diff --git a/src/vty/stats_vty.c b/src/vty/stats_vty.c
index f072b1b..0b7ac7a 100644
--- a/src/vty/stats_vty.c
+++ b/src/vty/stats_vty.c
@@ -45,6 +45,13 @@
 	1
 };
 
+static const struct value_string stats_class_strs[] = {
+	{ OSMO_STATS_CLASS_GLOBAL,     "global" },
+	{ OSMO_STATS_CLASS_PEER,       "peer" },
+	{ OSMO_STATS_CLASS_SUBSCRIBER, "subscriber" },
+	{ 0, NULL }
+};
+
 static struct osmo_stats_reporter *osmo_stats_vty2srep(struct vty *vty)
 {
 	if (vty->node == CFG_STATS_NODE)
@@ -163,6 +170,28 @@
 		"", "prefix string");
 }
 
+DEFUN(cfg_stats_reporter_level, cfg_stats_reporter_level_cmd,
+	"level (global|peer|subscriber)",
+	"Set the maximum group level\n"
+	"Report global groups only\n"
+	"Report global and network peer related groups\n"
+	"Report global, peer, and subscriber groups\n")
+{
+	int level = get_string_value(stats_class_strs, argv[0]);
+	int rc;
+	struct osmo_stats_reporter *srep = osmo_stats_vty2srep(vty);
+
+	OSMO_ASSERT(srep);
+	rc = osmo_stats_reporter_set_max_class(srep, level);
+	if (rc < 0) {
+		vty_out(vty, "%% Unable to set level: %s%s",
+			strerror(-rc), VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	return 0;
+}
+
 DEFUN(cfg_stats_reporter_enable, cfg_stats_reporter_enable_cmd,
 	"enable",
 	"Enable the reporter\n")
@@ -213,6 +242,7 @@
 				VTY_NEWLINE);
 			return CMD_WARNING;
 		}
+		srep->max_class = OSMO_STATS_CLASS_GLOBAL;
 		/* TODO: if needed, add osmo_stats_add_reporter(srep); */
 	}
 
@@ -272,6 +302,7 @@
 				VTY_NEWLINE);
 			return CMD_WARNING;
 		}
+		srep->max_class = OSMO_STATS_CLASS_GLOBAL;
 		/* TODO: if needed, add osmo_stats_add_reporter(srep); */
 	}
 
@@ -340,6 +371,11 @@
 				srep->mtu, VTY_NEWLINE);
 	}
 
+	if (srep->max_class)
+		vty_out(vty, "  level %s%s",
+			get_value_string(stats_class_strs, srep->max_class),
+			VTY_NEWLINE);
+
 	if (srep->name_prefix && *srep->name_prefix)
 		vty_out(vty, "  prefix %s%s",
 			srep->name_prefix, VTY_NEWLINE);
@@ -388,6 +424,7 @@
 	install_element(CFG_STATS_NODE, &cfg_no_stats_reporter_mtu_cmd);
 	install_element(CFG_STATS_NODE, &cfg_stats_reporter_prefix_cmd);
 	install_element(CFG_STATS_NODE, &cfg_no_stats_reporter_prefix_cmd);
+	install_element(CFG_STATS_NODE, &cfg_stats_reporter_level_cmd);
 	install_element(CFG_STATS_NODE, &cfg_stats_reporter_enable_cmd);
 	install_element(CFG_STATS_NODE, &cfg_stats_reporter_disable_cmd);
 }