#include <getopt.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <linux/inotify.h>

#ifndef __NR_inotify_init
# define __NR_inotify_init      291
# define __NR_inotify_add_watch 292
# define __NR_inotify_rm_watch  293
#endif

#define TMP_TEMPLATE "/tmp/inotify.XXXXXXXX"

static unsigned long long tvdiff(struct timeval a, struct timeval b)
{
        unsigned long long all = a.tv_sec*1000000ll+a.tv_usec;
        unsigned long long bll = b.tv_sec*1000000ll+b.tv_usec;

        return bll-all;
}

static int idev;        /* inotify device */
int niter, nfiles, verbose;
char **tmpfiles;

int read_inotify_event(void)
{
        static char buf[1024*1024];    /* 1 MB */
        int queue_len;
        int read_len;

        if (ioctl(idev, FIONREAD, &queue_len) < 0) {
                perror("ioctl");
                return 0;
        }
        if (queue_len == 0)
                return 0;

        read_len = read(idev, buf, sizeof(buf));

        return queue_len;
}

int main(int argc, char **argv)
{
        int fd, i, j, status = 1;
        fd_set rset;

        struct timeval tv_pre, tv_post;
        struct rusage usage_pre, usage_post;

        for (;;) {
                int c = getopt(argc, argv, "i:f:v");
                if (c == -1)
                        break;
                switch (c) {
                        case 'i':
                                niter = atoi(optarg);
                                break;

                        case 'f':
                                nfiles = atoi(optarg);
                                break;

                        case 'v':
                                verbose = 1;
                                break;

                        case '?':
                                fprintf(stderr, "error parsing args\n");
                                exit(1);

                        default:
                                printf("?? getopt returned character code 0%o ??\n", c);
                                exit(1);
                }
        }
        if (optind < argc) {
                fprintf(stderr, "unknown arg: %s\n", argv[optind]);
                exit(1);
        }

        /* defaults */
        if (niter  <= 0) niter  = 1;
        if (nfiles <= 0) nfiles = 1;
        printf("%s: niter = %d, nfiles = %d\n", argv[0], niter, nfiles);

        if ((idev = syscall(__NR_inotify_init)) < 0) {
                perror("inotify_init");
                return -1;
        }

        tmpfiles = malloc(sizeof(*tmpfiles) * nfiles);
        if (!tmpfiles) {
                perror("malloc");
                return -1;
        }
        memset(tmpfiles, 0, sizeof(*tmpfiles) * nfiles);

        for (i = 0; i < nfiles; i++) {
                char *fname = strdup(TMP_TEMPLATE);
                if (!fname) {
                        perror("strdup");
                        goto cleanup;
                }

                fd = mkstemp(fname);
                if (fd < 0) {
                        perror("mkstemp");
                        free(fname);
                        goto cleanup;
                }
                close(fd);

                tmpfiles[i] = fname;
        }

        for (i = 0; i < nfiles; i++) {
                if (syscall(__NR_inotify_add_watch, idev, tmpfiles[i],
                            IN_OPEN) < 0)
                        perror("inotify_add_watch");
        }
        (void)read_inotify_event();

        gettimeofday(&tv_pre, 0);
        getrusage(RUSAGE_SELF, &usage_pre);
        for (j = 0; j < niter; j++) {
                for (i = 0; i < nfiles; i++) {
                        if (verbose)
                                fprintf(stderr, "(%d,%d) ", j, i);
                        if ((fd = open(tmpfiles[i], O_RDWR)) == -1) {
                                perror("open");
                                goto cleanup;
                        }
                        close(fd);
                        if (verbose)
                                fprintf(stderr, "o");

                        FD_ZERO(&rset);
                        FD_SET(idev, &rset);
                        if (select(idev+1, &rset, NULL, NULL, NULL) > 0) {
                                if (verbose) {
                                        fprintf(stderr, read_inotify_event() ?
                                                "e\n" : "!\n");
                                } else
                                        (void)read_inotify_event();
                        }
                }
        }
        gettimeofday(&tv_post, 0);
        getrusage(RUSAGE_SELF, &usage_post);

        printf("Number of watches: %d Iterations: %d\n", nfiles, niter);
        printf("\naverage to generate and receive event\n");
        printf("real  %8llu usec\n", tvdiff(tv_pre, tv_post)/(nfiles * niter));
        printf("user  %8llu usec\n", tvdiff(usage_pre.ru_utime, usage_post.ru_utime)/(nfiles * niter));
        printf("sys   %8llu usec\n", tvdiff(usage_pre.ru_stime, usage_post.ru_stime)/(nfiles * niter));

        status = 0;
cleanup:
        for (i = 1; i < nfiles; i++) {
                if (!tmpfiles[i])
                        break;
                unlink(tmpfiles[i]);
                free(tmpfiles[i]);
                tmpfiles[i] = NULL;
        }
        free(tmpfiles);
        return status;
}
