手把手教你写fio 插件(一)

FIO是一款方便IO性能测试的工具,以统计全面、模式灵活深得用户欢心。当前支持libaio、sync等IO引擎。在存储系统开发中,如何快速快速全面评测系统IO性能?給FIO添加调用存储系统提供的IO的插件,是一个好办法。那么,该怎么添加一个插件呢?

首先,得熟悉几个基本的公共数据结构。

fio 插件的公共接口

单任务IO latency/IOPS/BW 统计信息

fio/fio.h:

struct thread_data {
    struct flist_head opt_list;
    unsigned long flags;
    struct thread_options o;
    void *eo;
    pthread_t thread;
    unsigned int thread_number;
    unsigned int subjob_number;
    unsigned int groupid;
    struct thread_stat ts __attribute__ ((aligned(8)));

    int client_type;

    struct io_log *slat_log;
    struct io_log *clat_log;
    struct io_log *clat_hist_log;
    struct io_log *lat_log;
    struct io_log *bw_log;
    struct io_log *iops_log;

    struct workqueue log_compress_wq;

    struct thread_data *parent;

    uint64_t stat_io_bytes[DDIR_RWDIR_CNT];
    struct timespec bw_sample_time;

    uint64_t stat_io_blocks[DDIR_RWDIR_CNT];
    struct timespec iops_sample_time;

    volatile int update_rusage;
    struct fio_sem *rusage_sem;
    struct rusage ru_start;
    struct rusage ru_end;

    struct fio_file **files;
    unsigned char *file_locks;
    unsigned int files_size;
    unsigned int files_index;
    unsigned int nr_open_files;
    unsigned int nr_done_files;
    union {
        unsigned int next_file;
        struct frand_state next_file_state;
.....
    int error;
    int sig;
    int done;
    int stop_io;
    pid_t pid;
    char *orig_buffer;
    size_t orig_buffer_size;
    volatile int runstate;
    volatile bool terminate;
    bool last_was_sync;
    enum fio_ddir last_ddir;

    int mmapfd;

    void *iolog_buf;
    FILE *iolog_f;

    unsigned long rand_seeds[FIO_RAND_NR_OFFS];

    struct frand_state bsrange_state[DDIR_RWDIR_CNT];
    struct frand_state verify_state;
    struct frand_state trim_state;
    struct frand_state delay_state;

    struct frand_state buf_state;
    struct frand_state buf_state_prev;
    struct frand_state dedupe_state;
    struct frand_state zone_state;

    struct zone_split_index **zone_state_index;

    unsigned int verify_batch;
    unsigned int trim_batch;

    struct thread_io_list *vstate;

    int shm_id;

    /*
     * IO engine hooks, contains everything needed to submit an io_u
     * to any of the available IO engines.
     */
    struct ioengine_ops *io_ops;
    int io_ops_init;

    /*
     * IO engine private data and dlhandle.
     */
    void *io_ops_data;
.....

基本读IO操作单元

接受fio 打出的IO请求,分发到具体的IO引擎。
fio/fio/io_u.h

struct io_u {
    struct timespec start_time;
    struct timespec issue_time;

    **struct fio_file *file;**
    unsigned int flags;
    enum fio_ddir ddir;

    /*
     * For replay workloads, we may want to account as a different
     * IO type than what is being submitted.
     */
    enum fio_ddir acct_ddir;

    /*
     * Write generation
     */
    unsigned short numberio;

    /*
     * Allocated/set buffer and length
     */
    unsigned long buflen;
    unsigned long long offset;
    void *buf;
.......
 struct io_piece *ipo;

    unsigned int resid;
    unsigned int error;

    /*
     * io engine private data
     */
    union {
        unsigned int index;
        unsigned int seen;
        void *engine_data;
    };

    union {
        struct flist_head verify_list;
        struct workqueue_work work;
    };

    /*
     * Callback for io completion
     */
    int (*end_io)(struct thread_data *, struct io_u **);

