blob: daf3e20c12c4cf34cb256b469830ba2cbadf8cc1 [file] [log] [blame]
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#ifdef __cplusplus
#define __STDC_FORMAT_MACROS
#endif
#include "experiments.h"
#include <inttypes.h>
#include <sstream>
#include <string>
#include "utils.h"
using namespace libexperiments_utils; // NOLINT
Experiments *experiments = NULL;
int DefaultExperimentsRegisterFunc(const char *name) {
std::vector<std::string> cmd({"register_experiment", name});
std::ostringstream out, err;
int64_t timeout_usec = secs_to_usecs(5);
int status;
int ret = run_cmd(cmd, "", &status, &out, &err, timeout_usec);
if (ret < 0 || status != 0) {
log("experiments:Error-Cannot register '%s', ret:%d status:%d stdout:%s "
"stderr:%s", name, ret, status, out.str().c_str(),
err.str().c_str());
return 0; // boolean false
}
return 1; // boolean true
}
int DummyExperimentsRegisterFunc(const char *name) {
return 1; // boolean true
}
bool Experiments::Initialize(
const std::string &config_dir, int64_t min_time_between_refresh_usec,
experiments_register_func_t register_func,
const std::vector<std::string> &names_to_register) {
log("experiments:initializing - config_dir:%s min_time_between_refresh:%"
PRId64 " us", config_dir.c_str(), min_time_between_refresh_usec);
std::lock_guard<std::mutex> lock_guard(lock_);
if (register_func == NULL) {
log("experiments:Error-register_func is NULL");
return false;
}
if (!directory_exists(config_dir.c_str())) {
log("experiments:Error-config_dir '%s' does not exist", config_dir.c_str());
return false;
}
if (min_time_between_refresh_usec < 0)
min_time_between_refresh_usec = 0;
config_dir_ = config_dir;
register_func_ = register_func;
min_time_between_refresh_usec_ = min_time_between_refresh_usec;
initialized_ = true; // initialization part succeeded at this point
// register any provided experiments
if (!names_to_register.empty()) {
if (!Register_Locked(names_to_register))
return false;
}
return true;
}
bool Experiments::Register(const std::vector<std::string> &names) {
if (!IsInitialized()) {
log("experiments:Cannot register, not initialized!");
return false;
}
return Register_Unlocked(names);
}
bool Experiments::Register_Unlocked(const std::vector<std::string> &names) {
std::lock_guard<std::mutex> lock_guard(lock_);
return Register_Locked(names);
}
bool Experiments::Register_Locked(const std::vector<std::string> &names) {
for (const auto &name : names) {
if (IsInRegisteredList(name)) {
log("experiments:'%s' already registered", name.c_str());
continue;
}
// call external register function
if (!register_func_(name.c_str()))
return false; // no reason to continue
registered_experiments_.insert(name);
log("experiments:Registered '%s'", name.c_str());
// check if experiment is active
std::string file_active = config_dir_ + "/" + name + ".active";
if (file_exists(file_active.c_str())) {
enabled_experiments_.insert(name);
log("experiments:'%s' is now enabled", name.c_str());
}
}
return true;
}
bool Experiments::IsRegistered(const std::string &name) {
std::lock_guard<std::mutex> lock_guard(lock_);
return IsInRegisteredList(name);
}
bool Experiments::IsEnabled(const std::string &name) {
if (!IsInitialized())
return false; // silent return to avoid log flooding
std::lock_guard<std::mutex> lock_guard(lock_);
if (us_elapse(last_time_refreshed_usec_) >= min_time_between_refresh_usec_) {
Refresh();
}
return IsInEnabledList(name);
}
void Experiments::Refresh() {
for (const auto &name : registered_experiments_)
UpdateState(name);
last_time_refreshed_usec_ = us_elapse(0);
}
void Experiments::UpdateState(const std::string &name) {
if (!IsInRegisteredList(name)) {
log("experiments:'%s' not registered", name.c_str());
return;
}
std::string experiments_path = config_dir_ + "/" + name;
if (IsInEnabledList(name)) {
// currently enabled, check if it was set to ".unrequested"
std::string file_unrequested = experiments_path + ".unrequested";
if (file_exists(file_unrequested.c_str())) {
// deactivate experiment
std::string file_active = experiments_path + ".active";
rm_file(file_active.c_str());
rm_file(file_unrequested.c_str());
enabled_experiments_.erase(name);
log("experiments:'%s' is now disabled", name.c_str());
}
} else {
// currently not enabled, check if it is requested
std::string file_requested = experiments_path + ".requested";
if (file_exists(file_requested.c_str())) {
// activate experiment
std::string file_active = experiments_path + ".active";
mv_file(file_requested.c_str(), file_active.c_str());
enabled_experiments_.insert(name);
log("experiments:'%s' is now enabled", name.c_str());
}
}
}
// API for C programs
int experiments_initialize(const char *config_dir,
int64_t min_time_between_refresh_usec,
experiments_register_func_t register_func) {
if (register_func == NULL)
register_func = DefaultExperimentsRegisterFunc;
experiments = new Experiments();
return experiments->Initialize(config_dir, min_time_between_refresh_usec,
register_func, {});
}
int experiments_is_initialized() {
return experiments ? experiments->IsInitialized() : false;
}
int experiments_register(const char *name) {
return experiments ? experiments->Register(name) : false;
}
int experiments_is_registered(const char *name) {
return experiments ? experiments->IsRegistered(name) : false;
}
int experiments_get_num_of_registered_experiments() {
return experiments ? experiments->GetNumOfRegisteredExperiments() : 0;
}
int experiments_is_enabled(const char *name) {
return experiments ? experiments->IsEnabled(name) : false;
}