Logo Search packages:      
Sourcecode: fio version File versions

diskutil.c

#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <libgen.h>
#include <math.h>

#include "fio.h"
#include "smalloc.h"
#include "diskutil.h"

static int last_majdev, last_mindev;
static struct disk_util *last_du;

static struct flist_head disk_list = FLIST_HEAD_INIT(disk_list);

static void disk_util_free(struct disk_util *du)
{
      if (du == last_du)
            last_du = NULL;

      fio_mutex_remove(du->lock);
      sfree(du->name);
      sfree(du);
}

static int get_io_ticks(struct disk_util *du, struct disk_util_stat *dus)
{
      unsigned in_flight;
      char line[256];
      FILE *f;
      char *p;
      int ret;

      dprint(FD_DISKUTIL, "open stat file: %s\n", du->path);

      f = fopen(du->path, "r");
      if (!f)
            return 1;

      p = fgets(line, sizeof(line), f);
      if (!p) {
            fclose(f);
            return 1;
      }

      dprint(FD_DISKUTIL, "%s: %s", du->path, p);

      ret = sscanf(p, "%u %u %llu %u %u %u %llu %u %u %u %u\n", &dus->ios[0],
                              &dus->merges[0], &dus->sectors[0],
                              &dus->ticks[0], &dus->ios[1],
                              &dus->merges[1], &dus->sectors[1],
                              &dus->ticks[1], &in_flight,
                              &dus->io_ticks, &dus->time_in_queue);
      fclose(f);
      dprint(FD_DISKUTIL, "%s: stat read ok? %d\n", du->path, ret == 1);
      return ret != 11;
}

static void update_io_tick_disk(struct disk_util *du)
{
      struct disk_util_stat __dus, *dus, *ldus;
      struct timeval t;

      if (get_io_ticks(du, &__dus))
            return;
      if (!du->users)
            return;

      dus = &du->dus;
      ldus = &du->last_dus;

      dus->sectors[0] += (__dus.sectors[0] - ldus->sectors[0]);
      dus->sectors[1] += (__dus.sectors[1] - ldus->sectors[1]);
      dus->ios[0] += (__dus.ios[0] - ldus->ios[0]);
      dus->ios[1] += (__dus.ios[1] - ldus->ios[1]);
      dus->merges[0] += (__dus.merges[0] - ldus->merges[0]);
      dus->merges[1] += (__dus.merges[1] - ldus->merges[1]);
      dus->ticks[0] += (__dus.ticks[0] - ldus->ticks[0]);
      dus->ticks[1] += (__dus.ticks[1] - ldus->ticks[1]);
      dus->io_ticks += (__dus.io_ticks - ldus->io_ticks);
      dus->time_in_queue += (__dus.time_in_queue - ldus->time_in_queue);

      fio_gettime(&t, NULL);
      du->msec += mtime_since(&du->time, &t);
      memcpy(&du->time, &t, sizeof(t));
      memcpy(ldus, &__dus, sizeof(__dus));
}

void update_io_ticks(void)
{
      struct flist_head *entry;
      struct disk_util *du;

      dprint(FD_DISKUTIL, "update io ticks\n");

      flist_for_each(entry, &disk_list) {
            du = flist_entry(entry, struct disk_util, list);
            update_io_tick_disk(du);
      }
}

static struct disk_util *disk_util_exists(int major, int minor)
{
      struct flist_head *entry;
      struct disk_util *du;

      flist_for_each(entry, &disk_list) {
            du = flist_entry(entry, struct disk_util, list);

            if (major == du->major && minor == du->minor)
                  return du;
      }

      return NULL;
}

