initial commit

This commit is contained in:
quackerd 2023-01-05 17:17:48 +01:00
parent b678a7cb3d
commit e2f4842849
23 changed files with 6421 additions and 0 deletions

53
CMakeLists.txt Normal file
View File

@ -0,0 +1,53 @@
cmake_minimum_required(VERSION 3.10.0)
project(pingpong)
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR})
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/msg/msg.pb.cc
COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/msg/
COMMAND protoc --cpp_out=${CMAKE_CURRENT_BINARY_DIR}/msg/ --proto_path=${CMAKE_CURRENT_SOURCE_DIR}/msg/ msg.proto
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/msg/msg.proto)
find_package(PkgConfig REQUIRED)
pkg_check_modules(rocksdb rocksdb)
pkg_check_modules(protobuf REQUIRED protobuf)
if (${ENABLE_FSTACK} MATCHES "y")
pkg_check_modules(dpdk REQUIRED libdpdk)
pkg_check_modules(bsdtopo REQUIRED bsdtopo)
pkg_check_modules(ssl REQUIRED libssl)
include_directories(${dpdk_INCLUDE_DIRS})
include_directories(${ssl_INCLUDE_DIRS})
include_directories(${bsdtopo_INCLUDE_DIRS})
endif()
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
include_directories(${rocksdb_INCLUDE_DIRS})
include_directories(${CMAKE_CURRENT_BINARY_DIR}/msg)
include_directories(${protobuf_INCLUDE_DIRS})
set(CFLAGS -Wall -Wextra -Werror -Wno-unused-parameter -Wno-unused-variable -std=c++17 -O2 -g)
add_executable(dismember ${CMAKE_CURRENT_SOURCE_DIR}/dismember/dismember.cc
${CMAKE_CURRENT_SOURCE_DIR}/dismember/Generator.cc
${CMAKE_CURRENT_SOURCE_DIR}/dismember/reqgen.cc
${CMAKE_CURRENT_SOURCE_DIR}/dismember/util.cc
${CMAKE_CURRENT_BINARY_DIR}/msg/msg.pb.cc)
target_link_libraries(dismember ${protobuf_LINK_LIBRARIES} ${rocksdb_LINK_LIBRARIES} bz2 z pthread)
target_compile_options(dismember PRIVATE ${CFLAGS})
add_executable(ppd ${CMAKE_CURRENT_SOURCE_DIR}/ppd/ppd.cc
${CMAKE_CURRENT_SOURCE_DIR}/ppd/reqproc.cc
${CMAKE_CURRENT_BINARY_DIR}/msg/msg.pb.cc)
target_link_libraries(ppd ${protobuf_LINK_LIBRARIES} ${rocksdb_LINK_LIBRARIES} bz2 z pthread)
target_compile_options(ppd PRIVATE ${CFLAGS})
if (${ENABLE_FSTACK} MATCHES "y")
add_executable(ppd_ff ${CMAKE_CURRENT_SOURCE_DIR}/ppd_ff/ppd.cc
${CMAKE_CURRENT_SOURCE_DIR}/ppd_ff/reqproc.cc
${CMAKE_CURRENT_BINARY_DIR}/msg/msg.pb.cc)
target_link_libraries(ppd_ff ${protobuf_LINK_LIBRARIES} fstack ${ssl_LINK_LIBRARIES} bz2 z crypto ${dpdk_LIBRARIES} ${bsdtopo_LIBRARIES} librte_net_bond.a librte_bus_vdev.a)
target_link_directories(ppd_ff PRIVATE /usr/local/lib ${dpdk_LIBRARY_DIRS} ${bsdtopo_LIBRARY_DIRS})
target_compile_options(ppd_ff PRIVATE ${CFLAGS} ${dpdk_CFLAGS})
endif()

76
dismember/Generator.cc Normal file
View File

@ -0,0 +1,76 @@
// modified from mutilate
#include "Generator.h"
Generator* createFacebookKey() { return new GEV(30.7984, 8.20449, 0.078688); }
Generator* createFacebookValue() {
Generator* g = new GPareto(15.0, 214.476, 0.348238);
Discrete* d = new Discrete(g);
d->add(0.00536, 0.0);
d->add(0.00047, 1.0);
d->add(0.17820, 2.0);
d->add(0.09239, 3.0);
d->add(0.00018, 4.0);
d->add(0.02740, 5.0);
d->add(0.00065, 6.0);
d->add(0.00606, 7.0);
d->add(0.00023, 8.0);
d->add(0.00837, 9.0);
d->add(0.00837, 10.0);
d->add(0.08989, 11.0);
d->add(0.00092, 12.0);
d->add(0.00326, 13.0);
d->add(0.01980, 14.0);
return d;
}
Generator* createFacebookIA() { return new GPareto(0, 16.0292, 0.154971); }
Generator* createGenerator(std::string str) {
if (!strcmp(str.c_str(), "fb_key")) return createFacebookKey();
else if (!strcmp(str.c_str(), "fb_value")) return createFacebookValue();
else if (!strcmp(str.c_str(), "fb_ia")) return createFacebookIA();
char *s_copy = new char[str.length() + 1];
strcpy(s_copy, str.c_str());
char *saveptr = NULL;
if (atoi(s_copy) != 0 || !strcmp(s_copy, "0")) {
double v = atof(s_copy);
delete[] s_copy;
return new Fixed(v);
}
char *t_ptr = strtok_r(s_copy, ":", &saveptr);
char *a_ptr = strtok_r(NULL, ":", &saveptr);
if (t_ptr == NULL) // || a_ptr == NULL)
DIE("strtok(.., \":\") failed to parse %s", str.c_str());
char t = t_ptr[0];
saveptr = NULL;
char *s1 = strtok_r(a_ptr, ",", &saveptr);
char *s2 = strtok_r(NULL, ",", &saveptr);
char *s3 = strtok_r(NULL, ",", &saveptr);
double a1 = s1 ? atof(s1) : 0.0;
double a2 = s2 ? atof(s2) : 0.0;
double a3 = s3 ? atof(s3) : 0.0;
delete[] s_copy;
if (strcasestr(str.c_str(), "fixed")) return new Fixed(a1);
else if (strcasestr(str.c_str(), "normal")) return new Normal(a1, a2);
else if (strcasestr(str.c_str(), "exponential")) return new Exponential(a1);
else if (strcasestr(str.c_str(), "pareto")) return new GPareto(a1, a2, a3);
else if (strcasestr(str.c_str(), "gev")) return new GEV(a1, a2, a3);
else if (strcasestr(str.c_str(), "uniform")) return new Uniform(a1);
DIE("Unable to create Generator '%s'", str.c_str());
return NULL;
}

217
dismember/Generator.h Normal file
View File

@ -0,0 +1,217 @@
// modified from mutilate
// -*- c++ -*-
// 1. implement "fixed" generator
// 2. implement discrete generator
// 3. implement combine generator?
#ifndef GENERATOR_H
#define GENERATOR_H
#include <netinet/in.h>
#include <string>
#include <vector>
#include <utility>
#include <assert.h>
#include <inttypes.h>
#include <limits.h>
#include <math.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include "util.h"
#define D(fmt, ...)
#define DIE(fmt, ...)
// Generator syntax:
//
// \d+ == fixed
// n[ormal]:mean,sd
// e[xponential]:lambda
// p[areto]:scale,shape
// g[ev]:loc,scale,shape
// fb_value, fb_key, fb_rate
class Generator {
public:
Generator() {}
// Generator(const Generator &g) = delete;
// virtual Generator& operator=(const Generator &g) = delete;
virtual ~Generator() {}
virtual double generate(double U = -1.0) = 0;
virtual void set_lambda(double lambda) {DIE("set_lambda() not implemented");}
protected:
std::string type;
};
class Fixed : public Generator {
public:
Fixed(double _value = 1.0) : value(_value) { D("Fixed(%f)", value); }
virtual double generate(double U = -1.0) { return value; }
virtual void set_lambda(double lambda) {
if (lambda > 0.0) value = 1.0 / lambda;
else value = 0.0;
}
private:
double value;
};
class Uniform : public Generator {
public:
Uniform(double _scale) : scale(_scale) { D("Uniform(%f)", scale); }
virtual double generate(double U = -1.0) {
if (U < 0.0) U = drand48();
return scale * U;
}
virtual void set_lambda(double lambda) {
if (lambda > 0.0) scale = 2.0 / lambda;
else scale = 0.0;
}
private:
double scale;
};
class Normal : public Generator {
public:
Normal(double _mean = 1.0, double _sd = 1.0) : mean(_mean), sd(_sd) {
D("Normal(mean=%f, sd=%f)", mean, sd);
}
virtual double generate(double U = -1.0) {
if (U < 0.0) U = drand48();
double V = U; // drand48();
double N = sqrt(-2 * log(U)) * cos(2 * M_PI * V);
return mean + sd * N;
}
virtual void set_lambda(double lambda) {
if (lambda > 0.0) mean = 1.0 / lambda;
else mean = 0.0;
}
private:
double mean, sd;
};
class Exponential : public Generator {
public:
Exponential(double _lambda = 1.0) : lambda(_lambda) {
D("Exponential(lambda=%f)", lambda);
}
virtual double generate(double U = -1.0) {
if (lambda <= 0.0) return 0.0;
if (U < 0.0) U = drand48();
return -log(U) / lambda;
}
virtual void set_lambda(double lambda) { this->lambda = lambda; }
private:
double lambda;
};
class GPareto : public Generator {
public:
GPareto(double _loc = 0.0, double _scale = 1.0, double _shape = 1.0) :
loc(_loc), scale(_scale), shape(_shape) {
assert(shape != 0.0);
D("GPareto(loc=%f, scale=%f, shape=%f)", loc, scale, shape);
}
virtual double generate(double U = -1.0) {
if (U < 0.0) U = drand48();
return loc + scale * (pow(U, -shape) - 1) / shape;
}
virtual void set_lambda(double lambda) {
if (lambda <= 0.0) scale = 0.0;
else scale = (1 - shape) / lambda - (1 - shape) * loc;
}
private:
double loc /* mu */;
double scale /* sigma */, shape /* k */;
};
class GEV : public Generator {
public:
GEV(double _loc = 0.0, double _scale = 1.0, double _shape = 1.0) :
e(1.0), loc(_loc), scale(_scale), shape(_shape) {
assert(shape != 0.0);
D("GEV(loc=%f, scale=%f, shape=%f)", loc, scale, shape);
}
virtual double generate(double U = -1.0) {
return loc + scale * (pow(e.generate(U), -shape) - 1) / shape;
}
private:
Exponential e;
double loc /* mu */, scale /* sigma */, shape /* k */;
};
class Discrete : public Generator {
public:
~Discrete() { delete def; }
Discrete(Generator* _def = NULL) : def(_def) {
if (def == NULL) def = new Fixed(0.0);
}
virtual double generate(double U = -1.0) {
double Uc = U;
if (pv.size() > 0 && U < 0.0) U = drand48();
double sum = 0;
for (auto p: pv) {
sum += p.first;
if (U < sum) return p.second;
}
return def->generate(Uc);
}
void add(double p, double v) {
pv.push_back(std::pair<double,double>(p, v));
}
private:
Generator *def;
std::vector< std::pair<double,double> > pv;
};
class KeyGenerator {
public:
KeyGenerator(Generator* _g, double _max = 10000) : g(_g), max(_max) {}
std::string generate(uint64_t ind) {
uint64_t h = fnv_64(ind);
double U = (double) h / (double)ULLONG_MAX;
double G = g->generate(U);
int keylen = MAX(round(G), floor(log10(max)) + 1);
char key[256];
snprintf(key, 256, "%0*" PRIu64, keylen, ind);
// D("%d = %s", ind, key);
return std::string(key);
}
private:
Generator* g;
double max;
};
Generator* createGenerator(std::string str);
Generator* createFacebookKey();
Generator* createFacebookValue();
Generator* createFacebookIA();
#endif // GENERATOR_H

1056
dismember/dismember.cc Executable file

File diff suppressed because it is too large Load Diff

124
dismember/options.h Normal file
View File

