Merge PR #25623 into master

* refs/pull/25623/head:
	common/ceph_time: 'mo' for month
	common/options: use new parse_timespan
	common/ceph_time: add parse_timespan
	common/config_proxy: pass err_ss through on set_val
	common/ceph_time: add exact_timespan_str

Reviewed-by: Kefu Chai <kchai@redhat.com>
This commit is contained in:
Sage Weil 2019-01-03 20:38:14 -06:00
commit 8917a153f4
5 changed files with 211 additions and 72 deletions

View File

@ -16,6 +16,7 @@
#include "ceph_time.h"
#include "log/LogClock.h"
#include "config.h"
#include "strtol.h"
#if defined(__APPLE__)
#include <mach/mach.h>
@ -181,4 +182,135 @@ namespace ceph {
ss << yr << "y";
return ss.str();
}
std::string exact_timespan_str(timespan t)
{
uint64_t nsec = std::chrono::nanoseconds(t).count();
uint64_t sec = nsec / 1000000000;
nsec %= 1000000000;
uint64_t yr = sec / (60 * 60 * 24 * 365);
ostringstream ss;
if (yr) {
ss << yr << "y";
sec -= yr * (60 * 60 * 24 * 365);
}
uint64_t mn = sec / (60 * 60 * 24 * 30);
if (mn >= 3) {
ss << mn << "mo";
sec -= mn * (60 * 60 * 24 * 30);
}
uint64_t wk = sec / (60 * 60 * 24 * 7);
if (wk >= 2) {
ss << wk << "w";
sec -= wk * (60 * 60 * 24 * 7);
}
uint64_t day = sec / (60 * 60 * 24);
if (day >= 2) {
ss << day << "d";
sec -= day * (60 * 60 * 24);
}
uint64_t hr = sec / (60 * 60);
if (hr >= 2) {
ss << hr << "h";
sec -= hr * (60 * 60);
}
uint64_t min = sec / 60;
if (min >= 2) {
ss << min << "m";
sec -= min * 60;
}
if (sec) {
ss << sec;
}
if (nsec) {
ss << ((float)nsec / 1000000000);
}
if (sec || nsec) {
ss << "s";
}
return ss.str();
}
std::chrono::seconds parse_timespan(const std::string& s)
{
static std::map<string,int> units = {
{ "s", 1 },
{ "sec", 1 },
{ "second", 1 },
{ "seconds", 1 },
{ "m", 60 },
{ "min", 60 },
{ "minute", 60 },
{ "minutes", 60 },
{ "h", 60*60 },
{ "hr", 60*60 },
{ "hour", 60*60 },
{ "hours", 60*60 },
{ "d", 24*60*60 },
{ "day", 24*60*60 },
{ "days", 24*60*60 },
{ "w", 7*24*60*60 },
{ "wk", 7*24*60*60 },
{ "week", 7*24*60*60 },
{ "weeks", 7*24*60*60 },
{ "mo", 30*24*60*60 },
{ "month", 30*24*60*60 },
{ "months", 30*24*60*60 },
{ "y", 365*24*60*60 },
{ "yr", 365*24*60*60 },
{ "year", 365*24*60*60 },
{ "years", 365*24*60*60 },
};
auto r = 0s;
auto pos = 0u;
while (pos < s.size()) {
// skip whitespace
while (std::isspace(s[pos])) {
++pos;
}
if (pos >= s.size()) {
break;
}
// consume any digits
auto val_start = pos;
while (std::isdigit(s[pos])) {
++pos;
}
if (val_start == pos) {
throw invalid_argument("expected digit");
}
string n = s.substr(val_start, pos - val_start);
string err;
auto val = strict_strtoll(n.c_str(), 10, &err);
if (err.size()) {
throw invalid_argument(err);
}
// skip whitespace
while (std::isspace(s[pos])) {
++pos;
}
// consume unit
auto unit_start = pos;
while (std::isalpha(s[pos])) {
++pos;
}
if (unit_start != pos) {
string unit = s.substr(unit_start, pos - unit_start);
auto p = units.find(unit);
if (p == units.end()) {
throw invalid_argument("unrecogized unit '"s + unit + "'");
}
val *= p->second;
} else if (pos < s.size()) {
throw invalid_argument("unexpected trailing '"s + s.substr(pos) + "'");
}
r += chrono::seconds(val);
}
return r;
}
}

