diff options
author | Steven Toth <[email protected]> | 2016-09-28 12:58:00 -0600 |
---|---|---|
committer | Brian Paul <[email protected]> | 2016-09-28 16:18:05 -0600 |
commit | 8c60bcb4c317026e017a8ecffe303fd4e7f0db33 (patch) | |
tree | a69adc68dd1f442409ff70b07b5f592157c154d7 /src/gallium/auxiliary/hud/hud_nic.c | |
parent | 29783c0887b3afd345e15e4e6910c04185219812 (diff) |
gallium/hud: Add support for block I/O, network I/O and lmsensor stats
V8: Feedback based on peer review
convert if block into a switch
Constify some func args
V7: Increase precision when measuring lmsensors volts
Flatten patch series.
V6: Feedback based on peer review
Simplify sensor initialization (arg passing).
Constify some func args
V5: Feedback based on peer review
Convert sprintf to snprintf
Convert char * to const char *
int arg converted to bool
Func changes to take a filename vs a larger struct.
Omit the space between '*' and the param name.
V4: Merged with master as of 2016/9/27 6pm
V3: Flatten the entire patchset ready for the ML
V2: Additional seperate patches based on feedback
a) configure.ac: Add a comment related to libsensors
b) HUD: Disable Block/NIC I/O stats by default.
Implement configuration option --enable-gallium-extra-hud=yes
and enable both statistics when this option is enabled.
c) Configure.ac: Minor cleanup to user visible configuration settings
d) Configure.ac: HUD stats - build system improvements
Move the -lsensors out of a deeper Makefile, bring it into the configure.ac.
Also, rename a compiler directive to more closely follow the standard.
V1: Initial release to the ML
Three new features:
1. Disk/block I/O device read/write stats MB/ps.
2. Network Interface RX/TX transfer statistics as a percentage
of the overall NIC speed.
3. lmsensor power, voltage and temperature sensors.
The lmsensor changes makes a dependency on libsensors so support
for the change is opt out by default.
Signed-off-by: Steven Toth <[email protected]>
Reviewed-by: Brian Paul <[email protected]>
Diffstat (limited to 'src/gallium/auxiliary/hud/hud_nic.c')
-rw-r--r-- | src/gallium/auxiliary/hud/hud_nic.c | 446 |
1 files changed, 446 insertions, 0 deletions
diff --git a/src/gallium/auxiliary/hud/hud_nic.c b/src/gallium/auxiliary/hud/hud_nic.c new file mode 100644 index 00000000000..36088a07ed4 --- /dev/null +++ b/src/gallium/auxiliary/hud/hud_nic.c @@ -0,0 +1,446 @@ +/************************************************************************** + * + * Copyright (C) 2016 Steven Toth <[email protected]> + * Copyright (C) 2016 Zodiac Inflight Innovations + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sub license, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial portions + * of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + **************************************************************************/ + +#if HAVE_GALLIUM_EXTRA_HUD + +/* Purpose: Reading network interface RX/TX throughput per second, + * displaying on the HUD. + */ + +#include "hud/hud_private.h" +#include "util/list.h" +#include "os/os_time.h" +#include "util/u_memory.h" +#include <stdio.h> +#include <unistd.h> +#include <dirent.h> +#include <stdlib.h> +#include <unistd.h> +#include <inttypes.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <linux/wireless.h> + +#define LOCAL_DEBUG 0 + +struct nic_info +{ + struct list_head list; + int mode; + char name[64]; + uint64_t speedMbps; + int is_wireless; + + char throughput_filename[128]; + uint64_t last_time; + uint64_t last_nic_bytes; +}; + +/* TODO: We don't handle dynamic NIC arrival or removal. + * Static globals specific to this HUD category. + */ +static int gnic_count = 0; +static struct list_head gnic_list; + +static struct nic_info * +find_nic_by_name(const char *n, int mode) +{ + list_for_each_entry(struct nic_info, nic, &gnic_list, list) { + if (nic->mode != mode) + continue; + + if (strcasecmp(nic->name, n) == 0) + return nic; + } + return 0; +} + +static int +get_file_value(const char *fname, uint64_t *value) +{ + FILE *fh = fopen(fname, "r"); + if (!fh) + return -1; + if (fscanf(fh, "%" PRIu64 "", value) != 0) { + /* Error */ + } + fclose(fh); + return 0; +} + +static boolean +get_nic_bytes(const char *fn, uint64_t *bytes) +{ + if (get_file_value(fn, bytes) < 0) + return FALSE; + + return TRUE; +} + +static void +query_wifi_bitrate(const struct nic_info *nic, uint64_t *bitrate) +{ + int sockfd; + struct iw_statistics stats; + struct iwreq req; + + memset(&stats, 0, sizeof(stats)); + memset(&req, 0, sizeof(req)); + + strcpy(req.ifr_name, nic->name); + req.u.data.pointer = &stats; + req.u.data.flags = 1; + req.u.data.length = sizeof(struct iw_statistics); + + /* Any old socket will do, and a datagram socket is pretty cheap */ + if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { + fprintf(stderr, "Unable to create socket for %s\n", nic->name); + return; + } + + if (ioctl(sockfd, SIOCGIWRATE, &req) == -1) { + fprintf(stderr, "Error performing SIOCGIWSTATS on %s\n", nic->name); + close(sockfd); + return; + } + *bitrate = req.u.bitrate.value; + + close(sockfd); +} + +static void +query_nic_rssi(const struct nic_info *nic, uint64_t *leveldBm) +{ + int sockfd; + struct iw_statistics stats; + struct iwreq req; + + memset(&stats, 0, sizeof(stats)); + memset(&req, 0, sizeof(req)); + + strcpy(req.ifr_name, nic->name); + req.u.data.pointer = &stats; + req.u.data.flags = 1; + req.u.data.length = sizeof(struct iw_statistics); + + if (nic->mode != NIC_RSSI_DBM) + return; + + /* Any old socket will do, and a datagram socket is pretty cheap */ + if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { + fprintf(stderr, "Unable to create socket for %s\n", nic->name); + return; + } + + /* Perform the ioctl */ + if (ioctl(sockfd, SIOCGIWSTATS, &req) == -1) { + fprintf(stderr, "Error performing SIOCGIWSTATS on %s\n", nic->name); + close(sockfd); + return; + } + *leveldBm = ((char) stats.qual.level * -1); + + close(sockfd); + +#if LOCAL_DEBUG + printf("NIC signal level%s is %d%s.\n", + (stats.qual.updated & IW_QUAL_DBM ? " (in dBm)" : ""), + (char) stats.qual.level, + (stats.qual.updated & IW_QUAL_LEVEL_UPDATED ? " (updated)" : "")); +#endif +} + +static void +query_nic_load(struct hud_graph *gr) +{ + /* The framework calls us at a regular but indefined period, + * not once per second, compensate the statistics accordingly. + */ + + struct nic_info *nic = gr->query_data; + uint64_t now = os_time_get(); + + if (nic->last_time) { + if (nic->last_time + gr->pane->period <= now) { + switch (nic->mode) { + case NIC_DIRECTION_RX: + case NIC_DIRECTION_TX: + { + uint64_t bytes; + get_nic_bytes(nic->throughput_filename, &bytes); + uint64_t nic_mbps = + ((bytes - nic->last_nic_bytes) / 1000000) * 8; + + float speedMbps = nic->speedMbps; + float periodMs = gr->pane->period / 1000; + float bits = nic_mbps; + float period_factor = periodMs / 1000; + float period_speed = speedMbps * period_factor; + float pct = (bits / period_speed) * 100; + + /* Scaling bps with a narrow time period into a second, + * potentially suffers from routing errors at higher + * periods. Eg 104%. Compensate. + */ + if (pct > 100) + pct = 100; + hud_graph_add_value(gr, (uint64_t) pct); + + nic->last_nic_bytes = bytes; + } + break; + case NIC_RSSI_DBM: + { + uint64_t leveldBm = 0; + query_nic_rssi(nic, &leveldBm); + hud_graph_add_value(gr, leveldBm); + } + break; + } + + nic->last_time = now; + } + } + else { + /* initialize */ + switch (nic->mode) { + case NIC_DIRECTION_RX: + case NIC_DIRECTION_TX: + get_nic_bytes(nic->throughput_filename, &nic->last_nic_bytes); + break; + case NIC_RSSI_DBM: + break; + } + + nic->last_time = now; + } +} + +static void +free_query_data(void *p) +{ + struct nic_info *nic = (struct nic_info *) p; + list_del(&nic->list); + FREE(nic); +} + +/** + * Create and initialize a new object for a specific network interface dev. + * \param pane parent context. + * \param nic_name logical block device name, EG. eth0. + * \param mode query type (NIC_DIRECTION_RX/WR/RSSI) statistics. + */ +void +hud_nic_graph_install(struct hud_pane *pane, const char *nic_name, + unsigned int mode) +{ + struct hud_graph *gr; + struct nic_info *nic; + + int num_nics = hud_get_num_nics(0); + if (num_nics <= 0) + return; + +#if LOCAL_DEBUG + printf("%s(%s, %s) - Creating HUD object\n", __func__, nic_name, + mode == NIC_DIRECTION_RX ? "RX" : + mode == NIC_DIRECTION_TX ? "TX" : + mode == NIC_RSSI_DBM ? "RSSI" : "UNDEFINED"); +#endif + + nic = find_nic_by_name(nic_name, mode); + if (!nic) + return; + + gr = CALLOC_STRUCT(hud_graph); + if (!gr) + return; + + nic->mode = mode; + if (nic->mode == NIC_DIRECTION_RX) { + snprintf(gr->name, sizeof(gr->name), "%s-rx-%lldMbps", nic->name, + nic->speedMbps); + } + else if (nic->mode == NIC_DIRECTION_TX) { + snprintf(gr->name, sizeof(gr->name), "%s-tx-%lldMbps", nic->name, + nic->speedMbps); + } + else if (nic->mode == NIC_RSSI_DBM) + snprintf(gr->name, sizeof(gr->name), "%s-rssi", nic->name); + else + return; + + gr->query_data = nic; + gr->query_new_value = query_nic_load; + + /* Don't use free() as our callback as that messes up Gallium's + * memory debugger. Use simple free_query_data() wrapper. + */ + gr->free_query_data = free_query_data; + + hud_pane_add_graph(pane, gr); + hud_pane_set_max_value(pane, 100); +} + +static int +is_wireless_nic(const char *dirbase) +{ + struct stat stat_buf; + + /* Check if its a wireless card */ + char fn[256]; + snprintf(fn, sizeof(fn), "%s/wireless", dirbase); + if (stat(fn, &stat_buf) == 0) + return 1; + + return 0; +} + +static void +query_nic_bitrate(struct nic_info *nic, const char *dirbase) +{ + struct stat stat_buf; + + /* Check if its a wireless card */ + char fn[256]; + snprintf(fn, sizeof(fn), "%s/wireless", dirbase); + if (stat(fn, &stat_buf) == 0) { + /* we're a wireless nic */ + query_wifi_bitrate(nic, &nic->speedMbps); + nic->speedMbps /= 1000000; + } + else { + /* Must be a wired nic */ + snprintf(fn, sizeof(fn), "%s/speed", dirbase); + get_file_value(fn, &nic->speedMbps); + } +} + +/** + * Initialize internal object arrays and display NIC HUD help. + * \param displayhelp true if the list of detected devices should be + displayed on the console. + * \return number of detected network interface devices. + */ +int +hud_get_num_nics(bool displayhelp) +{ + struct dirent *dp; + struct stat stat_buf; + struct nic_info *nic; + char name[64]; + + /* Return the number if network interfaces. */ + if (gnic_count) + return gnic_count; + + /* Scan /sys/block, for every object type we support, create and + * persist an object to represent its different statistics. + */ + list_inithead(&gnic_list); + DIR *dir = opendir("/sys/class/net/"); + if (!dir) + return 0; + + while ((dp = readdir(dir)) != NULL) { + + /* Avoid 'lo' and '..' and '.' */ + if (strlen(dp->d_name) <= 2) + continue; + + char basename[256]; + snprintf(basename, sizeof(basename), "/sys/class/net/%s", dp->d_name); + snprintf(name, sizeof(name), "%s/statistics/rx_bytes", basename); + if (stat(name, &stat_buf) < 0) + continue; + + if (!S_ISREG(stat_buf.st_mode)) + continue; /* Not a regular file */ + + int is_wireless = is_wireless_nic(basename); + + /* Add the RX object */ + nic = CALLOC_STRUCT(nic_info); + strcpy(nic->name, dp->d_name); + snprintf(nic->throughput_filename, sizeof(nic->throughput_filename), + "%s/statistics/rx_bytes", basename); + nic->mode = NIC_DIRECTION_RX; + nic->is_wireless = is_wireless; + query_nic_bitrate(nic, basename); + + list_addtail(&nic->list, &gnic_list); + gnic_count++; + + /* Add the TX object */ + nic = CALLOC_STRUCT(nic_info); + strcpy(nic->name, dp->d_name); + snprintf(nic->throughput_filename, + sizeof(nic->throughput_filename), + "/sys/class/net/%s/statistics/tx_bytes", dp->d_name); + nic->mode = NIC_DIRECTION_TX; + nic->is_wireless = is_wireless; + + query_nic_bitrate(nic, basename); + + list_addtail(&nic->list, &gnic_list); + gnic_count++; + + if (nic->is_wireless) { + /* RSSI Support */ + nic = CALLOC_STRUCT(nic_info); + strcpy(nic->name, dp->d_name); + snprintf(nic->throughput_filename, + sizeof(nic->throughput_filename), + "/sys/class/net/%s/statistics/tx_bytes", dp->d_name); + nic->mode = NIC_RSSI_DBM; + + query_nic_bitrate(nic, basename); + + list_addtail(&nic->list, &gnic_list); + gnic_count++; + } + + } + + list_for_each_entry(struct nic_info, nic, &gnic_list, list) { + char line[64]; + snprintf(line, sizeof(line), " nic-%s-%s", + nic->mode == NIC_DIRECTION_RX ? "rx" : + nic->mode == NIC_DIRECTION_TX ? "tx" : + nic->mode == NIC_RSSI_DBM ? "rssi" : "undefined", nic->name); + + puts(line); + + } + + return gnic_count; +} + +#endif /* HAVE_GALLIUM_EXTRA_HUD */ |