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