xaizek / etabench (License: GPLv3) (since 2022-09-08)
Benchmark for algorithms that compute I/O ETA
<root> / src / etabench.cpp (238463286996c0584c83e1faf50039845300342a) (6,205B) (mode 100644) [raw]
// etabench
// Copyright (C) 2022 xaizek <xaizek@posteo.net>
//
// This file is part of etabench.
//
// etabench is free software: you can redistribute it and/or modify
// it under the terms of version 3 of the GNU General Public License
// as published by the Free Software Foundation.
//
// etabench is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with etabench.  If not, see <https://www.gnu.org/licenses/>.

#include <cmath>

#include <algorithm>
#include <filesystem>
#include <iterator>
#include <memory>
#include <ranges>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>

#include <fmt/core.h>

#include "Args.hpp"
#include "EtaBench.hpp"
#include "Report.hpp"
#include "algs.hpp"
#include "core.hpp"
#include "profiles.hpp"

template <typename T, typename... Args>
inline std::vector<std::unique_ptr<T>>
make_unique_vector(Args &&...args)
{
    std::vector<std::unique_ptr<T>> v;
    (v.emplace_back(std::move(args)), ...);
    return v;
}

static std::vector<int>
parseList(const std::string &list, int max, const std::vector<int> &def)
{
    if (list == "*") {
        auto iota = std::views::iota(0, max);
        return std::vector<int>(iota.begin(), iota.end());
    }

    std::vector<int> result;
    for (const auto item : std::views::split(list, ',')) {
        auto b = item.begin();
        auto e = std::ranges::next(b, item.end());
        std::string part(b, e);

        std::size_t end;
        int num = std::stoi(part, &end);
        if (end != part.size()) {
            throw std::runtime_error("Bad list item: " + part);
        }
        if (num <= 0 || num > max) {
            throw std::runtime_error("Out of range: " + part);
        }

        result.push_back(num - 1);
    }

    if (result.empty()) {
        result = def;
    }

    std::ranges::sort(result, std::less<>());
    auto dups = std::ranges::unique(result);
    result.erase(dups.begin(), dups.end());

    return result;
}

int
main(int argc, char *argv[])
{
    try {
        Args args(argc, argv);

        int total = args.getTotal();

        auto algs = make_unique_vector<EtaAlg>(
            std::make_unique<AccelerationAlg>(),
            std::make_unique<AverageAlg>(),
            std::make_unique<FirefoxAlg>(),
            std::make_unique<GravityAlg>(),
            std::make_unique<ImmediateAlg>(),
            std::make_unique<LookBackAlg>(20),
            std::make_unique<SmoothingAlg>(10),
            std::make_unique<SwitchAlg>(),
            std::make_unique<WindowAlg>(20, 1.0f)
        );

        auto disabledAlgs = make_unique_vector<EtaAlg>(
            std::make_unique<AveChangeLimitAlg>(),
            std::make_unique<CombinedAlg>(make_unique_vector<EtaAlg>(
                std::make_unique<AverageAlg>(),
                std::make_unique<ImmediateAlg>()
            )),
            std::make_unique<ExponentialAlg>(1),
            std::make_unique<ImmChangeLimitAlg>(),
            std::make_unique<SlownessAlg>(1)
        );

        auto profs = make_unique_vector<Profile>(
            std::make_unique<ConstantProfile>(10),
            std::make_unique<LinearProfile>(total, -15),
            std::make_unique<LinearProfile>(total, -30),
            std::make_unique<LinearProfile>(total, -45),
            std::make_unique<LinearProfile>(total, -60),
            std::make_unique<LinearProfile>(total, -75),
            std::make_unique<LinearProfile>(total, 15),
            std::make_unique<LinearProfile>(total, 30),
            std::make_unique<LinearProfile>(total, 45),
            std::make_unique<LinearProfile>(total, 60),
            std::make_unique<LinearProfile>(total, 75),
            std::make_unique<RandomProfile>(10, 20),
            std::make_unique<ReplayProfile>(),
            std::make_unique<SawProfile>(20),
            std::make_unique<SquareProfile>(20),
            std::make_unique<StepProfile>(10)
        );

        auto algsIota = std::views::iota(0, (int)algs.size());
        std::vector<int> defAlgs(algsIota.begin(), algsIota.end());

        algs.insert(algs.cend(),
                    std::make_move_iterator(disabledAlgs.begin()),
                    std::make_move_iterator(disabledAlgs.end()));

        auto profsIota = std::views::iota(0, (int)profs.size());
        std::vector<int> defProfs(profsIota.begin(), profsIota.end());

        if (args.shouldListAlgs()) {
            fmt::print("Algorithms:\n");

            for (int i = 0; const auto &alg : algs) {
                fmt::print("{:2}. {}\n", ++i, alg->getName());
            }
            return 0;
        }

        if (args.shouldListProfs()) {
            fmt::print("Profiles:\n");

            for (int i = 0; const auto &prof : profs) {
                fmt::print("{:2}. {}\n", ++i, prof->getName());
            }
            return 0;
        }

        // Doing the check here to abort on incorrect information before doing
        // any work.
        std::string plotDir = args.getPlotDir();
        std::string montageFile = args.getMontageFile();
        if (std::filesystem::exists(plotDir)) {
            fmt::print("Plotting directory '{}' already exists\n", plotDir);
            return 1;
        }

        EtaBench bench;
        for (auto &profIdx :
             parseList(args.getProfs(), profs.size(), defProfs)) {
            bench.addProfile(std::move(profs[profIdx]));
        }
        for (auto &algIdx : parseList(args.getAlgs(), algs.size(), defAlgs)) {
            bench.addAlg(std::move(algs[algIdx]));
        }

        Report report;
        bench.run(total, report);
        report.print(args.isVerbose());
        if (!plotDir.empty() || !montageFile.empty()) {
            report.plot(plotDir, montageFile);
        }
    } catch (const std::exception &e) {
        fmt::print("Error: {}\n", e.what());
        return 1;
    } catch (...) {
        fmt::print("Unknown exception\n");
        return 1;
    }

    return 0;
}
Hints

Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://code.reversed.top/user/xaizek/etabench

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@code.reversed.top/user/xaizek/etabench

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a pull request:
... clone the repository ...
... make some changes and some commits ...
git push origin master