static int get_device_numbers(char *file_name, int *maj, int *min)
{
      struct stat st;
      int majdev, mindev;
      char tempname[PATH_MAX], *p;

      if (!lstat(file_name, &st)) {
            if (S_ISBLK(st.st_mode)) {
                  majdev = major(st.st_rdev);
                  mindev = minor(st.st_rdev);
            } else if (S_ISCHR(st.st_mode)) {
                  majdev = major(st.st_rdev);
                  mindev = minor(st.st_rdev);
                  if (fio_lookup_raw(st.st_rdev, &majdev, &mindev))
                        return -1;
            } else if (S_ISFIFO(st.st_mode))
                  return -1;
            else {
                  majdev = major(st.st_dev);
                  mindev = minor(st.st_dev);
            }
      } else {
            /*
             * must be a file, open "." in that path
             */
            strncpy(tempname, file_name, PATH_MAX - 1);
            p = dirname(tempname);
            if (stat(p, &st)) {
                  perror("disk util stat");
                  return -1;
            }

            majdev = major(st.st_dev);
            mindev = minor(st.st_dev);
      }

      *min = mindev;
      *maj = majdev;

      return 0;
}

static int read_block_dev_entry(char *path, int *maj, int *min)
{
      char line[256], *p;
      FILE *f;

      f = fopen(path, "r");
      if (!f) {
            perror("open path");
            return 1;
      }

      p = fgets(line, sizeof(line), f);
      fclose(f);

      if (!p)
            return 1;

      if (sscanf(p, "%u:%u", maj, min) != 2)
            return 1;

      return 0;
}

static struct disk_util *__init_per_file_disk_util(struct thread_data *td,
            int majdev, int mindev, char * path);

static void find_add_disk_slaves(struct thread_data *td, char *path,
            struct disk_util *masterdu)
{
      DIR *dirhandle = NULL;
      struct dirent *dirent = NULL;
      char slavesdir[PATH_MAX], temppath[PATH_MAX], slavepath[PATH_MAX];
      struct disk_util *slavedu = NULL;
      int majdev, mindev;
      ssize_t linklen;

      sprintf(slavesdir, "%s/%s",path, "slaves");
      dirhandle = opendir(slavesdir);
      if (!dirhandle)
            return;

      while ((dirent = readdir(dirhandle)) != NULL) {
            if (!strcmp(dirent->d_name, ".") || !strcmp(dirent->d_name, ".."))
                  continue;

            sprintf(temppath, "%s/%s", slavesdir, dirent->d_name);
            /* Can we always assume that the slaves device entries
             * are links to the real directories for the slave
             * devices?
             */
            if ((linklen = readlink(temppath, slavepath, PATH_MAX-1)) < 0) {
                  perror("readlink() for slave device.");
                  return;
            }
            slavepath[linklen]='\0';

            sprintf(temppath, "%s/%s/dev", slavesdir, slavepath);
            if (read_block_dev_entry(temppath, &majdev, &mindev)) {
                  perror("Error getting slave device numbers.");
                  return;
            }

            sprintf(temppath, "%s/%s", slavesdir, slavepath);
            __init_per_file_disk_util(td, majdev, mindev, temppath);
            slavedu = disk_util_exists(majdev, mindev);

            /* Should probably use an assert here. slavedu should
             * always be present at this point. */
            if (slavedu)
                  flist_add_tail(&slavedu->slavelist, &masterdu->slaves);
      }

      closedir(dirhandle);
      return;
}

static struct disk_util *disk_util_add(struct thread_data * td, int majdev,
                               int mindev, char *path)
{
      struct disk_util *du, *__du;
      struct flist_head *entry;

      dprint(FD_DISKUTIL, "add maj/min %d/%d: %s\n", majdev, mindev, path);

      du = smalloc(sizeof(*du));
      memset(du, 0, sizeof(*du));
      INIT_FLIST_HEAD(&du->list);
      sprintf(du->path, "%s/stat", path);
      du->name = smalloc_strdup(basename(path));
      du->sysfs_root = path;
      du->major = majdev;
      du->minor = mindev;
      INIT_FLIST_HEAD(&du->slavelist);
      INIT_FLIST_HEAD(&du->slaves);
      du->lock = fio_mutex_init(1);
      du->users = 0;

      flist_for_each(entry, &disk_list) {
            __du = flist_entry(entry, struct disk_util, list);

            dprint(FD_DISKUTIL, "found %s in list\n", __du->name);

            if (!strcmp(du->name, __du->name)) {
                  disk_util_free(du);
                  return __du;
            }
      }

      dprint(FD_DISKUTIL, "add %s to list\n", du->name);

      fio_gettime(&du->time, NULL);
      get_io_ticks(du, &du->last_dus);

      flist_add_tail(&du->list, &disk_list);
      find_add_disk_slaves(td, path, du);
      return du;
}