@ -0,0 +1,124 @@
#pragma once
#include <arpa/inet.h>
#include <string.h>
#include <atomic>
#include <const.h>
#include <util.h>
static constexpr const int MAX_GEN_LEN = 31;
static constexpr const int DEFAULT_SERVER_CLIENT_CONN_PORT = 9898;
static constexpr const int DEFAULT_CLIENT_CTL_PORT = 9901;
static constexpr const char* DEFAULT_OUTPUT = "output.sample";
static constexpr const int MAX_GEN_PARAMS = 8;
static constexpr const int MAX_GEN_PARAMS_LEN = 31;
struct option;
extern option options;
struct option {
int verbose;
enum WORKLOAD_TYPE workload_type;
const char *output_name;
int client_num;
int client_thread_count;
int master_thread_count;
int client_conn;
int master_conn;
int target_qps;
int master_qps;
int master_mode;
int client_mode;
std::atomic_int global_conn_start_idx;
char server_ip[INET_ADDRSTRLEN + 1];
char generator_name[MAX_GEN_LEN + 1];
char gen_params[MAX_GEN_PARAMS][MAX_GEN_PARAMS_LEN + 1];
int num_gen_params;
int master_server_ip_given;
char master_server_ip[INET_ADDRSTRLEN + 1];
int server_port;
int depth_limit;
int warmup;
int duration;
option()
{
this->verbose = 0;
this->depth_limit = 1;
this->output_name = DEFAULT_OUTPUT;
this->client_thread_count = 1;
this->master_thread_count = -1;
this->client_conn = 1;
this->master_conn = -1;
this->target_qps = 0;
this->master_qps = -1;
this->client_mode = 0;
this->master_mode = 0;
this->warmup = 0;
this->duration = 10;
this->server_port = DEFAULT_SERVER_CLIENT_CONN_PORT;
this->master_server_ip_given = 0;
this->workload_type = WORKLOAD_TYPE::ECHO;
this->num_gen_params = 0;
this->global_conn_start_idx = 0;
for(int i = 0; i < MAX_GEN_PARAMS; i++) {
memset(gen_params[i], 0, MAX_GEN_LEN + 1);
}
/* set default values */
strncpy(this->generator_name, "fb_ia" , MAX_GEN_LEN);
strncpy(this->server_ip, "127.0.0.1" , INET_ADDRSTRLEN);
strncpy(this->master_server_ip, "127.0.0.1", INET_ADDRSTRLEN);
}
void dump()
{
V ("Configuration:\n"
" Connections per thread: %d\n"
" Num threads: %d\n"
" Target QPS: %d\n"
" warmup: %d\n"
" duration: %d\n"
" master_mode: %d\n"
" client_mode: %d\n"
" output_file: %s\n"
" server_ip: %s\n"
" server_port: %d\n"
" IA_DIST: %s\n"
" master_server_ip: %s\n"
" workload_type: %d\n"
" num_workload_param: %d\n"
" global_conn_idx: %d\n",
this->client_conn,
this->client_thread_count,
this->target_qps,
this->warmup,
this->duration,
this->master_mode,
this->client_mode,
this->output_name,
this->server_ip,
this->server_port,
this->generator_name,
this->master_server_ip,
this->workload_type,
this->num_gen_params,
this->global_conn_start_idx.load());
}
};
/* Don't send god damn vtables */
static_assert(std::is_standard_layout<option>(), "struct option must be standard layout");

346
dismember/reqgen.cc Normal file
View File

@ -0,0 +1,346 @@
#include "google/protobuf/stubs/common.h"
#include "options.h"
#include <msg.pb.h>
#include <stdint.h>
#include <sstream>
#include <sys/_stdint.h>
#include <unistd.h>
#include <util.h>
#ifdef WITH_ROCKSDB
#include <rocksdb/db.h>
#endif
#include "reqgen.h"
////////////////
// TOUCH Generator
////////////////
touch_gen::touch_gen(const int conn_id, std::unordered_map<std::string, std::string>* args) : req_gen(conn_id)
{
this->ugen = createGenerator("uniform:100");
if (this->ugen == NULL) {
E("Failed to create ugen for touch_gen\n");
}
if (args->find(PARAM_GEN) == args->end()) {
this->wgen = createGenerator(PARAM_GEN_DEFAULT);
} else {
this->wgen = createGenerator(args->at(PARAM_GEN));
}
if (this->wgen == NULL) {
E("Failed to create wgen for touch_gen\n");
}
if (args->find(PARAM_UPDATE) == args->end()) {
this->update_ratio = PARAM_UPDATE_DEFAULT;
} else {
this->update_ratio = atoi(args->at(PARAM_UPDATE).c_str());
}
}
touch_gen::~touch_gen()
{
delete wgen;
delete ugen;
}
int touch_gen::send_req(int fd)
{
ppd_touch_req req;
if (options.master_mode) {
req.set_touch_cnt(0);
} else {
req.set_touch_cnt(this->wgen->generate());
}
if (this->ugen->generate() < this->update_ratio) {
req.set_inc(1);
} else {
req.set_inc(0);
}
if (!req.SerializeToArray(this->send_buf, MAX_SEND_BUF_SIZE)) {
E("Failed to serialize to array for fd %d", fd);
}
return writemsg(fd, this->send_buf, this->MAX_SEND_BUF_SIZE, this->send_buf, req.ByteSizeLong());
}
int touch_gen::read_resp(int fd)
{
ppd_touch_resp resp;
struct ppd_msg * msg = (struct ppd_msg *)this->send_buf;
if (readmsg(fd, this->send_buf, MAX_SEND_BUF_SIZE) < 0) {
E("Readbuf failed for fd %d\n", fd);
}
resp.ParseFromArray(msg->payload, msg->size);
return resp.status();
}
////////////////
// ECHO Generator
////////////////
int echo_gen::delay_table[DT_SZ];
std::atomic<int> echo_gen::delay_table_populated = ATOMIC_VAR_INIT(0);
void
echo_gen::populate_delay_table()
{
int idx = 0;
int expected = 0;
// hack
if (echo_gen::DT_SZ != 100) {
E("Delay table size isn't 100");
}
/* 95 + 4 + 1 = 100 */
if (!delay_table_populated.compare_exchange_weak(expected, 1)) {
return;
}
delay_table[idx++] = 200;
for(int i = 0; i < 4; i++) {
delay_table[idx++] = 50;
}
for(int i = 0; i < 95; i++) {
delay_table[idx++] = 10;
}
}
echo_gen::echo_gen(const int conn_id, std::unordered_map<std::string, std::string>* args) : req_gen(conn_id)
{
if (args->find(PARAM_GEN) == args->end()) {
this->wgen = createGenerator(PARAM_GEN_DEFAULT);
} else {
this->wgen = createGenerator(args->at(PARAM_GEN));
}
if (this->wgen == NULL) {
E("Failed to create wgen for echo_gen");
}
if (args->find(PARAM_CDELAY) == args->end()) {
this->cdelay = PARAM_CDELAY_DEFAULT;
} else {
this->cdelay = atoi(args->at(PARAM_CDELAY).c_str());
}
if (args->find(PARAM_SIZE) == args->end()) {
this->data_sz = PARAM_SIZE_DEFAULT;
} else {
this->data_sz = atoi(args->at(PARAM_SIZE).c_str());
}
if (this->cdelay) {
populate_delay_table();
}
}
echo_gen::~echo_gen()
{
delete wgen;
}
int echo_gen::get_delay()
{
if (cdelay) {
return delay_table[conn_id % DT_SZ];
} else {
return this->wgen->generate();
}
}
int echo_gen::send_req(int fd)
{
ppd_echo_req req;
if (options.master_mode) {
req.set_enable_delay(0);
req.set_data_size(PARAM_SIZE_DEFAULT);
} else {
req.set_enable_delay(get_delay());
req.set_data_size(this->data_sz);
}
if (!req.SerializeToArray(this->send_buf, MAX_SEND_BUF_SIZE)) {
E("Failed to serialize to array for fd %d\n", fd);
}
this->send_sz = req.ByteSizeLong() + sizeof(struct ppd_msg);
return writemsg(fd, this->send_buf, MAX_SEND_BUF_SIZE, this->send_buf, req.ByteSizeLong());
}
int echo_gen::read_resp(int fd)
{
ppd_echo_resp resp;
struct ppd_msg * msg = (struct ppd_msg *)this->send_buf;
if (readmsg(fd, this->send_buf, MAX_SEND_BUF_SIZE) < 0) {
E("Readbuf failed for fd %d\n", fd);
}
resp.ParseFromArray(msg->payload, msg->size);
this->recv_sz = msg->size + sizeof(struct ppd_msg);
return resp.status();
}
int echo_gen::get_send_sz()
{
return this->send_sz;
}
int echo_gen::get_recv_sz()
{
return this->recv_sz;
}
////////////////
// HTTP Generator
////////////////
char http_gen::cons_buf[CONS_SZ];
http_gen::http_gen(const int conn_id, const std::string& host, std::unordered_map<std::string, std::string>* args) : req_gen(conn_id)
{
// hack
method = "GET";
headers.insert({"Host", host});
headers.insert({"Connection", "keep-alive"});
major_ver = 1;
minor_ver = 1;
uri = "/";
}
http_gen::~http_gen()
{
}
std::string
http_gen::build_req()
{
std::stringstream ss;
ss << method << ' ' \
<< uri << ' ' \
<< "HTTP/" + std::to_string(major_ver) + "." + std::to_string(minor_ver) \
<< "\r\n";
for(auto &i : headers) {
ss << i.first.c_str() << ": " << i.second.c_str() << "\r\n";
}
ss << "\r\n";
return ss.str();
}
int http_gen::send_req(int fd)
{
std::string req = build_req();
//V("Sending Request: %s\n", req.c_str());
return writebuf(fd, (void*)req.c_str(), req.length());
}
int http_gen::read_resp(int fd)
{
// hack
// consume everything
return read(fd, cons_buf, CONS_SZ);;
}
#ifdef WITH_ROCKSDB
////////////////
// RDB Generator
////////////////
rdb_gen::rdb_gen(const int conn_id, std::unordered_map<std::string, std::string>* args) : req_gen(conn_id), rand(1000 + conn_id)
{
this->key = AllocateKey(&this->key_guard, KEY_SIZE);
std::vector<double> ratio {GET_RATIO, PUT_RATIO, SEEK_RATIO};
this->query.Initiate(ratio);
gen_exp.InitiateExpDistribution(TOTAL_KEYS, KEYRANGE_DIST_A, KEYRANGE_DIST_B, KEYRANGE_DIST_C, KEYRANGE_DIST_D);
}
rdb_gen::~rdb_gen()
{
}
int rdb_gen::send_req(int fd)
{
int status;
ppd_rdb_req req;
int64_t ini_rand = GetRandomKey(&this->rand);
int64_t key_rand = gen_exp.DistGetKeyID(ini_rand, this->KEYRANGE_DIST_A, this->KEYRANGE_DIST_B);
int64_t rand_v = ini_rand % TOTAL_KEYS;
double u = (double)rand_v / TOTAL_KEYS;
int query_type = options.master_mode ? 0 : query.GetType(rand_v);
GenerateKeyFromInt(key_rand, TOTAL_KEYS, KEY_SIZE, &this->key);
V("SENDING KEY: %s.\n", this->key.data());
switch (query_type) {
case 0: {
// get query
req.set_op(PPD_RDB_OP_GET);
req.set_key(this->key.data(), this->key.size());
break;
}
case 1: {
// put query
int val_sz = ParetoCdfInversion(u, VALUE_THETA, VALUE_K, VALUE_SIGMA);
rocksdb::Slice val = gen.Generate((unsigned int)val_sz);
req.set_op(PPD_RDB_OP_PUT);
req.set_key(this->key.data(), this->key.size());
req.set_val(val.data(), val.size());
break;
}
case 2: {
// seek query
int64_t scan_length = ParetoCdfInversion(u, ITER_THETA, ITER_K, ITER_SIGMA);
req.set_op(PPD_RDB_OP_SEEK);
req.set_key(this->key.data(), this->key.size());
req.set_optarg(scan_length);
break;
}
default: {
E("Unsupported query type %d", query_type);
}
}
if (!req.SerializeToArray(this->send_buf, MAX_SEND_BUF_SIZE)) {
E("Failed to serialize protobuf");
}
status = writemsg(fd, this->send_buf, MAX_SEND_BUF_SIZE, this->send_buf, req.ByteSizeLong());
return status;
}
int rdb_gen::read_resp(int fd)
{
ppd_rdb_resp resp;
struct ppd_msg * msg = (struct ppd_msg *)this->send_buf;
if (readmsg(fd, this->send_buf, MAX_SEND_BUF_SIZE) < 0) {
E("Readbuf failed for fd %d", fd);
}
resp.ParseFromArray(msg->payload, msg->size);
return resp.status();
}
#endif

622
dismember/reqgen.h Normal file
View File

