#!/usr/bin/python3

'''This script executes random-sized reads and writes to one or more block devices to test them.
It will destroy the contents of the block device.'''

import sys
import os
import mmap
import random
import time

class BlockDevice:
    def __init__(self, path, sectorsize = 512):
        self.path = path
        self.dev = os.fdopen(os.open(path, os.O_RDWR | os.O_DIRECT | os.O_SYNC), "rb+", 0)
        self.sectorsize = sectorsize

    def write_block(self, first_sector, sector_count, seed):
        rnd = random.Random(seed)
        buffer = mmap.mmap(-1, sector_count * self.sectorsize)
        buffer.write(rnd.randbytes(sector_count * self.sectorsize))
        
        start = time.time()
        self.dev.seek(first_sector * self.sectorsize)
        self.dev.write(buffer)
        elapsed = time.time() - start
        speed = sector_count * self.sectorsize / elapsed / 1e6

        print("Wrote  %16s, %8d, %8d, %8d, %8.3f MB/s" % (self.path, first_sector, sector_count, seed, speed))
        return speed

    def verify_block(self, first_sector, sector_count, seed):
        rnd = random.Random(seed)
        buffer = mmap.mmap(-1, sector_count * self.sectorsize)

        start = time.time()
        self.dev.seek(first_sector * self.sectorsize)
        self.dev.readinto(buffer)
        elapsed = time.time() - start
        speed = sector_count * self.sectorsize / elapsed / 1e6

        print("Verify %16s, %8d, %8d, %8d, %8.3f MB/s" % (self.path, first_sector, sector_count, seed, speed))

        buffer.seek(0)
        actual = buffer.read(sector_count * self.sectorsize)
        expected = rnd.randbytes(sector_count * self.sectorsize)
        if expected != actual:
            print("Compare error, device = %s, sectorsize = %d, first_sector = %d, sector_count = %d, seed = %d"
                % (self.path, self.sectorsize, first_sector, sector_count, seed))
            fname = "%d" % time.time()
            open(fname + ".expected", "wb").write(expected)
            open(fname + ".actual", "wb").write(actual)
            print("Saved data to %s.expected/actual" % fname)
            raise Exception("Compare error")
        
        return speed

if __name__ == "__main__":
    blockdev = BlockDevice(sys.argv[1])
    
    seed = 1
    
    results = '# ReqSize(B)  RdSpeed(MB/s)  WrSpeed(MB/s)\n'
    for i in range(12):
        seed += 1
        seccount = 2**i
        wr_speeds = []
        rd_speeds = []
        samplecount = 8
        for i in range(samplecount):
            wr_speeds.append(blockdev.write_block(0, seccount, seed))
            time.sleep(0.2)
            rd_speeds.append(blockdev.verify_block(0, seccount, seed))
            time.sleep(0.2)

        # Get median
        wr_speeds.sort()
        rd_speeds.sort()
        wr_speed = wr_speeds[samplecount//2]
        rd_speed = rd_speeds[samplecount//2]
        
        results += '%8d %8.3f %8.3f\n' % (seccount * 512, rd_speed, wr_speed)
    
    print(results)

