extern int (*cf_read_hook)(byte *buf, uint max, int fd);
+struct stats_term_config;
struct symbol {
node n; /* In list of symbols in config */
struct symbol *next;
struct ea_class *attribute; /* For SYM_ATTRIBUTE */
struct f_val *val; /* For SYM_CONSTANT */
uint offset; /* For SYM_VARIABLE */
+ struct channel_config *ch_config; /* For SYM_COUNTER */
+ struct stats_term_config *term; /* For SYM_COUNTER_TERM */
};
char name[0];
#define SYM_FILTER 4
#define SYM_TABLE 5
#define SYM_ATTRIBUTE 6
+#define SYM_COUNTER 7
+#define SYM_COUNTER_TERM 8
#define SYM_VARIABLE 0x100 /* 0x100-0x1ff are variable types */
#define SYM_VARIABLE_RANGE SYM_VARIABLE ... (SYM_VARIABLE | 0xff)
fi
# temporarily removed "mrt" from all_protocols to speed up 3.0-alpha1 release
-all_protocols="bfd babel bgp ospf perf pipe radv rip rpki static"
+all_protocols="bfd babel bgp ospf perf pipe radv rip rpki static stats"
all_protocols=`echo $all_protocols | sed 's/ /,/g'`
if test "$with_protocols" = all ; then
}
</code>
+<sect>Stats
+<label id="stats">
+
+<sect1>
+<label id="stats-intro">
+
+<p>The Statistics protocol allows you to measure number of exported routes from
+a table. You can also narrow the view by applying export filter inside stats
+channel. One instance of stats protocol can have multiple channels attached.
+Stats protocol could be particullary useful when making conditional route advertising.
+
+<p>Statistics are accessed in filters by same name as channel connected to desired
+table. Value of this expresion is evaluated to the sum of all routes with
+generation smaller than max generation (see below).
+
+<sect1>Configuration
+<label id="stats-config">
+
+<p><descrtip>
+ <tag><label id="stats-max-generation">max generation <m/expr/</tag>
+ Statistics counter contains sum of all routes with generation less
+ than or equal to max generation. This copies behavior of pipe's
+ <ref id="pipe-max-generation" name="max generetion">. Must be in range
+ from 0 to 254. Default: 16.
+
+ <tab><label id="stats-min-settle-time">min settle time <m/time/ </tag>
+ Specify a minimum value of the settle time. When a stats counter changes,
+ automatic recalcualtion of route filters may be triggered, after a short
+ settle time. Minimum settle time is a delay from the last counter change
+ to wait for more updates. No action is done if counter has same value
+ before and after changes. Default: 1 s.
+
+ <tab><label id="stats-max-settle-time">max settle time <m/time/ </tag>
+ Specify a maximum value of the settle time. When stats counter changes,
+ automatic route filter recalculation may be triggered, after a short
+ settle time. Maximum settle time is an upper limit to the settle time
+ from initial counter change even if there are consecutive updates
+ gradually renewing the settle time. No action is done if counter has
+ same value before and after changes. Defualt: 20 s.
+
+</descrip>
+
+<p>Example
+<code>
+protocol stats {
+ ipv4 stats1 { table bgp_tab1; };
+}
+
+protocol static {
+ # note that the stats1 is unrelated
+ ipv4 { import where stats1 > 200; };
+ route 0.0.0.0:0;
+}
+</code>
+
+<p>Beware that configuration with cyclic references (even logical ones) are
+considered invalid and the behaviour is not defined! You <em>should</em> avoid
+them. No detection is implemented yet.
<chapt>Conclusions
<label id="conclusion">
case SYM_ATTRIBUTE:
$$ = f_new_inst(FI_EA_GET, $1->attribute);
break;
+ case SYM_COUNTER:
+ $$ = f_new_inst(FI_COUNTER, $1);
+ break;
+ case SYM_COUNTER_TERM:
+ $$ = f_new_inst(FI_COUNTER_TERM, $1);
+ break;
default:
cf_error("Can't get value of symbol %s", $1->name);
}
RESULT_VAL(fstk->vstk[curline.vbase + sym->offset]);
}
+ INST(FI_COUNTER, 0, 1) {
+ SYMBOL;
+ NEVER_CONSTANT;
+ RESULT(T_INT, i, stats_get_counter(sym));
+ }
+
+ INST(FI_COUNTER_TERM, 0, 1) {
+ SYMBOL;
+ NEVER_CONSTANT;
+
+ RESULT_TYPE(sym->val->type);
+ RESULT_VAL(*sym->val);
+ }
+
INST(FI_CONSTANT, 0, 1) {
FID_MEMBER(
struct f_val,
#include "filter/filter.h"
#include "filter/f-inst.h"
#include "filter/data.h"
+#include "proto/stats/stats-pub.h" /* provides function get_stats_counter() used in f-inst.c */
/* Exception bits */
return strftime(x, max, tbuf, &tm);
}
+
+
+/*
+ * Settle timer
+ */
+
+static inline btime
+settled_time(struct settle_timer *st)
+{
+ ASSUME(st->base_settle_time != 0);
+
+ return MIN_(st->last_change + st->min_settle_time,
+ st->base_settle_time + st->max_settle_time);
+}
+
+inline void
+settle_timer_changed(struct settle_timer *st)
+{
+ st->last_change = current_time();
+}
+
+void
+settle_timer(timer *t)
+{
+ struct settle_timer *st = t->data;
+
+ if (!st->base_settle_time)
+ return;
+
+ btime settled_t = settled_time(st);
+ if (current_time() < settled_t)
+ {
+ tm_set(t, settled_t);
+ return;
+ }
+
+
+ /* Settled */
+ st->base_settle_time = 0;
+
+ /* t->hook already occupied by settle_timer() */
+ if (st->settle_hook)
+ st->settle_hook(st);
+}
+
+void
+stm_init(struct settle_timer *st, pool *p, void *data,
+ void (*settle_hook)(struct settle_timer *st))
+{
+ st->t = tm_new_init(p, (void *) st, settle_timer, 0, 0);
+ st->t->hook = settle_timer;
+ st->t->data = (void *) st;
+
+ st->settle_data = data;
+ st->settle_hook = settle_hook;
+}
+
+void
+kick_settle_timer(struct settle_timer *st)
+{
+ ASSUME(st != NULL);
+
+ st->base_settle_time = current_time();
+
+ timer *t = st->t;
+ if (!tm_active(t))
+ tm_set(t, settled_time(st));
+}
void tm_format_time(char *x, struct timeformat *fmt, btime t);
int tm_format_real_time(char *x, size_t max, const char *fmt, btime t);
+/*
+ * Settle timer
+ */
+
+struct settle_timer {
+ btime min_settle_time;
+ btime max_settle_time;
+ btime base_settle_time;
+ btime last_change;
+ timer *t;
+ void (*settle_hook)(struct settle_timer *t);
+ void *settle_data;
+};
+
+void stm_init(struct settle_timer *st, pool *p, void *data, void (*settle_hook)(struct settle_timer *st));
+void kick_settle_timer(struct settle_timer *st);
+void settle_timer_changed(struct settle_timer *st);
+
#endif
}
}
+ /* update pointers from config to channel and vice versa */
+ cf->channel = c;
+ c->config = cf;
+
/* Execute channel-specific reconfigure hook */
if (c->class->reconfigure && !c->class->reconfigure(c, cf, &import_changed, &export_changed))
return 0;
extern struct protocol
proto_device, proto_radv, proto_rip, proto_static, proto_mrt,
proto_ospf, proto_perf,
- proto_pipe, proto_bgp, proto_bfd, proto_babel, proto_rpki;
+ proto_pipe, proto_bgp, proto_bfd, proto_babel, proto_rpki,
+ proto_stats;
/*
* Routing Protocol Instance
};
extern struct channel_class channel_bgp;
+extern struct channel_class channel_stats;
struct channel_config {
node n;
C radv
C rip
C rpki
+C stats
C static
S ../nest/rt-dev.c
--- /dev/null
+src := stats.c
+obj := $(src-o-files)
+$(all-daemon)
+$(cf-local)
+$(call proto-build,stats_build)
+
+tests_objs := $(tests_objs) $(src-o-files)
--- /dev/null
+/*
+ * BIRD -- Statistics Protocol Configuration
+ *
+ * (c) 2022 Vojtech Vilimek <vojtech.vilimek@nic.cz>
+ * (c) 2022 CZ.NIC z.s.p.o.
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+CF_HDR
+
+#include "proto/stats/stats.h"
+
+CF_DEFINES
+
+#define STATS_CFG ((struct stats_config *) this_proto)
+#define STATS_CC ((struct stats_channel_config *) this_channel)
+
+CF_DECLS
+
+CF_KEYWORDS(STATS, TABLE, MAX, MIN, SETTLE, TIME)
+
+%type <cc> stats_channel_start
+
+CF_GRAMMAR
+
+proto: stats_proto '}' { this_channel = NULL; } ;
+
+stats_proto_start: proto_start STATS
+{
+ this_proto = proto_config_new(&proto_stats, $1);
+ init_list(&STATS_CFG->terms);
+}
+
+proto_name ;
+
+stats_channel_opt_list:
+ /* empty */
+ | '{' stats_opts '}'
+ ;
+
+stats_opt:
+ type symbol '=' term {
+
+ struct stats_term_config *tc = cfg_alloc(sizeof(struct stats_term_config));
+ struct f_val *val = cfg_allocz(sizeof(struct stats_term_config));
+ tc->code = (const struct f_line *) f_linearize($4);
+ tc->type = $1;
+ tc->name = $2->name;
+ tc->val = val;
+
+ /* greater then F_RETURN, therefore 2 */
+ //if (f_eval(f_linearize($4), &val) > 2) cf_error("Runtime error");
+ //if (val.type != $1) cf_error("The expresion does not match defined type");
+
+ add_tail(&STATS_CFG->terms, (node *) tc);
+ stats_eval_term(tc);
+
+ $2 = cf_define_symbol($2, SYM_COUNTER_TERM, val, val);
+ }
+ | MIN SETTLE TIME expr_us { STATS_CC->min_settle_time = $4; }
+ | MAX SETTLE TIME expr_us { STATS_CC->max_settle_time = $4; }
+ ;
+
+stats_opts:
+ /* empty */
+ | stats_opts channel_item ';'
+ | stats_opts stats_opt ';'
+ ;
+
+stats_proto_channel: stats_channel_start stats_channel_opt_list channel_end ;
+
+stats_channel_start: net_type symbol
+{
+ this_channel = channel_config_get(&channel_stats, $2->name, $1, this_proto);
+ STATS_CC->min_settle_time = 1 S_;
+ STATS_CC->max_settle_time = 20 S_;
+ $2 = cf_define_symbol($2, SYM_COUNTER, ch_config, this_channel);
+}
+
+stats_proto:
+ stats_proto_start '{'
+ | stats_proto proto_item ';'
+ | stats_proto stats_proto_channel ';'
+ | stats_proto ';'
+ ;
+
+CF_CODE
+
+CF_END
--- /dev/null
+#ifndef _BIRD_STATS_PUB_H_
+#define _BIRD_STATS_PUB_H_
+
+extern int stats_get_counter(struct symbol *sym);
+#endif
--- /dev/null
+/*
+ * BIRD -- Statistics Protocol
+ *
+ * (c) 2022 Vojtech Vilimek <vojtech.vilimek@nic.cz>
+ * (c) 2022 CZ.NIC z.s.p.o.
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+/**
+ * DOC: Stats
+ *
+ */
+
+#undef LOCAL_DEBUG
+
+#include "nest/bird.h"
+#include "nest/iface.h"
+#include "nest/protocol.h"
+#include "nest/rt.h"
+#include "nest/cli.h"
+#include "conf/conf.h"
+#include "filter/filter.h"
+#include "lib/string.h"
+#include "lib/timer.h"
+
+#include "stats.h"
+
+static void stats_settle_timer(struct settle_timer *st);
+
+static void
+stats_rt_notify(struct proto *P UNUSED, struct channel *src_ch, const net_addr *n UNUSED, rte *new, const rte *old)
+{
+ struct stats_channel *ch = (void *) src_ch;
+
+ int changed = 0;
+ if (new && old)
+ /* count of exported routes stays the same */
+ ;
+ else if (!old)
+ {
+ ch->_counter++;
+ changed = 1;
+ }
+ else if (!new)
+ {
+ ch->_counter--;
+ changed = 1;
+ }
+ else /* shouldn't happen */
+ {
+ bug("Both pointers *new and *old in rt_notify are NULL");
+ }
+
+ if (changed)
+ {
+ settle_timer_changed(&ch->settle_timer);
+ kick_settle_timer(&ch->settle_timer);
+ }
+}
+
+static void
+stats_reload_routes(struct channel *C)
+{
+ // TODO
+ struct stats_channel *c = (void *) C;
+
+ c->_counter = c->counter = 0;
+ channel_request_feeding(C);
+}
+
+static struct proto *
+stats_init(struct proto_config *CF)
+{
+ struct proto *P = proto_new(CF);
+ struct stats_proto *p = (void *) P;
+
+ P->rt_notify = stats_rt_notify;
+ P->reload_routes = stats_reload_routes;
+
+ p->rl_gen = (struct tbf) TBF_DEFAULT_LOG_LIMITS;
+
+ return P;
+}
+
+static void
+stats_configure_channels(struct proto *P, struct proto_config *CF)
+{
+ struct channel_config *cc;
+ WALK_LIST(cc, CF->channels)
+ {
+ struct channel *c = proto_find_channel_by_name(P, cc->name);
+ proto_configure_channel(P, &c, cc);
+ }
+}
+
+static int
+stats_start(struct proto *P)
+{
+ stats_configure_channels(P, P->cf);
+
+ /* evaluate terms on protocol start */
+ struct stats_term_config *tc;
+ WALK_LIST(tc, ((struct stats_config *) P->cf)->terms)
+ {
+ stats_eval_term(tc);
+ }
+
+ return PS_UP;
+}
+
+static int
+stats_reconfigure(struct proto *P, struct proto_config *CF)
+{
+ struct stats_proto *p = (void *) P;
+ struct stats_config *new = (void *) CF;
+
+ struct channel *c;
+ WALK_LIST(c, p->p.channels)
+ c->stale = 1;
+
+ struct channel_config *cc;
+ WALK_LIST(cc, new->c.channels)
+ {
+ c = proto_find_channel_by_name(P, cc->name);
+ if (!proto_configure_channel(P, &c, cc))
+ return 0;
+
+ if (c)
+ {
+ struct stats_channel *sc = (void *) c;
+ struct stats_channel_config *scc = (void *) cc;
+
+ sc->settle_timer.min_settle_time = scc->min_settle_time;
+ sc->settle_timer.max_settle_time = scc->max_settle_time;
+
+ if (sc->counter != sc->_counter)
+ {
+ sc->counter = sc->_counter;
+
+ /* notify all hooked filters */
+ // TODO here
+ }
+
+ c->stale = 0;
+ }
+ }
+
+ struct channel *c2;
+ WALK_LIST_DELSAFE(c, c2, p->p.channels)
+ if (c->stale && !proto_configure_channel(P, &c, NULL))
+ return 0;
+
+ return 1;
+}
+
+static void
+stats_show_proto_info(struct proto *P)
+{
+ struct stats_proto *p = (void *) P;
+
+ struct stats_channel *sc;
+ WALK_LIST(sc, p->p.channels)
+ {
+ cli_msg(-1006, " Channel %s", sc->c.name);
+ cli_msg(-1006, " Exports: %10u (currently: %10u)",
+ sc->counter,
+ sc->_counter);
+ if (!P->disabled)
+ {
+ cli_msg(-1006, " Settle time: %4u s", sc->settle_timer.min_settle_time TO_S);
+ cli_msg(-1006, " Settle time: %4u s", sc->settle_timer.max_settle_time TO_S);
+ }
+ }
+
+ cli_msg(-1006, " Terms:");
+
+ struct stats_term_config *tc;
+ WALK_LIST(tc, ((struct stats_config *) P->cf)->terms)
+ {
+ stats_eval_term(tc);
+ cli_msg(-1006, " %s = %s", tc->name, val_dump(tc->val));
+ }
+}
+
+void
+stats_update_debug(struct proto *P)
+{
+ struct channel *c;
+ WALK_LIST(c, P->channels)
+ {
+ c->debug = P->debug;
+ }
+}
+
+static void
+stats_settle_timer(struct settle_timer *st)
+{
+ struct stats_channel *c = st->settle_data;
+
+ /* update only if real change happen */
+ if (c->counter != c->_counter)
+ {
+ c->counter = c->_counter;
+ /* do update here */
+ // WALK_LIST(s, subscribers)
+ // { ... }
+ }
+}
+
+static int
+stats_channel_start(struct channel *C)
+{
+ struct stats_channel *c = (void *) C;
+ struct stats_channel_config *cc = (void *) C->config;
+ struct stats_proto *p = (void *) C->proto;
+
+ c->pool = p->p.pool;
+
+ stm_init(&c->settle_timer, c->pool, (void *)c, stats_settle_timer);
+
+ c->settle_timer.min_settle_time = cc->min_settle_time;
+ c->settle_timer.max_settle_time = cc->max_settle_time;
+
+ c->_counter = 0;
+ c->counter = 0;
+
+ return 0;
+}
+
+static void
+stats_channel_shutdown(struct channel *C)
+{
+ struct stats_channel *c = (void *) C;
+
+ tm_stop(c->settle_timer.t);
+
+ c->_counter = 0;
+ c->counter = 0;
+ c->pool = NULL;
+}
+
+int
+stats_get_counter(struct symbol *sym)
+{
+ if (sym->ch_config->channel)
+ return (int) ((struct stats_channel *) sym->ch_config->channel)->counter;
+ else
+ return 0;
+}
+
+void stats_eval_term(struct stats_term_config *tc)
+{
+ f_eval(tc->code, tc->val);
+}
+
+struct channel_class channel_stats = {
+ .channel_size = sizeof(struct stats_channel),
+ .config_size = sizeof(struct stats_channel_config),
+ .start = stats_channel_start,
+ .shutdown = stats_channel_shutdown,
+};
+
+struct protocol proto_stats = {
+ .name = "Stats",
+ .template = "stat%d",
+ .channel_mask = NB_ANY,
+ .proto_size = sizeof(struct stats_proto),
+ .config_size = sizeof(struct stats_config),
+ .init = stats_init,
+ .start = stats_start,
+ .reconfigure = stats_reconfigure,
+ .show_proto_info = stats_show_proto_info
+};
+
+void
+stats_build(void)
+{
+ proto_build(&proto_stats);
+}
--- /dev/null
+/*
+ * BIRD -- Statistics Protocol
+ *
+ * (c) 2022 Vojtech Vilimek <vojtech.vilimek@nic.cz>
+ * (c) 2022 CZ.NIC z.s.p.o.
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#ifndef _BIRD_STATS_H_
+#define _BIRD_STATS_H_
+#include "lib/timer.h"
+#include "filter/data.h"
+
+struct stats_channel;
+
+struct stats_term_config {
+ node n;
+ const struct f_line *code;
+ struct f_val *val;
+ int type; /* type declared in configuration */
+ const char *name;
+};
+
+struct stats_config {
+ struct proto_config c;
+ list terms; /* list of counter terms */
+};
+
+struct stats_proto {
+ struct proto p;
+ struct stats_channel *c;
+ struct tbf rl_gen;
+};
+
+struct stats_channel {
+ struct channel c;
+ pool *pool; /* copy of procotol pool */
+ u32 _counter; /* internal counter */
+ u32 counter; /* publicly accessible counter */
+ struct settle_timer settle_timer;
+};
+
+struct stats_channel_config {
+ struct channel_config c;
+ btime min_settle_time; /* wait before notifying filters */
+ btime max_settle_time;
+};
+
+int stats_get_counter(struct symbol *sym);
+void stats_eval_term(struct stats_term_config *tc);
+
+#endif