@ -0,0 +1,622 @@
#pragma once
#include <cstdlib>
#include <string>
#include <unordered_map>
#include <random>
#ifdef WITH_ROCKSDB
#include <rocksdb/db.h>
#endif
#include "Generator.h"
#include "options.h"
#include <msg.pb.h>
#define DISABLE_EVIL_CONSTRUCTORS(name) \
name(const name&) = delete; \
void operator=(const name) = delete
class req_gen {
protected:
const int conn_id;
char * send_buf;
constexpr static int MAX_SEND_BUF_SIZE = 1024 * 1024;
public:
req_gen(const int id) : conn_id(id) { this->send_buf = new char[MAX_SEND_BUF_SIZE]; };
virtual ~req_gen() { delete[] send_buf; };
virtual int send_req(int fd) = 0;
virtual int read_resp(int fd) = 0;
};
class touch_gen : public req_gen
{
private:
static constexpr const char* PARAM_GEN = "GEN";
static constexpr const char* PARAM_GEN_DEFAULT = "fixed:64";
static constexpr const char* PARAM_UPDATE = "UPDATE";
static constexpr const int PARAM_UPDATE_DEFAULT = 0;
Generator *wgen;
Generator *ugen;
int update_ratio;
public:
touch_gen(const int conn_id, std::unordered_map<std::string, std::string>* args);
touch_gen() = delete;
~touch_gen();
DISABLE_EVIL_CONSTRUCTORS(touch_gen);
int send_req(int fd);
int read_resp(int fd);
};
class echo_gen : public req_gen
{
private:
static constexpr const char* PARAM_GEN = "GEN";
static constexpr const char* PARAM_GEN_DEFAULT = "fixed:0";
static constexpr const char* PARAM_CDELAY = "CDELAY";
static constexpr const char* PARAM_SIZE = "SIZE";
static constexpr const int PARAM_SIZE_DEFAULT = 0;
static constexpr const int PARAM_CDELAY_DEFAULT = 0;
static constexpr const int DT_SZ = 100;
static int delay_table[DT_SZ];
static void populate_delay_table();
static std::atomic<int> delay_table_populated;
Generator *wgen;
int cdelay;
int data_sz;
int recv_sz;
int send_sz;
int get_delay();
public:
echo_gen(const int conn_id, std::unordered_map<std::string, std::string>* args);
echo_gen() = delete;
~echo_gen();
DISABLE_EVIL_CONSTRUCTORS(echo_gen);
int send_req(int fd);
int read_resp(int fd);
int get_send_sz();
int get_recv_sz();
};
class http_gen : public req_gen
{
private:
std::string build_req();
std::string method;
std::unordered_map<std::string, std::string> headers;
std::string uri;
int major_ver;
int minor_ver;
static constexpr const int CONS_SZ = 1024 * 1024 * 4;
static char cons_buf[CONS_SZ];
public:
http_gen(const int conn_id, const std::string& host, std::unordered_map<std::string, std::string>* args);
http_gen() = delete;
~http_gen();
DISABLE_EVIL_CONSTRUCTORS(http_gen);
int send_req(int fd);
int read_resp(int fd);
};
#ifdef WITH_ROCKSDB
class rdb_gen : public req_gen
{
private:
enum DistributionType : unsigned char {
kFixed = 0,
kUniform,
kNormal
};
constexpr static int64_t TOTAL_KEYS = 50000000;
constexpr static double GET_RATIO = 0.83;
constexpr static double PUT_RATIO = 0.14;
constexpr static double SEEK_RATIO = 0.03;
constexpr static int KEYRANGE_NUM = 30;
constexpr static double KEYRANGE_DIST_A = 14.18;
constexpr static double KEYRANGE_DIST_B = -2.917;
constexpr static double KEYRANGE_DIST_C = 0.0164;
constexpr static double KEYRANGE_DIST_D = -0.08082;
constexpr static double VALUE_THETA = 0;
constexpr static double VALUE_K = 0.2615;
constexpr static double VALUE_SIGMA = 25.45;
constexpr static int VALUESIZE_MIN = 100;
constexpr static int VALUESIZE_MAX = 102400;
constexpr static DistributionType VALUESIZE_DIST = kFixed;
constexpr static int KEY_SIZE = 48;
constexpr static double ITER_THETA = 0;
constexpr static double ITER_K = 2.517;
constexpr static double ITER_SIGMA = 14.236;
constexpr static double READ_RANDOM_EXP_RANGE = 0.0;
constexpr static bool IS_LITTLE_ENDIAN = true;
constexpr static int FIXED_VALUE_SIZE = 100;
// A good 64-bit random number generator based on std::mt19937_64
class Random64 {
private:
std::mt19937_64 generator_;
public:
explicit Random64(uint64_t s) : generator_(s) { }
// Generates the next random number
uint64_t Next() { return generator_(); }
// Returns a uniformly distributed value in the range [0..n-1]
// REQUIRES: n > 0
uint64_t Uniform(uint64_t n) {
return std::uniform_int_distribution<uint64_t>(0, n - 1)(generator_);
}
// Randomly returns true ~"1/n" of the time, and false otherwise.
// REQUIRES: n > 0
bool OneIn(uint64_t n) { return Uniform(n) == 0; }
// Skewed: pick "base" uniformly from range [0,max_log] and then
// return "base" random bits. The effect is to pick a number in the
// range [0,2^max_log-1] with exponential bias towards smaller numbers.
uint64_t Skewed(int max_log) {
return Uniform(uint64_t(1) << Uniform(max_log + 1));
}
};
struct KeyrangeUnit {
int64_t keyrange_start;
int64_t keyrange_access;
int64_t keyrange_keys;
};
class GenerateTwoTermExpKeys {
public:
int64_t keyrange_rand_max_;
int64_t keyrange_size_;
int64_t keyrange_num_;
bool initiated_;
std::vector<KeyrangeUnit> keyrange_set_;
GenerateTwoTermExpKeys() {
keyrange_rand_max_ = TOTAL_KEYS;
initiated_ = false;
}
~GenerateTwoTermExpKeys() {}
// Initiate the KeyrangeUnit vector and calculate the size of each
// KeyrangeUnit.
rocksdb::Status InitiateExpDistribution(int64_t total_keys, double prefix_a,
double prefix_b, double prefix_c,
double prefix_d) {
int64_t amplify = 0;
int64_t keyrange_start = 0;
initiated_ = true;
if (KEYRANGE_NUM <= 0) {
keyrange_num_ = 1;
} else {
keyrange_num_ = KEYRANGE_NUM;
}
keyrange_size_ = total_keys / keyrange_num_;
// Calculate the key-range shares size based on the input parameters
for (int64_t pfx = keyrange_num_; pfx >= 1; pfx--) {
// Step 1. Calculate the probability that this key range will be
// accessed in a query. It is based on the two-term expoential
// distribution
double keyrange_p = prefix_a * std::exp(prefix_b * pfx) +
prefix_c * std::exp(prefix_d * pfx);
if (keyrange_p < std::pow(10.0, -16.0)) {
keyrange_p = 0.0;
}
// Step 2. Calculate the amplify
// In order to allocate a query to a key-range based on the random
// number generated for this query, we need to extend the probability
// of each key range from [0,1] to [0, amplify]. Amplify is calculated
// by 1/(smallest key-range probability). In this way, we ensure that
// all key-ranges are assigned with an Integer that >=0
if (amplify == 0 && keyrange_p > 0) {
amplify = static_cast<int64_t>(std::floor(1 / keyrange_p)) + 1;
}
// Step 3. For each key-range, we calculate its position in the
// [0, amplify] range, including the start, the size (keyrange_access)
KeyrangeUnit p_unit;
p_unit.keyrange_start = keyrange_start;
if (0.0 >= keyrange_p) {
p_unit.keyrange_access = 0;
} else {
p_unit.keyrange_access =
static_cast<int64_t>(std::floor(amplify * keyrange_p));
}
p_unit.keyrange_keys = keyrange_size_;
keyrange_set_.push_back(p_unit);
keyrange_start += p_unit.keyrange_access;
}
keyrange_rand_max_ = keyrange_start;
// Step 4. Shuffle the key-ranges randomly
// Since the access probability is calculated from small to large,
// If we do not re-allocate them, hot key-ranges are always at the end
// and cold key-ranges are at the begin of the key space. Therefore, the
// key-ranges are shuffled and the rand seed is only decide by the
// key-range hotness distribution. With the same distribution parameters
// the shuffle results are the same.
Random64 rand_loca(keyrange_rand_max_);
for (int64_t i = 0; i < KEYRANGE_NUM; i++) {
int64_t pos = rand_loca.Next() % KEYRANGE_NUM;
assert(i >= 0 && i < static_cast<int64_t>(keyrange_set_.size()) &&
pos >= 0 && pos < static_cast<int64_t>(keyrange_set_.size()));
std::swap(keyrange_set_[i], keyrange_set_[pos]);
}
// Step 5. Recalculate the prefix start postion after shuffling
int64_t offset = 0;
for (auto& p_unit : keyrange_set_) {
p_unit.keyrange_start = offset;
offset += p_unit.keyrange_access;
}
return rocksdb::Status::OK();
}
// Generate the Key ID according to the input ini_rand and key distribution
int64_t DistGetKeyID(int64_t ini_rand, double key_dist_a,
double key_dist_b) {
int64_t keyrange_rand = ini_rand % keyrange_rand_max_;
// Calculate and select one key-range that contains the new key
int64_t start = 0, end = static_cast<int64_t>(keyrange_set_.size());
while (start + 1 < end) {
int64_t mid = start + (end - start) / 2;
assert(mid >= 0 && mid < static_cast<int64_t>(keyrange_set_.size()));
if (keyrange_rand < keyrange_set_[mid].keyrange_start) {
end = mid;
} else {
start = mid;
}
}
int64_t keyrange_id = start;
// Select one key in the key-range and compose the keyID
int64_t key_offset = 0, key_seed;
if (key_dist_a == 0.0 && key_dist_b == 0.0) {
key_offset = ini_rand % keyrange_size_;
} else {
key_seed = static_cast<int64_t>(
ceil(std::pow((ini_rand / key_dist_a), (1 / key_dist_b))));
Random64 rand_key(key_seed);
key_offset = static_cast<int64_t>(rand_key.Next()) % keyrange_size_;
}
return keyrange_size_ * keyrange_id + key_offset;
}
};
// Decide the ratio of different query types
// 0 Get, 1 Put, 2 Seek, 3 SeekForPrev, 4 Delete, 5 SingleDelete, 6 merge
class QueryDecider {
public:
std::vector<int> type_;
std::vector<double> ratio_;
int range_;
QueryDecider() {}
~QueryDecider() {}
rocksdb::Status Initiate(std::vector<double> ratio_input) {
int range_max = 1000;
double sum = 0.0;
for (auto& ratio : ratio_input) {
sum += ratio;
}
range_ = 0;
for (auto& ratio : ratio_input) {
range_ += static_cast<int>(ceil(range_max * (ratio / sum)));
type_.push_back(range_);
ratio_.push_back(ratio / sum);
}
return rocksdb::Status::OK();
}
int GetType(int64_t rand_num) {
if (rand_num < 0) {
rand_num = rand_num * (-1);
}
assert(range_ != 0);
int pos = static_cast<int>(rand_num % range_);
for (int i = 0; i < static_cast<int>(type_.size()); i++) {
if (pos < type_[i]) {
return i;
}
}
return 0;
}
};
class BaseDistribution {
public:
BaseDistribution(unsigned int _min, unsigned int _max)
: min_value_size_(_min), max_value_size_(_max) {}
virtual ~BaseDistribution() {}
unsigned int Generate() {
auto val = Get();
if (NeedTruncate()) {
val = std::max(min_value_size_, val);
val = std::min(max_value_size_, val);
}
return val;
}
private:
virtual unsigned int Get() = 0;
virtual bool NeedTruncate() {
return true;
}
unsigned int min_value_size_;
unsigned int max_value_size_;
};
class FixedDistribution : public BaseDistribution
{
public:
FixedDistribution(unsigned int size) :
BaseDistribution(size, size),
size_(size) {}
private:
virtual unsigned int Get() override {
return size_;
}
virtual bool NeedTruncate() override {
return false;
}
unsigned int size_;
};
class UniformDistribution
: public BaseDistribution,
public std::uniform_int_distribution<unsigned int> {
public:
UniformDistribution(unsigned int _min, unsigned int _max)
: BaseDistribution(_min, _max),
std::uniform_int_distribution<unsigned int>(_min, _max),
gen_(rd_()) {}
private:
virtual unsigned int Get() override {
return (*this)(gen_);
}
virtual bool NeedTruncate() override {
return false;
}
std::random_device rd_;
std::mt19937 gen_;
};
class NormalDistribution
: public BaseDistribution, public std::normal_distribution<double> {
public:
NormalDistribution(unsigned int _min, unsigned int _max)
: BaseDistribution(_min, _max),
// 99.7% values within the range [min, max].
std::normal_distribution<double>(
(double)(_min + _max) / 2.0 /*mean*/,
(double)(_max - _min) / 6.0 /*stddev*/),
gen_(rd_()) {}
private:
virtual unsigned int Get() override {
return static_cast<unsigned int>((*this)(gen_));
}
std::random_device rd_;
std::mt19937 gen_;
};
class Random {
private:
enum : uint32_t {
M = 2147483647L // 2^31-1
};
enum : uint64_t {
A = 16807 // bits 14, 8, 7, 5, 2, 1, 0
};
uint32_t seed_;
static uint32_t GoodSeed(uint32_t s) { return (s & M) != 0 ? (s & M) : 1; }
public:
// This is the largest value that can be returned from Next()
enum : uint32_t { kMaxNext = M };
explicit Random(uint32_t s) : seed_(GoodSeed(s)) {}
void Reset(uint32_t s) { seed_ = GoodSeed(s); }
uint32_t Next() {
// We are computing
// seed_ = (seed_ * A) % M, where M = 2^31-1
//
// seed_ must not be zero or M, or else all subsequent computed values
// will be zero or M respectively. For all other values, seed_ will end
// up cycling through every number in [1,M-1]
uint64_t product = seed_ * A;
// Compute (product % M) using the fact that ((x << 31) % M) == x.
seed_ = static_cast<uint32_t>((product >> 31) + (product & M));
// The first reduction may overflow by 1 bit, so we may need to
// repeat. mod == M is not possible; using > allows the faster
// sign-bit-based test.
if (seed_ > M) {
seed_ -= M;
}
return seed_;
}
// Returns a uniformly distributed value in the range [0..n-1]
// REQUIRES: n > 0
uint32_t Uniform(int n) { return Next() % n; }
// Randomly returns true ~"1/n" of the time, and false otherwise.
// REQUIRES: n > 0
bool OneIn(int n) { return Uniform(n) == 0; }
// "Optional" one-in-n, where 0 or negative always returns false
// (may or may not consume a random value)
bool OneInOpt(int n) { return n > 0 && OneIn(n); }
// Returns random bool that is true for the given percentage of
// calls on average. Zero or less is always false and 100 or more
// is always true (may or may not consume a random value)
bool PercentTrue(int percentage) {
return static_cast<int>(Uniform(100)) < percentage;
}
// Skewed: pick "base" uniformly from range [0,max_log] and then
// return "base" random bits. The effect is to pick a number in the
// range [0,2^max_log-1] with exponential bias towards smaller numbers.
uint32_t Skewed(int max_log) {
return Uniform(1 << Uniform(max_log + 1));
}
// Returns a Random instance for use by the current thread without
// additional locking
static Random* GetTLSInstance();
};
static rocksdb::Slice RandomString(Random* rnd, int len, std::string* dst) {
dst->resize(len);
for (int i = 0; i < len; i++) {
(*dst)[i] = static_cast<char>(' ' + rnd->Uniform(95)); // ' ' .. '~'
}
return rocksdb::Slice(*dst);
}
static rocksdb::Slice CompressibleString(Random* rnd, double compressed_fraction,
int len, std::string* dst) {
int raw = static_cast<int>(len * compressed_fraction);
if (raw < 1) raw = 1;
std::string raw_data;
RandomString(rnd, raw, &raw_data);
// Duplicate the random data until we have filled "len" bytes
dst->clear();
while (dst->size() < (unsigned int)len) {
dst->append(raw_data);
}
dst->resize(len);
return rocksdb::Slice(*dst);
}
class RandomGenerator {
private:
std::string data_;
unsigned int pos_;
std::unique_ptr<BaseDistribution> dist_;
public:
RandomGenerator() {
auto max_value_size = VALUESIZE_MAX;
switch (VALUESIZE_DIST) {
case kUniform:
dist_.reset(new UniformDistribution(VALUESIZE_MIN,
VALUESIZE_MAX));
break;
case kNormal:
dist_.reset(new NormalDistribution(VALUESIZE_MIN,
VALUESIZE_MAX));
break;
case kFixed:
default:
dist_.reset(new FixedDistribution(FIXED_VALUE_SIZE));
max_value_size = FIXED_VALUE_SIZE;
}
// We use a limited amount of data over and over again and ensure
// that it is larger than the compression window (32KB), and also
// large enough to serve all typical value sizes we want to write.
Random rnd(301);
std::string piece;
while (data_.size() < (unsigned)std::max(1048576, max_value_size)) {
// Add a short fragment that is as compressible as specified
// by FLAGS_compression_ratio.
CompressibleString(&rnd, 0.5, 100, &piece);
data_.append(piece);
}
pos_ = 0;
}
rocksdb::Slice Generate(unsigned int len) {
assert(len <= data_.size());
if (pos_ + len > data_.size()) {
pos_ = 0;
}
pos_ += len;
return rocksdb::Slice(data_.data() + pos_ - len, len);
}
rocksdb::Slice Generate() {
auto len = dist_->Generate();
return Generate(len);
}
};
// The inverse function of Pareto distribution
static int64_t ParetoCdfInversion(double u, double theta, double k, double sigma) {
double ret;
if (k == 0.0) {
ret = theta - sigma * std::log(u);
} else {
ret = theta + sigma * (std::pow(u, -1 * k) - 1) / k;
}
return static_cast<int64_t>(ceil(ret));
}
static int64_t GetRandomKey(Random64* rand) {
uint64_t rand_int = rand->Next();
int64_t key_rand = 0;
if (READ_RANDOM_EXP_RANGE == 0) {
key_rand = rand_int % TOTAL_KEYS;
}
return key_rand;
}
static rocksdb::Slice AllocateKey(std::unique_ptr<const char[]>* key_guard, int key_size_) {
char* data = new char[key_size_];
const char* const_data = data;
key_guard->reset(const_data);
return rocksdb::Slice(key_guard->get(), key_size_);
}
static void GenerateKeyFromInt(uint64_t v, int64_t num_keys, int key_size_, rocksdb::Slice* key) {
char* start = const_cast<char*>(key->data());
char* pos = start;
int bytes_to_fill = std::min(key_size_ - static_cast<int>(pos - start), 8);
if (IS_LITTLE_ENDIAN) {
for (int i = 0; i < bytes_to_fill; ++i) {
pos[i] = (v >> ((bytes_to_fill - i - 1) << 3)) & 0xFF;
}
} else {
memcpy(pos, static_cast<void*>(&v), bytes_to_fill);
}
pos += bytes_to_fill;
if (key_size_ > pos - start) {
memset(pos, '0', key_size_ - (pos - start));
}
}
GenerateTwoTermExpKeys gen_exp;
QueryDecider query;
Random64 rand;
RandomGenerator gen;
rocksdb::Slice key;
std::unique_ptr<const char[]> key_guard;
public:
rdb_gen(const int conn_id, std::unordered_map<std::string, std::string>* args);
rdb_gen() = delete;
~rdb_gen();
DISABLE_EVIL_CONSTRUCTORS(rdb_gen);
int send_req(int fd);
int read_resp(int fd);
};
#endif

