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 /sys/block/<*>/stat MB/s read/write 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 <unistd.h> 50 51struct stat_s 52{ 53 /* Read */ 54 uint64_t r_ios; 55 uint64_t r_merges; 56 uint64_t r_sectors; 57 uint64_t r_ticks; 58 /* Write */ 59 uint64_t w_ios; 60 uint64_t w_merges; 61 uint64_t w_sectors; 62 uint64_t w_ticks; 63 /* Misc */ 64 uint64_t in_flight; 65 uint64_t io_ticks; 66 uint64_t time_in_queue; 67}; 68 69struct diskstat_info 70{ 71 struct list_head list; 72 int mode; /* DISKSTAT_RD, DISKSTAT_WR */ 73 char name[64]; /* EG. sda5 */ 74 75 char sysfs_filename[128]; 76 uint64_t last_time; 77 struct stat_s last_stat; 78}; 79 80/* TODO: We don't handle dynamic block device / partition 81 * arrival or removal. 82 * Static globals specific to this HUD category. 83 */ 84static int gdiskstat_count = 0; 85static struct list_head gdiskstat_list; 86static mtx_t gdiskstat_mutex = _MTX_INITIALIZER_NP; 87 88static struct diskstat_info * 89find_dsi_by_name(const char *n, int mode) 90{ 91 list_for_each_entry(struct diskstat_info, dsi, &gdiskstat_list, list) { 92 if (dsi->mode != mode) 93 continue; 94 if (strcasecmp(dsi->name, n) == 0) 95 return dsi; 96 } 97 return 0; 98} 99 100static int 101get_file_values(const char *fn, struct stat_s *s) 102{ 103 int ret = 0; 104 FILE *fh = fopen(fn, "r"); 105 if (!fh) 106 return -1; 107 108 ret = fscanf(fh, 109 "%" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64 110 " %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64 "", 111 &s->r_ios, &s->r_merges, &s->r_sectors, &s->r_ticks, &s->w_ios, 112 &s->w_merges, &s->w_sectors, &s->w_ticks, &s->in_flight, &s->io_ticks, 113 &s->time_in_queue); 114 115 fclose(fh); 116 117 return ret; 118} 119 120static void 121query_dsi_load(struct hud_graph *gr, struct pipe_context *pipe) 122{ 123 /* The framework calls us periodically, compensate for the 124 * calling interval accordingly when reporting per second. 125 */ 126 struct diskstat_info *dsi = gr->query_data; 127 uint64_t now = os_time_get(); 128 129 if (dsi->last_time) { 130 if (dsi->last_time + gr->pane->period <= now) { 131 struct stat_s stat; 132 if (get_file_values(dsi->sysfs_filename, &stat) < 0) 133 return; 134 float val = 0; 135 136 switch (dsi->mode) { 137 case DISKSTAT_RD: 138 val = 139 ((stat.r_sectors - 140 dsi->last_stat.r_sectors) * 512) / 141 (((float) gr->pane->period / 1000) / 1000); 142 break; 143 case DISKSTAT_WR: 144 val = 145 ((stat.w_sectors - 146 dsi->last_stat.w_sectors) * 512) / 147 (((float) gr->pane->period / 1000) / 1000); 148 break; 149 } 150 151 hud_graph_add_value(gr, (uint64_t) val); 152 dsi->last_stat = stat; 153 dsi->last_time = now; 154 } 155 } 156 else { 157 /* initialize */ 158 switch (dsi->mode) { 159 case DISKSTAT_RD: 160 case DISKSTAT_WR: 161 get_file_values(dsi->sysfs_filename, &dsi->last_stat); 162 break; 163 } 164 dsi->last_time = now; 165 } 166} 167 168/** 169 * Create and initialize a new object for a specific block I/O device. 170 * \param pane parent context. 171 * \param dev_name logical block device name, EG. sda5. 172 * \param mode query read or write (DISKSTAT_RD/DISKSTAT_WR) statistics. 173 */ 174void 175hud_diskstat_graph_install(struct hud_pane *pane, const char *dev_name, 176 unsigned int mode) 177{ 178 struct hud_graph *gr; 179 struct diskstat_info *dsi; 180 181 int num_devs = hud_get_num_disks(0); 182 if (num_devs <= 0) 183 return; 184 185 dsi = find_dsi_by_name(dev_name, mode); 186 if (!dsi) 187 return; 188 189 gr = CALLOC_STRUCT(hud_graph); 190 if (!gr) 191 return; 192 193 dsi->mode = mode; 194 if (dsi->mode == DISKSTAT_RD) { 195 snprintf(gr->name, sizeof(gr->name), "%s-Read-MB/s", dsi->name); 196 } 197 else if (dsi->mode == DISKSTAT_WR) { 198 snprintf(gr->name, sizeof(gr->name), "%s-Write-MB/s", dsi->name); 199 } 200 else { 201 free(gr); 202 return; 203 } 204 205 gr->query_data = dsi; 206 gr->query_new_value = query_dsi_load; 207 208 hud_pane_add_graph(pane, gr); 209 hud_pane_set_max_value(pane, 100); 210} 211 212static void 213add_object_part(const char *basename, const char *name, int objmode) 214{ 215 struct diskstat_info *dsi = CALLOC_STRUCT(diskstat_info); 216 217 snprintf(dsi->name, sizeof(dsi->name), "%s", name); 218 snprintf(dsi->sysfs_filename, sizeof(dsi->sysfs_filename), "%s/%s/stat", 219 basename, name); 220 dsi->mode = objmode; 221 list_addtail(&dsi->list, &gdiskstat_list); 222 gdiskstat_count++; 223} 224 225static void 226add_object(const char *basename, const char *name, int objmode) 227{ 228 struct diskstat_info *dsi = CALLOC_STRUCT(diskstat_info); 229 230 snprintf(dsi->name, sizeof(dsi->name), "%s", name); 231 snprintf(dsi->sysfs_filename, sizeof(dsi->sysfs_filename), "%s/stat", 232 basename); 233 dsi->mode = objmode; 234 list_addtail(&dsi->list, &gdiskstat_list); 235 gdiskstat_count++; 236} 237 238/** 239 * Initialize internal object arrays and display block I/O HUD help. 240 * \param displayhelp true if the list of detected devices should be 241 displayed on the console. 242 * \return number of detected block I/O devices. 243 */ 244int 245hud_get_num_disks(bool displayhelp) 246{ 247 struct dirent *dp; 248 struct stat stat_buf; 249 char name[64]; 250 251 /* Return the number of block devices and partitions. */ 252 mtx_lock(&gdiskstat_mutex); 253 if (gdiskstat_count) { 254 mtx_unlock(&gdiskstat_mutex); 255 return gdiskstat_count; 256 } 257 258 /* Scan /sys/block, for every object type we support, create and 259 * persist an object to represent its different statistics. 260 */ 261 list_inithead(&gdiskstat_list); 262 DIR *dir = opendir("/sys/block/"); 263 if (!dir) { 264 mtx_unlock(&gdiskstat_mutex); 265 return 0; 266 } 267 268 while ((dp = readdir(dir)) != NULL) { 269 270 /* Avoid 'lo' and '..' and '.' */ 271 if (strlen(dp->d_name) <= 2) 272 continue; 273 274 char basename[256]; 275 snprintf(basename, sizeof(basename), "/sys/block/%s", dp->d_name); 276 snprintf(name, sizeof(name), "%s/stat", basename); 277 if (stat(name, &stat_buf) < 0) 278 continue; 279 280 if (!S_ISREG(stat_buf.st_mode)) 281 continue; /* Not a regular file */ 282 283 /* Add a physical block device with R/W stats */ 284 add_object(basename, dp->d_name, DISKSTAT_RD); 285 add_object(basename, dp->d_name, DISKSTAT_WR); 286 287 /* Add any partitions */ 288 struct dirent *dpart; 289 DIR *pdir = opendir(basename); 290 if (!pdir) { 291 mtx_unlock(&gdiskstat_mutex); 292 closedir(dir); 293 return 0; 294 } 295 296 while ((dpart = readdir(pdir)) != NULL) { 297 /* Avoid 'lo' and '..' and '.' */ 298 if (strlen(dpart->d_name) <= 2) 299 continue; 300 301 char p[64]; 302 snprintf(p, sizeof(p), "%s/%s/stat", basename, dpart->d_name); 303 if (stat(p, &stat_buf) < 0) 304 continue; 305 306 if (!S_ISREG(stat_buf.st_mode)) 307 continue; /* Not a regular file */ 308 309 /* Add a partition with R/W stats */ 310 add_object_part(basename, dpart->d_name, DISKSTAT_RD); 311 add_object_part(basename, dpart->d_name, DISKSTAT_WR); 312 } 313 } 314 closedir(dir); 315 316 if (displayhelp) { 317 list_for_each_entry(struct diskstat_info, dsi, &gdiskstat_list, list) { 318 char line[32]; 319 snprintf(line, sizeof(line), " diskstat-%s-%s", 320 dsi->mode == DISKSTAT_RD ? "rd" : 321 dsi->mode == DISKSTAT_WR ? "wr" : "undefined", dsi->name); 322 323 puts(line); 324 } 325 } 326 mtx_unlock(&gdiskstat_mutex); 327 328 return gdiskstat_count; 329} 330 331#endif /* HAVE_GALLIUM_EXTRA_HUD */ 332