302 lines
11 KiB
C
302 lines
11 KiB
C
/*
|
|
* 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 <simonduq@sics.se>
|
|
*/
|
|
|
|
#include "contiki.h"
|
|
#include "sys/clock.h"
|
|
#include "net/packetbuf.h"
|
|
#include "net/nbr-table.h"
|
|
#include "net/link-stats.h"
|
|
#include <stdio.h>
|
|
|
|
/* Log configuration */
|
|
#include "sys/log.h"
|
|
#define LOG_MODULE "Link Stats"
|
|
#define LOG_LEVEL LOG_LEVEL_MAC
|
|
|
|
/* Maximum value for the Tx count counter */
|
|
#define TX_COUNT_MAX 32
|
|
|
|
/* Statistics with no update in FRESHNESS_EXPIRATION_TIMEOUT is not fresh */
|
|
#define FRESHNESS_EXPIRATION_TIME (10 * 60 * (clock_time_t)CLOCK_SECOND)
|
|
/* Half time for the freshness counter */
|
|
#define FRESHNESS_HALF_LIFE (15 * 60 * (clock_time_t)CLOCK_SECOND)
|
|
/* 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
|
|
|
|
/* EWMA (exponential moving average) used to maintain statistics over time */
|
|
#define EWMA_SCALE 100
|
|
#define EWMA_ALPHA 10
|
|
#define EWMA_BOOTSTRAP_ALPHA 25
|
|
|
|
/* ETX fixed point divisor. 128 is the value used by RPL (RFC 6551 and RFC 6719) */
|
|
#define ETX_DIVISOR LINK_STATS_ETX_DIVISOR
|
|
/* In case of no-ACK, add ETX_NOACK_PENALTY to the real Tx count, as a penalty */
|
|
#define ETX_NOACK_PENALTY 12
|
|
/* Initial ETX value */
|
|
#define ETX_DEFAULT 2
|
|
|
|
/* Per-neighbor link statistics table */
|
|
NBR_TABLE(struct link_stats, link_stats);
|
|
|
|
/* Called at a period of FRESHNESS_HALF_LIFE */
|
|
struct ctimer periodic_timer;
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
/* 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);
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
/* Returns the neighbor's address given a link stats item */
|
|
const linkaddr_t *
|
|
link_stats_get_lladdr(const struct link_stats *stat)
|
|
{
|
|
return nbr_table_get_lladdr(link_stats, stat);
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
/* 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;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
#if LINK_STATS_INIT_ETX_FROM_RSSI
|
|
uint16_t
|
|
guess_etx_from_rssi(const struct link_stats *stats)
|
|
{
|
|
if(stats != NULL) {
|
|
if(stats->rssi == 0) {
|
|
return ETX_DEFAULT * 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;
|
|
}
|
|
#endif /* LINK_STATS_INIT_ETX_FROM_RSSI */
|
|
/*---------------------------------------------------------------------------*/
|
|
/* 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;
|
|
#if !LINK_STATS_ETX_FROM_PACKET_COUNT
|
|
uint16_t packet_etx;
|
|
uint8_t ewma_alpha;
|
|
#endif /* !LINK_STATS_ETX_FROM_PACKET_COUNT */
|
|
|
|
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, NBR_TABLE_REASON_LINK_STATS, NULL);
|
|
if(stats != NULL) {
|
|
#if LINK_STATS_INIT_ETX_FROM_RSSI
|
|
stats->etx = guess_etx_from_rssi(stats);
|
|
#else /* LINK_STATS_INIT_ETX_FROM_RSSI */
|
|
stats->etx = ETX_DEFAULT * ETX_DIVISOR;
|
|
#endif /* LINK_STATS_INIT_ETX_FROM_RSSI */
|
|
} 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);
|
|
|
|
#if LINK_STATS_PACKET_COUNTERS
|
|
/* Update paket counters */
|
|
stats->cnt_current.num_packets_tx += numtx;
|
|
if(status == MAC_TX_OK) {
|
|
stats->cnt_current.num_packets_acked++;
|
|
}
|
|
#endif
|
|
|
|
/* Add penalty in case of no-ACK */
|
|
if(status == MAC_TX_NOACK) {
|
|
numtx += ETX_NOACK_PENALTY;
|
|
}
|
|
|
|
#if LINK_STATS_ETX_FROM_PACKET_COUNT
|
|
/* Compute ETX from packet and ACK count */
|
|
/* Halve both counter after TX_COUNT_MAX */
|
|
if(stats->tx_count + numtx > TX_COUNT_MAX) {
|
|
stats->tx_count /= 2;
|
|
stats->ack_count /= 2;
|
|
}
|
|
/* Update tx_count and ack_count */
|
|
stats->tx_count += numtx;
|
|
if(status == MAC_TX_OK) {
|
|
stats->ack_count++;
|
|
}
|
|
/* Compute ETX */
|
|
if(stats->ack_count > 0) {
|
|
stats->etx = ((uint16_t)stats->tx_count * ETX_DIVISOR) / stats->ack_count;
|
|
} else {
|
|
stats->etx = (uint16_t)MAX(ETX_NOACK_PENALTY, stats->tx_count) * ETX_DIVISOR;
|
|
}
|
|
#else /* LINK_STATS_ETX_FROM_PACKET_COUNT */
|
|
/* Compute ETX using an EWMA */
|
|
|
|
/* ETX used for this update */
|
|
packet_etx = 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;
|
|
#endif /* LINK_STATS_ETX_FROM_PACKET_COUNT */
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
/* 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, NBR_TABLE_REASON_LINK_STATS, NULL);
|
|
if(stats != NULL) {
|
|
/* Initialize */
|
|
stats->rssi = packet_rssi;
|
|
#if LINK_STATS_INIT_ETX_FROM_RSSI
|
|
stats->etx = guess_etx_from_rssi(stats);
|
|
#else /* LINK_STATS_INIT_ETX_FROM_RSSI */
|
|
stats->etx = ETX_DEFAULT * ETX_DIVISOR;
|
|
#endif /* LINK_STATS_INIT_ETX_FROM_RSSI */
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* Update RSSI EWMA */
|
|
stats->rssi = ((int32_t)stats->rssi * (EWMA_SCALE - EWMA_ALPHA) +
|
|
(int32_t)packet_rssi * EWMA_ALPHA) / EWMA_SCALE;
|
|
|
|
#if LINK_STATS_PACKET_COUNTERS
|
|
stats->cnt_current.num_packets_rx++;
|
|
#endif
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
#if LINK_STATS_PACKET_COUNTERS
|
|
/*---------------------------------------------------------------------------*/
|
|
static void
|
|
print_and_update_counters(void)
|
|
{
|
|
struct link_stats *stats;
|
|
|
|
for(stats = nbr_table_head(link_stats); stats != NULL;
|
|
stats = nbr_table_next(link_stats, stats)) {
|
|
|
|
struct link_packet_counter *c = &stats->cnt_current;
|
|
|
|
LOG_INFO("num packets: tx=%u ack=%u rx=%u to=",
|
|
c->num_packets_tx, c->num_packets_acked, c->num_packets_rx);
|
|
LOG_INFO_LLADDR(link_stats_get_lladdr(stats));
|
|
LOG_INFO_("\n");
|
|
|
|
stats->cnt_total.num_packets_tx += stats->cnt_current.num_packets_tx;
|
|
stats->cnt_total.num_packets_acked += stats->cnt_current.num_packets_acked;
|
|
stats->cnt_total.num_packets_rx += stats->cnt_current.num_packets_rx;
|
|
memset(&stats->cnt_current, 0, sizeof(stats->cnt_current));
|
|
}
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
#endif /* LINK_STATS_PACKET_COUNTERS */
|
|
/*---------------------------------------------------------------------------*/
|
|
/* Periodic timer called at a period of FRESHNESS_HALF_LIFE */
|
|
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;
|
|
}
|
|
|
|
#if LINK_STATS_PACKET_COUNTERS
|
|
print_and_update_counters();
|
|
#endif
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
/* Resets link-stats module */
|
|
void
|
|
link_stats_reset(void)
|
|
{
|
|
struct link_stats *stats;
|
|
stats = nbr_table_head(link_stats);
|
|
while(stats != NULL) {
|
|
nbr_table_remove(link_stats, stats);
|
|
stats = nbr_table_next(link_stats, stats);
|
|
}
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
/* Initializes link-stats module */
|
|
void
|
|
link_stats_init(void)
|
|
{
|
|
nbr_table_register(link_stats, NULL);
|
|
ctimer_set(&periodic_timer, FRESHNESS_HALF_LIFE, periodic, NULL);
|
|
}
|