30
dismember/util.cc Normal file
View File

@ -0,0 +1,30 @@
#include <inttypes.h>
#include <stdio.h>
#include <sys/time.h>
#include <unistd.h>
#include "util.h"
void sleep_time(double duration) {
if (duration > 0) usleep((useconds_t) (duration * 1000000));
}
#define FNV_64_PRIME (0x100000001b3ULL)
#define FNV1_64_INIT (0xcbf29ce484222325ULL)
uint64_t fnv_64_buf(const void* buf, size_t len) {
uint64_t hval = FNV1_64_INIT;
unsigned char *bp = (unsigned char *)buf; /* start of buffer */
unsigned char *be = bp + len; /* beyond end of buffer */
while (bp < be) {
hval ^= (uint64_t)*bp++;
hval *= FNV_64_PRIME;
}
return hval;
}
void generate_key(int n, int length, char *buf) {
snprintf(buf, length + 1, "%0*d", length, n);
}

52
dismember/util.h Normal file
View File

@ -0,0 +1,52 @@
#ifndef UTIL_H
#define UTIL_H
#include <sys/time.h>
#include <time.h>
inline double tv_to_double(struct timeval *tv) {
return tv->tv_sec + (double) tv->tv_usec / 1000000;
}
inline void double_to_tv(double val, struct timeval *tv) {
long long secs = (long long) val;
long long usecs = (long long) ((val - secs) * 1000000);
tv->tv_sec = secs;
tv->tv_usec = usecs;
}
inline double get_time_accurate() {
#if USE_CLOCK_GETTIME
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
// clock_gettime(CLOCK_REALTIME, &ts);
return ts.tv_sec + (double) ts.tv_nsec / 1000000000;
#else
struct timeval tv;
gettimeofday(&tv, NULL);
return tv_to_double(&tv);
#endif
}
inline double get_time() {
//#if USE_CLOCK_GETTIME
// struct timespec ts;
// clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
// // clock_gettime(CLOCK_REALTIME, &ts);
// return ts.tv_sec + (double) ts.tv_nsec / 1000000000;
//#else
struct timeval tv;
gettimeofday(&tv, NULL);
return tv_to_double(&tv);
//#endif
}
void sleep_time(double duration);
uint64_t fnv_64_buf(const void* buf, size_t len);
inline uint64_t fnv_64(uint64_t in) { return fnv_64_buf(&in, sizeof(in)); }
void generate_key(int n, int length, char *buf);
#endif // UTIL_H

19
include/const.h Executable file
View File

@ -0,0 +1,19 @@
#pragma once
#include <stdint.h>
enum WORKLOAD_TYPE {
ECHO = 0,
TOUCH = 1,
RDB = 2,
HTTP = 3,
};
struct ppd_msg {
uint32_t size;
char payload[0];
};
static constexpr int PPD_RDB_OP_GET = 0;
static constexpr int PPD_RDB_OP_PUT = 1;
static constexpr int PPD_RDB_OP_SEEK = 2;

128
include/util.h Normal file
View File

@ -0,0 +1,128 @@
#pragma once
#include "const.h"
#include <cerrno>
#include <sys/ioctl.h>
#include <sys/event.h>
#include <stdint.h>
#include <sys/socket.h>
#include <thread>
#include <stdio.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/_cpuset.h>
#include <sys/cpuset.h>
#define W(fmt, ...) do { \
fprintf(stderr, "[WARN] %s: " fmt, __func__, ##__VA_ARGS__); \
} while(0)
#define E(fmt, ...) do { \
fprintf(stderr, "[ERROR] %s: " fmt, __func__, ##__VA_ARGS__); \
exit(1); \
} while(0)
#define V(fmt, ...) do { \
if (options.verbose) { \
fprintf(stdout, "[INFO] %s: " fmt, __func__, ##__VA_ARGS__); \
} \
} while(0)
static inline void
cpulist_to_cpuset(char * cpulist, cpuset_t * cpuset)
{
char * cpu = strtok(cpulist, ",");
CPU_ZERO(cpuset);
while (cpu != nullptr) {
CPU_SET(atoi(cpu), cpuset);
cpu = strtok(nullptr, ",");
}
}
static inline int
readbuf(int fd, void *buf, int len)
{
int status;
do {
if ((status = recv(fd, buf, len, 0)) < 0) {
return -1;
} else if (status == 0) { // connection disconnected.
return -1;
}
buf = (char*)buf + status;
len -= status;
} while (len > 0);
return 0;
}
static inline int
writebuf(int fd, void * buf, int len)
{
int status;
do {
if ((status = send(fd, buf, len, 0)) < 0) {
return -1;
} else if (status == 0) {
return -1;
}
buf = (char*) buf + status;
len -= status;
} while (len > 0);
return 0;
}
static inline int
readmsg(int fd, char *buf, int len)
{
if ((uint)len < sizeof(struct ppd_msg)) {
return EOVERFLOW;
}
int status = readbuf(fd, buf, sizeof(struct ppd_msg));
if (status != 0) {
return status;
}
if (((struct ppd_msg *)buf)->size + sizeof(ppd_msg) > (uint)len) {
return EOVERFLOW;
}
if (((struct ppd_msg *)buf)->size > 0) {
status = readbuf(fd, buf + sizeof(ppd_msg), ((struct ppd_msg *)buf)->size);
}
return status;
}
static inline int
writemsg(int fd, char *buf, int len, const char *msg, int mlen)
{
int real_len = sizeof(struct ppd_msg) + mlen;
if (len < real_len) {
return EOVERFLOW;
}
struct ppd_msg * m = (struct ppd_msg *)buf;
memmove(m->payload, msg, mlen);
m->size = mlen;
return writebuf(fd, buf, real_len);
}
static inline uint64_t
get_time_us()
{
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
// clock_gettime(CLOCK_REALTIME, &ts);
return ts.tv_sec * 1000000 + ts.tv_nsec / 1000;
}
#define UNUSED(x) (x)

116
include/util_ff.h Normal file
View File

@ -0,0 +1,116 @@
#pragma once
#include "const.h"
#include <cerrno>
#include <stdint.h>
#include <thread>
#include <stdio.h>
#include <ff_api.h>
#define W(fmt, ...) do { \
fprintf(stderr, "[WARN] " fmt, ##__VA_ARGS__); \
} while(0)
#define E(fmt, ...) do { \
fprintf(stderr, "[ERROR] " fmt, ##__VA_ARGS__); \
exit(1); \
} while(0)
#define V(fmt, ...) do { \
if (options.verbose) { \
fprintf(stdout, "[INFO] " fmt, ##__VA_ARGS__); \
} \
} while(0)
static inline int
readbuf(int fd, void *buf, int len)
{
int status;
do {
if ((status = ff_recv(fd, buf, len, 0)) < 0) {
return -1;
} else if (status == 0) { // connection disconnected.
return -1;
}
buf = (char*)buf + status;
len -= status;
} while (len > 0);
return 0;
}
static inline int
writebuf(int fd, void * buf, int len)
{
int status;
do {
if ((status = ff_send(fd, buf, len, 0)) < 0) {
return -1;
} else if (status == 0) {
return -1;
}
buf = (char*) buf + status;
len -= status;
} while (len > 0);
return 0;
}
static inline int
readmsg(int fd, char *buf, int len)
{
if ((uint)len < sizeof(struct ppd_msg)) {
return EOVERFLOW;
}
int status = readbuf(fd, buf, sizeof(struct ppd_msg));
if (status != 0) {
return status;
}
if (((struct ppd_msg *)buf)->size + sizeof(ppd_msg) > (uint)len) {
return EOVERFLOW;
}
if (((struct ppd_msg *)buf)->size > 0) {
status = readbuf(fd, buf + sizeof(ppd_msg), ((struct ppd_msg *)buf)->size);
}
return status;
}
static inline int
writemsg(int fd, char *buf, int len, const char *msg, int mlen)
{
int real_len = sizeof(struct ppd_msg) + mlen;
if (len < real_len) {
return EOVERFLOW;
}
struct ppd_msg * m = (struct ppd_msg *)buf;
memmove(m->payload, msg, mlen);
m->size = mlen;
return writebuf(fd, buf, real_len);
}
static inline int
get_numcpus()
{
return std::thread::hardware_concurrency();
}
static inline uint64_t
get_time_us()
{
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
// clock_gettime(CLOCK_REALTIME, &ts);
return ts.tv_sec * 1000000 + ts.tv_nsec / 1000;
}
#define UNUSED(x) (x)

1695
msg/msg.pb.h Normal file

File diff suppressed because it is too large Load Diff

43
msg/msg.proto Normal file
View File

@ -0,0 +1,43 @@
syntax = "proto3";
message ppd_slave_resp {
int32 qps = 1;
int32 send_sz = 2;
int32 resp_sz = 3;
}
message ppd_master_start {
int32 dummy = 0;
}
message ppd_rdb_req {
int32 op = 1;
int32 optarg = 2;
bytes key = 3;
bytes val = 4;
}
message ppd_rdb_resp {
int32 status = 1;
bytes result = 2;
}
message ppd_echo_req {
int32 enable_delay = 1;
int32 data_size = 2;
}
message ppd_echo_resp {
int32 status = 1;
bytes data = 2;
}
message ppd_touch_req {
int32 touch_cnt = 1;
int32 inc = 2;
}
message ppd_touch_resp {
int32 status = 1;
}

0
ppd/conection.cc Normal file
View File

39
ppd/options.h Normal file
View File

