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