static int check_dev_match(int majdev, int mindev, char *path)
{
      int major, minor;

      if (read_block_dev_entry(path, &major, &minor))
            return 1;

      if (majdev == major && mindev == minor)
            return 0;

      return 1;
}

static int find_block_dir(int majdev, int mindev, char *path, int link_ok)
{
      struct dirent *dir;
      struct stat st;
      int found = 0;
      DIR *D;

      D = opendir(path);
      if (!D)
            return 0;

      while ((dir = readdir(D)) != NULL) {
            char full_path[256];

            if (!strcmp(dir->d_name, ".") || !strcmp(dir->d_name, ".."))
                  continue;

            sprintf(full_path, "%s/%s", path, dir->d_name);

            if (!strcmp(dir->d_name, "dev")) {
                  if (!check_dev_match(majdev, mindev, full_path)) {
                        found = 1;
                        break;
                  }
            }

            if (link_ok) {
                  if (stat(full_path, &st) == -1) {
                        perror("stat");
                        break;
                  }
            } else {
                  if (lstat(full_path, &st) == -1) {
                        perror("stat");
                        break;
                  }
            }

            if (!S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode))
                  continue;

            found = find_block_dir(majdev, mindev, full_path, 0);
            if (found) {
                  strcpy(path, full_path);
                  break;
            }
      }

      closedir(D);
      return found;
}

static struct disk_util *__init_per_file_disk_util(struct thread_data *td,
                                       int majdev, int mindev,
                                       char *path)
{
      struct stat st;
      char tmp[PATH_MAX];
      char *p;

      /*
       * If there's a ../queue/ directory there, we are inside a partition.
       * Check if that is the case and jump back. For loop/md/dm etc we
       * are already in the right spot.
       */
      sprintf(tmp, "%s/../queue", path);
      if (!stat(tmp, &st)) {
            p = dirname(path);
            sprintf(tmp, "%s/queue", p);
            if (stat(tmp, &st)) {
                  log_err("unknown sysfs layout\n");
                  return NULL;
            }
            strncpy(tmp, p, PATH_MAX - 1);
            sprintf(path, "%s", tmp);
      }

      if (td->o.ioscheduler && !td->sysfs_root)
            td->sysfs_root = strdup(path);

      return disk_util_add(td, majdev, mindev, path);
}

static struct disk_util *init_per_file_disk_util(struct thread_data *td,
                                     char *filename)
{

      char foo[PATH_MAX];
      struct disk_util *du;
      int mindev, majdev;

      if (get_device_numbers(filename, &majdev, &mindev))
            return NULL;

      dprint(FD_DISKUTIL, "%s belongs to maj/min %d/%d\n", filename, majdev,
                  mindev);

      du = disk_util_exists(majdev, mindev);
      if (du) {
            if (td->o.ioscheduler && !td->sysfs_root)
                  td->sysfs_root = strdup(du->sysfs_root);

            return du;
      }

      /*
       * for an fs without a device, we will repeatedly stat through
       * sysfs which can take oodles of time for thousands of files. so
       * cache the last lookup and compare with that before going through
       * everything again.
       */
      if (mindev == last_mindev && majdev == last_majdev)
            return last_du;

      last_mindev = mindev;
      last_majdev = majdev;

      sprintf(foo, "/sys/block");
      if (!find_block_dir(majdev, mindev, foo, 1))
            return NULL;

      return __init_per_file_disk_util(td, majdev, mindev, foo);
}