@ -0,0 +1,39 @@
#pragma once
#include "openssl/types.h"
#include <vector>
#include <util.h>
#include <openssl/ssl.h>
static constexpr int MAX_MODE_PARAMS = 8;
static constexpr int MAX_MODE_PARAMS_LEN = 64;
struct ppd_options {
int port;
int shared_kq;
cpuset_t cpuset;
int verbose;
int enable_tls;
int enable_ktls;
char secret_key_fn[128];
char cert_fn[128];
std::vector<char*> hpip;
int skq_dump;
int skq_flag;
int skq_rtshare;
int skq_rtfreq;
/* the mode this server runs in */
int mode;
char mode_params[MAX_MODE_PARAMS][MAX_MODE_PARAMS_LEN + 1];
int num_mode_params;
/* runtime states */
SSL_CTX * ssl_ctx; // static SSL_CTX
};
extern ppd_options options;

679
ppd/ppd.cc Executable file
View File

@ -0,0 +1,679 @@
#include <cerrno>
#include <cstdio>
#include <unordered_map>
#include <cstring>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <stdalign.h>
#include <stdio.h>
#include <sys/cpuset.h>
#include <unistd.h>
#include <err.h>
#include <time.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/event.h>
#include <netinet/tcp.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread_np.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <netdb.h>
#include <vector>
#include <sstream>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include "options.h"
#include "reqproc.h"
#include <const.h>
#include <util.h>
#include <msg.pb.h>
static constexpr int NEVENT = 2048;
static constexpr int SOCK_BACKLOG = 10000;
static constexpr int SINGLE_LEGACY = -1;
static constexpr int DEFAULT_PORT = 9898;
static std::unordered_map<std::string, std::string> mode_params;
struct alignas(CACHE_LINE_SIZE) ppd_cache_item {
int val;
};
static_assert(sizeof(struct ppd_cache_item) == CACHE_LINE_SIZE, "cache_item not cache line sized");
struct ppd_conn {
int conn_fd;
req_proc * proc;
SSL * ssl;
};
struct ppd_thread_ctx {
int tid;
pthread_t thrd;
int kqfd;
long evcnt;
};
ppd_options options = {.port = DEFAULT_PORT,
.shared_kq = 0,
.cpuset = CPUSET_T_INITIALIZER(1), // first core
.verbose = 0,
.enable_tls = 0,
.enable_ktls = 0,
.secret_key_fn = {0},
.cert_fn = {0},
.skq_dump = 0,
.skq_flag = 0,
.skq_rtshare = 100,
.skq_rtfreq = 0,
.mode = WORKLOAD_TYPE::ECHO,
.num_mode_params = 0 };
static int
main_kqueue_create(std::vector<int> *server_socks)
{
int status;
struct kevent kev;
int mkqfd = kqueue();
if (mkqfd == -1) {
E("Failed to create kqueue: %d.\n", errno);
}
for(uint32_t i = 0; i < server_socks->size(); i++) {
EV_SET(&kev, server_socks->at(i), EVFILT_READ, EV_ADD, 0, 0, NULL);
status = kevent(mkqfd, &kev, 1, NULL, 0, NULL);
if (status == -1) {
E("kevent() failed: %d.\n", errno);
}
}
#define TIMER_FD (-1234)
EV_SET(&kev, TIMER_FD, EVFILT_TIMER, EV_ADD, NOTE_MSECONDS, 1, NULL);
status = kevent(mkqfd, &kev, 1, NULL, 0, NULL);
if (status == -1) {
E("kevent() timer failed: %d.\n", errno);
}
return mkqfd;
}
static void
create_tls_context()
{
SSL_CTX *ctx;
ctx = SSL_CTX_new(TLS_server_method());
if (!ctx) {
E("SSL_CTX_new() failed: %ld\n", ERR_get_error());
}
SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
SSL_CTX_set_max_proto_version(ctx, TLS1_2_VERSION);
if (options.enable_ktls) {
if ((SSL_CTX_set_options(ctx, SSL_OP_ENABLE_KTLS) & SSL_OP_ENABLE_KTLS) != SSL_OP_ENABLE_KTLS) {
E("SSL_CTX_set_options() SSL_OP_ENABLE_KTLS failed.\n");
}
}
/* Set the key and cert */
if (SSL_CTX_use_certificate_file(ctx, options.cert_fn, SSL_FILETYPE_PEM) <= 0) {
E("SSL_CTX_use_certificate_file() %s failed: %ld\n", options.cert_fn, ERR_get_error());
}
if (SSL_CTX_use_PrivateKey_file(ctx, options.secret_key_fn, SSL_FILETYPE_PEM) <= 0 ) {
E("SSL_CTX_use_PrivateKey_file() %s failed: %ld\n", options.secret_key_fn, ERR_get_error());
}
options.ssl_ctx = ctx;
}
static void
listen_socket_create(std::vector<int> *socks)
{
struct sockaddr_in server_addr;
int status;
const int enable = 1;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(options.port);
server_addr.sin_addr.s_addr = INADDR_ANY;
for (int i = 0; i < 1; i++) {
int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (fd < 0) {
E("socket() returned %d", errno);
}
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) < 0) {
E("setsockopt() reuseaddr %d", errno);
}
if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(enable)) < 0) {
E("setsockopt() reuseport", errno);
}
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(enable)) < 0) {
E("setsockopt() NODELAY %d", errno);
}
status = bind(fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
if (status < 0) {
E("bind() returned %d", errno);
}
status = listen(fd, SOCK_BACKLOG);
if (status < 0) {
E("listen() returned %d", errno);
}
socks->push_back(fd);
}
}
static void
ppd_conn_free(struct ppd_conn *conn)
{
SSL_shutdown(conn->ssl);
SSL_free(conn->ssl);
close(conn->conn_fd);
delete conn->proc;
delete conn;
}
static void
drop_conn(struct ppd_thread_ctx *tinfo, struct kevent *kev)
{
int status;
int conn_fd = kev->ident;
struct kevent ev;
EV_SET(&ev, conn_fd, EVFILT_READ, EV_DELETE, 0, 0, NULL);
status = kevent(tinfo->kqfd, &ev, 1, 0, 0, NULL);
if (status < 0) {
E("Failed to drop connection %d from kqueue: %d\n", conn_fd, errno);
}
ppd_conn_free((struct ppd_conn *)kev->udata);
}
static int
handle_event(struct ppd_thread_ctx *tinfo, struct kevent* kev)
{
int conn_fd;
int status;
struct ppd_conn *hint;
if (kev->filter != EVFILT_READ) {
E("Unknown event filter %d\n", kev->filter);
}
conn_fd = kev->ident;
hint = (struct ppd_conn*)kev->udata;
if (kev->flags & EV_EOF) {
V("Thread %d dropped connection %d due to EOF.\n", tinfo->tid, conn_fd);
drop_conn(tinfo, kev);
return ECONNRESET;
}
status = hint->proc->proc_req(conn_fd);
if (status < 0) {
W("Thread %d dropped connection %d due to req_proc error %d\n", tinfo->tid, conn_fd, status);
drop_conn(tinfo, kev);
return status;
}
tinfo->evcnt++;
return 0;
}
static void*
worker_main(void *info)
{
struct ppd_thread_ctx *tinfo = (struct ppd_thread_ctx *)info;
struct kevent kev[NEVENT];
struct kevent skev[NEVENT];
int skev_sz;
int status;
V("Thread %d started.\n", tinfo->tid);
skev_sz = 0;
while (1) {
status = kevent(tinfo->kqfd, skev, skev_sz, kev, NEVENT, NULL);
if (status <= 0) {
E("kevent() failed: %d\n", errno);
}
skev_sz = 0;
for (int i = 0; i < status; i++) {
if (handle_event(tinfo, &kev[i]) == 0) {
if (options.shared_kq && options.skq_flag == SINGLE_LEGACY) {
EV_SET(&skev[skev_sz], (int)kev[i].ident, EVFILT_READ, EV_ENABLE, 0, 0, kev[i].udata);
skev_sz++;
}
}
}
}
}
static void
dump_options()
{
std::stringstream ss;
ss << "Configuration:\n"
<< " port: " << options.port << std::endl
<< " mode: " << options.mode << std::endl
<< " enable TLS: " << options.enable_tls << std::endl
<< " enable kTLS: " << options.enable_ktls << std::endl
<< " TLS secret key: " << options.secret_key_fn << std::endl
<< " TLS certificate: " << options.cert_fn << std::endl
<< " shared Kqueue: " << options.shared_kq << std::endl
<< " number of threads: " << CPU_COUNT(&options.cpuset) << std::endl
<< " verbose: " << options.verbose << std::endl
<< " SKQ flags: " << options.skq_flag << std::endl
<< " SKQ dump:" << options.skq_dump << std::endl
<< " SKQ rtshare: " << options.skq_rtshare << std::endl
<< " SKQ rtfreq: " << options.skq_rtfreq << std::endl
<< " SKQ high priority clients (" << options.hpip.size() << "): " << std::endl;
for(uint i = 0; i < options.hpip.size(); i++) {
ss << " " << options.hpip.at(i) << std::endl;
}
V("%s", ss.str().c_str());
}
static void
usage()
{
fprintf(stdout, "Usage:\n"
" p: server port\n"
" v: verbose mode\n"
" m: shared Kqueue across workers\n"
" s: enable TLS\n"
" k: enable kTLS\n"
" S: TLS private key file\n"
" C: TLS certificate file\n"
" c: server thread cpu list\n"
" h: show help\n"
" d: SKQ dump interval (s)\n"
" r: SKQ realtime client hostname(s)\n"
" R: SKQ rt_share\n"
" F: SKQ rt_freq\n"
" M: server mode: 0 - ECHO, 1 - TOUCH, 2 - HTTP, 3 - RDB\n"
" O: mode specific parameters in the format \"key=value\"\n"
" Workload specific parameters:\n"
" TOUCH:\n"
" ENTRIES - Number of cache-aligned entries per connection.\n\n");
}
static void
create_workers(std::vector<struct ppd_thread_ctx*> *workers)
{
int kq = -1;
if (options.shared_kq) {
kq = kqueue();
if (kq <= 0) {
E("Failed to create kqueue. ERR %d\n", errno);
}
#ifdef FKQMULTI
if (options.skq_flag != SINGLE_LEGACY) {
status = ioctl(kq, FKQMULTI, &options.skq_flag);
if (status == -1) {
E("ioctl failed. ERR %d\n", errno);
}
V("SKQ enabled. SFLAG %d\n", options.skq_flag);
}
int para = KQTUNE_MAKE(KQTUNE_RTSHARE, options.kq_rtshare);
status = ioctl(kq, FKQTUNE, &para);
if (status == -1) {
E("rtshare ioctl failed. ERR %d\n", errno);
}
para = KQTUNE_MAKE(KQTUNE_FREQ, options.kq_tfreq);
status = ioctl(kq, FKQTUNE, &para);
if (status == -1) {
E("freq ioctl failed. ERR %d\n", errno);
}
V("KQ IOCTL: RTSHARE %d TFREQ %d\n", options.kq_rtshare, options.kq_tfreq);
#else
/* legacy single KQ only supports flag -1 */
options.skq_flag = SINGLE_LEGACY;
#endif
}
int tid = 0;
int core;
CPU_FOREACH_ISSET(core, &options.cpuset) {
V("Creating worker thread on core %d...\n", core);
struct ppd_thread_ctx *thrd = (struct ppd_thread_ctx *)aligned_alloc(CACHE_LINE_SIZE, sizeof(struct ppd_thread_ctx));
thrd->evcnt = 0;
thrd->tid = tid;
if (!options.shared_kq) {
kq = kqueue();
if (kq <= 0) {
E("kqueue() failed: %d\n", errno);
}
#ifdef FKQMULTI
int para = KQTUNE_MAKE(KQTUNE_RTSHARE, options.kq_rtshare);
status = ioctl(kq, FKQTUNE, &para);
if (status == -1) {
E("rtshare ioctl failed. ERR %d\n", errno);
}
para = KQTUNE_MAKE(KQTUNE_FREQ, options.kq_tfreq);
status = ioctl(kq, FKQTUNE, &para);
if (status == -1) {
E("freq ioctl failed. ERR %d\n", errno);
}
#endif
}
thrd->kqfd = kq;
pthread_attr_t attr;
pthread_attr_init(&attr);
cpuset_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(core, &cpuset);
int status = pthread_attr_setaffinity_np(&attr, sizeof(cpuset), &cpuset);
if (status != 0) {
E("pthread_attr_setaffinity_np() failed : %d\n", status);
}
status = pthread_create(&thrd->thrd, &attr, worker_main, thrd);
workers->push_back(thrd);
tid++;
}
}
static void
get_ip_from_hostname(const char* hostname, char* buf, int len)
{
struct in_addr **addr;
struct hostent *he;
if ((he = gethostbyname(hostname)) == NULL) {
E("Hostname %s cannot be resolved.\n", hostname);
}
addr = (struct in_addr**)he->h_addr_list;
for (int i=0;addr[i]!=NULL;i++) {
strncpy(buf, inet_ntoa(*addr[i]), len);
break;
}
}
static void
parse_mode_params()
{
char * saveptr;
for (int i = 0; i < options.num_mode_params; i++) {
saveptr = NULL;
char *key = strtok_r(options.mode_params[i], "=", &saveptr);
char *val = strtok_r(NULL, "=", &saveptr);
mode_params.insert({key, val});
V("Parsed workload specific parameter: %s = %s\n", key, val);
}
}
static SSL *
tls_handshake_server(int conn_fd)
{
SSL * ssl;
int r;
ssl = SSL_new(options.ssl_ctx);
if (ssl == NULL) {
E("SSL_new() failed: %ld\n", ERR_get_error());
}
if (SSL_set_fd(ssl, conn_fd) == 0) {
E("SSL_set_fd() failed: %ld\n", ERR_get_error());
}
if ((r = SSL_accept(ssl)) <= 0) {
E("SSL_accept() failed: %ld\n", ERR_get_error());
}
if (options.enable_ktls) {
// make sure ktls is enabled
if (!(BIO_get_ktls_send(SSL_get_wbio(ssl)) && BIO_get_ktls_recv(SSL_get_rbio(ssl)))) {
E("kTLS not enabled on %d: %ld\n", conn_fd, ERR_get_error());
}
}
return ssl;
}
static void
loop_main(int m_kq, std::vector<struct ppd_thread_ctx*> *workers)
{
struct kevent kev;
int cur_conn = 0;
long cur_ts = 0;
while (1) {
if (kevent(m_kq, NULL, 0, &kev, 1, NULL) != 1) {
E("kevent() failed: %d\n", errno);
}
if (kev.ident == (uintptr_t)TIMER_FD) {
#ifdef FKQMULTI
/* timer event */
if (options.skq_dump > 0 && cur_ts % (options.skq_dump * 1000) == 0) {
uintptr_t args = (uintptr_t)dbuf;
memset(dbuf, 0, 1024 * 1024 + 1);
status = ioctl(kq, FKQMPRNT, &args);
if (status == -1) {
E("SKQ dump failed %d\n", errno);
} else {
fprintf(stdout, "====== KQ DUMP ======\n%s\n", dbuf);
}
}
#endif
cur_ts++;
continue;
}
// new connection
struct sockaddr_in client_addr;
socklen_t client_addr_size = sizeof(client_addr);
int conn_fd = accept(kev.ident, (struct sockaddr*)&client_addr, &client_addr_size);
if (conn_fd < 0) {
W("accept() failed: %d\n", errno);
continue;
}
{
const int enable = 1;
if (setsockopt(conn_fd, IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(enable)) < 0) {
W("setsockopt() nodelay failed on conn %d: err %d\n", conn_fd, errno);
continue;
}
}
char ipaddr[INET_ADDRSTRLEN + 1];
strncpy(ipaddr, inet_ntoa(client_addr.sin_addr), INET_ADDRSTRLEN);
ipaddr[INET_ADDRSTRLEN] = 0;
V("Accepted new connection %d from %s\n", conn_fd, ipaddr);
struct ppd_conn *conn = new struct ppd_conn;
conn->conn_fd = conn_fd;
switch (options.mode) {
case WORKLOAD_TYPE::ECHO:
conn->proc = new echo_proc(&mode_params);
break;
case WORKLOAD_TYPE::TOUCH:
conn->proc = new touch_proc(&mode_params);
break;
#ifdef WITH_ROCKSDB
case WORKLOAD_TYPE::RDB:
hint->proc = new rdb_proc(&mode_params);
break;
#endif
default:
E("Unknown server mode %d", options.mode);
}
if (options.enable_tls) {
conn->ssl = tls_handshake_server(conn_fd);
V("Established TLS on connection %d...\n", conn_fd);
}
int target_kq = workers->at(cur_conn % workers->size())->kqfd;
int ev_flags = EV_ADD;
#ifdef FKQMULTI
for (uint32_t i = 0; i < options.hpip.size(); i++) {
if (strcmp(ipaddr, options.hpip.at(i)) == 0) {
V("Connection %d marked as realtime.\n", conn_fd);
ev_flags |= EV_REALTIME;
}
}
#endif
if (options.shared_kq && options.skq_flag == SINGLE_LEGACY) {
ev_flags |= EV_DISPATCH;
}
EV_SET(&kev, conn_fd, EVFILT_READ, ev_flags, 0, 0, conn);
int status = kevent(target_kq, &kev, 1, NULL, 0, NULL);
if (status == -1) {
E("kevent() failed: %d\n", errno);
}
V("Connection %d assigned to thread %d\n", conn_fd, workers->at(cur_conn % workers->size())->tid);
cur_conn++;
}
}
int
main(int argc, char *argv[])
{
GOOGLE_PROTOBUF_VERIFY_VERSION;
SSL_library_init();
srand(time(NULL));
// don't raise SIGPIPE when sending into broken TCP connections
::signal(SIGPIPE, SIG_IGN);
#ifdef FKQMULTI
char dbuf[1024 * 1024 + 1];
#endif
char ch;
while ((ch = getopt(argc, argv, "d:p:c:m:vhr:R:F:M:O:skS:C:")) != -1) {
switch (ch) {
case 'd':
options.skq_dump = atoi(optarg);
break;
case 'p':
options.port = atoi(optarg);
break;
case 'c':
cpulist_to_cpuset(optarg, &options.cpuset);
break;
case 'm':
options.shared_kq = 1;
options.skq_flag = atoi(optarg);
break;
case 'O': {
if (options.num_mode_params >= MAX_MODE_PARAMS) {
E("Too many workload parameters.\n");
}
snprintf(options.mode_params[options.num_mode_params], MAX_MODE_PARAMS_LEN, "%s", optarg);
options.num_mode_params++;
break;
}
case 'M':
options.mode = atoi(optarg);
break;
case 'v':
options.verbose = 1;
W("Verbose mode can cause SUBSTANTIAL latency fluctuations in some terminals!\n");
break;
case 'r': {
char* eip = new char[INET_ADDRSTRLEN + 1];
get_ip_from_hostname(optarg, eip, INET_ADDRSTRLEN);
options.hpip.push_back(eip);
break;
}
case 'R':
options.skq_rtshare = atoi(optarg);
break;
case 'F':
options.skq_rtfreq = atoi(optarg);
break;
case 's':
options.enable_tls = 1;
break;
case 'k':
options.enable_ktls = 1;
break;
case 'S':
snprintf(options.secret_key_fn, sizeof(options.secret_key_fn), "%s", optarg);
break;
case 'C':
snprintf(options.cert_fn, sizeof(options.cert_fn), "%s", optarg);
break;
case 'h':
usage();
exit(0);
default:
E("Unrecognized option -%c. See -h.\n\n", ch);
}
}
dump_options();
parse_mode_params();
std::vector<struct ppd_thread_ctx *> wrk_thrds;
std::vector<int> server_socks;
V("Setting up server sockets...\n");
listen_socket_create(&server_socks);
if (options.enable_tls) {
V("Setting up TLS context...\n");
create_tls_context();
}
V("Setting up main thread kqueue...\n");
int m_kq = main_kqueue_create(&server_socks);
V("Creating worker threads...\n");
create_workers(&wrk_thrds);
V("Entering main event loop...\n");
loop_main(m_kq, &wrk_thrds);
E("Critical bug - main loop exited\n");
return 0;
}

