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