1/**************************************************************************
2 *
3 * Copyright (C) 2016 Steven Toth <stoth@kernellabs.com>
4 * Copyright (C) 2016 Zodiac Inflight Innovations
5 * All Rights Reserved.
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a
8 * copy of this software and associated documentation files (the
9 * "Software"), to deal in the Software without restriction, including
10 * without limitation the rights to use, copy, modify, merge, publish,
11 * distribute, sub license, and/or sell copies of the Software, and to
12 * permit persons to whom the Software is furnished to do so, subject to
13 * the following conditions:
14 *
15 * The above copyright notice and this permission notice (including the
16 * next paragraph) shall be included in all copies or substantial portions
17 * of the Software.
18 *
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
20 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
22 * IN NO EVENT SHALL THE AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR
23 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
24 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
25 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 *
27 **************************************************************************/
28
29#ifdef HAVE_GALLIUM_EXTRA_HUD
30
31/* Purpose: Reading network interface RX/TX throughput per second,
32 * displaying on the HUD.
33 */
34
35#include "hud/hud_private.h"
36#include "util/list.h"
37#include "util/os_time.h"
38#include "os/os_thread.h"
39#include "util/u_memory.h"
40#include <stdio.h>
41#include <unistd.h>
42#include <dirent.h>
43#include <stdlib.h>
44#include <unistd.h>
45#include <inttypes.h>
46#include <sys/types.h>
47#include <sys/stat.h>
48#include <sys/socket.h>
49#include <sys/ioctl.h>
50#include <linux/wireless.h>
51
52struct nic_info
53{
54   struct list_head list;
55   int mode;
56   char name[64];
57   uint64_t speedMbps;
58   int is_wireless;
59
60   char throughput_filename[128];
61   uint64_t last_time;
62   uint64_t last_nic_bytes;
63};
64
65/* TODO: We don't handle dynamic NIC arrival or removal.
66 * Static globals specific to this HUD category.
67 */
68static int gnic_count = 0;
69static struct list_head gnic_list;
70static mtx_t gnic_mutex = _MTX_INITIALIZER_NP;
71
72static struct nic_info *
73find_nic_by_name(const char *n, int mode)
74{
75   list_for_each_entry(struct nic_info, nic, &gnic_list, list) {
76      if (nic->mode != mode)
77         continue;
78
79      if (strcasecmp(nic->name, n) == 0)
80         return nic;
81   }
82   return 0;
83}
84
85static int
86get_file_value(const char *fname, uint64_t *value)
87{
88   FILE *fh = fopen(fname, "r");
89   if (!fh)
90      return -1;
91   if (fscanf(fh, "%" PRIu64 "", value) != 0) {
92      /* Error */
93   }
94   fclose(fh);
95   return 0;
96}
97
98static boolean
99get_nic_bytes(const char *fn, uint64_t *bytes)
100{
101   if (get_file_value(fn, bytes) < 0)
102      return FALSE;
103
104   return TRUE;
105}
106
107static void
108query_wifi_bitrate(const struct nic_info *nic, uint64_t *bitrate)
109{
110   int sockfd;
111   struct iw_statistics stats;
112   struct iwreq req;
113
114   memset(&stats, 0, sizeof(stats));
115   memset(&req, 0, sizeof(req));
116
117   snprintf(req.ifr_name, sizeof(req.ifr_name), "%s", nic->name);
118   req.u.data.pointer = &stats;
119   req.u.data.flags = 1;
120   req.u.data.length = sizeof(struct iw_statistics);
121
122   /* Any old socket will do, and a datagram socket is pretty cheap */
123   if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
124      fprintf(stderr, "Unable to create socket for %s\n", nic->name);
125      return;
126   }
127
128   if (ioctl(sockfd, SIOCGIWRATE, &req) == -1) {
129      fprintf(stderr, "Error performing SIOCGIWSTATS on %s\n", nic->name);
130      close(sockfd);
131      return;
132   }
133   *bitrate = req.u.bitrate.value;
134
135   close(sockfd);
136}
137
138static void
139query_nic_rssi(const struct nic_info *nic, uint64_t *leveldBm)
140{
141   int sockfd;
142   struct iw_statistics stats;
143   struct iwreq req;
144
145   memset(&stats, 0, sizeof(stats));
146   memset(&req, 0, sizeof(req));
147
148   snprintf(req.ifr_name, sizeof(req.ifr_name), "%s", nic->name);
149   req.u.data.pointer = &stats;
150   req.u.data.flags = 1;
151   req.u.data.length = sizeof(struct iw_statistics);
152
153   if (nic->mode != NIC_RSSI_DBM)
154      return;
155
156   /* Any old socket will do, and a datagram socket is pretty cheap */
157   if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
158      fprintf(stderr, "Unable to create socket for %s\n", nic->name);
159      return;
160   }
161
162   /* Perform the ioctl */
163   if (ioctl(sockfd, SIOCGIWSTATS, &req) == -1) {
164      fprintf(stderr, "Error performing SIOCGIWSTATS on %s\n", nic->name);
165      close(sockfd);
166      return;
167   }
168   *leveldBm = ((char) stats.qual.level * -1);
169
170   close(sockfd);
171}
172
173static void
174query_nic_load(struct hud_graph *gr, struct pipe_context *pipe)
175{
176   /* The framework calls us at a regular but indefined period,
177    * not once per second, compensate the statistics accordingly.
178    */
179
180   struct nic_info *nic = gr->query_data;
181   uint64_t now = os_time_get();
182
183   if (nic->last_time) {
184      if (nic->last_time + gr->pane->period <= now) {
185         switch (nic->mode) {
186         case NIC_DIRECTION_RX:
187         case NIC_DIRECTION_TX:
188            {
189               uint64_t bytes;
190               get_nic_bytes(nic->throughput_filename, &bytes);
191               uint64_t nic_mbps =
192                  ((bytes - nic->last_nic_bytes) / 1000000) * 8;
193
194               float speedMbps = nic->speedMbps;
195               float periodMs = gr->pane->period / 1000.0;
196               float bits = nic_mbps;
197               float period_factor = periodMs / 1000;
198               float period_speed = speedMbps * period_factor;
199               float pct = (bits / period_speed) * 100;
200
201               /* Scaling bps with a narrow time period into a second,
202                * potentially suffers from rounding errors at higher
203                * periods. Eg 104%. Compensate.
204                */
205               if (pct > 100)
206                  pct = 100;
207               hud_graph_add_value(gr, (uint64_t) pct);
208
209               nic->last_nic_bytes = bytes;
210            }
211            break;
212         case NIC_RSSI_DBM:
213            {
214               uint64_t leveldBm = 0;
215               query_nic_rssi(nic, &leveldBm);
216               hud_graph_add_value(gr, leveldBm);
217            }
218            break;
219         }
220
221         nic->last_time = now;
222      }
223   }
224   else {
225      /* initialize */
226      switch (nic->mode) {
227      case NIC_DIRECTION_RX:
228      case NIC_DIRECTION_TX:
229         get_nic_bytes(nic->throughput_filename, &nic->last_nic_bytes);
230         break;
231      case NIC_RSSI_DBM:
232         break;
233      }
234
235      nic->last_time = now;
236   }
237}
238
239/**
240  * Create and initialize a new object for a specific network interface dev.
241  * \param  pane  parent context.
242  * \param  nic_name  logical block device name, EG. eth0.
243  * \param  mode  query type (NIC_DIRECTION_RX/WR/RSSI) statistics.
244  */
245void
246hud_nic_graph_install(struct hud_pane *pane, const char *nic_name,
247                      unsigned int mode)
248{
249   struct hud_graph *gr;
250   struct nic_info *nic;
251
252   int num_nics = hud_get_num_nics(0);
253   if (num_nics <= 0)
254      return;
255
256   nic = find_nic_by_name(nic_name, mode);
257   if (!nic)
258      return;
259
260   gr = CALLOC_STRUCT(hud_graph);
261   if (!gr)
262      return;
263
264   nic->mode = mode;
265   if (nic->mode == NIC_DIRECTION_RX) {
266      snprintf(gr->name, sizeof(gr->name), "%s-rx-%"PRId64"Mbps", nic->name,
267         nic->speedMbps);
268   }
269   else if (nic->mode == NIC_DIRECTION_TX) {
270      snprintf(gr->name, sizeof(gr->name), "%s-tx-%"PRId64"Mbps", nic->name,
271         nic->speedMbps);
272   }
273   else if (nic->mode == NIC_RSSI_DBM)
274      snprintf(gr->name, sizeof(gr->name), "%s-rssi", nic->name);
275   else {
276      free(gr);
277      return;
278   }
279
280   gr->query_data = nic;
281   gr->query_new_value = query_nic_load;
282
283   hud_pane_add_graph(pane, gr);
284   hud_pane_set_max_value(pane, 100);
285}
286
287static int
288is_wireless_nic(const char *dirbase)
289{
290   struct stat stat_buf;
291
292   /* Check if its a wireless card */
293   char fn[256];
294   snprintf(fn, sizeof(fn), "%s/wireless", dirbase);
295   if (stat(fn, &stat_buf) == 0)
296      return 1;
297
298   return 0;
299}
300
301static void
302query_nic_bitrate(struct nic_info *nic, const char *dirbase)
303{
304   struct stat stat_buf;
305
306   /* Check if its a wireless card */
307   char fn[256];
308   snprintf(fn, sizeof(fn), "%s/wireless", dirbase);
309   if (stat(fn, &stat_buf) == 0) {
310      /* we're a wireless nic */
311      query_wifi_bitrate(nic, &nic->speedMbps);
312      nic->speedMbps /= 1000000;
313   }
314   else {
315      /* Must be a wired nic */
316      snprintf(fn, sizeof(fn), "%s/speed", dirbase);
317      get_file_value(fn, &nic->speedMbps);
318   }
319}
320
321/**
322  * Initialize internal object arrays and display NIC HUD help.
323  * \param  displayhelp  true if the list of detected devices should be
324                         displayed on the console.
325  * \return  number of detected network interface devices.
326  */
327int
328hud_get_num_nics(bool displayhelp)
329{
330   struct dirent *dp;
331   struct stat stat_buf;
332   struct nic_info *nic;
333   char name[64];
334
335   /* Return the number if network interfaces. */
336   mtx_lock(&gnic_mutex);
337   if (gnic_count) {
338      mtx_unlock(&gnic_mutex);
339      return gnic_count;
340   }
341
342   /* Scan /sys/block, for every object type we support, create and
343    * persist an object to represent its different statistics.
344    */
345   list_inithead(&gnic_list);
346   DIR *dir = opendir("/sys/class/net/");
347   if (!dir) {
348      mtx_unlock(&gnic_mutex);
349      return 0;
350   }
351
352   while ((dp = readdir(dir)) != NULL) {
353
354      /* Avoid 'lo' and '..' and '.' */
355      if (strlen(dp->d_name) <= 2)
356         continue;
357
358      char basename[256];
359      snprintf(basename, sizeof(basename), "/sys/class/net/%s", dp->d_name);
360      snprintf(name, sizeof(name), "%s/statistics/rx_bytes", basename);
361      if (stat(name, &stat_buf) < 0)
362         continue;
363
364      if (!S_ISREG(stat_buf.st_mode))
365         continue;              /* Not a regular file */
366
367      int is_wireless = is_wireless_nic(basename);
368
369      /* Add the RX object */
370      nic = CALLOC_STRUCT(nic_info);
371      strcpy(nic->name, dp->d_name);
372      snprintf(nic->throughput_filename, sizeof(nic->throughput_filename),
373         "%s/statistics/rx_bytes", basename);
374      nic->mode = NIC_DIRECTION_RX;
375      nic->is_wireless = is_wireless;
376      query_nic_bitrate(nic, basename);
377
378      list_addtail(&nic->list, &gnic_list);
379      gnic_count++;
380
381      /* Add the TX object */
382      nic = CALLOC_STRUCT(nic_info);
383      strcpy(nic->name, dp->d_name);
384      snprintf(nic->throughput_filename,
385         sizeof(nic->throughput_filename),
386         "/sys/class/net/%s/statistics/tx_bytes", dp->d_name);
387      nic->mode = NIC_DIRECTION_TX;
388      nic->is_wireless = is_wireless;
389
390      query_nic_bitrate(nic, basename);
391
392      list_addtail(&nic->list, &gnic_list);
393      gnic_count++;
394
395      if (nic->is_wireless) {
396         /* RSSI Support */
397         nic = CALLOC_STRUCT(nic_info);
398         strcpy(nic->name, dp->d_name);
399         snprintf(nic->throughput_filename,
400            sizeof(nic->throughput_filename),
401            "/sys/class/net/%s/statistics/tx_bytes", dp->d_name);
402         nic->mode = NIC_RSSI_DBM;
403
404         query_nic_bitrate(nic, basename);
405
406         list_addtail(&nic->list, &gnic_list);
407         gnic_count++;
408      }
409
410   }
411   closedir(dir);
412
413   list_for_each_entry(struct nic_info, nic, &gnic_list, list) {
414      char line[64];
415      snprintf(line, sizeof(line), "    nic-%s-%s",
416              nic->mode == NIC_DIRECTION_RX ? "rx" :
417              nic->mode == NIC_DIRECTION_TX ? "tx" :
418              nic->mode == NIC_RSSI_DBM ? "rssi" : "undefined", nic->name);
419
420      puts(line);
421
422   }
423
424   mtx_unlock(&gnic_mutex);
425   return gnic_count;
426}
427
428#endif /* HAVE_GALLIUM_EXTRA_HUD */
429