View File

@ -487,6 +487,8 @@ inline timespan to_timespan(signedspan z) {
}
std::string timespan_str(timespan t);
std::string exact_timespan_str(timespan t);
std::chrono::seconds parse_timespan(const std::string& s);
// detects presence of Clock::to_timespec() and from_timespec()
template <typename Clock, typename = std::void_t<>>

View File

@ -266,7 +266,7 @@ public:
int set_val(const std::string& key, const std::string& s,
std::stringstream* err_ss=nullptr) {
std::lock_guard l{lock};
return config.set_val(values, obs_mgr, key, s);
return config.set_val(values, obs_mgr, key, s, err_ss);
}
void set_val_default(const std::string& key, const std::string& val) {
std::lock_guard l{lock};

View File

@ -124,72 +124,6 @@ int Option::validate(const Option::value_t &new_value, std::string *err) const
return 0;
}
namespace {
template<class Duration>
std::chrono::seconds
do_parse_duration(const char* unit, string val,
size_t start, size_t* new_start)
{
auto found = val.find(unit, start);
if (found == val.npos) {
*new_start = start;
return Duration{0};
}
val[found] = '\0';
string err;
char* s = &val[start];
auto intervals = strict_strtoll(s, 10, &err);
if (!err.empty()) {
throw invalid_argument(s);
}
auto secs = chrono::duration_cast<chrono::seconds>(Duration{intervals});
*new_start = found + strlen(unit);
return secs;
}
std::chrono::seconds parse_duration(const std::string& s)
{
using namespace std::chrono;
auto secs = 0s;
size_t start = 0;
size_t new_start = 0;
using days_t = duration<int, std::ratio<3600 * 24>>;
auto v = s;
v.erase(std::remove_if(begin(v), end(v),
[](char c){ return std::isspace(c);}), end(v));
if (auto delta = do_parse_duration<days_t>("days", v, start, &new_start);
delta.count()) {
start = new_start;
secs += delta;
}
if (auto delta = do_parse_duration<hours>("hours", v, start, &new_start);
delta.count()) {
start = new_start;
secs += delta;
}
if (auto delta = do_parse_duration<minutes>("minutes", v, start, &new_start);
delta.count()) {
start = new_start;
secs += delta;
}
if (auto delta = do_parse_duration<seconds>("seconds", v, start, &new_start);
delta.count()) {
start = new_start;
secs += delta;
}
if (new_start == 0) {
string err;
if (auto delta = std::chrono::seconds{strict_strtoll(s.c_str(), 10, &err)};
err.empty()) {
secs += delta;
} else {
throw invalid_argument(err);
}
}
return secs;
}
} // anonymous namespace
int Option::parse_value(
const std::string& raw_val,
value_t *out,
@ -262,8 +196,9 @@ int Option::parse_value(
*out = sz;
} else if (type == Option::TYPE_SECS) {
try {
*out = parse_duration(val);
} catch (const invalid_argument&) {
*out = parse_timespan(val);
} catch (const invalid_argument& e) {
*error_message = e.what();
return -EINVAL;
}
} else {

View File

@ -159,14 +159,84 @@ TEST(md_config_t, set_val)
const string s{"1 days 2 hours 4 minutes"};
using days_t = duration<int, std::ratio<3600 * 24>>;
auto expected = (duration_cast<seconds>(days_t{1}) +
duration_cast<seconds>(hours{2}) +
duration_cast<seconds>(minutes{4}));
duration_cast<seconds>(hours{2}) +
duration_cast<seconds>(minutes{4}));
EXPECT_EQ(0, conf.set_val("mgr_tick_period",
"1 days 2 hours 4 minutes", nullptr));
"1 days 2 hours 4 minutes", nullptr));
EXPECT_EQ(expected.count(), conf.get_val<seconds>("mgr_tick_period").count());
EXPECT_EQ(-EINVAL, conf.set_val("mgr_tick_period", "21 centuries", nullptr));
EXPECT_EQ(expected.count(), conf.get_val<seconds>("mgr_tick_period").count());
}
using namespace std::chrono;
using days_t = duration<int, std::ratio<3600 * 24>>;
struct testcase {
std::string s;
std::chrono::seconds r;
};
std::vector<testcase> good = {
{ "23"s, duration_cast<seconds>(seconds{23}) },
{ " 23 "s, duration_cast<seconds>(seconds{23}) },
{ " 23s "s, duration_cast<seconds>(seconds{23}) },
{ " 23 s "s, duration_cast<seconds>(seconds{23}) },
{ " 23 sec "s, duration_cast<seconds>(seconds{23}) },
{ "23 second "s, duration_cast<seconds>(seconds{23}) },
{ "23 seconds"s, duration_cast<seconds>(seconds{23}) },
{ "2m5s"s, duration_cast<seconds>(seconds{2*60+5}) },
{ "2 m 5 s "s, duration_cast<seconds>(seconds{2*60+5}) },
{ "2 m5"s, duration_cast<seconds>(seconds{2*60+5}) },
{ "2 min5"s, duration_cast<seconds>(seconds{2*60+5}) },
{ "2 minutes 5"s, duration_cast<seconds>(seconds{2*60+5}) },
{ "1w"s, duration_cast<seconds>(seconds{3600*24*7}) },
{ "1wk"s, duration_cast<seconds>(seconds{3600*24*7}) },
{ "1week"s, duration_cast<seconds>(seconds{3600*24*7}) },
{ "1weeks"s, duration_cast<seconds>(seconds{3600*24*7}) },
{ "1month"s, duration_cast<seconds>(seconds{3600*24*30}) },
{ "1months"s, duration_cast<seconds>(seconds{3600*24*30}) },
{ "1mo"s, duration_cast<seconds>(seconds{3600*24*30}) },
{ "1y"s, duration_cast<seconds>(seconds{3600*24*365}) },
{ "1yr"s, duration_cast<seconds>(seconds{3600*24*365}) },
{ "1year"s, duration_cast<seconds>(seconds{3600*24*365}) },
{ "1years"s, duration_cast<seconds>(seconds{3600*24*365}) },
{ "1d2h3m4s"s,
duration_cast<seconds>(days_t{1}) +
duration_cast<seconds>(hours{2}) +
duration_cast<seconds>(minutes{3}) +
duration_cast<seconds>(seconds{4}) },
{ "1 days 2 hours 4 minutes"s,
duration_cast<seconds>(days_t{1}) +
duration_cast<seconds>(hours{2}) +
duration_cast<seconds>(minutes{4}) },
};
for (auto& i : good) {
cout << "good: " << i.s << " -> " << i.r.count() << std::endl;
EXPECT_EQ(0, conf.set_val("mgr_tick_period", i.s, nullptr));
EXPECT_EQ(i.r.count(), conf.get_val<seconds>("mgr_tick_period").count());
}
std::vector<std::string> bad = {
"12x",
"_ 12",
"1 2",
"21 centuries",
"1 y m",
};
for (auto& i : bad) {
std::stringstream err;
EXPECT_EQ(-EINVAL, conf.set_val("mgr_tick_period", i, &err));
cout << "bad: " << i << " -> " << err.str() << std::endl;
}
for (int i = 0; i < 100; ++i) {
std::chrono::seconds j = std::chrono::seconds(rand());
string s = exact_timespan_str(j);
std::chrono::seconds k = parse_timespan(s);
cout << "rt: " << j.count() << " -> " << s << " -> " << k.count() << std::endl;
EXPECT_EQ(j.count(), k.count());
}
}
TEST(Option, validation)