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