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