summaryrefslogtreecommitdiff
path: root/Userland/Utilities/disk_benchmark.cpp
blob: b0a4cdc7eda0c9f503f79cff7065cdcc379f259f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
/*
 * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
 * Copyright (c) 2022, Kenneth Myhra <kennethmyhra@serenityos.org>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <AK/ByteBuffer.h>
#include <AK/DeprecatedString.h>
#include <AK/ScopeGuard.h>
#include <AK/Types.h>
#include <AK/Vector.h>
#include <LibCore/ArgsParser.h>
#include <LibCore/ElapsedTimer.h>
#include <LibCore/System.h>
#include <LibMain/Main.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>

struct Result {
    u64 write_bps {};
    u64 read_bps {};
};

static Result average_result(Vector<Result> const& results)
{
    Result average;

    for (auto& res : results) {
        average.write_bps += res.write_bps;
        average.read_bps += res.read_bps;
    }

    average.write_bps /= results.size();
    average.read_bps /= results.size();

    return average;
}

static ErrorOr<Result> benchmark(DeprecatedString const& filename, int file_size, ByteBuffer& buffer, bool allow_cache);

ErrorOr<int> serenity_main(Main::Arguments arguments)
{
    DeprecatedString directory = ".";
    int time_per_benchmark = 10;
    Vector<size_t> file_sizes;
    Vector<size_t> block_sizes;
    bool allow_cache = false;

    Core::ArgsParser args_parser;
    args_parser.add_option(allow_cache, "Allow using disk cache", "cache", 'c');
    args_parser.add_option(directory, "Path to a directory where we can store the disk benchmark temp file", "directory", 'd', "directory");
    args_parser.add_option(time_per_benchmark, "Time elapsed per benchmark", "time-per-benchmark", 't', "time-per-benchmark");
    args_parser.add_option(file_sizes, "A comma-separated list of file sizes", "file-size", 'f', "file-size");
    args_parser.add_option(block_sizes, "A comma-separated list of block sizes", "block-size", 'b', "block-size");
    args_parser.parse(arguments);

    if (file_sizes.size() == 0) {
        file_sizes = { 131072, 262144, 524288, 1048576, 5242880 };
    }
    if (block_sizes.size() == 0) {
        block_sizes = { 8192, 32768, 65536 };
    }

    umask(0644);

    auto filename = DeprecatedString::formatted("{}/disk_benchmark.tmp", directory);

    for (auto file_size : file_sizes) {
        for (auto block_size : block_sizes) {
            if (block_size > file_size)
                continue;

            auto buffer_result = ByteBuffer::create_uninitialized(block_size);
            if (buffer_result.is_error()) {
                warnln("Not enough memory to allocate space for block size = {}", block_size);
                continue;
            }
            Vector<Result> results;

            outln("Running: file_size={} block_size={}", file_size, block_size);
            auto timer = Core::ElapsedTimer::start_new();
            while (timer.elapsed() < time_per_benchmark * 1000) {
                out(".");
                fflush(stdout);
                auto result = TRY(benchmark(filename, file_size, buffer_result.value(), allow_cache));
                results.append(result);
                usleep(100);
            }
            auto average = average_result(results);
            outln("Finished: runs={} time={}ms write_bps={} read_bps={}", results.size(), timer.elapsed(), average.write_bps, average.read_bps);

            sleep(1);
        }
    }

    return 0;
}

ErrorOr<Result> benchmark(DeprecatedString const& filename, int file_size, ByteBuffer& buffer, bool allow_cache)
{
    int flags = O_CREAT | O_TRUNC | O_RDWR;
    if (!allow_cache)
        flags |= O_DIRECT;

    int fd = TRY(Core::System::open(filename, flags, 0644));

    auto fd_cleanup = ScopeGuard([fd, filename] {
        auto void_or_error = Core::System::close(fd);
        if (void_or_error.is_error())
            warnln("{}", void_or_error.release_error());

        void_or_error = Core::System::unlink(filename);
        if (void_or_error.is_error())
            warnln("{}", void_or_error.release_error());
    });

    Result result;

    auto timer = Core::ElapsedTimer::start_new();

    ssize_t total_written = 0;
    while (total_written < file_size) {
        auto nwritten = TRY(Core::System::write(fd, buffer));
        total_written += nwritten;
    }

    result.write_bps = (u64)(timer.elapsed() ? (file_size / timer.elapsed()) : file_size) * 1000;

    TRY(Core::System::lseek(fd, 0, SEEK_SET));

    timer.start();
    ssize_t total_read = 0;
    while (total_read < file_size) {
        auto nread = TRY(Core::System::read(fd, buffer));
        total_read += nread;
    }

    result.read_bps = (u64)(timer.elapsed() ? (file_size / timer.elapsed()) : file_size) * 1000;
    return result;
}