/* Test the performance of various file locking mechanisms
(C) 2016-2017 Niall Douglas (6 commits)
File Created: Mar 2016
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License in the accompanying file
Licence.txt or at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Distributed under the Boost Software License, Version 1.0.
(See accompanying file Licence.txt or copy at
http://www.boost.org/LICENSE_1_0.txt)
*/
//! On exit dumps a CSV file of the LLFIO log, one per child worker
#define DEBUG_CSV 1
//! Seconds to run the benchmark
#define BENCHMARK_DURATION 10
#define _CRT_SECURE_NO_WARNINGS 1
#include "../../include/llfio/llfio.hpp"
#include "kerneltest/v1.0/child_process.hpp"
#include
#include
#include
#ifdef _WIN32
#undef _CRT_NONSTDC_DEPRECATE
#define _CRT_NONSTDC_DEPRECATE(a)
#include // for kbhit()
#else
#include
#include
bool kbhit()
{
termios term;
tcgetattr(0, &term);
termios term2 = term;
term2.c_lflag &= ~ICANON;
tcsetattr(0, TCSANOW, &term2);
int byteswaiting;
ioctl(0, FIONREAD, &byteswaiting);
tcsetattr(0, TCSANOW, &term);
return byteswaiting > 0;
}
#endif
namespace llfio = LLFIO_V2_NAMESPACE;
namespace child_process = KERNELTEST_V1_NAMESPACE::child_process;
static volatile size_t *shared_memory;
static void initialise_shared_memory()
{
auto fh = llfio::file_handle::file({}, "shared_memory", llfio::file_handle::mode::write, llfio::file_handle::creation::if_needed, llfio::file_handle::caching::temporary).value();
auto sh = llfio::section_handle::section(fh, 8, llfio::section_handle::flag::write).value();
auto mp = llfio::map_handle::map(sh).value();
shared_memory = (size_t *) mp.address();
if(!shared_memory)
abort();
*shared_memory = (size_t) -1;
}
static void child_locks(size_t id)
{
size_t current = *shared_memory;
if(current != (size_t) -1)
{
std::cerr << "FATAL: Lock algorithm is broken! " << current << " still holds the lock!" << std::endl;
std::terminate();
}
*shared_memory = id;
}
static void child_unlocks(size_t id)
{
size_t current = *shared_memory;
if(current != id)
{
std::cerr << "FATAL: Lock algorithm is broken! " << current << " has stolen the lock!" << std::endl;
std::terminate();
}
*shared_memory = (size_t) -1;
}
int main(int argc, char *argv[])
{
if(argc < 4)
{
std::cerr << "Usage: " << argv[0] << " [!] " << std::endl;
return 1;
}
initialise_shared_memory();
// ******** MASTER PROCESS BEGINS HERE ********
if(strcmp(argv[1], "spawned") && strcmp(argv[1], "!spawned"))
{
size_t waiters = atoi(argv[3]);
if(!waiters || !atoi(argv[2]))
{
std::cerr << "Usage: " << argv[0] << " [!] " << std::endl;
return 1;
}
std::vector children;
auto mypath = child_process::current_process_path();
#ifdef UNICODE
std::vector args = {L"spawned", L"", L"", L"", L"00"};
args[1].resize(strlen(argv[1]));
for(size_t n = 0; n < args[1].size(); n++)
args[1][n] = argv[1][n];
args[2].resize(strlen(argv[2]));
for(size_t n = 0; n < args[2].size(); n++)
args[2][n] = argv[2][n];
args[3].resize(strlen(argv[3]));
for(size_t n = 0; n < args[3].size(); n++)
args[3][n] = argv[3][n];
#else
std::vector args = {"spawned", argv[1], argv[2], argv[3], "00"};
#endif
auto env = child_process::current_process_env();
std::cout << "Launching " << waiters << " copies of myself as a child process ..." << std::endl;
for(size_t n = 0; n < waiters; n++)
{
if(n >= 10)
{
args[4][0] = (char) ('0' + (n / 10));
args[4][1] = (char) ('0' + (n % 10));
}
else
{
args[4][0] = (char) ('0' + n);
args[4][1] = 0;
}
auto child = child_process::child_process::launch(mypath, args, env, true);
if(child.has_error())
{
std::cerr << "FATAL: Child " << n << " could not be launched due to " << child.error().message() << std::endl;
return 1;
}
children.push_back(std::move(child.value()));
}
// Wait for all children to tell me they are ready
char buffer[1024];
std::cout << "Waiting for all children to become ready ..." << std::endl;
for(auto &child : children)
{
auto &i = child.cout();
if(!i.getline(buffer, sizeof(buffer)))
{
std::cerr << "ERROR: Child seems to have vanished!" << std::endl;
return 1;
}
if(0 != strncmp(buffer, "READY", 5))
{
std::cerr << "ERROR: Child wrote unexpected output '" << buffer << "'" << std::endl;
return 1;
}
}
#if 0
std::cout << "Attach your debugger now and press Return" << std::endl;
getchar();
#endif
#if 0
auto begin = std::chrono::steady_clock::now();
while(std::chrono::duration_cast(std::chrono::steady_clock::now() - begin).count() < 2)
;
#endif
std::cout << "Benchmarking for " << BENCHMARK_DURATION << " seconds ..." << std::endl;
// Issue go command to all children
for(auto &child : children)
child.cin() << "GO" << std::endl;
// Wait for benchmark to complete
std::this_thread::sleep_for(std::chrono::seconds(BENCHMARK_DURATION));
std::cout << "Stopping benchmark and telling children to report results ..." << std::endl;
// Tell children to quit
for(auto &child : children)
child.cin() << "STOP" << std::endl;
unsigned long long results = 0, result;
std::cout << std::endl;
std::ofstream oh("benchmark_locking.csv");
for(size_t n = 0; n < children.size(); n++)
{
auto &child = children[n];
if(!child.cout().getline(buffer, sizeof(buffer)))
{
std::cerr << "ERROR: Child seems to have vanished!" << std::endl;
return 1;
}
if(0 != strncmp(buffer, "RESULTS(", 8))
{
std::cerr << "ERROR: Child wrote unexpected output '" << buffer << "'." << std::endl;
return 1;
}
result = atol(&buffer[8]);
std::cout << "Child " << n << " reports result " << result << std::endl;
results += result;
if(n)
oh << ",";
oh << result;
}
results /= BENCHMARK_DURATION;
std::cout << "Total result: " << results << " ops/sec" << std::endl;
oh << "\n" << results << std::endl;
return 0;
}
// ******** CHILD PROCESS BEGINS HERE ********
if(argc < 6)
{
std::cerr << "ERROR: args too short" << std::endl;
return 1;
}
enum class lock_algorithm
{
unknown,
atomic_append,
byte_ranges,
lock_files,
memory_map
} test = lock_algorithm::unknown;
bool contended = true;
if(!strcmp(argv[2], "atomic_append"))
test = lock_algorithm::atomic_append;
else if(!strcmp(argv[2], "byte_ranges"))
test = lock_algorithm::byte_ranges;
else if(!strcmp(argv[2], "lock_files"))
test = lock_algorithm::lock_files;
else if(!strcmp(argv[2], "memory_map"))
test = lock_algorithm::memory_map;
else if(!strcmp(argv[2], "!atomic_append"))
{
test = lock_algorithm::atomic_append;
contended = false;
}
else if(!strcmp(argv[2], "!byte_ranges"))
{
test = lock_algorithm::byte_ranges;
contended = false;
}
else if(!strcmp(argv[2], "!lock_files"))
{
test = lock_algorithm::lock_files;
contended = false;
}
else if(!strcmp(argv[2], "!memory_map"))
{
test = lock_algorithm::memory_map;
contended = false;
}
if(test == lock_algorithm::unknown)
{
std::cerr << "ERROR: unknown test requested" << std::endl;
return 1;
}
size_t total_locks = atoi(argv[3]), waiters = atoi(argv[4]), this_child = atoi(argv[5]), count = 0;
(void) waiters;
if(!total_locks)
{
std::cerr << "ERROR: unknown total locks requested" << std::endl;
return 1;
}
// I am a spawned child. Tell parent I am ready.
std::cout << "READY(" << this_child << ")" << std::endl;
// Wait for parent to let me proceed
std::atomic done(-1);
std::thread worker([test, contended, total_locks, this_child, &done, &count] {
std::unique_ptr algorithm;
auto base = llfio::path_handle::path(".").value();
switch(test)
{
case lock_algorithm::atomic_append:
{
auto v = llfio::algorithm::shared_fs_mutex::atomic_append::fs_mutex_append({}, "lockfile");
if(v.has_error())
{
std::cerr << "ERROR: Creation of lock algorithm returns " << v.error().message() << std::endl;
return;
}
algorithm = std::make_unique(std::move(v.value()));
break;
}
case lock_algorithm::byte_ranges:
{
auto v = llfio::algorithm::shared_fs_mutex::byte_ranges::fs_mutex_byte_ranges({}, "lockfile");
if(v.has_error())
{
std::cerr << "ERROR: Creation of lock algorithm returns " << v.error().message() << std::endl;
return;
}
algorithm = std::make_unique(std::move(v.value()));
break;
}
case lock_algorithm::lock_files:
{
auto v = llfio::algorithm::shared_fs_mutex::lock_files::fs_mutex_lock_files(base);
if(v.has_error())
{
std::cerr << "ERROR: Creation of lock algorithm returns " << v.error().message() << std::endl;
return;
}
algorithm = std::make_unique(std::move(v.value()));
break;
}
case lock_algorithm::memory_map:
{
auto v = llfio::algorithm::shared_fs_mutex::memory_map::fs_mutex_map({}, "lockfile");
if(v.has_error())
{
std::cerr << "ERROR: Creation of lock algorithm returns " << v.error().message() << std::endl;
return;
}
algorithm = std::make_unique>(std::move(v.value()));
break;
}
case lock_algorithm::unknown:
break;
}
// Create entities named 0 to total_locks
std::vector entities(total_locks);
for(size_t n = 0; n < total_locks; n++)
{
if(contended)
{
entities[n].value = n;
entities[n].exclusive = true;
}
else
{
entities[n].value = (this_child << 4) + n; // guaranteed unique
entities[n].exclusive = true;
}
}
while(done == -1)
std::this_thread::yield();
while(!done)
{
auto result = algorithm->lock(entities, llfio::deadline(), false);
if(result.has_error())
{
std::cerr << "ERROR: Algorithm lock returns " << result.error().message() << std::endl;
return;
}
if(contended)
child_locks(this_child);
++count;
auto guard = std::move(result.value());
if(contended)
child_unlocks(this_child);
guard.unlock();
}
});
if(!strcmp(argv[1], "!spawned"))
{
auto lastcount = count;
size_t secs = 0;
done = 0;
while(!kbhit())
{
std::this_thread::sleep_for(std::chrono::seconds(1));
++secs;
std::cout << "\ncount=" << count << " (+" << (count - lastcount) << "), average=" << (count / secs) << std::endl;
lastcount = count;
#if 1
auto it = llfio::log().cbegin();
for(size_t n = 0; n < 10; n++)
{
if(it == llfio::log().cend())
break;
std::cout << " " << *it;
++it;
}
#endif
}
done = 1;
worker.join();
}
else
for(;;)
{
char buffer[1024];
// This blocks
if(!std::cin.getline(buffer, sizeof(buffer)))
{
return 1;
}
if(0 == strcmp(buffer, "GO"))
{
// Launch worker thread
done = 0;
}
else if(0 == strcmp(buffer, "STOP"))
{
done = 1;
worker.join();
std::cout << "RESULTS(" << count << ")" << std::endl;
#if DEBUG_CSV
std::ofstream s("benchmark_locking_llfio_log" + std::to_string(this_child) + ".csv");
s << csv(llfio::log());
#endif
return 0;
}
}
}