324
ppd/reqproc.cc Normal file
View File

@ -0,0 +1,324 @@
#include "reqproc.h"
#include <arpa/inet.h>
#include <atomic>
#include <cassert>
#include <cerrno>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <new>
#include <random>
#include <stdint.h>
#include <sstream>
#include <sys/endian.h>
#include <sys/param.h>
#include <chrono>
#include <unistd.h>
#include "options.h"
#ifdef WITH_ROCKSDB
#include "rocksdb/cache.h"
#include "rocksdb/status.h"
#include <rocksdb/cache.h>
#include <rocksdb/db.h>
#include <rocksdb/env.h>
#include <rocksdb/filter_policy.h>
#include <rocksdb/memtablerep.h>
#include <rocksdb/options.h>
#include <rocksdb/perf_context.h>
#include <rocksdb/persistent_cache.h>
#include <rocksdb/rate_limiter.h>
#include <rocksdb/slice.h>
#include <rocksdb/slice_transform.h>
#include <rocksdb/stats_history.h>
#include <rocksdb/utilities/object_registry.h>
#include <rocksdb/utilities/optimistic_transaction_db.h>
#include <rocksdb/utilities/options_util.h>
#include <rocksdb/utilities/sim_cache.h>
#include <rocksdb/utilities/transaction.h>
#include <rocksdb/utilities/transaction_db.h>
#include <rocksdb/write_batch.h>
#include <rocksdb/table.h>
#endif
#include <msg.pb.h>
#include <util.h>
#include <const.h>
////////////////
// TOUCH Generator
////////////////
touch_proc::touch_proc(std::unordered_map<std::string, std::string>* args) : req_proc()
{
if (args->find(PARAM_TBSZ) != args->end()) {
this->buffer_sz = atoi(args->at(PARAM_TBSZ).c_str());
} else {
this->buffer_sz = PARAM_TBSZ_DEFAULT;
}
V("Allocating %d items x %d CASZ for connnection\n", this->buffer_sz, CACHE_LINE_SIZE);
this->buffer = (struct ppd_touch_cache_item *)aligned_alloc(CACHE_LINE_SIZE, this->buffer_sz * sizeof(struct ppd_touch_cache_item));
this->rgen = new std::default_random_engine();
this->rdist = new std::uniform_int_distribution<int>(0, INT32_MAX);
}
touch_proc::~touch_proc()
{
delete this->buffer;
delete this->rgen;
delete this->rdist;
}
int touch_proc::proc_req(int fd)
{
ppd_touch_req req;
ppd_touch_resp resp;
struct ppd_msg *msg = (struct ppd_msg *)this->read_buf;
int ret = readmsg(fd, this->read_buf, this->MAX_READ_BUF_SIZE);
if (ret < 0) {
W("Readmsg failed with %d for connection %d\n", ret, fd);
return ret;
}
if (!req.ParseFromArray(msg->payload, msg->size)) {
W("ParseFromArray failed for connection %d\n", fd);
return EINVAL;
}
V("Conn %d touching %d items...\n", fd, req.touch_cnt());
int sum = 0;
int rand = (*this->rdist)(*this->rgen);
for(int64_t i = rand; i < rand + req.touch_cnt(); i++) {
if (req.inc() > 0) {
this->buffer[i % this->buffer_sz].val += 1;
} else {
/* only read */
sum += this->buffer[i % this->buffer_sz].val;
}
}
sum = sum + 1;
resp.set_status(0);
if (!resp.SerializeToArray(this->read_buf, MAX_READ_BUF_SIZE)) {
W("Couldn't searialize to array connection %d\n", fd);
}
ret = writemsg(fd, this->read_buf, MAX_READ_BUF_SIZE, this->read_buf, resp.ByteSizeLong());
if (ret < 0) {
W("Writemsg failed with %d for connection %d\n", ret, fd);
}
return ret;
}
////////////////
// ECHO Generator
////////////////
echo_proc::echo_proc(std::unordered_map<std::string, std::string>*) : req_proc()
{
this->buf = new char[BUFFER_SZ];
srand(time(NULL));
for(size_t i = 0; i < BUFFER_SZ; i++) {
char ch = (char)rand();
this->buf[i] = ch;
}
}
echo_proc::~echo_proc()
{
delete this->buf;
}
int echo_proc::proc_req(int fd)
{
uint64_t ms1, ms2;
struct ppd_msg *msg = (struct ppd_msg *)this->read_buf;
int ret = readmsg(fd, this->read_buf, MAX_READ_BUF_SIZE);
if (ret < 0) {
W("Readbuf failed with %d for connection %d\n", ret, fd);
return ret;
}
ms1 = get_time_us();
if (!req.ParseFromArray(msg->payload, msg->size)) {
W("ParseFromArray failed for connection %d\n", fd);
return EINVAL;
}
int data_sz = req.data_size();
V("Connection %d delay %d us %d data size \n", fd, req.enable_delay(), data_sz);
if (req.enable_delay() > 0) {
uint64_t server_delay = req.enable_delay();
uint64_t now = get_time_us();
while (get_time_us() - now <= server_delay) {};
}
if (data_sz > (int)BUFFER_SZ) {
data_sz = BUFFER_SZ;
}
if (data_sz <= 0) {
data_sz = 1;
}
resp.set_data(this->buf, data_sz);
resp.set_status(0);
if (!resp.SerializeToArray(this->read_buf, MAX_READ_BUF_SIZE)) {
W("Couldn't searialize to array connection %d\n", fd);
}
ms2 = get_time_us();
V("Serialization: TIME: %ld\n", ms2 - ms1);
ret = writemsg(fd, this->read_buf, MAX_READ_BUF_SIZE, this->read_buf, resp.ByteSizeLong());
if (ret < 0) {
W("Writemsg failed with %d for connection %d\n", ret, fd);
}
return ret;
}
#ifdef WITH_ROCKSDB
////////////////
// rdb Generator
////////////////
rocksdb::DB * rdb_proc::db = nullptr;
std::atomic<int> rdb_proc::db_init {0};
rdb_proc::rdb_proc(std::unordered_map<std::string, std::string>* args) : req_proc()
{
const char * db_path;
int desired = 0;
int target = 1;
if (std::atomic_compare_exchange_strong(&rdb_proc::db_init, &desired, target)) {
if (args->find(PARAM_PATH) != args->end()) {
db_path = args->at(PARAM_PATH).c_str();
} else {
E("Must specify -OPATH for rocksdb.\n");
}
V("Initializing rocksdb, path: %s.\n", db_path);
rocksdb::Options opt;
std::shared_ptr<rocksdb::Cache> cache = rocksdb::NewLRUCache(CACHE_SIZE, 6, false, 0.0);
opt.use_direct_io_for_flush_and_compaction = USE_DIRECT_IO_FOR_FLUSH_AND_COMPACTION;
opt.use_direct_reads = USE_DIRECT_READS;
rocksdb::BlockBasedTableOptions block_based_options;
block_based_options.index_type = rocksdb::BlockBasedTableOptions::kBinarySearch;
block_based_options.block_cache = cache;
opt.table_factory.reset(rocksdb::NewBlockBasedTableFactory(block_based_options));
opt.IncreaseParallelism(12);
opt.OptimizeLevelStyleCompaction(1024 * 1024 * 1024);
opt.OptimizeUniversalStyleCompaction(1024 * 1024 * 1024);
opt.write_buffer_size = 1024 * 1024 * 1024;
opt.create_if_missing = false;
opt.compression = rocksdb::kNoCompression;
rocksdb::Status s = rocksdb::DB::Open(opt, std::string(db_path), &this->db);
if (!s.ok()) {
E("Could not open rocksdb! Err %s\n", s.ToString().c_str());
}
rdb_proc::db_init.store(2);
V("Finished initializing rocksdb.\n");
} else {
V("Checking for rocksdb initialization...\n");
while(rdb_proc::db_init.load() != 2) {};
V("Detected initialized rocksdb.\n");
}
}
rdb_proc::~rdb_proc()
{
}
int rdb_proc::proc_req(int fd)
{
ppd_rdb_resp resp;
ppd_rdb_req req;
rocksdb::Status s;
struct ppd_msg *msg = (struct ppd_msg *)this->read_buf;
int i = 0;
int status = readmsg(fd, this->read_buf, MAX_READ_BUF_SIZE);
if (status != 0) {
W("Readmsg failed with %d for connection %d\n", status, fd);
return status;
}
if (!req.ParseFromArray(msg->payload, msg->size)) {
W("ParseFromArray failed for connection %d\n", fd);
return EINVAL;
}
V("Connection %d op: %d, key: %s. val: %s. optarg: %d.\n", fd, req.op(), req.key().c_str(), req.val().c_str(), req.optarg());
switch (req.op()) {
case PPD_RDB_OP_PUT:{
s = this->db->Put(rocksdb::WriteOptions(), req.key(), req.val());
resp.set_status(s.code());
break;
}
case PPD_RDB_OP_GET: {
std::string val;
s = this->db->Get(rocksdb::ReadOptions(), req.key(), &val);
if (s.ok()) {
resp.set_result(val);
}
resp.set_status(s.code());
break;
}
case PPD_RDB_OP_SEEK: {
rocksdb::Slice val;
rocksdb::Iterator *it = this->db->NewIterator(rocksdb::ReadOptions(false, true));
it->Seek(req.key());
resp.set_status(it->status().code());
if (it->Valid()) {
val = it->value();
resp.set_result(val.data(), val.size());
}
for(int64_t j = 0; j < req.optarg() && it->Valid(); j++) {
rocksdb::Slice val = it->value();
// do something about the key
std::memcpy(this->read_buf, val.data(), MIN(val.size(), MAX_READ_BUF_SIZE));
it->Next();
if (!it->status().ok()) {
resp.set_status(it->status().code());
break;
}
}
delete it;
break;
}
default: {
W("Invalid opcode %d for connection %d\n", req.op(), fd);
return EINVAL;
}
}
resp.set_status(0);
status = writemsg(fd, this->read_buf, MAX_READ_BUF_SIZE, this->read_buf, resp.ByteSizeLong());
if (status < 0) {
W("Writemsg failed with %d for connection %d\n", status, fd);
}
return status;
}
#endif

