mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
402 lines
10 KiB
C
402 lines
10 KiB
C
/*
|
|
* Copyright (C) Koen Zandberg <koen@bergzand.net>
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU Lesser
|
|
* General Public License v2.1. See the file LICENSE in the top level
|
|
* directory for more details.
|
|
*/
|
|
|
|
/**
|
|
* @{
|
|
* @ingroup net
|
|
* @file
|
|
* @brief Neighbor level stats for netdev
|
|
*
|
|
* @author Koen Zandberg <koen@bergzand.net>
|
|
* @author Benjamin Valentin <benpicco@beuth-hochschule.de>
|
|
* @}
|
|
*/
|
|
|
|
#include <errno.h>
|
|
|
|
#include "net/l2util.h"
|
|
#include "net/netdev.h"
|
|
#include "net/netstats/neighbor.h"
|
|
#include "xtimer.h"
|
|
|
|
#define ENABLE_DEBUG 0
|
|
#include "debug.h"
|
|
|
|
static inline void _lock(netif_t *dev)
|
|
{
|
|
mutex_lock(&dev->neighbors.lock);
|
|
}
|
|
|
|
static inline void _unlock(netif_t *dev)
|
|
{
|
|
mutex_unlock(&dev->neighbors.lock);
|
|
}
|
|
|
|
/**
|
|
* @brief Compare the freshness of two records
|
|
*
|
|
* @param[in] a pointer to the first record
|
|
* @param[in] b pointer to the second record
|
|
* @param[in] now current timestamp in seconds
|
|
*
|
|
* @return pointer to the least fresh record
|
|
*/
|
|
static inline netstats_nb_t *netstats_nb_comp(const netstats_nb_t *a,
|
|
const netstats_nb_t *b,
|
|
uint16_t now)
|
|
{
|
|
return (netstats_nb_t *)(((now - a->last_updated) > now - b->last_updated) ? a : b);
|
|
}
|
|
|
|
static void half_freshness(netstats_nb_t *stats, uint16_t now_sec)
|
|
{
|
|
uint8_t diff = (now_sec - stats->last_halved) / NETSTATS_NB_FRESHNESS_HALF;
|
|
stats->freshness >>= diff;
|
|
|
|
if (diff) {
|
|
/* Set to the last time point where this should have been halved */
|
|
stats->last_halved = now_sec - diff;
|
|
}
|
|
}
|
|
|
|
static void incr_freshness(netstats_nb_t *stats)
|
|
{
|
|
uint16_t now = xtimer_now_usec() / US_PER_SEC;;
|
|
|
|
/* First halve the freshness if applicable */
|
|
half_freshness(stats, now);
|
|
|
|
/* Increment the freshness capped at FRESHNESS_MAX */
|
|
if (stats->freshness < NETSTATS_NB_FRESHNESS_MAX) {
|
|
stats->freshness++;
|
|
}
|
|
|
|
stats->last_updated = now;
|
|
}
|
|
|
|
static bool isfresh(netstats_nb_t *stats)
|
|
{
|
|
uint16_t now = xtimer_now_usec() / US_PER_SEC;
|
|
|
|
/* Half freshness if applicable to update to current freshness */
|
|
half_freshness(stats, now);
|
|
|
|
return (stats->freshness >= NETSTATS_NB_FRESHNESS_TARGET) &&
|
|
(now - stats->last_updated < NETSTATS_NB_FRESHNESS_EXPIRATION);
|
|
}
|
|
|
|
bool netstats_nb_isfresh(netif_t *dev, netstats_nb_t *stats)
|
|
{
|
|
bool ret;
|
|
|
|
_lock(dev);
|
|
ret = isfresh(stats);
|
|
_unlock(dev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void netstats_nb_init(netif_t *dev)
|
|
{
|
|
mutex_init(&dev->neighbors.lock);
|
|
|
|
_lock(dev);
|
|
memset(dev->neighbors.pstats, 0, sizeof(netstats_nb_t) * NETSTATS_NB_SIZE);
|
|
cib_init(&dev->neighbors.stats_idx, NETSTATS_NB_QUEUE_SIZE);
|
|
_unlock(dev);
|
|
}
|
|
|
|
static void netstats_nb_create(netstats_nb_t *entry, const uint8_t *l2_addr, uint8_t l2_len)
|
|
{
|
|
memset(entry, 0, sizeof(netstats_nb_t));
|
|
memcpy(entry->l2_addr, l2_addr, l2_len);
|
|
entry->l2_addr_len = l2_len;
|
|
|
|
#ifdef MODULE_NETSTATS_NEIGHBOR_ETX
|
|
entry->etx = NETSTATS_NB_ETX_INIT * NETSTATS_NB_ETX_DIVISOR;
|
|
#endif
|
|
}
|
|
|
|
bool netstats_nb_get(netif_t *dev, const uint8_t *l2_addr, uint8_t len, netstats_nb_t *out)
|
|
{
|
|
_lock(dev);
|
|
|
|
netstats_nb_t *stats = dev->neighbors.pstats;
|
|
bool found = false;
|
|
|
|
for (int i = 0; i < NETSTATS_NB_SIZE; i++) {
|
|
|
|
/* Check if this is the matching entry */
|
|
if (l2util_addr_equal(stats[i].l2_addr, stats[i].l2_addr_len, l2_addr, len)) {
|
|
*out = stats[i];
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
_unlock(dev);
|
|
return found;
|
|
}
|
|
|
|
/* find the oldest inactive entry to replace. Empty entries are infinity old */
|
|
static netstats_nb_t *netstats_nb_get_or_create(netif_t *dev, const uint8_t *l2_addr, uint8_t len)
|
|
{
|
|
netstats_nb_t *old_entry = NULL;
|
|
netstats_nb_t *stats = dev->neighbors.pstats;
|
|
uint16_t now = xtimer_now_usec() / US_PER_SEC;
|
|
|
|
for (int i = 0; i < NETSTATS_NB_SIZE; i++) {
|
|
|
|
/* Check if this is the matching entry */
|
|
if (l2util_addr_equal(stats[i].l2_addr, stats[i].l2_addr_len, l2_addr, len)) {
|
|
return &stats[i];
|
|
}
|
|
|
|
/* Entry is oldest if it is empty */
|
|
if (stats[i].l2_addr_len == 0) {
|
|
old_entry = &stats[i];
|
|
}
|
|
/* Check if the entry is expired */
|
|
else if (!isfresh(&stats[i])) {
|
|
/* Entry is oldest if it is expired */
|
|
if (old_entry == NULL) {
|
|
old_entry = &stats[i];
|
|
}
|
|
/* don't replace old entry if there are still empty ones */
|
|
else if (old_entry->l2_addr_len > 0) {
|
|
/* Check if current entry is older than current oldest entry */
|
|
old_entry = netstats_nb_comp(old_entry, &stats[i], now);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* if there is no matching entry,
|
|
* create a new entry if we have an expired one */
|
|
if (old_entry) {
|
|
netstats_nb_create(old_entry, l2_addr, len);
|
|
}
|
|
|
|
return old_entry;
|
|
}
|
|
|
|
void netstats_nb_record(netif_t *dev, const uint8_t *l2_addr, uint8_t len)
|
|
{
|
|
_lock(dev);
|
|
|
|
int idx = cib_put(&dev->neighbors.stats_idx);
|
|
|
|
if (idx < 0) {
|
|
DEBUG("%s: put buffer empty\n", __func__);
|
|
goto out;
|
|
}
|
|
|
|
DEBUG("put %d\n", idx);
|
|
|
|
if (len == 0) {
|
|
/* Fill queue with a NOP */
|
|
dev->neighbors.stats_queue[idx] = NULL;
|
|
} else {
|
|
dev->neighbors.stats_queue[idx] = netstats_nb_get_or_create(dev, l2_addr, len);
|
|
dev->neighbors.stats_queue_time_tx[idx] = xtimer_now_usec();
|
|
}
|
|
|
|
out:
|
|
_unlock(dev);
|
|
}
|
|
|
|
/* Get the first available neighbor in the transmission queue
|
|
* and increment pointer. */
|
|
static netstats_nb_t *netstats_nb_get_recorded(netif_t *dev, uint32_t *time_tx)
|
|
{
|
|
netstats_nb_t *res;
|
|
int idx = cib_get(&dev->neighbors.stats_idx);
|
|
|
|
if (idx < 0) {
|
|
DEBUG("%s: can't get record\n", __func__);
|
|
return NULL;
|
|
}
|
|
|
|
DEBUG("get %d (%d left)\n", idx, cib_avail(&dev->neighbors.stats_idx));
|
|
|
|
res = dev->neighbors.stats_queue[idx];
|
|
dev->neighbors.stats_queue[idx] = NULL;
|
|
|
|
*time_tx = dev->neighbors.stats_queue_time_tx[idx];
|
|
|
|
return res;
|
|
}
|
|
|
|
__attribute__((unused))
|
|
static uint32_t _ewma(bool fresh, uint32_t old_val, uint32_t new_val)
|
|
{
|
|
uint8_t ewma_alpha;
|
|
|
|
if (old_val == 0) {
|
|
return new_val;
|
|
}
|
|
|
|
/* If the stats are not fresh, use a larger alpha to average aggressive */
|
|
if (fresh) {
|
|
ewma_alpha = NETSTATS_NB_EWMA_ALPHA;
|
|
} else {
|
|
ewma_alpha = NETSTATS_NB_EWMA_ALPHA_RAMP;
|
|
}
|
|
|
|
/* Exponential weighted moving average */
|
|
return (old_val * (NETSTATS_NB_EWMA_SCALE - ewma_alpha)
|
|
+ new_val * ewma_alpha) / NETSTATS_NB_EWMA_SCALE;
|
|
}
|
|
|
|
static void netstats_nb_update_etx(netstats_nb_t *stats, netstats_nb_result_t result,
|
|
uint8_t transmissions, bool fresh)
|
|
{
|
|
/* don't do anything if driver does not report ETX */
|
|
if (transmissions == 0) {
|
|
return;
|
|
}
|
|
|
|
if (result != NETSTATS_NB_SUCCESS) {
|
|
transmissions = NETSTATS_NB_ETX_NOACK_PENALTY;
|
|
}
|
|
|
|
#ifdef MODULE_NETSTATS_NEIGHBOR_ETX
|
|
stats->etx = _ewma(fresh, stats->etx, transmissions * NETSTATS_NB_ETX_DIVISOR);
|
|
#else
|
|
(void)stats;
|
|
(void)result;
|
|
(void)transmissions;
|
|
(void)fresh;
|
|
#endif
|
|
}
|
|
|
|
static void netstats_nb_update_time(netstats_nb_t *stats, netstats_nb_result_t result,
|
|
uint32_t duration, bool fresh)
|
|
{
|
|
/* TX time already got a penalty due to retransmissions */
|
|
if (result != NETSTATS_NB_SUCCESS) {
|
|
duration *= 2;
|
|
}
|
|
|
|
#if MODULE_NETSTATS_NEIGHBOR_TX_TIME
|
|
stats->time_tx_avg = _ewma(fresh, stats->time_tx_avg, duration);
|
|
#else
|
|
(void)stats;
|
|
(void)result;
|
|
(void)duration;
|
|
(void)fresh;
|
|
#endif
|
|
}
|
|
|
|
static void netstats_nb_update_rssi(netstats_nb_t *stats, uint8_t rssi, bool fresh)
|
|
{
|
|
#ifdef MODULE_NETSTATS_NEIGHBOR_RSSI
|
|
stats->rssi = _ewma(fresh, stats->rssi, rssi);
|
|
#else
|
|
(void)stats;
|
|
(void)rssi;
|
|
(void)fresh;
|
|
#endif
|
|
}
|
|
|
|
static void netstats_nb_update_lqi(netstats_nb_t *stats, uint8_t lqi, bool fresh)
|
|
{
|
|
#ifdef MODULE_NETSTATS_NEIGHBOR_LQI
|
|
stats->lqi = _ewma(fresh, stats->lqi, lqi);
|
|
#else
|
|
(void)stats;
|
|
(void)lqi;
|
|
(void)fresh;
|
|
#endif
|
|
}
|
|
|
|
static void netstats_nb_incr_count_tx(netstats_nb_t *stats, netstats_nb_result_t result)
|
|
{
|
|
#ifdef MODULE_NETSTATS_NEIGHBOR_COUNT
|
|
stats->tx_count++;
|
|
|
|
/* gracefully handle overflow */
|
|
if (stats->tx_count == 0) {
|
|
stats->tx_count = ~stats->tx_count;
|
|
stats->tx_count = stats->tx_count >> 4;
|
|
stats->tx_fail = stats->tx_fail >> 4;
|
|
}
|
|
|
|
if (result != NETSTATS_NB_SUCCESS) {
|
|
stats->tx_fail++;
|
|
}
|
|
#else
|
|
(void)stats;
|
|
(void)result;
|
|
#endif
|
|
}
|
|
|
|
static void netstats_nb_incr_count_rx(netstats_nb_t *stats)
|
|
{
|
|
#ifdef MODULE_NETSTATS_NEIGHBOR_COUNT
|
|
stats->rx_count++;
|
|
#else
|
|
(void)stats;
|
|
#endif
|
|
}
|
|
|
|
netstats_nb_t *netstats_nb_update_tx(netif_t *dev, netstats_nb_result_t result,
|
|
uint8_t transmissions)
|
|
{
|
|
uint32_t now = xtimer_now_usec();
|
|
netstats_nb_t *stats;
|
|
uint32_t time_tx = 0;
|
|
|
|
_lock(dev);
|
|
|
|
/* Buggy drivers don't always generate TX done events.
|
|
* Discard old events to prevent the tx start <-> tx done correlation
|
|
* from getting out of sync. */
|
|
do {
|
|
stats = netstats_nb_get_recorded(dev, &time_tx);
|
|
} while (cib_avail(&dev->neighbors.stats_idx)
|
|
&& ((now - time_tx) > NETSTATS_NB_TX_TIMEOUT_MS * US_PER_MS));
|
|
|
|
/* Nothing to do for multicast or if packet was not sent */
|
|
if (result == NETSTATS_NB_BUSY || stats == NULL) {
|
|
goto out;
|
|
}
|
|
|
|
bool fresh = isfresh(stats);
|
|
|
|
netstats_nb_update_time(stats, result, now - time_tx, fresh);
|
|
netstats_nb_update_etx(stats, result, transmissions, fresh);
|
|
netstats_nb_incr_count_tx(stats, result);
|
|
|
|
incr_freshness(stats);
|
|
|
|
out:
|
|
_unlock(dev);
|
|
return stats;
|
|
}
|
|
|
|
netstats_nb_t *netstats_nb_update_rx(netif_t *dev, const uint8_t *l2_addr,
|
|
uint8_t l2_addr_len, uint8_t rssi, uint8_t lqi)
|
|
{
|
|
_lock(dev);
|
|
|
|
netstats_nb_t *stats = netstats_nb_get_or_create(dev, l2_addr, l2_addr_len);
|
|
|
|
if (stats != NULL) {
|
|
bool fresh = isfresh(stats);
|
|
|
|
netstats_nb_update_rssi(stats, rssi, fresh);
|
|
netstats_nb_update_lqi(stats, lqi, fresh);
|
|
netstats_nb_incr_count_rx(stats);
|
|
|
|
incr_freshness(stats);
|
|
}
|
|
|
|
_unlock(dev);
|
|
return stats;
|
|
}
|