From 2fa239ee8d592b6cb8a6e29860f1b383ccb51f7c Mon Sep 17 00:00:00 2001 From: Simon Duquennoy Date: Sun, 29 Nov 2015 21:31:59 +0100 Subject: [PATCH] Added link-stats module to keep strack of link statistics and their freshness --- core/net/ipv6/sicslowpan.c | 4 + core/net/ipv6/uip-ds6-nbr.c | 5 + core/net/link-stats.c | 212 ++++++++++++++++++++++++++++++++++++ core/net/link-stats.h | 65 +++++++++++ 4 files changed, 286 insertions(+) create mode 100644 core/net/link-stats.c create mode 100644 core/net/link-stats.h diff --git a/core/net/ipv6/sicslowpan.c b/core/net/ipv6/sicslowpan.c index ebbd8768e..8c174b228 100644 --- a/core/net/ipv6/sicslowpan.c +++ b/core/net/ipv6/sicslowpan.c @@ -62,6 +62,7 @@ #include "contiki.h" #include "dev/watchdog.h" +#include "net/link-stats.h" #include "net/ip/tcpip.h" #include "net/ip/uip.h" #include "net/ipv6/uip-ds6.h" @@ -1516,6 +1517,9 @@ input(void) uint8_t first_fragment = 0, last_fragment = 0; #endif /*SICSLOWPAN_CONF_FRAG*/ + /* Update link statistics */ + link_stats_input_callback(packetbuf_addr(PACKETBUF_ADDR_SENDER)); + /* init */ uncomp_hdr_len = 0; packetbuf_hdr_len = 0; diff --git a/core/net/ipv6/uip-ds6-nbr.c b/core/net/ipv6/uip-ds6-nbr.c index e5c6167e5..3ccebddd1 100644 --- a/core/net/ipv6/uip-ds6-nbr.c +++ b/core/net/ipv6/uip-ds6-nbr.c @@ -47,6 +47,7 @@ #include #include #include "lib/list.h" +#include "net/link-stats.h" #include "net/linkaddr.h" #include "net/packetbuf.h" #include "net/ipv6/uip-ds6-nbr.h" @@ -74,6 +75,7 @@ NBR_TABLE_GLOBAL(uip_ds6_nbr_t, ds6_neighbors); void uip_ds6_neighbors_init(void) { + link_stats_init(); nbr_table_register(ds6_neighbors, (nbr_table_callback *)uip_ds6_nbr_rm); } /*---------------------------------------------------------------------------*/ @@ -204,6 +206,9 @@ uip_ds6_link_neighbor_callback(int status, int numtx) return; } + /* Update neighbor link statistics */ + link_stats_packet_sent(dest, status, numtx); + /* Call upper-layer callback (e.g. RPL) */ LINK_NEIGHBOR_CALLBACK(dest, status, numtx); #if UIP_DS6_LL_NUD diff --git a/core/net/link-stats.c b/core/net/link-stats.c new file mode 100644 index 000000000..2312f5b23 --- /dev/null +++ b/core/net/link-stats.c @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2015, SICS Swedish ICT. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * + * Authors: Simon Duquennoy + */ + +#include "contiki.h" +#include "sys/clock.h" +#include "net/packetbuf.h" +#include "net/nbr-table.h" +#include "net/link-stats.h" +#include + +#define DEBUG 0 +#if DEBUG +#define PRINTF(...) printf(__VA_ARGS__) +#else +#define PRINTF(...) +#endif + +/* Half time for the freshness counter, in minutes */ +#define FRESHNESS_HALF_LIFE 20 +/* Statistics are fresh if the freshness counter is FRESHNESS_TARGET or more */ +#define FRESHNESS_TARGET 4 +/* Maximum value for the freshness counter */ +#define FRESHNESS_MAX 16 +/* Statistics with no update in FRESHNESS_EXPIRATION_TIMEOUT is not fresh */ +#define FRESHNESS_EXPIRATION_TIME (10 * 60 * CLOCK_SECOND) + +/* EWMA (exponential moving average) used to maintain statistics over time */ +#define EWMA_SCALE 100 +#define EWMA_ALPHA 10 +#define EWMA_BOOTSTRAP_ALPHA 30 + +/* ETX fixed point divisor. 128 is the value used by RPL (RFC 6551 and RFC 6719) */ +#define ETX_DIVISOR LINK_STATS_ETX_DIVISOR +/* Number of Tx used to update the ETX EWMA in case of no-ACK */ +#define ETX_NOACK_PENALTY 16 +/* Initial ETX value */ +#define ETX_INIT 2 + +/* Per-neighbor link statistics table */ +NBR_TABLE(struct link_stats, link_stats); + +/* Called every FRESHNESS_HALF_LIFE minutes */ +struct ctimer periodic_timer; + +/* Used to initialize ETX before any transmission occurs. In order to + * infer the initial ETX from the RSSI of previously received packets, use: */ +/* #define LINK_STATS_CONF_INIT_ETX(stats) guess_etx_from_rssi(stats) */ + +#ifdef LINK_STATS_CONF_INIT_ETX +#define LINK_STATS_INIT_ETX(stats) LINK_STATS_CONF_INIT_ETX(stats) +#else /* LINK_STATS_INIT_ETX */ +#define LINK_STATS_INIT_ETX(stats) (ETX_INIT * ETX_DIVISOR) +#endif /* LINK_STATS_INIT_ETX */ + +/*---------------------------------------------------------------------------*/ +/* Returns the neighbor's link stats */ +const struct link_stats * +link_stats_from_lladdr(const linkaddr_t *lladdr) +{ + return nbr_table_get_from_lladdr(link_stats, lladdr); +} +/*---------------------------------------------------------------------------*/ +/* Are the statistics fresh? */ +int +link_stats_is_fresh(const struct link_stats *stats) +{ + return (stats != NULL) + && clock_time() - stats->last_tx_time < FRESHNESS_EXPIRATION_TIME + && stats->freshness >= FRESHNESS_TARGET; +} +/*---------------------------------------------------------------------------*/ +uint16_t +guess_etx_from_rssi(const struct link_stats *stats) +{ + if(stats != NULL) { + if(stats->rssi == 0) { + return ETX_INIT * ETX_DIVISOR; + } else { + /* A rough estimate of PRR from RSSI, as a linear function where: + * RSSI >= -60 results in PRR of 1 + * RSSI <= -90 results in PRR of 0 + * prr = (bounded_rssi - RSSI_LOW) / (RSSI_DIFF) + * etx = ETX_DIVOSOR / ((bounded_rssi - RSSI_LOW) / RSSI_DIFF) + * etx = (RSSI_DIFF * ETX_DIVOSOR) / (bounded_rssi - RSSI_LOW) + * */ +#define ETX_INIT_MAX 3 +#define RSSI_HIGH -60 +#define RSSI_LOW -90 +#define RSSI_DIFF (RSSI_HIGH - RSSI_LOW) + uint16_t etx; + int16_t bounded_rssi = stats->rssi; + bounded_rssi = MIN(bounded_rssi, RSSI_HIGH); + bounded_rssi = MAX(bounded_rssi, RSSI_LOW + 1); + etx = RSSI_DIFF * ETX_DIVISOR / (bounded_rssi - RSSI_LOW); + return MIN(etx, ETX_INIT_MAX * ETX_DIVISOR); + } + } + return 0xffff; +} +/*---------------------------------------------------------------------------*/ +/* Packet sent callback. Updates stats for transmissions to lladdr */ +void +link_stats_packet_sent(const linkaddr_t *lladdr, int status, int numtx) +{ + struct link_stats *stats; + uint16_t packet_etx; + uint8_t ewma_alpha; + + if(status != MAC_TX_OK && status != MAC_TX_NOACK) { + /* Do not penalize the ETX when collisions or transmission errors occur. */ + return; + } + + stats = nbr_table_get_from_lladdr(link_stats, lladdr); + if(stats == NULL) { + /* Add the neighbor */ + stats = nbr_table_add_lladdr(link_stats, lladdr); + if(stats != NULL) { + stats->etx = LINK_STATS_INIT_ETX(stats); + } else { + return; /* No space left, return */ + } + } + + /* Update last timestamp and freshness */ + stats->last_tx_time = clock_time(); + stats->freshness = MIN(stats->freshness + numtx, FRESHNESS_MAX); + + /* ETX used for this update */ + packet_etx = ((status == MAC_TX_NOACK) ? ETX_NOACK_PENALTY : numtx) * ETX_DIVISOR; + /* ETX alpha used for this update */ + ewma_alpha = link_stats_is_fresh(stats) ? EWMA_ALPHA : EWMA_BOOTSTRAP_ALPHA; + + /* Compute EWMA and update ETX */ + stats->etx = ((uint32_t)stats->etx * (EWMA_SCALE - ewma_alpha) + + (uint32_t)packet_etx * ewma_alpha) / EWMA_SCALE; +} +/*---------------------------------------------------------------------------*/ +/* Packet input callback. Updates statistics for receptions on a given link */ +void +link_stats_input_callback(const linkaddr_t *lladdr) +{ + struct link_stats *stats; + int16_t packet_rssi = packetbuf_attr(PACKETBUF_ATTR_RSSI); + + stats = nbr_table_get_from_lladdr(link_stats, lladdr); + if(stats == NULL) { + /* Add the neighbor */ + stats = nbr_table_add_lladdr(link_stats, lladdr); + if(stats != NULL) { + /* Initialize */ + stats->rssi = packet_rssi; + stats->etx = LINK_STATS_INIT_ETX(stats); + } else { + return; /* No space left, return */ + } + } + + /* Update RSSI EWMA */ + stats->rssi = ((int32_t)stats->rssi * (EWMA_SCALE - EWMA_ALPHA) + + (int32_t)packet_rssi * EWMA_ALPHA) / EWMA_SCALE; +} +/*---------------------------------------------------------------------------*/ +/* Periodic timer called every FRESHNESS_HALF_LIFE minutes */ +static void +periodic(void *ptr) +{ + /* Age (by halving) freshness counter of all neighbors */ + struct link_stats *stats; + ctimer_reset(&periodic_timer); + for(stats = nbr_table_head(link_stats); stats != NULL; stats = nbr_table_next(link_stats, stats)) { + stats->freshness >>= 1; + } +} +/*---------------------------------------------------------------------------*/ +/* Initializes link-stats module */ +void +link_stats_init(void) +{ + nbr_table_register(link_stats, NULL); + ctimer_set(&periodic_timer, 60 * CLOCK_SECOND * FRESHNESS_HALF_LIFE, + periodic, NULL); +} diff --git a/core/net/link-stats.h b/core/net/link-stats.h new file mode 100644 index 000000000..d28a1c1a4 --- /dev/null +++ b/core/net/link-stats.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2015, SICS Swedish ICT. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * + * Authors: Simon Duquennoy + */ + +#ifndef LINK_STATS_H_ +#define LINK_STATS_H_ + +#include "core/net/linkaddr.h" + +/* ETX fixed point divisor. 128 is the value used by RPL (RFC 6551 and RFC 6719) */ +#ifdef LINK_STATS_CONF_ETX_DIVISOR +#define LINK_STATS_ETX_DIVISOR LINK_STATS_CONF_ETX_DIVISOR +#else /* LINK_STATS_CONF_ETX_DIVISOR */ +#define LINK_STATS_ETX_DIVISOR 128 +#endif /* LINK_STATS_CONF_ETX_DIVISOR */ + +/* All statistics of a given link */ +struct link_stats { + uint16_t etx; /* ETX using ETX_DIVISOR as fixed point divisor */ + int16_t rssi; /* RSSI (received signal strength) */ + uint8_t freshness; /* Freshness of the statistics */ + clock_time_t last_tx_time; /* Last Tx timestamp */ +}; + +/* Returns the neighbor's link statistics */ +const struct link_stats *link_stats_from_lladdr(const linkaddr_t *lladdr); +/* Are the statistics fresh? */ +int link_stats_is_fresh(const struct link_stats *stats); + +/* Initializes link-stats module */ +void link_stats_init(void); +/* Packet sent callback. Updates statistics for transmissions on a given link */ +void link_stats_packet_sent(const linkaddr_t *lladdr, int status, int numtx); +/* Packet input callback. Updates statistics for receptions on a given link */ +void link_stats_input_callback(const linkaddr_t *lladdr); + +#endif /* LINK_STATS_H_ */