Skip to content

Commit

Permalink
Add initial config file parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
IsaacKhor committed Aug 8, 2024
1 parent 9293eb9 commit 6bf5b28
Show file tree
Hide file tree
Showing 11 changed files with 152 additions and 189 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ qemu/bzImage

subprojects/liburing-*
subprojects/packagecache
subprojects/nlohmann_json*
14 changes: 14 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Configuring LSVD

LSVD is configured using a JSON file. When creating an image, we will
try to read the following paths and parse them for configuration options:

- Default built-in configuration
- `/usr/local/etc/lsvd.json`
- `./lsvd.json`
- user supplied path

The file read last has highest priority.

We will also first try to parse the user-supplied path as a JSON object, and if
that fails try treat it as a path and read it from a file.
18 changes: 18 additions & 0 deletions docs/example_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"rcache_dir": "/tmp/lsvd",
"rcache_size": 524288000,
"rcache_fetch_window": 12,
"wlog_dir": "/tmp/lsvd",
"wlog_bytes": 524288000,
"wlog_write_window": 8,
"wlog_chunk_bytes": 2097152,
"antithrash_ratio": 67,
"backend_obj_bytes": 8388608,
"backend_write_window": 8,
"checkpoint_interval_objs": 500,
"flush_timeout_ms": 2000,
"flush_interval_ms": 1000,
"gc_threshold_pc": 60,
"gc_write_window": 4,
"no_gc": false
}
204 changes: 71 additions & 133 deletions src/config.cc
Original file line number Diff line number Diff line change
@@ -1,153 +1,91 @@
/*
* file: config.cc
* description: quick and dirty config file parser
* env var overrides modeled on github.com/spf13/viper
*
* author: Peter Desnoyers, Northeastern University
* Copyright 2021, 2022 Peter Desnoyers
* license: GNU LGPL v2.1 or newer
* LGPL-2.1-or-later
*/

#include "src/utils.h"
#include <ctype.h>
#include <nlohmann/json.hpp>
#include <stdlib.h>
#include <string>
#include <unistd.h>
#include <uuid/uuid.h>

#include <filesystem>
#include <fstream>
#include <map>
#include <string>
#include <vector>
namespace fs = std::filesystem;

#include "config.h"
#include "config_macros.h"
#include "utils.h"

vec<std::string> cfg_path({"lsvd.conf", "/usr/local/etc/lsvd.conf"});

static void split(std::string s, vec<std::string> &words)
{
std::string w = "";
for (auto c : s) {
if (!isspace(c))
w = w + c;
else {
words.push_back(w);
w = "";
}
}
if (w.size() > 0)
words.push_back(w);
}

static std::map<std::string, cfg_backend> m = {{"file", BACKEND_FILE},
{"rados", BACKEND_RADOS}};

/* fancy-ass macros to parse config file lines.
* config keyword = field name
* environment var = LSVD_ + uppercase(field name)
* skips blank lines and lines that don't match a keyword
*/

int lsvd_config::read()
opt<lsvd_config> lsvd_config::from_file(str path)
{
auto explicit_cfg = getenv("LSVD_CONFIG_FILE");
if (explicit_cfg) {
std::string f(explicit_cfg);
cfg_path.insert(cfg_path.begin(), f);
}
for (auto f : cfg_path) {
std::ifstream fp(f);
if (!fp.is_open())
continue;
std::string line;
while (getline(fp, line)) {
if (line[0] == '#')
continue;
vec<std::string> words;
split(line, words);
if (words.size() != 2)
continue;
F_CONFIG_H_INT(words[0], words[1], backend_obj_size);
F_CONFIG_INT(words[0], words[1], wcache_batch);
F_CONFIG_H_INT(words[0], words[1], wcache_chunk);
F_CONFIG_STR(words[0], words[1], rcache_dir);
F_CONFIG_STR(words[0], words[1], wcache_dir);
F_CONFIG_INT(words[0], words[1], num_parallel_writes);
F_CONFIG_TABLE(words[0], words[1], backend, m);
F_CONFIG_H_INT(words[0], words[1], cache_size);
F_CONFIG_H_INT(words[0], words[1], wlog_size);
F_CONFIG_INT(words[0], words[1], hard_sync);
F_CONFIG_INT(words[0], words[1], ckpt_interval);
F_CONFIG_INT(words[0], words[1], flush_timeout_msec);
F_CONFIG_INT(words[0], words[1], gc_threshold);
F_CONFIG_INT(words[0], words[1], fetch_window);
F_CONFIG_INT(words[0], words[1], fetch_ratio);
F_CONFIG_INT(words[0], words[1], no_gc);
F_CONFIG_INT(words[0], words[1], gc_window);
}
fp.close();
break;
}
// write just a simple braindead parser
// syntax: lines of `key = value`

ENV_CONFIG_H_INT(backend_obj_size);
ENV_CONFIG_INT(wcache_batch);
ENV_CONFIG_H_INT(wcache_chunk);
ENV_CONFIG_STR(rcache_dir);
ENV_CONFIG_STR(wcache_dir);
ENV_CONFIG_INT(num_parallel_writes);
ENV_CONFIG_TABLE(backend, m);
ENV_CONFIG_H_INT(cache_size);
ENV_CONFIG_H_INT(wlog_size);
ENV_CONFIG_INT(hard_sync);
ENV_CONFIG_INT(ckpt_interval);
ENV_CONFIG_INT(flush_timeout_msec);
ENV_CONFIG_INT(gc_threshold);
ENV_CONFIG_INT(fetch_window);
ENV_CONFIG_INT(fetch_ratio);
ENV_CONFIG_INT(no_gc);
ENV_CONFIG_INT(gc_window);

return 0; // success
UNIMPLEMENTED();
}