    union {
#ifdef CONFIG_LIBAIO
    **    struct iocb iocb;**
#endif
#ifdef CONFIG_POSIXAIO
        os_aiocb_t aiocb;
#endif
#ifdef FIO_HAVE_SGIO
        struct sg_io_hdr hdr;
#endif
#ifdef CONFIG_GUASI
        guasi_req_t greq;
#endif
#ifdef CONFIG_SOLARISAIO
        aio_result_t resultp;
#endif
#ifdef FIO_HAVE_BINJECT
        struct b_user_cmd buc;
#endif
#ifdef CONFIG_RDMA
        struct ibv_mr *mr;
#endif
        void *mmap_data;
    };

uio 提供的基本操作:

extern struct io_u *__get_io_u(struct thread_data *);
extern struct io_u *get_io_u(struct thread_data *);
extern void put_io_u(struct thread_data *, struct io_u *);
extern void clear_io_u(struct thread_data *, struct io_u *);
extern void requeue_io_u(struct thread_data *, struct io_u **);
extern int __must_check io_u_sync_complete(struct thread_data *, struct io_u *);
extern int __must_check io_u_queued_complete(struct thread_data *, int);
extern void io_u_queued(struct thread_data *, struct io_u *);
extern int io_u_quiesce(struct thread_data *);
extern void io_u_log_error(struct thread_data *, struct io_u *);
extern void io_u_mark_depth(struct thread_data *, unsigned int);
extern void fill_io_buffer(struct thread_data *, void *, unsigned int, unsigned int);
extern void io_u_fill_buffer(struct thread_data *td, struct io_u *, unsigned int, unsigned int);
void io_u_mark_complete(struct thread_data *, unsigned int);
void io_u_mark_submit(struct thread_data *, unsigned int);
bool queue_full(const struct thread_data *);

int do_io_u_sync(const struct thread_data *, struct io_u *);
int do_io_u_trim(const struct thread_data *, struct io_u *);

代码位置: fio/engines/engine.h

定义并实现ioengine ops

struct ioengine_ops {
    struct flist_head list;
    const char *name;
    int version;
    int flags;
    int (*setup)(struct thread_data *);
    int (*init)(struct thread_data *);
    int (*prep)(struct thread_data *, struct io_u *);
    int (*queue)(struct thread_data *, struct io_u *);
    int (*commit)(struct thread_data *);
    int (*getevents)(struct thread_data *, unsigned int, unsigned int, const struct timespec *);
    struct io_u *(*event)(struct thread_data *, int);
    char *(*errdetails)(struct io_u *);
    int (*cancel)(struct thread_data *, struct io_u *);
    void (*cleanup)(struct thread_data *);
    int (*open_file)(struct thread_data *, struct fio_file *);
    int (*close_file)(struct thread_data *, struct fio_file *);
    int (*invalidate)(struct thread_data *, struct fio_file *);
    int (*unlink_file)(struct thread_data *, struct fio_file *);
    int (*get_file_size)(struct thread_data *, struct fio_file *);
    void (*terminate)(struct thread_data *);
    int (*iomem_alloc)(struct thread_data *, size_t);
    void (*iomem_free)(struct thread_data *);
    int (*io_u_init)(struct thread_data *, struct io_u *);
    void (*io_u_free)(struct thread_data *, struct io_u *);
    int option_struct_size;
    struct fio_option *options;
};

注册ioengine ops

extern struct ioengine_ops *load_ioengine(struct thread_data *);
extern void register_ioengine(struct ioengine_ops *);
extern void unregister_ioengine(struct ioengine_ops *);
extern void free_ioengine(struct thread_data *);
extern void close_ioengine(struct thread_data *);

fio libaio 插件的实现和分析

下面,以libaio的支持为例,进行分析:

代码位置: fio/engines/libaio.c

static struct ioengine_ops ioengine = {
    .name           = "libaio",
    .version        = FIO_IOOPS_VERSION,
    .init           = fio_libaio_init,
    .prep           = fio_libaio_prep,
    .queue          = fio_libaio_queue,
    .commit         = fio_libaio_commit,
    .cancel         = fio_libaio_cancel,
    .getevents      = fio_libaio_getevents,
    .event          = fio_libaio_event,
    .cleanup        = fio_libaio_cleanup,
    .open_file      = generic_open_file,
    .close_file     = generic_close_file,
    .get_file_size      = generic_get_file_size,
    .options        = options,
    .option_struct_size = sizeof(struct libaio_options),
};

name

定义支持fio的IO引擎的名称

version

定义当前改实现的版本

init

调用libaio的初始化函数;申请空间;倒腾对应的成员到thread data中去。

static int fio_libaio_init(struct thread_data *td)
{
    struct libaio_options *o = td->eo;
    struct libaio_data *ld;
    int err = 0;

    ld = calloc(1, sizeof(*ld));

    /*
     * First try passing in 0 for queue depth, since we don‘t
     * care about the user ring. If that fails, the kernel is too old
     * and we need the right depth.
     */
    if (!o->userspace_reap)
        err = io_queue_init(INT_MAX, &ld->aio_ctx);
    if (o->userspace_reap || err == -EINVAL)
        err = io_queue_init(td->o.iodepth, &ld->aio_ctx);
    if (err) {
        td_verror(td, -err, "io_queue_init");
        log_err("fio: check /proc/sys/fs/aio-max-nr\n");
        free(ld);
        return 1;
    }

    ld->entries = td->o.iodepth;
    ld->is_pow2 = is_power_of_2(ld->entries);
    ld->aio_events = calloc(ld->entries, sizeof(struct io_event));
    ld->iocbs = calloc(ld->entries, sizeof(struct iocb *));
    ld->io_us = calloc(ld->entries, sizeof(struct io_u *));

    td->io_ops_data = ld;
    return 0;
}

prep

处理从FIO传过来的IO请求,拿到xfer_buf/xfer_buflen/ io_u->offset,作为 io_prep_pread的参数, 执行libaio的单个请求的异步IO操作。

static int fio_libaio_prep(struct thread_data fio_unused *td, struct io_u *io_u)
{
    struct fio_file *f = io_u->file;

    if (io_u->ddir == DDIR_READ)
        io_prep_pread(&io_u->iocb, f->fd, io_u->xfer_buf, io_u->xfer_buflen, io_u->offset);
    else if (io_u->ddir == DDIR_WRITE)
        io_prep_pwrite(&io_u->iocb, f->fd, io_u->xfer_buf, io_u->xfer_buflen, io_u->offset);
    else if (ddir_sync(io_u->ddir))
        io_prep_fsync(&io_u->iocb, f->fd);

    return 0;
}

请求入队

把fio套件打出来的请求加入libaio内部的请求队列进去:

static int fio_libaio_queue(struct thread_data *td, struct io_u *io_u)
{
    struct libaio_data *ld = td->io_ops_data;

    fio_ro_check(td, io_u);

    if (ld->queued == td->o.iodepth)
        return FIO_Q_BUSY;

    /*
     * fsync is tricky, since it can fail and we need to do it
     * serialized with other io. the reason is that linux doesn‘t
     * support aio fsync yet. So return busy for the case where we
     * have pending io, to let fio complete those first.
     */
    if (ddir_sync(io_u->ddir)) {
        if (ld->queued)
            return FIO_Q_BUSY;

        do_io_u_sync(td, io_u);
        return FIO_Q_COMPLETED;
    }

    if (io_u->ddir == DDIR_TRIM) {
        if (ld->queued)
            return FIO_Q_BUSY;

        do_io_u_trim(td, io_u);
        return FIO_Q_COMPLETED;
    }

    ld->iocbs[ld->head] = &io_u->iocb;
    ld->io_us[ld->head] = io_u;
    ring_inc(ld, &ld->head, 1);
    return FIO_Q_QUEUED;
}

提交IO请求

把上面queue中的请求通过 libaio io_submit API 真正提交:

static int fio_libaio_commit(struct thread_data *td)
{
    struct libaio_data *ld = td->io_ops_data;
    struct iocb **iocbs;
    struct io_u **io_us;
    struct timespec ts;
    int ret, wait_start = 0;

    if (!ld->queued)
        return 0;

    do {
        long nr = ld->queued;

        nr = min((unsigned int) nr, ld->entries - ld->tail);
        io_us = ld->io_us + ld->tail;
        iocbs = ld->iocbs + ld->tail;

        ret = io_submit(ld->aio_ctx, nr, iocbs);
        if (ret > 0) {
            fio_libaio_queued(td, io_us, ret);
            io_u_mark_submit(td, ret);

            ld->queued -= ret;
            ring_inc(ld, &ld->tail, ret);
            ret = 0;
            wait_start = 0;

取消执行

取消提交的IO请求。同样是从统一的thread_data 和 uio请求里拿相关参数, 返回实际完成的io 请求的数量。

static int fio_libaio_cancel(struct thread_data *td, struct io_u *io_u)
{
    struct libaio_data *ld = td->io_ops_data;

    return io_cancel(ld->aio_ctx, &io_u->iocb, ld->aio_events);
}

抓取已经完成的IO的请求

上面提交的IO请求,多少已经完成,需要统计。同样调用libaio 对应的getevents接口:

static int fio_libaio_getevents(struct thread_data *td, unsigned int min,
                unsigned int max, const struct timespec *t)
{
    struct libaio_data *ld = td->io_ops_data;
    struct libaio_options *o = td->eo;
    unsigned actual_min = td->o.iodepth_batch_complete_min == 0 ? 0 : min;
    struct timespec __lt, *lt = NULL;
    int r, events = 0;

    if (t) {
        __lt = *t;
        lt = &__lt;
    }

    do {
        if (o->userspace_reap == 1
            && actual_min == 0
            && ((struct aio_ring *)(ld->aio_ctx))->magic
                == AIO_RING_MAGIC) {
            r = user_io_getevents(ld->aio_ctx, max,
                ld->aio_events + events);
        } else {
            r = io_getevents(ld->aio_ctx, actual_min,
                max, ld->aio_events + events, lt);
        }
        if (r > 0)
            events += r;
        else if ((min && r == 0) || r == -EAGAIN) {
            fio_libaio_commit(td);
            usleep(100);
        } else if (r != -EINTR)
            break;
    } while (events < min);

    return r < 0 ? r : events;
}

从IO 完成的请求的event中找到对应的IO请求

统计时需要,因为libaio可能是一批发射请求的,需要知道哪些IO请求已经完成:

static struct io_u *fio_libaio_event(struct thread_data *td, int event)
{
    struct libaio_data *ld = td->io_ops_data;
    struct io_event *ev;
    struct io_u *io_u;

    ev = ld->aio_events + event;
    io_u = container_of(ev->obj, struct io_u, iocb);

    if (ev->res != io_u->xfer_buflen) {
        if (ev->res > io_u->xfer_buflen)
            io_u->error = -ev->res;
        else
            io_u->resid = io_u->xfer_buflen - ev->res;
    } else
        io_u->error = 0;

    return io_u;
}

清理环境

测试完成之后,需要释放之前申请的内存。

static void fio_libaio_cleanup(struct thread_data *td)
{
    struct libaio_data *ld = td->io_ops_data;

    if (ld) {
        /*
         * Work-around to avoid huge RCU stalls at exit time. If we
         * don‘t do this here, then it‘ll be torn down by exit_aio().
         * But for that case we can parallellize the freeing, thus
         * speeding it up a lot.
         */
        if (!(td->flags & TD_F_CHILD))
            io_destroy(ld->aio_ctx);
        free(ld->aio_events);
        free(ld->iocbs);
        free(ld->io_us);
        free(ld);
    }
}

打开文件

测试前需要打开文件。对于通用的块设备、文件系统中的文件,直接调用generic_open_file就可以,它主要内容如下:

int generic_open_file(struct thread_data *td, struct fio_file *f)
{
    int is_std = 0;
    int flags = 0;
    int from_hash = 0;
....
 if (td_trim(td))
        goto skip_flags;
    if (td->o.odirect)
        flags |= OS_O_DIRECT;
    if (td->o.oatomic) {
        if (!FIO_O_ATOMIC) {
            td_verror(td, EINVAL, "OS does not support atomic IO");
            return 1;
        }
        flags |= OS_O_DIRECT | FIO_O_ATOMIC;
    }
    if (td->o.sync_io)
        flags |= O_SYNC;
    if (td->o.create_on_open && td->o.allow_create)
        flags |= O_CREAT;
skip_flags:
    if (f->filetype != FIO_TYPE_FILE)
        flags |= FIO_O_NOATIME;

open_again:
    if (td_write(td)) {
        if (!read_only)
            flags |= O_RDWR;

        if (f->filetype == FIO_TYPE_FILE && td->o.allow_create)
            flags |= O_CREAT;

        if (is_std)
            f->fd = dup(STDOUT_FILENO);
        else
            from_hash = file_lookup_open(f, flags);
    } else if (td_read(td)) {
        if (f->filetype == FIO_TYPE_CHAR && !read_only)
            flags |= O_RDWR;
        else
            flags |= O_RDONLY;

        if (is_std)
            f->fd = dup(STDIN_FILENO);
        else
            from_hash = file_lookup_open(f, flags);
    } else if (td_trim(td)) {
        assert(!td_rw(td)); /* should have matched above */
        flags |= O_RDWR;
        from_hash = file_lookup_open(f, flags);
    }
....
}

关闭文件

同上,关闭测试完成的文件,对于块设备和文件系统中的文件,调用close系统调用就可以generic_close_file:

int generic_close_file(struct thread_data fio_unused *td, struct fio_file *f)
{
    int ret = 0;

    dprint(FD_FILE, "fd close %s\n", f->file_name);

    remove_file_hash(f);

    if (close(f->fd) < 0)
        ret = errno;

    f->fd = -1;

    if (f->shadow_fd != -1) {
        close(f->shadow_fd);
        f->shadow_fd = -1;
    }

    f->engine_pos = 0;
    return ret;
}

获取文件大小

对于libaio支持的块设备或文件,也是调用通用的接口去stat 文件大小:

/*
 * This function i.e. get_file_size() is the default .get_file_size
 * implementation of majority of I/O engines.
 */
int generic_get_file_size(struct thread_data *td, struct fio_file *f)
{
    return get_file_size(td, f);
}

选性描述

对于libaio的option,具体描述如下:

static struct fio_option options[] = {
    {
        .name   = "userspace_reap",
        .lname  = "Libaio userspace reaping",
        .type   = FIO_OPT_STR_SET,
        .off1   = offsetof(struct libaio_options, userspace_reap),
        .help   = "Use alternative user-space reap implementation",
        .category = FIO_OPT_C_ENGINE,
        .group  = FIO_OPT_G_LIBAIO,
    },
    {
        .name   = NULL,
    },
};

选项大小

告诉fio 测试套件,libaio引擎的option需占用的空间大小:

 .option_struct_size = sizeof(struct libaio_options),

libaio引擎的注册和注销

调用engine通用的注册和注销接口如下:

static void fio_init fio_libaio_register(void)
{
    register_ioengine(&ioengine);
}

static void fio_exit fio_libaio_unregister(void)
{
    unregister_ioengine(&ioengine);
}

原文地址:https://blog.51cto.com/xiamachao/2366984

时间: 2024-07-31 19:01:06

手把手教你写fio 插件(一)的相关文章

手把手教你写电商爬虫-第四课 淘宝网商品爬虫自动JS渲染

系列教程: 手把手教你写电商爬虫-第一课 找个软柿子捏捏 手把手教你写电商爬虫-第二课 实战尚妆网分页商品采集爬虫 手把手教你写电商爬虫-第三课 实战尚妆网AJAX请求处理和内容提取 老规矩,爬之前首先感谢淘宝公布出这么多有价值的数据,才让我们这些爬虫们有东西可以搜集啊,不过淘宝就不用我来安利了 广大剁手党相信睡觉的时候都能把网址打出来吧. 工欲善其事,必先利其器,先上工具: 1.神箭手云爬虫,2.Chrome浏览器 3.Chrome的插件XpathHelper 不知道是干嘛的同学请移步第一课

手把手教你写电商爬虫-第五课 京东商品评论爬虫 一起来对付反爬虫

系列教程: 手把手教你写电商爬虫-第一课 找个软柿子捏捏 手把手教你写电商爬虫-第二课 实战尚妆网分页商品采集爬虫 手把手教你写电商爬虫-第三课 实战尚妆网AJAX请求处理和内容提取 手把手教你写电商爬虫-第四课 淘宝网商品爬虫自动JS渲染 四节课过去了,咱们在爬虫界也都算见过世面的人,现在再来一些什么ajax加载之类的小鱼小虾应该不在话下了,即使是淘宝这种大量的ajax,我们 祭上我们的核武器,也轻松应对了,这一课主要是来看看除了技术上的页面处理外,我们还会遇上更棘手的问题,就是反爬虫,当然现

手把手教你写电商爬虫-第二课 实战尚妆网分页商品采集爬虫

系列教程 手把手教你写电商爬虫-第一课 找个软柿子捏捏 如果没有看过第一课的朋友,请先移步第一课,第一课讲了一些基础性的东西,通过软柿子"切糕王子"这个电商网站好好的练了一次手,相信大家都应该对写爬虫的流程有了一个大概的了解,那么这课咱们就话不多说,正式上战场,对垒尚妆网. 首先,向我们被爬网站致敬,没有他们提供数据,我们更是无从爬起,所以先安利一下尚妆网: 经营化妆品时尚购物,大数据为驱动,并依托智能首饰为入口的新一代智慧美妆正品电子商务平台.其创始团队来自天猫.支付宝.欧莱雅.薇姿

手把手教你写网络爬虫(3):开源爬虫框架对比

手把手教你写网络爬虫(3) 作者:拓海 摘要:从零开始写爬虫,初学者的速成指南! 封面: 介绍 大家好!我们从今天开始学习开源爬虫框架Scrapy,如果你看过<手把手>系列的前两篇,那么今天的内容就非常容易理解了.细心的读者也许会有疑问,为什么不学出身名门的Apache顶级项目Nutch,或者人气飙升的国内大神开发的Pyspider等框架呢?原因很简单,我们来看一下主流爬虫框架在GitHub上的活跃度: Project Language Star Watch Fork Nutch Java 1

Android开发之手把手教你写ButterKnife框架(二)

欢迎转载,转载请标明出处: http://blog.csdn.net/johnny901114/article/details/52664112 本文出自:[余志强的博客] 上一篇博客Android开发之手把手教你写ButterKnife框架(一)我们讲了ButterKnife是什么.ButterKnife的作用和功能介绍以及ButterKnife的实现原理. 本篇博客主要讲在android studio中如何使用apt. 一.新建个项目, 然后创建一个module名叫processor 新建m

手把手教你写Sublime中的Snippet

手把手教你写Sublime中的Snippet Sublime Text号称最性感的编辑器, 并且越来越多人使用, 美观, 高效 关于如何使用Sublime text可以参考我的另一篇文章, 相信你会喜欢上的..Sublime Text 2使用心得 现在介绍一下Snippet, Snippets are smart templates that will insert text for you and adapt it to their context. Snippet 是插入到文本中的智能模板并

手把手教你写专利申请书/怎样申请专利

手把手教你写专利申请书·怎样申请专利 摘要小前言(一)申请前的准备工作    1.申请前查询    2.其它方面的考虑    3.申请文件准备(二)填写专利申请系列文档    1.实际操作步骤    2.详细操作    3.经验分享.注意事项(三)关于费用(四)其它的话參考资源提示常见问题的问与答 摘要: 怎样写好专利申请?由于非常多专利申请人都是第一次申请,因此,可能有一种神奇和些许恐惧.本文写的是怎样写专利申请书,手把手教你写专利申请并提供申请专利时的注意事项,专利申请费用及费用减缓等相关參

手把手教你写Windows 64位平台调试器

本文网页排版有些差,已上传了doc,可以下载阅读.本文中的所有代码已打包,下载地址在此. -------------------------------------------------------------------------------------------------------------------------------------------------------------- 手写一个调试器有助于我们理解hook.进程注入等底层黑客技术具体实现,在编写过程中需要涉及大

手把手教你写电商爬虫-第三课 实战尚妆网AJAX请求处理和内容提取

系列教材: 手把手教你写电商爬虫-第一课 找个软柿子捏捏 手把手教你写电商爬虫-第二课 实战尚妆网分页商品采集爬虫 看完两篇,相信大家已经从开始的小菜鸟晋升为中级菜鸟了,好了,那我们就继续我们的爬虫课程. 上一课呢一定是因为对手太强,导致我们并没有完整的完成尚妆网的爬虫. 吭吭~,我们这一课继续,争取彻底搞定尚妆网,不留任何遗憾. 我们先回顾一下,上一课主要遗留了两个问题,两个问题都和ajax有关. 1.由于是ajax加载下一页,导致下一页url并不会被系统自动发现. 2.商品页面的价格是通过a