/*
  cc read-by.c -O2 -o read-by
  dd if=/dev/urandom of=bigfile bs=1000000 count=200
  for i in $(seq 3); do for MODE in mmap seek readall; do sync && sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches' && ./read-by $MODE 1048576 bigfile; done; done

  Adjust as necessary if not on Linux.
*/

#include <sys/time.h>
#include <string.h>
#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
#include <stdint.h>
#include <fcntl.h>

typedef enum 
{
    MMAP,
    SEEK,
    READALL,
} mode_t;

double now() 
{
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return tv.tv_sec + 1e-6 * tv.tv_usec;
}

uint64_t filesize(const char * path) 
{
    struct stat buf;
    if (stat(path, &buf))
    {
        perror("stat");
        exit(2);
    }
    return buf.st_size;
}

uint32_t read_by_mmap(int fd, uint64_t rowsize, uint64_t rows) 
{
    uint32_t total = 0;
    char * image = mmap(NULL, rowsize * rows, PROT_READ, MAP_SHARED, fd, 0);
    assert(image);
    uint64_t i;
    for (i = 0; i < rows; ++i) 
        total += *(uint32_t *) (image + i * rowsize);
    return total;
}

uint32_t read_by_seek(int fd, uint64_t rowsize, uint64_t rows)
{
    uint32_t total = 0;
    uint32_t buf;
    uint64_t i;
    for (i = 0; i < rows; ++i)
    {
        assert(pread(fd, &buf, 4, i * rowsize) == 4);
        total += buf;
    }
    return total;
}

uint32_t read_by_readall(int fd, uint64_t rowsize, uint64_t rows) 
{
    char buf[rowsize];
    uint32_t total = 0;
    uint64_t i;
    uint64_t nread, nread_this_time;
    for (i = 0; i < rows; ++i) 
    {
        nread = 0;
        while (nread < rowsize) 
        {
            nread_this_time = read(fd, buf + nread, rowsize - nread);
            assert(nread_this_time > 0);
            nread += nread_this_time;
        }
        total += *(uint32_t *) buf;
    }
    return total;
}

int main(int argc, char ** argv) 
{
    if (argc != 4) 
    {
        fprintf(stderr,
                "Usage:\n"
                "  %s mmap ROWSIZE PATH\n"
                "  %s seek ROWSIZE PATH\n"
                "  %s readall ROWSIZE PATH\n",
                argv[0], argv[0], argv[0]);
        return 2;
    }
    
    mode_t mode;
    if (!strcmp(argv[1], "mmap"))
        mode = MMAP;
    else if (!strcmp(argv[1], "seek"))
        mode = SEEK;
    else if (!strcmp(argv[1], "readall"))
        mode = READALL;
    else 
    {
        fprintf(stderr, "bad mode\n");
        return 2;
    }

    uint64_t rowsize = strtoul(argv[2], NULL, 10);
    if (rowsize < 4) 
    {
        fprintf(stderr, "rowsize must be > 4\n");
        return 2;
    }
    const char * path = argv[3];

    uint64_t size = filesize(path);

    uint64_t rows = size / rowsize;
    assert(rows * rowsize <= size);

    int fd = open(path, O_RDONLY);
    if (!fd) 
    {
        perror("open");
        return 2;
    }

    uint32_t total;
    double start = now();
    const char * mode_name;
    switch (mode)
    {
    case MMAP:
        total = read_by_mmap(fd, rowsize, rows);
        mode_name = "MMAP";
        break;
    case SEEK:
        total = read_by_seek(fd, rowsize, rows);
        mode_name = "SEEK";
        break;
    case READALL:
        total = read_by_readall(fd, rowsize, rows);
        mode_name = "READALL";
        break;
    default:
        assert(0);
    }
    double end = now();

    close(fd);
    printf("Mode: %s. Checksum: %x. Time: %.2f s\n", mode_name, total, end - start);
    return 0;
}