opt<lsvd_config> lsvd_config::from_file(str path) { UNIMPLEMENTED(); }

std::string lsvd_config::cache_filename(uuid_t &uuid, const char *name,
cfg_cache_type type)
lsvd_config lsvd_config::get_default() { return lsvd_config(); }

#define JSON_GET_BOOL(key) \
do { \
if (js.contains(#key)) { \
auto v = js[#key]; \
if (v.is_boolean()) { \
cfg.key = js[#key]; \
} else { \
log_error("Invalid value for key (must be bool): {}", #key); \
} \
} \
} while (0)

#define JSON_GET_UINT(key) \
do { \
if (js.contains(#key)) { \
auto v = js[#key]; \
if (v.is_number_unsigned()) { \
cfg.key = js[#key]; \
} else { \
log_error("Invalid value for key (must be uint): {}", #key); \
} \
} \
} while (0)

#define JSON_GET_STR(key) \
do { \
if (js.contains(#key)) { \
auto v = js[#key]; \
if (v.is_string()) { \
cfg.key = js[#key]; \
} else { \
log_error("Invalid value for key (must be str): {}", #key); \
} \
} \
} while (0)

opt<lsvd_config> lsvd_config::from_json(str json)
{
char buf[256]; // PATH_MAX
std::string file(name);
file = fs::path(file).filename();
const char *dir;
const char *f_ext;
nlohmann::json js;

dir = (type == LSVD_CFG_READ) ? rcache_dir.c_str() : wcache_dir.c_str();
f_ext = (type == LSVD_CFG_READ) ? "rcache" : "wcache";
try {
js = nlohmann::json::parse(json);
} catch (nlohmann::json::parse_error &e) {
log_error("Failed to parse JSON: {}", e.what());
return std::nullopt;
}

sprintf(buf, "%s/%s.%s", dir, file.c_str(), f_ext);
if (access(buf, R_OK | W_OK) == 0)
return std::string((const char *)buf);
lsvd_config cfg;
JSON_GET_STR(rcache_dir);
JSON_GET_UINT(rcache_bytes);
JSON_GET_UINT(rcache_fetch_window);

char uuid_s[64];
uuid_unparse(uuid, uuid_s);
sprintf(buf, "%s/%s.%s", dir, uuid_s, f_ext);
return std::string((const char *)buf);
}
JSON_GET_STR(wlog_dir);
JSON_GET_UINT(wlog_bytes);
JSON_GET_UINT(wlog_write_window);
JSON_GET_UINT(wlog_chunk_bytes);

#if 0
int main(int argc, char **argv) {
auto cfg = new lsvd_config;
cfg->read();
JSON_GET_UINT(antithrash_ratio);
JSON_GET_UINT(backend_obj_bytes);
JSON_GET_UINT(backend_write_window);
JSON_GET_UINT(checkpoint_interval_objs);
JSON_GET_UINT(flush_timeout_ms);
JSON_GET_UINT(flush_interval_ms);

printf("batch: %d\n", cfg->batch_size); // h_int
printf("wc batch %d\n", cfg->wcache_batch); // int
printf("cache: %s\n", cfg->cache_dir.c_str()); // str
printf("backend: %d\n", (int)cfg->backend); // table
JSON_GET_UINT(gc_threshold_pc);
JSON_GET_UINT(gc_write_window);
JSON_GET_BOOL(no_gc);

uuid_t uu;
std::cout << cfg->cache_filename(uu, "foobar") << "\n";
return cfg;
}
#endif
63 changes: 27 additions & 36 deletions src/config.h
Original file line number Diff line number Diff line change
@@ -1,57 +1,48 @@
#pragma once

/*
* file: config.h
* description: quick and dirty config file parser
* env var overrides modeled on github.com/spf13/viper
*
* author: Peter Desnoyers, Northeastern University
* Copyright 2021, 2022 Peter Desnoyers
* license: GNU LGPL v2.1 or newer
* LGPL-2.1-or-later
*/

#include <string>
#include <uuid/uuid.h>

#include "utils.h"

enum cfg_backend { BACKEND_FILE = 1, BACKEND_RADOS = 2 };

enum cfg_cache_type { LSVD_CFG_READ = 1, LSVD_CFG_WRITE = 2 };

class lsvd_config
{
public:
int backend_obj_size = 8 * 1024 * 1024; // in bytes
int wcache_batch = 8; // requests
int wcache_chunk = 2 * 1024 * 1024; // bytes
std::string rcache_dir = "/tmp/lsvd/";
std::string wcache_dir = "/tmp/lsvd/";
u32 num_parallel_writes = 8;
int hard_sync = 0;
enum cfg_backend backend = BACKEND_RADOS;
long cache_size = 500 * 1024 * 1024; // in bytes
long wlog_size = 500 * 1024 * 1024; // in bytes
int ckpt_interval = 500; // objects
int flush_timeout_msec = 2000; // flush timeout
int flush_interval_msec = 1000; // flush interval
int gc_threshold = 60; // GC threshold, percent
int gc_window = 4; // max GC writes outstanding
int fetch_window = 12; // read cache fetches
int fetch_ratio = 67; // anti-thrash served:backend ratio
int no_gc = 0; // turn off GC
// read cache
str rcache_dir = "/tmp/lsvd/"; // directory to store read cache files
u64 rcache_bytes = 500 * 1024 * 1024; // in bytes
u64 rcache_fetch_window = 12; // read cache fetches

// write log
str wlog_dir = "/tmp/lsvd/";
u64 wlog_bytes = 500 * 1024 * 1024; // in bytes
u64 wlog_write_window = 8; // requests
u64 wlog_chunk_bytes = 2 * 1024 * 1024; // bytes

// backend
u64 antithrash_ratio = 67; // anti-thrash served:backend ratio
u64 backend_obj_bytes = 8 * 1024 * 1024; // in bytes
u64 backend_write_window = 8; // backend parallel writes

u64 checkpoint_interval_objs = 500; // objects
u64 flush_timeout_ms = 2000; // flush timeout
u64 flush_interval_ms = 1000; // flush interval

// GC
u64 gc_threshold_pc = 60; // GC threshold, percent
u64 gc_write_window = 4; // max GC writes outstanding
bool no_gc = false; // turn off GC

lsvd_config() {}
~lsvd_config() {}
int read();
str cache_filename(uuid_t &uuid, const char *name, cfg_cache_type type);

inline fspath wlog_path(str imgname)
{
auto filename = imgname + ".wlog";
return fspath(wcache_dir) / filename;
return fspath(wlog_dir) / filename;
}

static opt<lsvd_config> from_json(str json);
static opt<lsvd_config> from_file(str path);
static lsvd_config get_default();
};
6 changes: 3 additions & 3 deletions src/image.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ lsvd_image::lsvd_image(std::string name, rados_ioctx_t io, lsvd_config cfg_)
: imgname(name), cfg(cfg_), io(io)
{
objstore = make_rados_backend(io);
rcache = get_read_cache_instance(cfg.rcache_dir, cfg.cache_size, objstore);
rcache = get_read_cache_instance(cfg.rcache_dir, cfg.rcache_size, objstore);

read_superblock();
debug("Found checkpoints: {}", checkpoints);
Expand Down Expand Up @@ -165,7 +165,7 @@ seqnum_t lsvd_image::roll_forward_from_last_checkpoint()
// This must be larger than the max backend batch size to avoid
// potential corruption if subsequent breaks overlap with current dangling
// objects and we get writes from two different "generations"
for (seqnum_t i = 1; i < cfg.num_parallel_writes * 4; i++)
for (seqnum_t i = 1; i < cfg.backend_write_window * 4; i++)
objstore->delete_obj(oname(imgname, seq + i));

return seq;
Expand Down Expand Up @@ -478,7 +478,7 @@ class lsvd_image::write_request : public lsvd_image::aio_request
sector_t size_sectors = req_bytes / 512;

// split large requests into 2MB (default) chunks
sector_t max_sectors = img->cfg.wcache_chunk / 512;
sector_t max_sectors = img->cfg.wlog_chunk_bytes / 512;
n_req += div_round_up(req_bytes / 512, max_sectors);
// TODO: this is horribly ugly

Expand Down
1 change: 1 addition & 0 deletions src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ lsvd_deps = [
dependency('boost', modules: ['system', 'filesystem', 'program_options', 'thread', 'regex']),
dependency('liburing', static: true),
dependency('uuid'),
dependency('nlohmann_json'),
cxx.find_library('rados', required: true),
cxx.find_library('jemalloc', required: false),
]
Expand Down
2 changes: 1 addition & 1 deletion src/rpc_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def bdev_lsvd_create(args):
p = subparsers.add_parser('bdev_lsvd_create', help='Create a bdev with LSVD backend')
p.add_argument('pool_name', help='Name of the ceph pool')
p.add_argument('name', help='Name of the lsvd disk image')
p.add_argument('-c', '--cfg', help='Path to config file', required=False)
p.add_argument('-c', '--cfg', help='Path to config file OR inline JSON string', required=False)
p.set_defaults(func=bdev_lsvd_create)

def bdev_lsvd_delete(args):
Expand Down
Loading

0 comments on commit 6bf5b28

Please sign in to comment.