Home | History | Annotate | Download | only in hud
      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 #if 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 "os/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 pipe_static_mutex(gdiskstat_mutex);
     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)
    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    pipe_mutex_lock(gdiskstat_mutex);
    250    if (gdiskstat_count) {
    251       pipe_mutex_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       pipe_mutex_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          pipe_mutex_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    pipe_mutex_unlock(gdiskstat_mutex);
    324 
    325    return gdiskstat_count;
    326 }
    327 
    328 #endif /* HAVE_GALLIUM_EXTRA_HUD */
    329