diff --git a/cpu/nrf5x_common/radio/nrfmin/Makefile b/cpu/nrf5x_common/radio/nrfmin/Makefile index 5b85af78ae..4b7d1da9ab 100644 --- a/cpu/nrf5x_common/radio/nrfmin/Makefile +++ b/cpu/nrf5x_common/radio/nrfmin/Makefile @@ -2,7 +2,7 @@ MODULE = nrfmin SRC = nrfmin.c -ifneq (,$(filter gnrc_netdev_default,$(USEMODULE))) +ifneq (,$(filter gnrc_netif,$(USEMODULE))) SRC += nrfmin_gnrc.c endif diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index 18652804d4..01d6cd01a1 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -91,6 +91,11 @@ PSEUDOMODULES += netdev_layer PSEUDOMODULES += netdev_register PSEUDOMODULES += netstats PSEUDOMODULES += netstats_l2 +PSEUDOMODULES += netstats_neighbor_etx +PSEUDOMODULES += netstats_neighbor_count +PSEUDOMODULES += netstats_neighbor_rssi +PSEUDOMODULES += netstats_neighbor_lqi +PSEUDOMODULES += netstats_neighbor_tx_time PSEUDOMODULES += netstats_ipv6 PSEUDOMODULES += netstats_rpl PSEUDOMODULES += nimble diff --git a/sys/Makefile b/sys/Makefile index 96942b1585..aaa3528747 100644 --- a/sys/Makefile +++ b/sys/Makefile @@ -107,6 +107,9 @@ endif ifneq (,$(filter netopt,$(USEMODULE))) DIRS += net/crosslayer/netopt endif +ifneq (,$(filter netstats_neighbor,$(USEMODULE))) + DIRS += net/netstats +endif ifneq (,$(filter sema,$(USEMODULE))) DIRS += sema endif diff --git a/sys/Makefile.dep b/sys/Makefile.dep index 18ae9cffbc..1af2827552 100644 --- a/sys/Makefile.dep +++ b/sys/Makefile.dep @@ -704,6 +704,11 @@ ifneq (,$(filter netstats_%, $(USEMODULE))) USEMODULE += netstats endif +ifneq (,$(filter netstats_neighbor_%, $(USEMODULE))) + USEMODULE += netstats_neighbor + USEMODULE += xtimer +endif + ifneq (,$(filter gnrc_lwmac,$(USEMODULE))) USEMODULE += gnrc_netif USEMODULE += gnrc_nettype_lwmac diff --git a/sys/include/net/l2util.h b/sys/include/net/l2util.h index 0159072b67..bf27531919 100644 --- a/sys/include/net/l2util.h +++ b/sys/include/net/l2util.h @@ -191,6 +191,26 @@ char *l2util_addr_to_str(const uint8_t *addr, size_t addr_len, char *out); */ size_t l2util_addr_from_str(const char *str, uint8_t *out); +/** + * @brief Checks if two l2 addresses are equal. + * + * @param[in] addr_a First hardware address. + * @param[in] addr_a_len Length of first hardware address. + * @param[in] addr_b Second hardware address. + * @param[in] addr_b_len Length of second hardware address. + * + * @return true if the addresses match, false if not. + */ +static inline bool l2util_addr_equal(const uint8_t *addr_a, uint8_t addr_a_len, + const uint8_t *addr_b, uint8_t addr_b_len) +{ + if (addr_a_len != addr_b_len) { + return false; + } + + return memcmp(addr_a, addr_b, addr_a_len) == 0; +} + #ifdef __cplusplus } #endif diff --git a/sys/include/net/netif.h b/sys/include/net/netif.h index a3fddc6fb3..046d8dc934 100644 --- a/sys/include/net/netif.h +++ b/sys/include/net/netif.h @@ -38,6 +38,11 @@ #include "list.h" #include "net/netopt.h" +#ifdef MODULE_NETSTATS_NEIGHBOR +#include "cib.h" +#include "net/netstats.h" +#endif + #ifdef __cplusplus extern "C" { #endif @@ -61,7 +66,10 @@ extern "C" { * @note All network interfaces should inherit from this structure. */ typedef struct { - list_node_t node; /**< Pointer to the next interface */ + list_node_t node; /**< Pointer to the next interface */ +#ifdef MODULE_NETSTATS_NEIGHBOR + netstats_nb_table_t neighbors; /**< Structure containing all L2 neighbors */ +#endif } netif_t; /** diff --git a/sys/include/net/netstats.h b/sys/include/net/netstats.h index c517b1e3c8..2b19958887 100644 --- a/sys/include/net/netstats.h +++ b/sys/include/net/netstats.h @@ -19,6 +19,8 @@ */ #include +#include "net/l2util.h" +#include "mutex.h" #ifndef NET_NETSTATS_H #define NET_NETSTATS_H @@ -27,6 +29,20 @@ extern "C" { #endif +/** + * @brief The max number of entries in the peer stats table + */ +#ifndef NETSTATS_NB_SIZE +#define NETSTATS_NB_SIZE (8) +#endif + +/** + * @brief The CIB size for tx correlation + */ +#ifndef NETSTATS_NB_QUEUE_SIZE +#define NETSTATS_NB_QUEUE_SIZE (4) +#endif + /** * @name @ref net_netstats module names * @{ @@ -53,6 +69,64 @@ typedef struct { uint32_t rx_bytes; /**< received bytes */ } netstats_t; +/** + * @brief Stats per peer struct + */ +typedef struct { +#if IS_USED(MODULE_NETSTATS_NEIGHBOR_TX_TIME) || DOXYGEN + uint32_t time_tx_avg; /**< Average frame TX time in µs */ +#endif +#if IS_USED(MODULE_NETSTATS_NEIGHBOR_ETX) || DOXYGEN + uint16_t etx; /**< ETX of this peer */ +#endif +#if IS_USED(MODULE_NETSTATS_NEIGHBOR_COUNT) || DOXYGEN + uint16_t tx_count; /**< Number of sent frames to this peer */ + uint16_t tx_fail; /**< Number of sent frames that did not get ACKed */ + uint16_t rx_count; /**< Number of received frames */ +#endif + uint16_t last_updated; /**< seconds timestamp of last update */ + uint16_t last_halved; /**< seconds timestamp of last halving */ + uint8_t l2_addr[L2UTIL_ADDR_MAX_LEN]; /**< Link layer address of the neighbor */ + uint8_t l2_addr_len; /**< Length of netstats_nb::l2_addr */ + uint8_t freshness; /**< Freshness counter */ +#if IS_USED(MODULE_NETSTATS_NEIGHBOR_RSSI) || DOXYGEN + uint8_t rssi; /**< Average RSSI of received frames in abs([dBm]) */ +#endif +#if IS_USED(MODULE_NETSTATS_NEIGHBOR_LQI) || DOXYGEN + uint8_t lqi; /**< Average LQI of received frames */ +#endif +} netstats_nb_t; + +/** + * @brief L2 Peer Info struct + */ +typedef struct { + /** + * @brief CIB for the tx correlation + */ + cib_t stats_idx; + + /** + * @brief send/callback mac association array + */ + netstats_nb_t *stats_queue[NETSTATS_NB_QUEUE_SIZE]; + + /** + * @brief TX timestamp of stats_queue entries + */ + uint32_t stats_queue_time_tx[NETSTATS_NB_QUEUE_SIZE]; + + /** + * @brief Per neighbor statistics array + */ + netstats_nb_t pstats[NETSTATS_NB_SIZE]; + + /** + * @brief Neighbor Table access lock + */ + mutex_t lock; +} netstats_nb_table_t; + #ifdef __cplusplus } #endif diff --git a/sys/include/net/netstats/neighbor.h b/sys/include/net/netstats/neighbor.h new file mode 100644 index 0000000000..35c3c2d1f6 --- /dev/null +++ b/sys/include/net/netstats/neighbor.h @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2017 Koen Zandberg + * + * 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_netstats + * @brief Records statistics about link layer neighbors + * @{ + * + * @file + * @brief Neighbor stats definitions + * + * @author Koen Zandberg + */ +#ifndef NET_NETSTATS_NEIGHBOR_H +#define NET_NETSTATS_NEIGHBOR_H + +#include +#include "net/netif.h" +#include "xtimer.h" +#include "timex.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Result of the transmission + * @{ + */ +typedef enum { + NETSTATS_NB_BUSY, /**< Failed due to medium busy */ + NETSTATS_NB_NOACK, /**< Failed due to no ack received */ + NETSTATS_NB_SUCCESS, /**< Successful transmission */ +} netstats_nb_result_t; +/** @} */ + +/** + * @name @ref EWMA parameters + * @{ + */ +/** + * @brief Multiplication factor of the EWMA + */ +#define NETSTATS_NB_EWMA_SCALE 100 + +/** + * @brief Alpha factor of the EWMA + */ +#define NETSTATS_NB_EWMA_ALPHA 15 + +/** + * @brief Alpha factor of the EWMA when stats are not fresh + */ +#define NETSTATS_NB_EWMA_ALPHA_RAMP 30 +/** @} */ + +/** + * @name @ref ETX parameters + * @{ + */ +/** + * @brief ETX penalty for not receiving any ACK + */ +#define NETSTATS_NB_ETX_NOACK_PENALTY 6 +/** + * @brief ETX fixed point divisor (rfc 6551) + */ +#define NETSTATS_NB_ETX_DIVISOR 128 +/** + * @brief Initial ETX, assume a mediocre link + */ +#define NETSTATS_NB_ETX_INIT 2 +/** @} */ + +/** + * @name @ref Freshness parameters + * @{ + */ +/** + * @brief seconds after the freshness counter is halved + */ +#define NETSTATS_NB_FRESHNESS_HALF 600 +/** + * @brief freshness count needed before considering the statistics fresh + */ +#define NETSTATS_NB_FRESHNESS_TARGET 4 +/** + * @brief Maximum freshness + */ +#define NETSTATS_NB_FRESHNESS_MAX 16 +/** + * @brief seconds after statistics have expired + */ +#define NETSTATS_NB_FRESHNESS_EXPIRATION 1200 +/** @} */ +/** + * @name @ref Timeout Parameters + * @{ + */ +/** + * @brief milliseconds without TX done notification after which + * a TX event is discarded + */ +#define NETSTATS_NB_TX_TIMEOUT_MS 100 +/** @} */ + +/** + * @brief Initialize the neighbor stats + * + * @param[in] netif network interface descriptor + * + */ +void netstats_nb_init(netif_t *netif); + +/** + * @brief Find a neighbor stat by the mac address. + * + * @param[in] netif network interface descriptor + * @param[in] l2_addr pointer to the L2 address + * @param[in] len length of the L2 address + * @param[out] out destination for the matching neighbor entry + * + * @return true if a matching peer was found, false otherwise + */ +bool netstats_nb_get(netif_t *netif, const uint8_t *l2_addr, uint8_t len, netstats_nb_t *out); + +/** + * @brief Store this neighbor as next in the transmission queue. + * + * Set @p len to zero if a nop record is needed, for example if the + * transmission has a multicast address as a destination. + * + * @param[in] netif network interface descriptor + * @param[in] l2_addr pointer to the L2 address + * @param[in] len length of the L2 address + * + */ +void netstats_nb_record(netif_t *netif, const uint8_t *l2_addr, uint8_t len); + +/** + * @brief Update the next recorded neighbor with the provided numbers + * + * This only increments the statistics if the length of the l2-address of the retrieved record + * is non-zero. See also @ref netstats_nb_record. The numbers indicate the number of transmissions + * the radio had to perform before a successful transmission was performed. For example: in the case + * of a single send operation needing 3 tries before an ACK was received, there are 2 failed + * transmissions and 1 successful transmission. + * + * @param[in] netif network interface descriptor + * @param[in] result Result of the transmission + * @param[in] transmissions Number of times the packet was sent over the air + * + * @return pointer to the record + */ +netstats_nb_t *netstats_nb_update_tx(netif_t *netif, netstats_nb_result_t result, + uint8_t transmissions); + +/** + * @brief Record rx stats for the l2_addr + * + * @param[in] netif network interface descriptor + * @param[in] l2_addr pointer to the L2 address + * @param[in] l2_addr_len length of the L2 address + * @param[in] rssi RSSI of the received transmission in abs([dBm]) + * @param[in] lqi Link Quality Indication provided by the radio + * + * @return pointer to the updated record + */ +netstats_nb_t *netstats_nb_update_rx(netif_t *netif, const uint8_t *l2_addr, + uint8_t l2_addr_len, uint8_t rssi, uint8_t lqi); + +/** + * @brief Check if a record is fresh + * + * Freshness half time is checked and updated before verifying freshness. + * + * @param[in] netif network interface the statistic belongs to + * @param[in] stats pointer to the statistic + */ +bool netstats_nb_isfresh(netif_t *netif, netstats_nb_t *stats); + +#ifdef __cplusplus +} +#endif + +#endif /* NET_NETSTATS_NEIGHBOR_H */ +/** + * @} + */ diff --git a/sys/net/gnrc/netif/gnrc_netif.c b/sys/net/gnrc/netif/gnrc_netif.c index c295a89fdb..7e9527a5ed 100644 --- a/sys/net/gnrc/netif/gnrc_netif.c +++ b/sys/net/gnrc/netif/gnrc_netif.c @@ -35,9 +35,8 @@ #if IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR) #include "net/gnrc/sixlowpan/frag/sfr.h" #endif /* IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR) */ -#if IS_USED(MODULE_NETSTATS) #include "net/netstats.h" -#endif /* IS_USED(MODULE_NETSTATS) */ +#include "net/netstats/neighbor.h" #include "fmt.h" #include "log.h" #include "sched.h" @@ -78,6 +77,11 @@ int gnrc_netif_create(gnrc_netif_t *netif, char *stack, int stacksize, netif_register((netif_t*) netif); assert(netif->dev == NULL); netif->dev = netdev; + +#ifdef MODULE_NETSTATS_NEIGHBOR + netstats_nb_init(&netif->netif); +#endif + res = thread_create(stack, stacksize, priority, THREAD_CREATE_STACKTEST, _gnrc_netif_thread, (void *)netif, name); (void)res; @@ -1432,6 +1436,27 @@ static inline void _event_post(gnrc_netif_t *netif) #endif } +static void _process_receive_stats(gnrc_netif_t *netdev, gnrc_pktsnip_t *pkt) +{ + if (!IS_USED(MODULE_NETSTATS_NEIGHBOR)) { + return; + } + + gnrc_netif_hdr_t *hdr; + const uint8_t *src = NULL; + gnrc_pktsnip_t *netif = gnrc_pktsnip_search_type(pkt, GNRC_NETTYPE_NETIF); + + if (netif == NULL) { + return; + } + + size_t src_len; + hdr = netif->data; + src = gnrc_netif_hdr_get_src_addr(hdr); + src_len = hdr->src_l2addr_len; + netstats_nb_update_rx(&netdev->netif, src, src_len, hdr->rssi, hdr->lqi); +} + /** * @brief Retrieve the netif event queue if enabled * @@ -1535,6 +1560,23 @@ static void _send(gnrc_netif_t *netif, gnrc_pktsnip_t *pkt, bool push_back) * layer implementations in case `gnrc_netif_pktq` is included */ gnrc_pktbuf_hold(pkt, 1); #endif /* IS_USED(MODULE_GNRC_NETIF_PKTQ) */ + + /* Record send in neighbor statistics if destination is unicast */ + if (IS_USED(MODULE_NETSTATS_NEIGHBOR)) { + gnrc_netif_hdr_t *netif_hdr = pkt->data; + if (netif_hdr->flags & + (GNRC_NETIF_HDR_FLAGS_BROADCAST | GNRC_NETIF_HDR_FLAGS_MULTICAST)) { + DEBUG("l2 stats: Destination is multicast or unicast, NULL recorded\n"); + netstats_nb_record(&netif->netif, NULL, 0); + } else { + DEBUG("l2 stats: recording transmission\n"); + netstats_nb_record(&netif->netif, + gnrc_netif_hdr_get_dst_addr(netif_hdr), + netif_hdr->dst_l2addr_len); + } + } + + /* Split off the TX sync snip */ gnrc_pktsnip_t *tx_sync = IS_USED(MODULE_GNRC_TX_SYNC) ? gnrc_tx_sync_split(pkt) : NULL; res = netif->ops->send(netif, pkt); @@ -1542,6 +1584,21 @@ static void _send(gnrc_netif_t *netif, gnrc_pktsnip_t *pkt, bool push_back) uint32_t err = (res < 0) ? -res : GNRC_NETERR_SUCCESS; gnrc_pktbuf_release_error(tx_sync, err); } + + /* no frame was transmitted */ + if (res < 0) { + DEBUG("gnrc_netif: error sending packet %p (code: %i)\n", + (void *)pkt, res); + + if (IS_USED(MODULE_NETSTATS_NEIGHBOR)) { + netstats_nb_update_tx(&netif->netif, NETSTATS_NB_BUSY, 0); + } + } +#ifdef MODULE_NETSTATS_L2 + else { + netif->stats.tx_bytes += res; + } +#endif #if IS_USED(MODULE_GNRC_NETIF_PKTQ) if (res == -EBUSY) { int put_res; @@ -1576,15 +1633,6 @@ static void _send(gnrc_netif_t *netif, gnrc_pktsnip_t *pkt, bool push_back) gnrc_pktbuf_release(pkt); } #endif /* IS_USED(MODULE_GNRC_NETIF_PKTQ) */ - if (res < 0) { - DEBUG("gnrc_netif: error sending packet %p (code: %i)\n", - (void *)pkt, res); - } -#ifdef MODULE_NETSTATS_L2 - else { - netif->stats.tx_bytes += res; - } -#endif } static void *_gnrc_netif_thread(void *args) @@ -1757,6 +1805,7 @@ static void _event_cb(netdev_t *dev, netdev_event_t event) * Further packets will be sent on later TX_COMPLETE */ _send_queued_pkt(netif); if (pkt) { + _process_receive_stats(netif, pkt); _pass_on_packet(pkt); } break; @@ -1773,11 +1822,29 @@ static void _event_cb(netdev_t *dev, netdev_event_t event) * so no acquire necessary */ netif->stats.tx_success++; #endif /* IS_USED(MODULE_NETSTATS_L2) */ + if (IS_USED(MODULE_NETSTATS_NEIGHBOR)) { + int8_t retries = -1; + dev->driver->get(dev, NETOPT_TX_RETRIES_NEEDED, &retries, sizeof(retries)); + netstats_nb_update_tx(&netif->netif, NETSTATS_NB_SUCCESS, retries + 1); + } break; #endif /* IS_USED(MODULE_NETSTATS_L2) || IS_USED(MODULE_GNRC_NETIF_PKTQ) */ -#if IS_USED(MODULE_NETSTATS_L2) || IS_USED(MODULE_GNRC_NETIF_PKTQ) +#if IS_USED(MODULE_NETSTATS_L2) || IS_USED(MODULE_GNRC_NETIF_PKTQ) || \ + IS_USED(MODULE_NETSTATS_NEIGHBOR) case NETDEV_EVENT_TX_MEDIUM_BUSY: case NETDEV_EVENT_TX_NOACK: + /* update neighbor statistics */ + if (IS_USED(MODULE_NETSTATS_NEIGHBOR)) { + int8_t retries = -1; + netstats_nb_result_t result; + if (event == NETDEV_EVENT_TX_NOACK) { + result = NETSTATS_NB_NOACK; + dev->driver->get(dev, NETOPT_TX_RETRIES_NEEDED, &retries, sizeof(retries)); + } else { + result = NETSTATS_NB_BUSY; + } + netstats_nb_update_tx(&netif->netif, result, retries + 1); + } /* send packet previously queued within netif due to the lower * layer being busy. * Further packets will be sent on later TX_COMPLETE or diff --git a/sys/net/netstats/Makefile b/sys/net/netstats/Makefile new file mode 100644 index 0000000000..adb30c19ac --- /dev/null +++ b/sys/net/netstats/Makefile @@ -0,0 +1,3 @@ +MODULE = netstats_neighbor + +include $(RIOTBASE)/Makefile.base diff --git a/sys/net/netstats/netstats_neighbor.c b/sys/net/netstats/netstats_neighbor.c new file mode 100644 index 0000000000..2c95b6ddba --- /dev/null +++ b/sys/net/netstats/netstats_neighbor.c @@ -0,0 +1,400 @@ +/* + * Copyright (C) Koen Zandberg + * + * 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 + * @author Benjamin Valentin + * @} + */ + +#include + +#include "net/l2util.h" +#include "net/netdev.h" +#include "net/netstats/neighbor.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; +} diff --git a/sys/shell/commands/Makefile b/sys/shell/commands/Makefile index 7aead94d83..d923c19ca5 100644 --- a/sys/shell/commands/Makefile +++ b/sys/shell/commands/Makefile @@ -35,6 +35,9 @@ endif ifneq (,$(filter gnrc_netif,$(USEMODULE))) SRC += sc_gnrc_netif.c endif +ifneq (,$(filter netstats_neighbor,$(USEMODULE))) + SRC += sc_netstats_nb.c +endif ifneq (,$(filter fib,$(USEMODULE))) SRC += sc_fib.c endif diff --git a/sys/shell/commands/sc_netstats_nb.c b/sys/shell/commands/sc_netstats_nb.c new file mode 100644 index 0000000000..32f8ede7dc --- /dev/null +++ b/sys/shell/commands/sc_netstats_nb.c @@ -0,0 +1,102 @@ +/* + * Copyright (C) Koen Zandberg + * + * 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 sys_shell_commands + * @{ + * + * @file + * @brief Shell commands for displaying neighbor statistics + * + * @author Koen Zandberg + * @author Benjamin Valentin + * + * @} + */ + +#include + +#include "net/gnrc/netif.h" +#include "net/netstats.h" +#include "net/netstats/neighbor.h" + +static void _print_neighbors(netif_t *dev) +{ + netstats_nb_t *stats = &dev->neighbors.pstats[0]; + unsigned header_len = 0; + char l2addr_str[3 * L2UTIL_ADDR_MAX_LEN]; + puts("Neighbor link layer stats:"); + header_len += printf("L2 address fresh"); + if (IS_USED(MODULE_NETSTATS_NEIGHBOR_ETX)) { + header_len += printf(" etx"); + } + if (IS_USED(MODULE_NETSTATS_NEIGHBOR_COUNT)) { + header_len += printf(" sent received"); + } + if (IS_USED(MODULE_NETSTATS_NEIGHBOR_RSSI)) { + header_len += printf(" rssi "); + } + if (IS_USED(MODULE_NETSTATS_NEIGHBOR_LQI)) { + header_len += printf(" lqi"); + } + if (IS_USED(MODULE_NETSTATS_NEIGHBOR_TX_TIME)) { + header_len += printf(" avg tx time"); + } + printf("\n"); + + while (header_len--) { + printf("-"); + } + printf("\n"); + + for (unsigned i = 0; i < NETSTATS_NB_SIZE; ++i) { + netstats_nb_t *entry = &stats[i]; + + if (entry->l2_addr_len == 0) { + continue; + } + + printf("%-24s ", + gnrc_netif_addr_to_str(entry->l2_addr, entry->l2_addr_len, l2addr_str)); + if (netstats_nb_isfresh(dev, entry)) { + printf("%5u", (unsigned)entry->freshness); + } else { + printf("STALE"); + } + +#if IS_USED(MODULE_NETSTATS_NEIGHBOR_ETX) + printf(" %3u%%", (100 * entry->etx) / NETSTATS_NB_ETX_DIVISOR); +#endif +#if IS_USED(MODULE_NETSTATS_NEIGHBOR_COUNT) + printf(" %4"PRIu16" %8"PRIu16, entry->tx_count, entry->rx_count); +#endif +#if IS_USED(MODULE_NETSTATS_NEIGHBOR_RSSI) + printf(" %4i dBm", (int8_t) entry->rssi); +#endif +#if IS_USED(MODULE_NETSTATS_NEIGHBOR_LQI) + printf(" %u", entry->lqi); +#endif +#if IS_USED(MODULE_NETSTATS_NEIGHBOR_TX_TIME) + printf(" %7"PRIu32" µs", entry->time_tx_avg); +#endif + printf("\n"); + } +} + +int _netstats_nb(int argc, char **argv) +{ + (void) argc; + (void) argv; + + gnrc_netif_t *netif = NULL; + while ((netif = gnrc_netif_iter(netif))) { + _print_neighbors(&netif->netif); + } + + return 0; +} diff --git a/sys/shell/commands/shell_commands.c b/sys/shell/commands/shell_commands.c index 53cb988066..149b717597 100644 --- a/sys/shell/commands/shell_commands.c +++ b/sys/shell/commands/shell_commands.c @@ -92,6 +92,10 @@ extern int _random_get(int argc, char **argv); extern int _gnrc_ipv6_nib(int argc, char **argv); #endif +#ifdef MODULE_NETSTATS_NEIGHBOR +extern int _netstats_nb(int argc, char **argv); +#endif + #ifdef MODULE_GNRC_NETIF extern int _gnrc_netif_config(int argc, char **argv); #ifdef MODULE_GNRC_TXTSND @@ -245,6 +249,9 @@ const shell_command_t _shell_command_list[] = { #ifdef MODULE_GNRC_IPV6_NIB {"nib", "Configure neighbor information base", _gnrc_ipv6_nib}, #endif +#ifdef MODULE_NETSTATS_NEIGHBOR + {"neigh", "Show neighbor statistics", _netstats_nb}, +#endif #ifdef MODULE_GNRC_NETIF {"ifconfig", "Configure network interfaces", _gnrc_netif_config}, #ifdef MODULE_GNRC_TXTSND diff --git a/tests/netstats_neighbor/Makefile b/tests/netstats_neighbor/Makefile new file mode 100644 index 0000000000..9d9283b779 --- /dev/null +++ b/tests/netstats_neighbor/Makefile @@ -0,0 +1,9 @@ +USEMODULE = netdev_default + +USEMODULE += netstats_neighbor_etx +USEMODULE += netstats_neighbor_count +USEMODULE += netstats_neighbor_rssi +USEMODULE += netstats_neighbor_lqi +USEMODULE += netstats_neighbor_tx_time + +include ../driver_netdev_common/Makefile.netdev.mk diff --git a/tests/netstats_neighbor/Makefile.ci b/tests/netstats_neighbor/Makefile.ci new file mode 100644 index 0000000000..c881c6a742 --- /dev/null +++ b/tests/netstats_neighbor/Makefile.ci @@ -0,0 +1,23 @@ +BOARD_INSUFFICIENT_MEMORY := \ + arduino-duemilanove \ + arduino-leonardo \ + arduino-mega2560 \ + arduino-nano \ + arduino-uno \ + atmega328p \ + bluepill-stm32f030c8 \ + i-nucleo-lrwan1 \ + nucleo-f030r8 \ + nucleo-f031k6 \ + nucleo-f042k6 \ + nucleo-l011k4 \ + nucleo-l031k6 \ + nucleo-l053r8 \ + samd10-xmini \ + slstk3400a \ + stk3200 \ + stm32f030f4-demo \ + stm32f0discovery \ + stm32l0538-disco \ + waspmote-pro \ + # diff --git a/tests/netstats_neighbor/main.c b/tests/netstats_neighbor/main.c new file mode 120000 index 0000000000..a3f88db08e --- /dev/null +++ b/tests/netstats_neighbor/main.c @@ -0,0 +1 @@ +../driver_netdev_common/main.c \ No newline at end of file