1 /************************************************************************** 2 * 3 * Copyright (C) 2016 Steven Toth <stoth (at) 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 52 struct 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 */ 68 static int gnic_count = 0; 69 static struct list_head gnic_list; 70 static mtx_t gnic_mutex = _MTX_INITIALIZER_NP; 71 72 static struct nic_info * 73 find_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 85 static int 86 get_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 98 static boolean 99 get_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 107 static void 108 query_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 strcpy(req.ifr_name, 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 138 static void 139 query_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 strcpy(req.ifr_name, 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 173 static void 174 query_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; 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 routing 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 */ 245 void 246 hud_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 return; 277 278 gr->query_data = nic; 279 gr->query_new_value = query_nic_load; 280 281 hud_pane_add_graph(pane, gr); 282 hud_pane_set_max_value(pane, 100); 283 } 284 285 static int 286 is_wireless_nic(const char *dirbase) 287 { 288 struct stat stat_buf; 289 290 /* Check if its a wireless card */ 291 char fn[256]; 292 snprintf(fn, sizeof(fn), "%s/wireless", dirbase); 293 if (stat(fn, &stat_buf) == 0) 294 return 1; 295 296 return 0; 297 } 298 299 static void 300 query_nic_bitrate(struct nic_info *nic, const char *dirbase) 301 { 302 struct stat stat_buf; 303 304 /* Check if its a wireless card */ 305 char fn[256]; 306 snprintf(fn, sizeof(fn), "%s/wireless", dirbase); 307 if (stat(fn, &stat_buf) == 0) { 308 /* we're a wireless nic */ 309 query_wifi_bitrate(nic, &nic->speedMbps); 310 nic->speedMbps /= 1000000; 311 } 312 else { 313 /* Must be a wired nic */ 314 snprintf(fn, sizeof(fn), "%s/speed", dirbase); 315 get_file_value(fn, &nic->speedMbps); 316 } 317 } 318 319 /** 320 * Initialize internal object arrays and display NIC HUD help. 321 * \param displayhelp true if the list of detected devices should be 322 displayed on the console. 323 * \return number of detected network interface devices. 324 */ 325 int 326 hud_get_num_nics(bool displayhelp) 327 { 328 struct dirent *dp; 329 struct stat stat_buf; 330 struct nic_info *nic; 331 char name[64]; 332 333 /* Return the number if network interfaces. */ 334 mtx_lock(&gnic_mutex); 335 if (gnic_count) { 336 mtx_unlock(&gnic_mutex); 337 return gnic_count; 338 } 339 340 /* Scan /sys/block, for every object type we support, create and 341 * persist an object to represent its different statistics. 342 */ 343 list_inithead(&gnic_list); 344 DIR *dir = opendir("/sys/class/net/"); 345 if (!dir) { 346 mtx_unlock(&gnic_mutex); 347 return 0; 348 } 349 350 while ((dp = readdir(dir)) != NULL) { 351 352 /* Avoid 'lo' and '..' and '.' */ 353 if (strlen(dp->d_name) <= 2) 354 continue; 355 356 char basename[256]; 357 snprintf(basename, sizeof(basename), "/sys/class/net/%s", dp->d_name); 358 snprintf(name, sizeof(name), "%s/statistics/rx_bytes", basename); 359 if (stat(name, &stat_buf) < 0) 360 continue; 361 362 if (!S_ISREG(stat_buf.st_mode)) 363 continue; /* Not a regular file */ 364 365 int is_wireless = is_wireless_nic(basename); 366 367 /* Add the RX object */ 368 nic = CALLOC_STRUCT(nic_info); 369 strcpy(nic->name, dp->d_name); 370 snprintf(nic->throughput_filename, sizeof(nic->throughput_filename), 371 "%s/statistics/rx_bytes", basename); 372 nic->mode = NIC_DIRECTION_RX; 373 nic->is_wireless = is_wireless; 374 query_nic_bitrate(nic, basename); 375 376 list_addtail(&nic->list, &gnic_list); 377 gnic_count++; 378 379 /* Add the TX object */ 380 nic = CALLOC_STRUCT(nic_info); 381 strcpy(nic->name, dp->d_name); 382 snprintf(nic->throughput_filename, 383 sizeof(nic->throughput_filename), 384 "/sys/class/net/%s/statistics/tx_bytes", dp->d_name); 385 nic->mode = NIC_DIRECTION_TX; 386 nic->is_wireless = is_wireless; 387 388 query_nic_bitrate(nic, basename); 389 390 list_addtail(&nic->list, &gnic_list); 391 gnic_count++; 392 393 if (nic->is_wireless) { 394 /* RSSI Support */ 395 nic = CALLOC_STRUCT(nic_info); 396 strcpy(nic->name, dp->d_name); 397 snprintf(nic->throughput_filename, 398 sizeof(nic->throughput_filename), 399 "/sys/class/net/%s/statistics/tx_bytes", dp->d_name); 400 nic->mode = NIC_RSSI_DBM; 401 402 query_nic_bitrate(nic, basename); 403 404 list_addtail(&nic->list, &gnic_list); 405 gnic_count++; 406 } 407 408 } 409 closedir(dir); 410 411 list_for_each_entry(struct nic_info, nic, &gnic_list, list) { 412 char line[64]; 413 snprintf(line, sizeof(line), " nic-%s-%s", 414 nic->mode == NIC_DIRECTION_RX ? "rx" : 415 nic->mode == NIC_DIRECTION_TX ? "tx" : 416 nic->mode == NIC_RSSI_DBM ? "rssi" : "undefined", nic->name); 417 418 puts(line); 419 420 } 421 422 mtx_unlock(&gnic_mutex); 423 return gnic_count; 424 } 425 426 #endif /* HAVE_GALLIUM_EXTRA_HUD */ 427