0
0
mirror of https://gitlab.nic.cz/labs/bird.git synced 2024-12-22 09:41:54 +00:00

Protocol stats: a WIP implementation of conditional routes

Add new global counter for stats channel

New symbol is added for each stats protocol channel, the symbol is accessed by
same name as the underlying channel. Symbols evaluate to the sum of all routes
exported from connected table with generation less than max generation of
particular channel. Default max generation is 16.

Beware you shouldn't make cyclic references as the behavior of such
configuration is not defined!
This commit is contained in:
Vojtech Vilimek 2022-07-13 15:39:14 +02:00 committed by Maria Matejka
parent 860fbf0d65
commit d648c6b602
17 changed files with 615 additions and 2 deletions

View File

@ -112,6 +112,7 @@ void cfg_copy_list(list *dest, list *src, unsigned node_size);
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;
@ -127,6 +128,8 @@ struct symbol {
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];
@ -161,6 +164,8 @@ struct bytestring {
#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)

View File

@ -307,7 +307,7 @@ if test "$enable_mpls_kernel" != no ; then
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

View File

@ -5560,6 +5560,64 @@ protocol static {
}
</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">

View File

@ -801,6 +801,12 @@ symbol_value: symbol_known
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);
}

View File

@ -542,6 +542,20 @@
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,

View File

@ -43,6 +43,7 @@
#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 */

View File

@ -325,3 +325,71 @@ tm_format_real_time(char *x, size_t max, const char *fmt, btime t)
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));
}

View File

@ -134,4 +134,22 @@ btime tm_parse_time(const char *x);
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

View File

@ -1031,6 +1031,10 @@ channel_reconfigure(struct channel *c, struct channel_config *cf)
}
}
/* 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;

View File

@ -93,7 +93,8 @@ void protos_dump_all(void);
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
@ -492,6 +493,7 @@ struct channel_class {
};
extern struct channel_class channel_bgp;
extern struct channel_class channel_stats;
struct channel_config {
node n;

View File

@ -7,5 +7,6 @@ C pipe
C radv
C rip
C rpki
C stats
C static
S ../nest/rt-dev.c

1
proto/stats/Doc Normal file
View File

@ -0,0 +1 @@
S stats.c

7
proto/stats/Makefile Normal file
View File

@ -0,0 +1,7 @@
src := stats.c
obj := $(src-o-files)
$(all-daemon)
$(cf-local)
$(call proto-build,stats_build)
tests_objs := $(tests_objs) $(src-o-files)

90
proto/stats/config.Y Normal file
View File

@ -0,0 +1,90 @@
/*
* 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

5
proto/stats/stats-pub.h Normal file
View File

@ -0,0 +1,5 @@
#ifndef _BIRD_STATS_PUB_H_
#define _BIRD_STATS_PUB_H_
extern int stats_get_counter(struct symbol *sym);
#endif

280
proto/stats/stats.c Normal file
View File

@ -0,0 +1,280 @@
/*
* 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);
}

53
proto/stats/stats.h Normal file
View File

@ -0,0 +1,53 @@
/*
* 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