static struct disk_util *__init_disk_util(struct thread_data *td,
                                struct fio_file *f)
{
      return init_per_file_disk_util(td, f->file_name);
}

void init_disk_util(struct thread_data *td)
{
      struct fio_file *f;
      unsigned int i;

      if (!td->o.do_disk_util ||
          (td->io_ops->flags & (FIO_DISKLESSIO | FIO_NODISKUTIL)))
            return;

      for_each_file(td, f, i)
            f->du = __init_disk_util(td, f);
}

static void aggregate_slaves_stats(struct disk_util *masterdu)
{
      struct disk_util_stat *dus;
      struct flist_head *entry;
      struct disk_util *slavedu;
      double util, max_util = 0;
      int slavecount = 0;

      unsigned merges[2] = { 0, };
      unsigned ticks[2] = { 0, };
      unsigned time_in_queue = { 0, };
      unsigned long long sectors[2] = { 0, };
      unsigned ios[2] = { 0, };

      flist_for_each(entry, &masterdu->slaves) {
            slavedu = flist_entry(entry, struct disk_util, slavelist);
            dus = &slavedu->dus;
            ios[0] += dus->ios[0];
            ios[1] += dus->ios[1];
            merges[0] += dus->merges[0];
            merges[1] += dus->merges[1];
            sectors[0] += dus->sectors[0];
            sectors[1] += dus->sectors[1];
            ticks[0] += dus->ticks[0];
            ticks[1] += dus->ticks[1];
            time_in_queue += dus->time_in_queue;
            ++slavecount;

            util = (double) (100 * dus->io_ticks / (double) slavedu->msec);
            /* System utilization is the utilization of the
             * component with the highest utilization.
             */
            if (util > max_util)
                  max_util = util;

      }

      if (max_util > 100.0)
            max_util = 100.0;

      log_info(", aggrios=%u/%u, aggrmerge=%u/%u, aggrticks=%u/%u,"
                  " aggrin_queue=%u, aggrutil=%3.2f%%",
                  ios[0]/slavecount, ios[1]/slavecount,
                  merges[0]/slavecount, merges[1]/slavecount,
                  ticks[0]/slavecount, ticks[1]/slavecount,
                  time_in_queue/slavecount, max_util);

}

void show_disk_util(void)
{
      struct disk_util_stat *dus;
      struct flist_head *entry, *next;
      struct disk_util *du;
      double util;

      if (flist_empty(&disk_list))
            return;

      log_info("\nDisk stats (read/write):\n");

      flist_for_each(entry, &disk_list) {
            du = flist_entry(entry, struct disk_util, list);
            dus = &du->dus;

            util = (double) 100 * du->dus.io_ticks / (double) du->msec;
            if (util > 100.0)
                  util = 100.0;

            /* If this node is the slave of a master device, as
             * happens in case of software RAIDs, inward-indent
             * this stats line to reflect a master-slave
             * relationship. Because the master device gets added
             * before the slave devices, we can safely assume that
             * the master's stats line has been displayed in a
             * previous iteration of this loop.
             */
            if(!flist_empty(&du->slavelist))
                  log_info("  ");

            log_info("  %s: ios=%u/%u, merge=%u/%u, ticks=%u/%u, "
                   "in_queue=%u, util=%3.2f%%", du->name,
                                    dus->ios[0], dus->ios[1],
                                    dus->merges[0], dus->merges[1],
                                    dus->ticks[0], dus->ticks[1],
                                    dus->time_in_queue, util);

            /* If the device has slaves, aggregate the stats for
             * those slave devices also.
             */
            if(!flist_empty(&du->slaves))
                  aggregate_slaves_stats(du);

            log_info("\n");
      }

      /*
       * now free the list
       */
      flist_for_each_safe(entry, next, &disk_list) {
            flist_del(entry);
            du = flist_entry(entry, struct disk_util, list);
            disk_util_free(du);
      }
}

Generated by  Doxygen 1.6.0   Back to index