Contiki-NG
link-stats.c
1 /*
2  * Copyright (c) 2015, SICS Swedish ICT.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  * notice, this list of conditions and the following disclaimer in the
12  * documentation and/or other materials provided with the distribution.
13  * 3. Neither the name of the Institute nor the names of its contributors
14  * may be used to endorse or promote products derived from this software
15  * without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  *
29  *
30  * Authors: Simon Duquennoy <simonduq@sics.se>
31  * Atis Elsts <atis.elsts@edi.lv>
32  */
33 
34 #include "contiki.h"
35 #include "sys/clock.h"
36 #include "net/packetbuf.h"
37 #include "net/nbr-table.h"
38 #include "net/link-stats.h"
39 #include <stdio.h>
40 
41 /* Log configuration */
42 #include "sys/log.h"
43 #define LOG_MODULE "Link Stats"
44 #define LOG_LEVEL LOG_LEVEL_MAC
45 
46 /* Maximum value for the Tx count counter */
47 #define TX_COUNT_MAX 32
48 
49 /* Statistics with no update in FRESHNESS_EXPIRATION_TIMEOUT is not fresh */
50 #define FRESHNESS_EXPIRATION_TIME (10 * 60 * (clock_time_t)CLOCK_SECOND)
51 /* Half time for the freshness counter */
52 #define FRESHNESS_HALF_LIFE (15 * 60 * (clock_time_t)CLOCK_SECOND)
53 /* Statistics are fresh if the freshness counter is FRESHNESS_TARGET or more */
54 #define FRESHNESS_TARGET 4
55 /* Maximum value for the freshness counter */
56 #define FRESHNESS_MAX 16
57 
58 /* EWMA (exponential moving average) used to maintain statistics over time */
59 #define EWMA_SCALE 100
60 #define EWMA_ALPHA 10
61 #define EWMA_BOOTSTRAP_ALPHA 25
62 
63 /* ETX fixed point divisor. 128 is the value used by RPL (RFC 6551 and RFC 6719) */
64 #define ETX_DIVISOR LINK_STATS_ETX_DIVISOR
65 /* In case of no-ACK, add ETX_NOACK_PENALTY to the real Tx count, as a penalty */
66 #define ETX_NOACK_PENALTY 12
67 /* Initial ETX value */
68 #define ETX_DEFAULT 2
69 
70 /* Per-neighbor link statistics table */
71 NBR_TABLE(struct link_stats, link_stats);
72 
73 /* Called at a period of FRESHNESS_HALF_LIFE */
74 struct ctimer periodic_timer;
75 
76 /*---------------------------------------------------------------------------*/
77 /* Returns the neighbor's link stats */
78 const struct link_stats *
79 link_stats_from_lladdr(const linkaddr_t *lladdr)
80 {
81  return nbr_table_get_from_lladdr(link_stats, lladdr);
82 }
83 /*---------------------------------------------------------------------------*/
84 /* Returns the neighbor's address given a link stats item */
85 const linkaddr_t *
86 link_stats_get_lladdr(const struct link_stats *stat)
87 {
88  return nbr_table_get_lladdr(link_stats, stat);
89 }
90 /*---------------------------------------------------------------------------*/
91 /* Are the statistics fresh? */
92 int
93 link_stats_is_fresh(const struct link_stats *stats)
94 {
95  return (stats != NULL)
96  && clock_time() - stats->last_tx_time < FRESHNESS_EXPIRATION_TIME
97  && stats->freshness >= FRESHNESS_TARGET;
98 }
99 /*---------------------------------------------------------------------------*/
100 #if LINK_STATS_INIT_ETX_FROM_RSSI
101 uint16_t
102 guess_etx_from_rssi(const struct link_stats *stats)
103 {
104  if(stats != NULL) {
105  if(stats->rssi == LINK_STATS_RSSI_UNKNOWN) {
106  return ETX_DEFAULT * ETX_DIVISOR;
107  } else {
108  /* A rough estimate of PRR from RSSI, as a linear function where:
109  * RSSI >= -60 results in PRR of 1
110  * RSSI <= -90 results in PRR of 0
111  * prr = (bounded_rssi - RSSI_LOW) / (RSSI_DIFF)
112  * etx = ETX_DIVOSOR / ((bounded_rssi - RSSI_LOW) / RSSI_DIFF)
113  * etx = (RSSI_DIFF * ETX_DIVOSOR) / (bounded_rssi - RSSI_LOW)
114  * */
115 #define ETX_INIT_MAX 3
116 #define RSSI_HIGH -60
117 #define RSSI_LOW -90
118 #define RSSI_DIFF (RSSI_HIGH - RSSI_LOW)
119  uint16_t etx;
120  int16_t bounded_rssi = stats->rssi;
121  bounded_rssi = MIN(bounded_rssi, RSSI_HIGH);
122  bounded_rssi = MAX(bounded_rssi, RSSI_LOW + 1);
123  etx = RSSI_DIFF * ETX_DIVISOR / (bounded_rssi - RSSI_LOW);
124  return MIN(etx, ETX_INIT_MAX * ETX_DIVISOR);
125  }
126  }
127  return 0xffff;
128 }
129 #endif /* LINK_STATS_INIT_ETX_FROM_RSSI */
130 /*---------------------------------------------------------------------------*/
131 /* Packet sent callback. Updates stats for transmissions to lladdr */
132 void
133 link_stats_packet_sent(const linkaddr_t *lladdr, int status, int numtx)
134 {
135  struct link_stats *stats;
136 #if !LINK_STATS_ETX_FROM_PACKET_COUNT
137  uint16_t packet_etx;
138  uint8_t ewma_alpha;
139 #endif /* !LINK_STATS_ETX_FROM_PACKET_COUNT */
140 
141  if(status != MAC_TX_OK && status != MAC_TX_NOACK && status != MAC_TX_QUEUE_FULL) {
142  /* Do not penalize the ETX when collisions or transmission errors occur. */
143  return;
144  }
145 
146  stats = nbr_table_get_from_lladdr(link_stats, lladdr);
147  if(stats == NULL) {
148  /* If transmission failed, do not add the neighbor, as the neighbor might not exist anymore */
149  if(status != MAC_TX_OK) {
150  return;
151  }
152 
153  /* Add the neighbor */
154  stats = nbr_table_add_lladdr(link_stats, lladdr, NBR_TABLE_REASON_LINK_STATS, NULL);
155  if(stats == NULL) {
156  return; /* No space left, return */
157  }
158  stats->rssi = LINK_STATS_RSSI_UNKNOWN;
159  }
160 
161  if(status == MAC_TX_QUEUE_FULL) {
162 #if LINK_STATS_PACKET_COUNTERS
163  stats->cnt_current.num_queue_drops += 1;
164 #endif
165  /* Do not penalize the ETX when the packet is dropped due to a full queue */
166  return;
167  }
168 
169  /* Update last timestamp and freshness */
170  stats->last_tx_time = clock_time();
171  stats->freshness = MIN(stats->freshness + numtx, FRESHNESS_MAX);
172 
173 #if LINK_STATS_PACKET_COUNTERS
174  /* Update paket counters */
175  stats->cnt_current.num_packets_tx += numtx;
176  if(status == MAC_TX_OK) {
177  stats->cnt_current.num_packets_acked++;
178  }
179 #endif
180 
181  /* Add penalty in case of no-ACK */
182  if(status == MAC_TX_NOACK) {
183  numtx += ETX_NOACK_PENALTY;
184  }
185 
186 #if LINK_STATS_ETX_FROM_PACKET_COUNT
187  /* Compute ETX from packet and ACK count */
188  /* Halve both counter after TX_COUNT_MAX */
189  if(stats->tx_count + numtx > TX_COUNT_MAX) {
190  stats->tx_count /= 2;
191  stats->ack_count /= 2;
192  }
193  /* Update tx_count and ack_count */
194  stats->tx_count += numtx;
195  if(status == MAC_TX_OK) {
196  stats->ack_count++;
197  }
198  /* Compute ETX */
199  if(stats->ack_count > 0) {
200  stats->etx = ((uint16_t)stats->tx_count * ETX_DIVISOR) / stats->ack_count;
201  } else {
202  stats->etx = (uint16_t)MAX(ETX_NOACK_PENALTY, stats->tx_count) * ETX_DIVISOR;
203  }
204 #else /* LINK_STATS_ETX_FROM_PACKET_COUNT */
205  /* Compute ETX using an EWMA */
206 
207  /* ETX used for this update */
208  packet_etx = numtx * ETX_DIVISOR;
209  /* ETX alpha used for this update */
210  ewma_alpha = link_stats_is_fresh(stats) ? EWMA_ALPHA : EWMA_BOOTSTRAP_ALPHA;
211 
212  if(stats->etx == 0) {
213  /* Initialize ETX */
214  stats->etx = packet_etx;
215  } else {
216  /* Compute EWMA and update ETX */
217  stats->etx = ((uint32_t)stats->etx * (EWMA_SCALE - ewma_alpha) +
218  (uint32_t)packet_etx * ewma_alpha) / EWMA_SCALE;
219  }
220 #endif /* LINK_STATS_ETX_FROM_PACKET_COUNT */
221 }
222 /*---------------------------------------------------------------------------*/
223 /* Packet input callback. Updates statistics for receptions on a given link */
224 void
225 link_stats_input_callback(const linkaddr_t *lladdr)
226 {
227  struct link_stats *stats;
228  int16_t packet_rssi = packetbuf_attr(PACKETBUF_ATTR_RSSI);
229 
230  stats = nbr_table_get_from_lladdr(link_stats, lladdr);
231  if(stats == NULL) {
232  /* Add the neighbor */
233  stats = nbr_table_add_lladdr(link_stats, lladdr, NBR_TABLE_REASON_LINK_STATS, NULL);
234  if(stats == NULL) {
235  return; /* No space left, return */
236  }
237  stats->rssi = LINK_STATS_RSSI_UNKNOWN;
238  }
239 
240  if(stats->rssi == LINK_STATS_RSSI_UNKNOWN) {
241  /* Initialize RSSI */
242  stats->rssi = packet_rssi;
243  } else {
244  /* Update RSSI EWMA */
245  stats->rssi = ((int32_t)stats->rssi * (EWMA_SCALE - EWMA_ALPHA) +
246  (int32_t)packet_rssi * EWMA_ALPHA) / EWMA_SCALE;
247  }
248 
249  if(stats->etx == 0) {
250  /* Initialize ETX */
251 #if LINK_STATS_INIT_ETX_FROM_RSSI
252  stats->etx = guess_etx_from_rssi(stats);
253 #else /* LINK_STATS_INIT_ETX_FROM_RSSI */
254  stats->etx = ETX_DEFAULT * ETX_DIVISOR;
255 #endif /* LINK_STATS_INIT_ETX_FROM_RSSI */
256  }
257 
258 #if LINK_STATS_PACKET_COUNTERS
259  stats->cnt_current.num_packets_rx++;
260 #endif
261 }
262 /*---------------------------------------------------------------------------*/
263 #if LINK_STATS_PACKET_COUNTERS
264 /*---------------------------------------------------------------------------*/
265 static void
266 print_and_update_counters(void)
267 {
268  struct link_stats *stats;
269 
270  for(stats = nbr_table_head(link_stats); stats != NULL;
271  stats = nbr_table_next(link_stats, stats)) {
272 
273  struct link_packet_counter *c = &stats->cnt_current;
274 
275  LOG_INFO("num packets: tx=%u ack=%u rx=%u queue_drops=%u to=",
276  c->num_packets_tx, c->num_packets_acked,
277  c->num_packets_rx, c->num_queue_drops);
278  LOG_INFO_LLADDR(link_stats_get_lladdr(stats));
279  LOG_INFO_("\n");
280 
281  stats->cnt_total.num_packets_tx += stats->cnt_current.num_packets_tx;
282  stats->cnt_total.num_packets_acked += stats->cnt_current.num_packets_acked;
283  stats->cnt_total.num_packets_rx += stats->cnt_current.num_packets_rx;
284  stats->cnt_total.num_queue_drops += stats->cnt_current.num_queue_drops;
285  memset(&stats->cnt_current, 0, sizeof(stats->cnt_current));
286  }
287 }
288 /*---------------------------------------------------------------------------*/
289 #endif /* LINK_STATS_PACKET_COUNTERS */
290 /*---------------------------------------------------------------------------*/
291 /* Periodic timer called at a period of FRESHNESS_HALF_LIFE */
292 static void
293 periodic(void *ptr)
294 {
295  /* Age (by halving) freshness counter of all neighbors */
296  struct link_stats *stats;
297  ctimer_reset(&periodic_timer);
298  for(stats = nbr_table_head(link_stats); stats != NULL; stats = nbr_table_next(link_stats, stats)) {
299  stats->freshness >>= 1;
300  }
301 
302 #if LINK_STATS_PACKET_COUNTERS
303  print_and_update_counters();
304 #endif
305 }
306 /*---------------------------------------------------------------------------*/
307 /* Resets link-stats module */
308 void
309 link_stats_reset(void)
310 {
311  struct link_stats *stats;
312  stats = nbr_table_head(link_stats);
313  while(stats != NULL) {
314  nbr_table_remove(link_stats, stats);
315  stats = nbr_table_next(link_stats, stats);
316  }
317 }
318 /*---------------------------------------------------------------------------*/
319 /* Initializes link-stats module */
320 void
321 link_stats_init(void)
322 {
323  nbr_table_register(link_stats, NULL);
324  ctimer_set(&periodic_timer, FRESHNESS_HALF_LIFE, periodic, NULL);
325 }
The MAC layer deferred the transmission for a later time.
Definition: mac.h:94
void ctimer_reset(struct ctimer *c)
Reset a callback timer with the same interval as was previously set.
Definition: ctimer.c:125
void ctimer_set(struct ctimer *c, clock_time_t t, void(*f)(void *), void *ptr)
Set a callback timer.
Definition: ctimer.c:99
clock_time_t clock_time(void)
Get the current clock time.
Definition: clock.c:118
The MAC layer transmission was OK.
Definition: mac.h:87
Header file for the Packet buffer (packetbuf) management
Header file for the logging system