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 53struct 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 */ 69static int gnic_count = 0; 70static struct list_head gnic_list; 71static mtx_t gnic_mutex = _MTX_INITIALIZER_NP; 72 73static struct nic_info * 74find_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 86static int 87get_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 99static boolean 100get_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 108static void 109query_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 139static void 140query_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 174static void 175query_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 */ 246void 247hud_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 288static int 289is_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 302static void 303query_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 */ 328int 329hud_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