gallium/hud: Add support for block I/O, network I/O and lmsensor stats
[mesa.git] / src / gallium / auxiliary / hud / hud_nic.c
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 #if 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 "os/os_time.h"
38 #include "util/u_memory.h"
39 #include <stdio.h>
40 #include <unistd.h>
41 #include <dirent.h>
42 #include <stdlib.h>
43 #include <unistd.h>
44 #include <inttypes.h>
45 #include <sys/types.h>
46 #include <sys/stat.h>
47 #include <sys/socket.h>
48 #include <sys/ioctl.h>
49 #include <linux/wireless.h>
50
51 #define LOCAL_DEBUG 0
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
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 #if LOCAL_DEBUG
173 printf("NIC signal level%s is %d%s.\n",
174 (stats.qual.updated & IW_QUAL_DBM ? " (in dBm)" : ""),
175 (char) stats.qual.level,
176 (stats.qual.updated & IW_QUAL_LEVEL_UPDATED ? " (updated)" : ""));
177 #endif
178 }
179
180 static void
181 query_nic_load(struct hud_graph *gr)
182 {
183 /* The framework calls us at a regular but indefined period,
184 * not once per second, compensate the statistics accordingly.
185 */
186
187 struct nic_info *nic = gr->query_data;
188 uint64_t now = os_time_get();
189
190 if (nic->last_time) {
191 if (nic->last_time + gr->pane->period <= now) {
192 switch (nic->mode) {
193 case NIC_DIRECTION_RX:
194 case NIC_DIRECTION_TX:
195 {
196 uint64_t bytes;
197 get_nic_bytes(nic->throughput_filename, &bytes);
198 uint64_t nic_mbps =
199 ((bytes - nic->last_nic_bytes) / 1000000) * 8;
200
201 float speedMbps = nic->speedMbps;
202 float periodMs = gr->pane->period / 1000;
203 float bits = nic_mbps;
204 float period_factor = periodMs / 1000;
205 float period_speed = speedMbps * period_factor;
206 float pct = (bits / period_speed) * 100;
207
208 /* Scaling bps with a narrow time period into a second,
209 * potentially suffers from routing errors at higher
210 * periods. Eg 104%. Compensate.
211 */
212 if (pct > 100)
213 pct = 100;
214 hud_graph_add_value(gr, (uint64_t) pct);
215
216 nic->last_nic_bytes = bytes;
217 }
218 break;
219 case NIC_RSSI_DBM:
220 {
221 uint64_t leveldBm = 0;
222 query_nic_rssi(nic, &leveldBm);
223 hud_graph_add_value(gr, leveldBm);
224 }
225 break;
226 }
227
228 nic->last_time = now;
229 }
230 }
231 else {
232 /* initialize */
233 switch (nic->mode) {
234 case NIC_DIRECTION_RX:
235 case NIC_DIRECTION_TX:
236 get_nic_bytes(nic->throughput_filename, &nic->last_nic_bytes);
237 break;
238 case NIC_RSSI_DBM:
239 break;
240 }
241
242 nic->last_time = now;
243 }
244 }
245
246 static void
247 free_query_data(void *p)
248 {
249 struct nic_info *nic = (struct nic_info *) p;
250 list_del(&nic->list);
251 FREE(nic);
252 }
253
254 /**
255 * Create and initialize a new object for a specific network interface dev.
256 * \param pane parent context.
257 * \param nic_name logical block device name, EG. eth0.
258 * \param mode query type (NIC_DIRECTION_RX/WR/RSSI) statistics.
259 */
260 void
261 hud_nic_graph_install(struct hud_pane *pane, const char *nic_name,
262 unsigned int mode)
263 {
264 struct hud_graph *gr;
265 struct nic_info *nic;
266
267 int num_nics = hud_get_num_nics(0);
268 if (num_nics <= 0)
269 return;
270
271 #if LOCAL_DEBUG
272 printf("%s(%s, %s) - Creating HUD object\n", __func__, nic_name,
273 mode == NIC_DIRECTION_RX ? "RX" :
274 mode == NIC_DIRECTION_TX ? "TX" :
275 mode == NIC_RSSI_DBM ? "RSSI" : "UNDEFINED");
276 #endif
277
278 nic = find_nic_by_name(nic_name, mode);
279 if (!nic)
280 return;
281
282 gr = CALLOC_STRUCT(hud_graph);
283 if (!gr)
284 return;
285
286 nic->mode = mode;
287 if (nic->mode == NIC_DIRECTION_RX) {
288 snprintf(gr->name, sizeof(gr->name), "%s-rx-%lldMbps", nic->name,
289 nic->speedMbps);
290 }
291 else if (nic->mode == NIC_DIRECTION_TX) {
292 snprintf(gr->name, sizeof(gr->name), "%s-tx-%lldMbps", nic->name,
293 nic->speedMbps);
294 }
295 else if (nic->mode == NIC_RSSI_DBM)
296 snprintf(gr->name, sizeof(gr->name), "%s-rssi", nic->name);
297 else
298 return;
299
300 gr->query_data = nic;
301 gr->query_new_value = query_nic_load;
302
303 /* Don't use free() as our callback as that messes up Gallium's
304 * memory debugger. Use simple free_query_data() wrapper.
305 */
306 gr->free_query_data = free_query_data;
307
308 hud_pane_add_graph(pane, gr);
309 hud_pane_set_max_value(pane, 100);
310 }
311
312 static int
313 is_wireless_nic(const char *dirbase)
314 {
315 struct stat stat_buf;
316
317 /* Check if its a wireless card */
318 char fn[256];
319 snprintf(fn, sizeof(fn), "%s/wireless", dirbase);
320 if (stat(fn, &stat_buf) == 0)
321 return 1;
322
323 return 0;
324 }
325
326 static void
327 query_nic_bitrate(struct nic_info *nic, const char *dirbase)
328 {
329 struct stat stat_buf;
330
331 /* Check if its a wireless card */
332 char fn[256];
333 snprintf(fn, sizeof(fn), "%s/wireless", dirbase);
334 if (stat(fn, &stat_buf) == 0) {
335 /* we're a wireless nic */
336 query_wifi_bitrate(nic, &nic->speedMbps);
337 nic->speedMbps /= 1000000;
338 }
339 else {
340 /* Must be a wired nic */
341 snprintf(fn, sizeof(fn), "%s/speed", dirbase);
342 get_file_value(fn, &nic->speedMbps);
343 }
344 }
345
346 /**
347 * Initialize internal object arrays and display NIC HUD help.
348 * \param displayhelp true if the list of detected devices should be
349 displayed on the console.
350 * \return number of detected network interface devices.
351 */
352 int
353 hud_get_num_nics(bool displayhelp)
354 {
355 struct dirent *dp;
356 struct stat stat_buf;
357 struct nic_info *nic;
358 char name[64];
359
360 /* Return the number if network interfaces. */
361 if (gnic_count)
362 return gnic_count;
363
364 /* Scan /sys/block, for every object type we support, create and
365 * persist an object to represent its different statistics.
366 */
367 list_inithead(&gnic_list);
368 DIR *dir = opendir("/sys/class/net/");
369 if (!dir)
370 return 0;
371
372 while ((dp = readdir(dir)) != NULL) {
373
374 /* Avoid 'lo' and '..' and '.' */
375 if (strlen(dp->d_name) <= 2)
376 continue;
377
378 char basename[256];
379 snprintf(basename, sizeof(basename), "/sys/class/net/%s", dp->d_name);
380 snprintf(name, sizeof(name), "%s/statistics/rx_bytes", basename);
381 if (stat(name, &stat_buf) < 0)
382 continue;
383
384 if (!S_ISREG(stat_buf.st_mode))
385 continue; /* Not a regular file */
386
387 int is_wireless = is_wireless_nic(basename);
388
389 /* Add the RX object */
390 nic = CALLOC_STRUCT(nic_info);
391 strcpy(nic->name, dp->d_name);
392 snprintf(nic->throughput_filename, sizeof(nic->throughput_filename),
393 "%s/statistics/rx_bytes", basename);
394 nic->mode = NIC_DIRECTION_RX;
395 nic->is_wireless = is_wireless;
396 query_nic_bitrate(nic, basename);
397
398 list_addtail(&nic->list, &gnic_list);
399 gnic_count++;
400
401 /* Add the TX object */
402 nic = CALLOC_STRUCT(nic_info);
403 strcpy(nic->name, dp->d_name);
404 snprintf(nic->throughput_filename,
405 sizeof(nic->throughput_filename),
406 "/sys/class/net/%s/statistics/tx_bytes", dp->d_name);
407 nic->mode = NIC_DIRECTION_TX;
408 nic->is_wireless = is_wireless;
409
410 query_nic_bitrate(nic, basename);
411
412 list_addtail(&nic->list, &gnic_list);
413 gnic_count++;
414
415 if (nic->is_wireless) {
416 /* RSSI Support */
417 nic = CALLOC_STRUCT(nic_info);
418 strcpy(nic->name, dp->d_name);
419 snprintf(nic->throughput_filename,
420 sizeof(nic->throughput_filename),
421 "/sys/class/net/%s/statistics/tx_bytes", dp->d_name);
422 nic->mode = NIC_RSSI_DBM;
423
424 query_nic_bitrate(nic, basename);
425
426 list_addtail(&nic->list, &gnic_list);
427 gnic_count++;
428 }
429
430 }
431
432 list_for_each_entry(struct nic_info, nic, &gnic_list, list) {
433 char line[64];
434 snprintf(line, sizeof(line), " nic-%s-%s",
435 nic->mode == NIC_DIRECTION_RX ? "rx" :
436 nic->mode == NIC_DIRECTION_TX ? "tx" :
437 nic->mode == NIC_RSSI_DBM ? "rssi" : "undefined", nic->name);
438
439 puts(line);
440
441 }
442
443 return gnic_count;
444 }
445
446 #endif /* HAVE_GALLIUM_EXTRA_HUD */