// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
// Licensed under the MIT License:
|
//
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
// of this software and associated documentation files (the "Software"), to deal
|
// in the Software without restriction, including without limitation the rights
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
// copies of the Software, and to permit persons to whom the Software is
|
// furnished to do so, subject to the following conditions:
|
//
|
// The above copyright notice and this permission notice shall be included in
|
// all copies or substantial portions of the Software.
|
//
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
// THE SOFTWARE.
|
|
#ifndef CAPNP_BENCHMARK_COMMON_H_
|
#define CAPNP_BENCHMARK_COMMON_H_
|
|
#if defined(__GNUC__) && !defined(CAPNP_HEADER_WARNINGS)
|
#pragma GCC system_header
|
#endif
|
|
#include <unistd.h>
|
#include <limits>
|
#include <errno.h>
|
#include <sys/types.h>
|
#include <sys/time.h>
|
#include <sys/resource.h>
|
#include <sys/wait.h>
|
#include <stdlib.h>
|
#include <semaphore.h>
|
#include <algorithm>
|
#include <stdexcept>
|
#include <stdio.h>
|
#include <string.h>
|
#include <string>
|
#include <vector>
|
|
namespace capnp {
|
namespace benchmark {
|
|
// Use a 128-bit Xorshift algorithm.
|
static inline uint32_t nextFastRand() {
|
// These values are arbitrary. Any seed other than all zeroes is OK.
|
static uint32_t x = 0x1d2acd47;
|
static uint32_t y = 0x58ca3e14;
|
static uint32_t z = 0xf563f232;
|
static uint32_t w = 0x0bc76199;
|
|
uint32_t tmp = x ^ (x << 11);
|
x = y;
|
y = z;
|
z = w;
|
w = w ^ (w >> 19) ^ tmp ^ (tmp >> 8);
|
return w;
|
}
|
|
static inline uint32_t fastRand(uint32_t range) {
|
return nextFastRand() % range;
|
}
|
|
static inline double fastRandDouble(double range) {
|
return nextFastRand() * range / std::numeric_limits<uint32_t>::max();
|
}
|
|
inline int32_t div(int32_t a, int32_t b) {
|
if (b == 0) return std::numeric_limits<int32_t>::max();
|
// INT_MIN / -1 => SIGFPE. Who knew?
|
if (a == std::numeric_limits<int32_t>::min() && b == -1) {
|
return std::numeric_limits<int32_t>::max();
|
}
|
return a / b;
|
}
|
|
inline int32_t mod(int32_t a, int32_t b) {
|
if (b == 0) return std::numeric_limits<int32_t>::max();
|
// INT_MIN % -1 => SIGFPE. Who knew?
|
if (a == std::numeric_limits<int32_t>::min() && b == -1) {
|
return std::numeric_limits<int32_t>::max();
|
}
|
return a % b;
|
}
|
|
static const char* const WORDS[] = {
|
"foo ", "bar ", "baz ", "qux ", "quux ", "corge ", "grault ", "garply ", "waldo ", "fred ",
|
"plugh ", "xyzzy ", "thud "
|
};
|
constexpr size_t WORDS_COUNT = sizeof(WORDS) / sizeof(WORDS[0]);
|
|
template <typename T>
|
class ProducerConsumerQueue {
|
public:
|
ProducerConsumerQueue() {
|
front = new Node;
|
back = front;
|
sem_init(&semaphore, 0, 0);
|
}
|
|
~ProducerConsumerQueue() noexcept(false) {
|
while (front != nullptr) {
|
Node* oldFront = front;
|
front = front->next;
|
delete oldFront;
|
}
|
sem_destroy(&semaphore);
|
}
|
|
void post(T t) {
|
back->next = new Node(t);
|
back = back->next;
|
sem_post(&semaphore);
|
}
|
|
T next() {
|
sem_wait(&semaphore);
|
Node* oldFront = front;
|
front = front->next;
|
delete oldFront;
|
return front->value;
|
}
|
|
private:
|
struct Node {
|
T value;
|
Node* next;
|
|
Node(): next(nullptr) {}
|
Node(T value): value(value), next(nullptr) {}
|
};
|
|
Node* front; // Last node that has been consumed.
|
Node* back; // Last node in list.
|
sem_t semaphore;
|
};
|
|
// TODO(cleanup): Use SYSCALL(), get rid of this exception class.
|
class OsException: public std::exception {
|
public:
|
OsException(int error): error(error) {}
|
~OsException() noexcept {}
|
|
const char* what() const noexcept override {
|
return strerror(error);
|
}
|
|
private:
|
int error;
|
};
|
|
static void writeAll(int fd, const void* buffer, size_t size) {
|
const char* pos = reinterpret_cast<const char*>(buffer);
|
while (size > 0) {
|
ssize_t n = write(fd, pos, size);
|
if (n <= 0) {
|
throw OsException(errno);
|
}
|
pos += n;
|
size -= n;
|
}
|
}
|
|
static void readAll(int fd, void* buffer, size_t size) {
|
char* pos = reinterpret_cast<char*>(buffer);
|
while (size > 0) {
|
ssize_t n = read(fd, pos, size);
|
if (n <= 0) {
|
throw OsException(errno);
|
}
|
pos += n;
|
size -= n;
|
}
|
}
|
|
template <typename BenchmarkMethods, typename Func>
|
uint64_t passByPipe(Func&& clientFunc, uint64_t iters) {
|
int clientToServer[2];
|
int serverToClient[2];
|
if (pipe(clientToServer) < 0) throw OsException(errno);
|
if (pipe(serverToClient) < 0) throw OsException(errno);
|
|
pid_t child = fork();
|
if (child == 0) {
|
// Client.
|
close(clientToServer[0]);
|
close(serverToClient[1]);
|
|
uint64_t throughput = clientFunc(serverToClient[0], clientToServer[1], iters);
|
writeAll(clientToServer[1], &throughput, sizeof(throughput));
|
|
exit(0);
|
} else {
|
// Server.
|
close(clientToServer[1]);
|
close(serverToClient[0]);
|
|
uint64_t throughput = BenchmarkMethods::server(clientToServer[0], serverToClient[1], iters);
|
|
uint64_t clientThroughput = 0;
|
readAll(clientToServer[0], &clientThroughput, sizeof(clientThroughput));
|
throughput += clientThroughput;
|
|
int status;
|
if (waitpid(child, &status, 0) != child) {
|
throw OsException(errno);
|
}
|
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
|
throw std::logic_error("Child exited abnormally.");
|
}
|
|
return throughput;
|
}
|
}
|
|
template <typename BenchmarkTypes, typename TestCase, typename Reuse, typename Compression>
|
uint64_t doBenchmark(const std::string& mode, uint64_t iters) {
|
typedef typename BenchmarkTypes::template BenchmarkMethods<TestCase, Reuse, Compression>
|
BenchmarkMethods;
|
if (mode == "client") {
|
return BenchmarkMethods::syncClient(STDIN_FILENO, STDOUT_FILENO, iters);
|
} else if (mode == "server") {
|
return BenchmarkMethods::server(STDIN_FILENO, STDOUT_FILENO, iters);
|
} else if (mode == "object") {
|
return BenchmarkMethods::passByObject(iters, false);
|
} else if (mode == "object-size") {
|
return BenchmarkMethods::passByObject(iters, true);
|
} else if (mode == "bytes") {
|
return BenchmarkMethods::passByBytes(iters);
|
} else if (mode == "pipe") {
|
return passByPipe<BenchmarkMethods>(BenchmarkMethods::syncClient, iters);
|
} else if (mode == "pipe-async") {
|
return passByPipe<BenchmarkMethods>(BenchmarkMethods::asyncClient, iters);
|
} else {
|
fprintf(stderr, "Unknown mode: %s\n", mode.c_str());
|
exit(1);
|
}
|
}
|
|
template <typename BenchmarkTypes, typename TestCase, typename Compression>
|
uint64_t doBenchmark2(const std::string& mode, const std::string& reuse, uint64_t iters) {
|
if (reuse == "reuse") {
|
return doBenchmark<
|
BenchmarkTypes, TestCase, typename BenchmarkTypes::ReusableResources, Compression>(
|
mode, iters);
|
} else if (reuse == "no-reuse") {
|
return doBenchmark<
|
BenchmarkTypes, TestCase, typename BenchmarkTypes::SingleUseResources, Compression>(
|
mode, iters);
|
} else {
|
fprintf(stderr, "Unknown reuse mode: %s\n", reuse.c_str());
|
exit(1);
|
}
|
}
|
|
template <typename BenchmarkTypes, typename TestCase>
|
uint64_t doBenchmark3(const std::string& mode, const std::string& reuse,
|
const std::string& compression, uint64_t iters) {
|
if (compression == "none") {
|
return doBenchmark2<BenchmarkTypes, TestCase, typename BenchmarkTypes::Uncompressed>(
|
mode, reuse, iters);
|
} else if (compression == "packed") {
|
return doBenchmark2<BenchmarkTypes, TestCase, typename BenchmarkTypes::Packed>(
|
mode, reuse, iters);
|
#if HAVE_SNAPPY
|
} else if (compression == "snappy") {
|
return doBenchmark2<BenchmarkTypes, TestCase, typename BenchmarkTypes::SnappyCompressed>(
|
mode, reuse, iters);
|
#endif // HAVE_SNAPPY
|
} else {
|
fprintf(stderr, "Unknown compression mode: %s\n", compression.c_str());
|
exit(1);
|
}
|
}
|
|
template <typename BenchmarkTypes, typename TestCase>
|
int benchmarkMain(int argc, char* argv[]) {
|
if (argc != 5) {
|
fprintf(stderr, "USAGE: %s MODE REUSE COMPRESSION ITERATION_COUNT\n", argv[0]);
|
return 1;
|
}
|
|
uint64_t iters = strtoull(argv[4], nullptr, 0);
|
uint64_t throughput = doBenchmark3<BenchmarkTypes, TestCase>(argv[1], argv[2], argv[3], iters);
|
fprintf(stdout, "%llu\n", (long long unsigned int)throughput);
|
|
return 0;
|
}
|
|
} // namespace capnp
|
} // namespace benchmark
|
|
#endif // CAPNP_BENCHMARK_COMMON_H_
|