85
ppd/reqproc.h Normal file
View File

@ -0,0 +1,85 @@
#pragma once
#ifdef WITH_ROCKSDB
#include <rocksdb/db.h>
#endif
#include <cstdint>
#include <string>
#include <random>
#include <unordered_map>
#include <const.h>
#include <atomic>
#include <sys/param.h>
#include <msg.pb.h>
#define DISABLE_EVIL_CONSTRUCTORS(name) \
name(const name&) = delete; \
void operator=(const name) = delete
struct alignas(CACHE_LINE_SIZE) ppd_touch_cache_item {
int val;
};
class req_proc {
protected:
constexpr static int MAX_READ_BUF_SIZE = 1024 * 1024;
char * read_buf;
public:
req_proc() {this->read_buf = new char[MAX_READ_BUF_SIZE]; };
virtual ~req_proc() {delete[] this->read_buf;};
virtual int proc_req(int fd) = 0;
};
class touch_proc : public req_proc
{
private:
int buffer_sz;
std::default_random_engine * rgen;
std::uniform_int_distribution<int> * rdist;
struct ppd_touch_cache_item* buffer;
static constexpr const char* PARAM_TBSZ = "ENTRIES";
static constexpr const int PARAM_TBSZ_DEFAULT = 64;
constexpr static int MAX_SZ = 1024 * 1024;
public:
touch_proc(std::unordered_map<std::string, std::string>* args);
touch_proc() = delete;
~touch_proc();
DISABLE_EVIL_CONSTRUCTORS(touch_proc);
int proc_req(int fd);
};
class echo_proc : public req_proc
{
private:
ppd_echo_req req;
ppd_echo_resp resp;
char * buf;
static constexpr size_t BUFFER_SZ = 1024 * 1024;
public:
echo_proc(std::unordered_map<std::string, std::string>* args);
echo_proc() = delete;
~echo_proc();
DISABLE_EVIL_CONSTRUCTORS(echo_proc);
int proc_req(int fd);
};
#ifdef WITH_ROCKSDB
class rdb_proc : public req_proc
{
private:
constexpr static bool USE_DIRECT_IO_FOR_FLUSH_AND_COMPACTION = true;
constexpr static bool USE_DIRECT_READS = true;
constexpr static int CACHE_SIZE = 268435456;
constexpr static int MAX_MSG_SZ = 4096;
static constexpr const char* PARAM_PATH = "PATH";
static std::atomic<int> db_init;
static rocksdb::DB *db;
public:
rdb_proc(std::unordered_map<std::string, std::string>* args);
rdb_proc() = delete;
~rdb_proc();
DISABLE_EVIL_CONSTRUCTORS(rdb_proc);
int proc_req(int fd);
};
#endif

19
ppd_ff/options.h Normal file
View File

@ -0,0 +1,19 @@
#pragma once
#include <vector>
#include <unordered_map>
#include <string>
#include "const.h"
static constexpr int MAX_MODE_PARAMS = 8;
static constexpr int MAX_MODE_PARAMS_LEN = 64;
struct server_options {
int port = 9898;
WORKLOAD_TYPE mode = WORKLOAD_TYPE::ECHO;
int verbose = 0;
int num_mode_params;
char mode_params[MAX_MODE_PARAMS][MAX_MODE_PARAMS_LEN];
std::unordered_map<std::string, std::string> parsed_mode_params;
};
extern server_options options;

315
ppd_ff/ppd.cc Executable file
View File

@ -0,0 +1,315 @@
#include <cstdlib>
#include <strings.h>
#include <vector>
#include <sstream>
#include <getopt.h>
#include <unordered_map>
#include <ff_api.h>
#include <ff_config.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include "const.h"
#include "util_ff.h"
#include "reqproc.h"
#include "msg.pb.h"
#include "options.h"
static constexpr int NEVENT = 128;
static constexpr int SOCK_BACKLOG = 512;
static constexpr int DEFAULT_PORT = 9898;
struct alignas(CACHE_LINE_SIZE) cache_item {
int val;
};
static_assert(sizeof(struct cache_item) == CACHE_LINE_SIZE, "cache_item not cache line sized");
struct conn_hint {
req_proc * proc;
};
struct alignas(CACHE_LINE_SIZE) loop_info {
int kqfd;
int srv_sock;
};
static_assert(sizeof(struct loop_info) == CACHE_LINE_SIZE, "loop_info not cache line sized");
struct server_options options;
static void
conn_hint_destroy(struct conn_hint *hint)
{
delete hint->proc;
delete hint;
}
static int
server_socket_create(int port)
{
struct sockaddr_in server_addr;
int status;
const int enable = 1;
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
int fd = ff_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (fd < 0) {
E("server listen socket");
}
// if (ff_setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) < 0) {
// E("server listen setsockopt reuseaddr");
// }
// if (ff_setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(enable)) < 0) {
// E("server listen setsockopt reuseport");
// }
if (ff_setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(enable)) < 0) {
E("server listen setsockopt NODELAY");
}
status = ff_bind(fd, (struct linux_sockaddr*)&server_addr, sizeof(server_addr));
if (status < 0) {
E("server listen bind");
}
status = ff_listen(fd, SOCK_BACKLOG);
if (status < 0) {
E("listen");
}
return fd;
}
static void
drop_conn(int kqfd, int connfd, struct conn_hint * hint)
{
int status;
struct kevent ev;
EV_SET(&ev, connfd, EVFILT_READ, EV_DELETE, 0, 0, NULL);
status = ff_kevent(kqfd, &ev, 1, 0, 0, NULL);
if (status < 0) {
E("Failed to delete connection %d from kqueue\n", connfd);
}
ff_close(connfd);
conn_hint_destroy(hint);
}
static int
loopm(void *info)
{
struct loop_info *linfo = (struct loop_info *)info;
struct kevent kev[NEVENT];
struct kevent skev[NEVENT + 1];
std::vector<int> socks;
int skev_sz = 0;
int status;
status = ff_kevent(linfo->kqfd, NULL, 0, kev, NEVENT, NULL);
if (status < 0) {
E("main_loop: kevent() failed with %d\n", errno);
}
skev_sz = 0;
for (int i = 0; i < status; i++) {
int fd = (int)kev[i].ident;
struct conn_hint * conn_hint = (struct conn_hint *)kev[i].udata;
V("Event fired on fd %d\n", fd);
if (kev->flags & EV_EOF) {
if (fd != linfo->srv_sock) {
V("main_loop: connection %d dropped due to EOF. ERR: %d\n", fd, kev->fflags);
drop_conn(linfo->kqfd, fd, conn_hint);
continue;
} else {
E("main_loop: unexpected EOF received on server socket.\n");
}
} else if (kev->flags & EV_ERROR) {
W("main_loop: kevent() fd %d EV_ERROR set : 0x%lx. Dropping...\n", fd, kev[i].data);
continue;
}
if (fd == linfo->srv_sock) {
struct sockaddr_in client_addr;
socklen_t client_addr_size = sizeof(client_addr);
int client_fd = ff_accept(fd, (struct linux_sockaddr*)&client_addr, &client_addr_size);
if (client_fd < 0) {
W("main_loop: ff_accept() failed\n");
continue;
}
// const int enable = 1;
// if (setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(enable)) < 0) {
// W("setsockopt() failed on socket %d\n", client_fd);
// ff_close(client_fd);
// continue;
// }
char ipaddr[INET_ADDRSTRLEN + 1];
strncpy(ipaddr, inet_ntoa(client_addr.sin_addr), INET_ADDRSTRLEN);
ipaddr[INET_ADDRSTRLEN] = 0;
V("main_loop: accepted new connection %d from %s\n", client_fd, ipaddr);
struct conn_hint *hint = new struct conn_hint;
switch (options.mode) {
case WORKLOAD_TYPE::ECHO:
hint->proc = new echo_proc(&options.parsed_mode_params);
break;
case WORKLOAD_TYPE::TOUCH:
hint->proc = new touch_proc(&options.parsed_mode_params);
break;
// case WORKLOAD_TYPE::RDB:
// hint->proc = new rdb_proc(&options.parsed_mode_params);
// break;
default:
E("Unknown server mode %d", options.mode);
}
EV_SET(&skev[skev_sz], client_fd, EVFILT_READ, EV_ADD, 0, 0, hint);
skev_sz++;
} else {
status = conn_hint->proc->proc_req(fd);
if (status < 0) {
W("Connection %d proc_req returned error %d\n", fd, status);
drop_conn(linfo->kqfd, fd, conn_hint);
} else {
// EV_SET(&skev[skev_sz], fd, EVFILT_READ, EV_ENABLE, 0, 0, conn_hint);
// skev_sz++;
}
}
}
assert(skev_sz <= NEVENT + 1);
status = ff_kevent(linfo->kqfd, skev, skev_sz, NULL, 0, NULL);
return 0;
}
void dump_options()
{
std::stringstream ss;
ss << "Configuration:" << std::endl
<< " port: " << options.port << std::endl
<< " mode: " << options.mode << std::endl
<< " verbose: " << options.verbose << std::endl
<< " mode parameters: " << std::endl;
for(int i = 0; i < options.num_mode_params; i++) {
ss << " " << options.mode_params[i] << std::endl;
}
V("%s", ss.str().c_str());
}
static void
usage()
{
fprintf(stdout, "Usage:\n"
" p: listen port\n"
" v: verbose mode\n"
" h: show help\n"
" M: server mode: 0 - ECHO, 1 - TOUCH, 2 - HTTP, 3 - RDB\n"
" O: mode specific parameters in the format \"key=value\"\n"
" Workload specific parameters:\n"
" TOUCH:\n"
" ENTRIES - Number of cache-aligned entries per connection.\n\n");
}
void parse_mode_params()
{
char * saveptr;
for (int i = 0; i < options.num_mode_params; i++) {
saveptr = NULL;
char *key = strtok_r(options.mode_params[i], "=", &saveptr);
char *val = strtok_r(NULL, "=", &saveptr);
options.parsed_mode_params.insert({key, val});
V("Parsed workload parameter: %s = %s\n", key, val);
}
}
int
main(int argc, char *argv[])
{
GOOGLE_PROTOBUF_VERIFY_VERSION;
// don't raise SIGPIPE when sending into broken TCP connections
::signal(SIGPIPE, SIG_IGN);
srand(time(NULL));
ff_init(argc, argv);
char ch;
while ((ch = getopt(argc, argv, "M:h:O:vp:")) != -1) {
switch (ch) {
case 'O': {
strncpy(options.mode_params[options.num_mode_params], optarg, MAX_MODE_PARAMS_LEN);
options.num_mode_params++;
break;
}
case 'M':
options.mode = (WORKLOAD_TYPE)strtod(optarg, nullptr);
break;
case 'v':
options.verbose = 1;
W("Verbose mode can cause SUBSTANTIAL latency fluctuations in some terminals!\n");
break;
case 'p':
options.port = strtod(optarg, nullptr);
break;
case 'h':
usage();
exit(0);
default:
E("Unrecognized option -%c. See -h.\n\n", ch);
}
}
parse_mode_params();
dump_options();
V("Setting up listen sockets...\n");
int srv_sock = server_socket_create(options.port);
int kq = ff_kqueue();
if (kq <= 0) {
E("Cannot create kqueue\n");
}
struct kevent kev;
EV_SET(&kev, srv_sock, EVFILT_READ, EV_ADD, 0, 0, NULL);
int status = ff_kevent(kq, &kev, 1, NULL, 0, NULL);
if (status == -1) {
E("kevent() add srv_sock failed %d\n", errno);
}
V("Entering main event loop...\n");
struct loop_info info;
info.kqfd = kq;
info.srv_sock = srv_sock;
ff_run(loopm, &info);
// shouldn't get here
assert(false);
google::protobuf::ShutdownProtobufLibrary();
return 0;
}

