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 /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 50 struct 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 68 struct 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 */ 83 static int gdiskstat_count = 0; 84 static struct list_head gdiskstat_list; 85 static mtx_t gdiskstat_mutex = _MTX_INITIALIZER_NP; 86 87 static struct diskstat_info * 88 find_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 99 static int 100 get_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 119 static void 120 query_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 */ 173 void 174 hud_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 return; 201 202 gr->query_data = dsi; 203 gr->query_new_value = query_dsi_load; 204 205 hud_pane_add_graph(pane, gr); 206 hud_pane_set_max_value(pane, 100); 207 } 208 209 static void 210 add_object_part(const char *basename, const char *name, int objmode) 211 { 212 struct diskstat_info *dsi = CALLOC_STRUCT(diskstat_info); 213 214 strcpy(dsi->name, name); 215 snprintf(dsi->sysfs_filename, sizeof(dsi->sysfs_filename), "%s/%s/stat", 216 basename, name); 217 dsi->mode = objmode; 218 list_addtail(&dsi->list, &gdiskstat_list); 219 gdiskstat_count++; 220 } 221 222 static void 223 add_object(const char *basename, const char *name, int objmode) 224 { 225 struct diskstat_info *dsi = CALLOC_STRUCT(diskstat_info); 226 227 strcpy(dsi->name, name); 228 snprintf(dsi->sysfs_filename, sizeof(dsi->sysfs_filename), "%s/stat", 229 basename); 230 dsi->mode = objmode; 231 list_addtail(&dsi->list, &gdiskstat_list); 232 gdiskstat_count++; 233 } 234 235 /** 236 * Initialize internal object arrays and display block I/O HUD help. 237 * \param displayhelp true if the list of detected devices should be 238 displayed on the console. 239 * \return number of detected block I/O devices. 240 */ 241 int 242 hud_get_num_disks(bool displayhelp) 243 { 244 struct dirent *dp; 245 struct stat stat_buf; 246 char name[64]; 247 248 /* Return the number of block devices and partitions. */ 249 mtx_lock(&gdiskstat_mutex); 250 if (gdiskstat_count) { 251 mtx_unlock(&gdiskstat_mutex); 252 return gdiskstat_count; 253 } 254 255 /* Scan /sys/block, for every object type we support, create and 256 * persist an object to represent its different statistics. 257 */ 258 list_inithead(&gdiskstat_list); 259 DIR *dir = opendir("/sys/block/"); 260 if (!dir) { 261 mtx_unlock(&gdiskstat_mutex); 262 return 0; 263 } 264 265 while ((dp = readdir(dir)) != NULL) { 266 267 /* Avoid 'lo' and '..' and '.' */ 268 if (strlen(dp->d_name) <= 2) 269 continue; 270 271 char basename[256]; 272 snprintf(basename, sizeof(basename), "/sys/block/%s", dp->d_name); 273 snprintf(name, sizeof(name), "%s/stat", basename); 274 if (stat(name, &stat_buf) < 0) 275 continue; 276 277 if (!S_ISREG(stat_buf.st_mode)) 278 continue; /* Not a regular file */ 279 280 /* Add a physical block device with R/W stats */ 281 add_object(basename, dp->d_name, DISKSTAT_RD); 282 add_object(basename, dp->d_name, DISKSTAT_WR); 283 284 /* Add any partitions */ 285 struct dirent *dpart; 286 DIR *pdir = opendir(basename); 287 if (!pdir) { 288 mtx_unlock(&gdiskstat_mutex); 289 closedir(dir); 290 return 0; 291 } 292 293 while ((dpart = readdir(pdir)) != NULL) { 294 /* Avoid 'lo' and '..' and '.' */ 295 if (strlen(dpart->d_name) <= 2) 296 continue; 297 298 char p[64]; 299 snprintf(p, sizeof(p), "%s/%s/stat", basename, dpart->d_name); 300 if (stat(p, &stat_buf) < 0) 301 continue; 302 303 if (!S_ISREG(stat_buf.st_mode)) 304 continue; /* Not a regular file */ 305 306 /* Add a partition with R/W stats */ 307 add_object_part(basename, dpart->d_name, DISKSTAT_RD); 308 add_object_part(basename, dpart->d_name, DISKSTAT_WR); 309 } 310 } 311 closedir(dir); 312 313 if (displayhelp) { 314 list_for_each_entry(struct diskstat_info, dsi, &gdiskstat_list, list) { 315 char line[32]; 316 snprintf(line, sizeof(line), " diskstat-%s-%s", 317 dsi->mode == DISKSTAT_RD ? "rd" : 318 dsi->mode == DISKSTAT_WR ? "wr" : "undefined", dsi->name); 319 320 puts(line); 321 } 322 } 323 mtx_unlock(&gdiskstat_mutex); 324 325 return gdiskstat_count; 326 } 327 328 #endif /* HAVE_GALLIUM_EXTRA_HUD */ 329