//------------------------------------------------------------------------------
// File: DiskMeasurements.cc
// Author: Elvin Sindrilaru - CERN
// ----------------------------------------------------------------------
/************************************************************************
* EOS - the CERN Disk Storage System *
* Copyright (C) 2023 CERN/Switzerland *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program 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 this program. If not, see .*
************************************************************************/
#include "fst/utils/DiskMeasurements.hh"
#include "common/Logging.hh"
#include "common/BufferManager.hh"
#include
#include
#include
#include
#include
EOSFSTNAMESPACE_BEGIN
//------------------------------------------------------------------------------
// Generate random data
//------------------------------------------------------------------------------
void
GenerateRandomData(char* data, size_t length)
{
std::ifstream urandom("/dev/urandom", std::ios::in | std::ios::binary);
urandom.read(data, length);
urandom.close();
}
//------------------------------------------------------------------------------
// Create file path with given size
//------------------------------------------------------------------------------
bool FillFileGivenSize(int fd, size_t length)
{
using namespace eos::common;
int retc = 0, nwrite = 0;
const size_t sz {4 * 1024 * 1024};
auto buffer = GetAlignedBuffer(sz);
GenerateRandomData(buffer.get(), sz);
while (length > 0) {
nwrite = (length < sz) ? length : sz;
retc = write(fd, buffer.get(), nwrite);
if (retc != nwrite) {
return false;
}
length -= nwrite;
}
fsync(fd);
return true;
}
//------------------------------------------------------------------------------
// Create random temporary file in given location
//------------------------------------------------------------------------------
std::string MakeTemporaryFile(std::string base_path)
{
// Absolute base path specified
if (base_path.empty() || (*base_path.begin() != '/')) {
eos_static_err("msg=\"base path needs to a an absolute path\" base_path=%s",
base_path.c_str());
return "";
}
// Make sure path is / terminated
if (*base_path.rbegin() != '/') {
base_path += '/';
}
char tmp_path[1024];
snprintf(tmp_path, sizeof(tmp_path), "%sfst.ioping.XXXXXX", base_path.c_str());
int tmp_fd = mkstemp(tmp_path);
if (tmp_fd == -1) {
eos_static_crit("%s", "msg=\"failed to create temporary file!\"");
return "";
}
(void) close(tmp_fd);
return tmp_path;
}
//------------------------------------------------------------------------------
// Get IOPS measurement for the given path
//------------------------------------------------------------------------------
int ComputeIops(int fd, uint64_t rd_buf_size, std::chrono::seconds timeout)
{
using namespace eos::common;
using namespace std::chrono;
int IOPS = -1;
// Get file size
struct stat info;
if (fstat(fd, &info)) {
std::cerr << "err: failed to stat file fd=" << fd << std::endl;
eos_static_err("msg=\"failed to stat file\" fd=%i", fd);
return IOPS;
}
uint64_t fn_size = info.st_size;
auto buf = GetAlignedBuffer(rd_buf_size);
// Get a uniform int distribution for offset generation
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> distrib(0, 1024);
int iterations = 10000;
int actual_iter = 0;
uint64_t offset = 0ull;
microseconds duration {0};
time_point start, end;
for (; actual_iter < iterations; ++actual_iter) {
// Generate offset 4kB aligned inside the given file size
offset = (((fn_size * distrib(gen)) >> 10) >> 12) << 12;
start = high_resolution_clock::now();
if (pread(fd, buf.get(), rd_buf_size, offset) == -1) {
std::cerr << "error: failed to read at offset=" << offset << std::endl;
eos_static_err("msg=\"failed read\" offset=%llu", offset);
return IOPS;
}
end = high_resolution_clock::now();
duration += duration_cast(end - start);
if (actual_iter % 10 == 0) {
if (duration.count() > timeout.count() * 1000000) {
break;
}
}
}
IOPS = (actual_iter * 1000000.0) / duration.count();
return IOPS;
}
//------------------------------------------------------------------------------
// Get disk bandwidth for the given path
//------------------------------------------------------------------------------
int ComputeBandwidth(int fd, uint64_t rd_buf_size, std::chrono::seconds timeout)
{
using namespace eos::common;
using namespace std::chrono;
int bandwidth = -1;
// Get file size
struct stat info;
if (fstat(fd, &info)) {
std::cerr << "err: failed to stat file fd=" << fd << std::endl;
eos_static_err("msg=\"failed to stat file\" fd=%i", fd);
return bandwidth;
}
uint64_t fn_size = info.st_size;
auto buf = GetAlignedBuffer(rd_buf_size);
uint64_t offset = 0ull;
uint64_t max_read = 1 << 28; // 256 MB
time_point start, end;
start = high_resolution_clock::now();
while ((offset < fn_size) && (offset < max_read)) {
if (pread(fd, buf.get(), rd_buf_size, offset) == -1) {
std::cerr << "error: failed to read at offset=" << offset << std::endl;
eos_static_err("msg=\"failed read\" offset=%llu", offset);
return bandwidth;
}
offset += rd_buf_size;
if ((offset & (eos::common::MB - 1)) == 0) {
if (duration_cast(high_resolution_clock::now() - start) > timeout) {
break;
}
}
}
end = high_resolution_clock::now();
auto duration = duration_cast (end - start).count();
bandwidth = ((offset >> 20) * 1000000.0) / duration;
return bandwidth;
}
EOSFSTNAMESPACE_END