305
ppd_ff/reqproc.cc Normal file
View File

@ -0,0 +1,305 @@
#include <arpa/inet.h>
#include <atomic>
#include <cassert>
#include <cerrno>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <new>
#include <random>
#include <stdint.h>
#include <sstream>
#include <sys/endian.h>
#include <sys/param.h>
#include <chrono>
#include <unistd.h>
// #include <rocksdb/cache.h>
// #include <rocksdb/status.h>
// #include <rocksdb/cache.h>
// #include <rocksdb/db.h>
// #include <rocksdb/env.h>
// #include <rocksdb/filter_policy.h>
// #include <rocksdb/memtablerep.h>
// #include <rocksdb/options.h>
// #include <rocksdb/perf_context.h>
// #include <rocksdb/persistent_cache.h>
// #include <rocksdb/rate_limiter.h>
// #include <rocksdb/slice.h>
// #include <rocksdb/slice_transform.h>
// #include <rocksdb/stats_history.h>
// #include <rocksdb/utilities/object_registry.h>
// #include <rocksdb/utilities/optimistic_transaction_db.h>
// #include <rocksdb/utilities/options_util.h>
// #include <rocksdb/utilities/sim_cache.h>
// #include <rocksdb/utilities/transaction.h>
// #include <rocksdb/utilities/transaction_db.h>
// #include <rocksdb/write_batch.h>
// #include <rocksdb/table.h>
#include "msg.pb.h"
#include "util_ff.h"
#include "const.h"
#include "reqproc.h"
#include "options.h"
////////////////
// TOUCH Generator
////////////////
touch_proc::touch_proc(std::unordered_map<std::string, std::string>* args) : req_proc()
{
if (args->find(PARAM_TBSZ) != args->end()) {
this->buffer_sz = atoi(args->at(PARAM_TBSZ).c_str());
} else {
this->buffer_sz = PARAM_TBSZ_DEFAULT;
}
V("Allocating %d items x %d CASZ for connnection\n", this->buffer_sz, CACHE_LINE_SIZE);
this->buffer = (struct ppd_touch_cache_item *)aligned_alloc(CACHE_LINE_SIZE, this->buffer_sz * sizeof(struct ppd_touch_cache_item));
this->rgen = new std::default_random_engine();
this->rdist = new std::uniform_int_distribution<int>(0, INT32_MAX);
}
touch_proc::~touch_proc()
{
delete this->buffer;
delete this->rgen;
delete this->rdist;
}
int touch_proc::proc_req(int fd)
{
ppd_touch_req req;
ppd_touch_resp resp;
struct ppd_msg *msg = (struct ppd_msg *)this->read_buf;
int ret = readmsg(fd, this->read_buf, this->MAX_READ_BUF_SIZE);
if (ret < 0) {
W("Readmsg failed with %d for connection %d\n", ret, fd);
return ret;
}
if (!req.ParseFromArray(msg->payload, msg->size)) {
W("ParseFromArray failed for connection %d\n", fd);
return EINVAL;
}
V("Conn %d touching %d items...\n", fd, req.touch_cnt());
int sum = 0;
int rand = (*this->rdist)(*this->rgen);
for(int64_t i = rand; i < rand + req.touch_cnt(); i++) {
if (req.inc() > 0) {
this->buffer[i % this->buffer_sz].val += 1;
} else {
/* only read */
sum += this->buffer[i % this->buffer_sz].val;
}
}
sum = sum + 1;
resp.set_status(0);
if (!resp.SerializeToArray(this->read_buf, MAX_READ_BUF_SIZE)) {
W("Couldn't searialize to array connection %d\n", fd);
}
ret = writemsg(fd, this->read_buf, MAX_READ_BUF_SIZE, this->read_buf, resp.ByteSizeLong());
if (ret < 0) {
W("Writemsg failed with %d for connection %d\n", ret, fd);
}
return ret;
}
////////////////
// ECHO Generator
////////////////
echo_proc::echo_proc(std::unordered_map<std::string, std::string>*) : req_proc()
{
}
echo_proc::~echo_proc()
{
}
int echo_proc::proc_req(int fd)
{
uint64_t ms1, ms2;
struct ppd_msg *msg = (struct ppd_msg *)this->read_buf;
int ret = readmsg(fd, this->read_buf, MAX_READ_BUF_SIZE);
if (ret < 0) {
W("Readbuf failed with %d for connection %d\n", ret, fd);
return ret;
}
ms1 = get_time_us();
if (!req.ParseFromArray(msg->payload, msg->size)) {
W("ParseFromArray failed for connection %d\n", fd);
return EINVAL;
}
V("Connection %d delay %d us \n", fd, req.enable_delay());
if (req.enable_delay() > 0) {
uint64_t server_delay = req.enable_delay();
uint64_t now = get_time_us();
while (get_time_us() - now <= server_delay) {};
}
resp.set_status(0);
if (!resp.SerializeToArray(this->read_buf, MAX_READ_BUF_SIZE)) {
W("Couldn't searialize to array connection %d\n", fd);
}
ms2 = get_time_us();
V("Serialization: TIME: %ld\n", ms2 - ms1);
ret = writemsg(fd, this->read_buf, MAX_READ_BUF_SIZE, this->read_buf, resp.ByteSizeLong());
if (ret < 0) {
W("Writemsg failed with %d for connection %d\n", ret, fd);
}
return ret;
}
// ////////////////
// // rdb Generator
// ////////////////
// rocksdb::DB * rdb_proc::db = nullptr;
// std::atomic<int> rdb_proc::db_init {0};
// rdb_proc::rdb_proc(std::unordered_map<std::string, std::string>* args) : req_proc()
// {
// const char * db_path;
// int desired = 0;
// int target = 1;
// if (std::atomic_compare_exchange_strong(&rdb_proc::db_init, &desired, target)) {
// if (args->find(PARAM_PATH) != args->end()) {
// db_path = args->at(PARAM_PATH).c_str();
// } else {
// E("Must specify -OPATH for rocksdb.\n");
// }
// V("Initializing rocksdb, path: %s.\n", db_path);
// rocksdb::Options opt;
// std::shared_ptr<rocksdb::Cache> cache = rocksdb::NewLRUCache(CACHE_SIZE, 6, false, 0.0);
// opt.use_direct_io_for_flush_and_compaction = USE_DIRECT_IO_FOR_FLUSH_AND_COMPACTION;
// opt.use_direct_reads = USE_DIRECT_READS;
// rocksdb::BlockBasedTableOptions block_based_options;
// block_based_options.index_type = rocksdb::BlockBasedTableOptions::kBinarySearch;
// block_based_options.block_cache = cache;
// opt.table_factory.reset(rocksdb::NewBlockBasedTableFactory(block_based_options));
// opt.IncreaseParallelism(12);
// opt.OptimizeLevelStyleCompaction(1024 * 1024 * 1024);
// opt.OptimizeUniversalStyleCompaction(1024 * 1024 * 1024);
// opt.write_buffer_size = 1024 * 1024 * 1024;
// opt.create_if_missing = false;
// opt.compression = rocksdb::kNoCompression;
// rocksdb::Status s = rocksdb::DB::Open(opt, std::string(db_path), &this->db);
// if (!s.ok()) {
// E("Could not open rocksdb! Err %s\n", s.ToString().c_str());
// }
// rdb_proc::db_init.store(2);
// V("Finished initializing rocksdb.\n");
// } else {
// V("Checking for rocksdb initialization...\n");
// while(rdb_proc::db_init.load() != 2) {};
// V("Detected initialized rocksdb.\n");
// }
// }
// rdb_proc::~rdb_proc()
// {
// }
// int rdb_proc::proc_req(int fd)
// {
// ppd_rdb_resp resp;
// ppd_rdb_req req;
// rocksdb::Status s;
// struct ppd_msg *msg = (struct ppd_msg *)this->read_buf;
// int i = 0;
// int status = readmsg(fd, this->read_buf, MAX_READ_BUF_SIZE);
// if (status != 0) {
// W("Readmsg failed with %d for connection %d\n", status, fd);
// return status;
// }
// if (!req.ParseFromArray(msg->payload, msg->size)) {
// W("ParseFromArray failed for connection %d\n", fd);
// return EINVAL;
// }
// V("Connection %d op: %d, key: %s. val: %s. optarg: %d.\n", fd, req.op(), req.key().c_str(), req.val().c_str(), req.optarg());
// switch (req.op()) {
// case PPD_RDB_OP_PUT:{
// s = this->db->Put(rocksdb::WriteOptions(), req.key(), req.val());
// resp.set_status(s.code());
// break;
// }
// case PPD_RDB_OP_GET: {
// std::string val;
// s = this->db->Get(rocksdb::ReadOptions(), req.key(), &val);
// if (s.ok()) {
// resp.set_result(val);
// }
// resp.set_status(s.code());
// break;
// }
// case PPD_RDB_OP_SEEK: {
// rocksdb::Slice val;
// rocksdb::Iterator *it = this->db->NewIterator(rocksdb::ReadOptions(false, true));
// it->Seek(req.key());
// resp.set_status(it->status().code());
// if (it->Valid()) {
// val = it->value();
// resp.set_result(val.data(), val.size());
// }
// for(int64_t j = 0; j < req.optarg() && it->Valid(); j++) {
// rocksdb::Slice val = it->value();
// // do something about the key
// std::memcpy(this->read_buf, val.data(), MIN(val.size(), MAX_READ_BUF_SIZE));
// it->Next();
// if (!it->status().ok()) {
// resp.set_status(it->status().code());
// break;
// }
// }
// delete it;
// break;
// }
// default: {
// W("Invalid opcode %d for connection %d\n", req.op(), fd);
// return EINVAL;
// }
// }
// resp.set_status(0);
// status = writemsg(fd, this->read_buf, MAX_READ_BUF_SIZE, this->read_buf, resp.ByteSizeLong());
// if (status < 0) {
// W("Writemsg failed with %d for connection %d\n", status, fd);
// }
// return status;
// }

78
ppd_ff/reqproc.h Normal file
View File

@ -0,0 +1,78 @@
#pragma once
#include <cstdint>
#include <string>
#include <random>
#include <unordered_map>
#include <const.h>
#include <atomic>
#include <sys/param.h>
#include <msg.pb.h>
#define DISABLE_EVIL_CONSTRUCTORS(name) \
name(const name&) = delete; \
void operator=(const name) = delete
struct alignas(CACHE_LINE_SIZE) ppd_touch_cache_item {
int val;
};
class req_proc {
protected:
constexpr static int MAX_READ_BUF_SIZE = 1024 * 1024;
char * read_buf;
public:
req_proc() {this->read_buf = new char[MAX_READ_BUF_SIZE]; };
virtual ~req_proc() {delete[] this->read_buf;};
virtual int proc_req(int fd) = 0;
};
class touch_proc : public req_proc
{
private:
int buffer_sz;
std::default_random_engine * rgen;
std::uniform_int_distribution<int> * rdist;
struct ppd_touch_cache_item* buffer;
static constexpr const char* PARAM_TBSZ = "ENTRIES";
static constexpr const int PARAM_TBSZ_DEFAULT = 64;
constexpr static int MAX_SZ = 1024 * 1024;
public:
touch_proc(std::unordered_map<std::string, std::string>* args);
touch_proc() = delete;
~touch_proc();
DISABLE_EVIL_CONSTRUCTORS(touch_proc);
int proc_req(int fd);
};
class echo_proc : public req_proc
{
private:
ppd_echo_req req;
ppd_echo_resp resp;
public:
echo_proc(std::unordered_map<std::string, std::string>* args);
echo_proc() = delete;
~echo_proc();
DISABLE_EVIL_CONSTRUCTORS(echo_proc);
int proc_req(int fd);
};
// class rdb_proc : public req_proc
// {
// private:
// constexpr static bool USE_DIRECT_IO_FOR_FLUSH_AND_COMPACTION = true;
// constexpr static bool USE_DIRECT_READS = true;
// constexpr static int CACHE_SIZE = 268435456;
// constexpr static int MAX_MSG_SZ = 4096;
// static constexpr const char* PARAM_PATH = "PATH";
// static std::atomic<int> db_init;
// static rocksdb::DB *db;
// public:
// rdb_proc(std::unordered_map<std::string, std::string>* args);
// rdb_proc() = delete;
// ~rdb_proc();
// DISABLE_EVIL_CONSTRUCTORS(rdb_proc);
// int proc_req(int fd);
// };