fb303/ServiceData.cpp (595 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <fb303/ServiceData.h>
#include <boost/regex.hpp>
#include <stdexcept>
#include <fb303/LegacyClock.h>
#include <folly/Conv.h>
#include <folly/ExceptionString.h>
#include <folly/Indestructible.h>
#include <folly/MapUtil.h>
#include <folly/String.h>
#include <gflags/gflags.h>
using folly::StringPiece;
using std::map;
using std::string;
using std::string_view;
using std::vector;
namespace facebook {
namespace fb303 {
template <typename T>
static T& as_mutable(T const& t) {
return const_cast<T&>(t);
}
/*
* The constructor used to create additional ServiceData instances.
* IMPORTANT NOTE: There already is a global singleton instance living,
* which is accessible via the static "get()" method or via the classic
* "fbData->" style (both access the same single global instance).
*/
ServiceData::ServiceData()
: aliveSince_(time(nullptr)),
useOptionsAsFlags_(false),
dynamicCounters_(),
statsMap_(&dynamicCounters_),
histMap_(
&dynamicCounters_,
&dynamicStrings_,
ExportedHistogram(1000, 0, 10000)) {}
ServiceData::~ServiceData() {}
std::shared_ptr<ServiceData> ServiceData::getShared() {
static folly::Indestructible<std::shared_ptr<ServiceData>> serviceData(
std::make_unique<ServiceData>());
return *serviceData;
}
ServiceData* ServiceData::get() {
static auto serviceData = getShared().get();
return serviceData;
}
void ServiceData::flushAllData() {
statsMap_.flushAllStats();
quantileMap_.flushAll();
// NOTE: histMap_ is not buffered.
}
void ServiceData::resetAllData() {
options_.wlock()->clear();
counters_.wlock()->clear();
exportedValues_.wlock()->clear();
statsMap_.forgetAllStats();
quantileMap_.forgetAll();
histMap_.forgetAllHistograms();
dynamicStrings_.clear();
dynamicCounters_.clear();
}
void ServiceData::zeroStats() {
counters_.withRLock([&](auto const& counters) {
for (auto const& elem : counters) {
// this const-cast is safe: the lock protects the map structure only
as_mutable(elem.second).store(0, std::memory_order_relaxed);
}
});
statsMap_.clearAllStats();
histMap_.clearAllHistograms();
}
void ServiceData::addStatExportType(
StringPiece key,
ExportType type,
const ExportedStat* statPrototype) {
statsMap_.exportStat(key, type, statPrototype);
}
void ServiceData::addStatExports(
StringPiece key,
StringPiece stats,
int64_t bucketSize,
int64_t min,
int64_t max,
const ExportedStat* statPrototype) {
if (histMap_.contains(key)) {
return; // already exists
}
bool addedHist = false;
vector<string> statsSplit;
folly::split(",", stats, statsSplit);
for (const string& stat : statsSplit) {
if (stat == "AVG") {
statsMap_.exportStat(key, AVG, statPrototype);
} else if (stat == "RATE") {
statsMap_.exportStat(key, RATE, statPrototype);
} else if (stat == "SUM") {
statsMap_.exportStat(key, SUM, statPrototype);
} else if (stat == "COUNT") {
statsMap_.exportStat(key, COUNT, statPrototype);
} else { // No match on stat type - assume it's a histogram percentile
if (!addedHist) {
if (bucketSize <= 0) {
throw std::runtime_error(folly::to<string>(
"bucketSize for ",
key,
" must be greater than zero (",
bucketSize,
")"));
}
ExportedHistogram hist(
bucketSize,
min,
max,
statPrototype != nullptr ? *statPrototype
: histMap_.getDefaultStat());
histMap_.addHistogram(key, &hist);
addedHist = true;
}
exportHistogramPercentile(key, folly::to<int32_t>(stat));
}
}
}
void ServiceData::addStatValue(StringPiece key, int64_t value) {
statsMap_.addValue(key, get_legacy_stats_time(), value);
}
void ServiceData::addStatValue(
StringPiece key,
int64_t value,
ExportType exportType) {
statsMap_.addValue(key, get_legacy_stats_time(), value, exportType);
}
void ServiceData::addStatValueAggregated(
StringPiece key,
int64_t sum,
int64_t numSamples) {
statsMap_.addValueAggregated(key, get_legacy_stats_time(), sum, numSamples);
}
bool ServiceData::addHistogram(
StringPiece key,
int64_t bucketSize,
int64_t min,
int64_t max) {
return histMap_.addHistogram(key, bucketSize, min, max);
}
bool ServiceData::addHistogram(StringPiece key, const ExportedHistogram& hist) {
return histMap_.addHistogram(key, &hist);
}
void ServiceData::exportHistogramPercentile(StringPiece key, int pct) {
histMap_.exportPercentile(key, pct);
}
void ServiceData::exportHistogram(StringPiece key, ExportType stat) {
histMap_.exportStat(key, stat);
}
void ServiceData::exportHistogram(StringPiece key, int pct) {
histMap_.exportStat(key, pct);
}
std::shared_ptr<QuantileStat> ServiceData::getQuantileStat(
folly::StringPiece name,
folly::Range<const ExportType*> stats,
folly::Range<const double*> quantiles,
folly::Range<const size_t*> slidingWindowPeriods) {
auto stat = quantileMap_.get(name);
if (stat) {
return stat;
}
std::vector<QuantileStat::SlidingWindow> slidingWindowDefs;
slidingWindowDefs.reserve(slidingWindowPeriods.size());
for (const auto& slidingWindowLength : slidingWindowPeriods) {
if (slidingWindowLength >= 60) {
auto duration = std::chrono::seconds{slidingWindowLength};
CHECK_EQ(0, duration.count() % 60);
slidingWindowDefs.emplace_back(duration / 60, 60);
} else {
slidingWindowDefs.emplace_back(
std::chrono::seconds(1), slidingWindowLength);
}
}
stat = std::make_shared<QuantileStat>(std::move(slidingWindowDefs));
std::vector<detail::QuantileStatMap::StatDef> statDefs;
statDefs.reserve(stats.size() + quantiles.size());
for (auto statType : stats) {
detail::QuantileStatMap::StatDef statDef;
statDef.type = statType;
statDefs.push_back(statDef);
}
for (auto quantile : quantiles) {
detail::QuantileStatMap::StatDef statDef;
statDef.type = ExportType::PERCENT;
statDef.quantile = quantile;
statDefs.push_back(statDef);
}
return quantileMap_.registerQuantileStat(
name, std::move(stat), std::move(statDefs));
}
void ServiceData::addHistogramValue(
StringPiece key,
int64_t value,
bool checkContains) {
if (!checkContains || histMap_.contains(key)) {
histMap_.addValue(key, get_legacy_stats_time(), value);
}
}
void ServiceData::addHistogramValueMult(
StringPiece key,
int64_t value,
int64_t times,
bool checkContains) {
if (!checkContains || histMap_.contains(key)) {
histMap_.addValue(key, get_legacy_stats_time(), value, times);
}
}
void ServiceData::addHistAndStatValue(
StringPiece key,
int64_t value,
bool checkContains) {
time_t now = get_legacy_stats_time();
statsMap_.addValue(key, now, value);
if (!checkContains || histMap_.contains(key)) {
histMap_.addValue(key, now, value);
}
}
void ServiceData::addHistAndStatValues(
StringPiece key,
const folly::Histogram<int64_t>& values,
time_t now,
int64_t sum,
int64_t nsamples,
bool checkContains) {
statsMap_.addValueAggregated(key, now, sum, nsamples);
if (!checkContains || histMap_.contains(key)) {
histMap_.addValues(key, now, values);
}
}
int64_t ServiceData::incrementCounter(StringPiece key, int64_t amount) {
{
// optimistically, the key is certainly present; update under rlock
auto countersRLock = counters_.rlock();
if (auto ptr = folly::get_ptr(*countersRLock, key)) {
// this const-cast is safe: the lock protects the map structure only
auto& ref = as_mutable(*ptr);
return ref.fetch_add(amount, std::memory_order_relaxed) + amount;
}
}
// pessimistically, the key is possibly absent; upsert under wlock
auto countersWLock = counters_.wlock();
auto& ref = (*countersWLock)[key];
return ref.fetch_add(amount, std::memory_order_relaxed) + amount;
}
int64_t ServiceData::setCounter(StringPiece key, int64_t value) {
{
// optimistically, the key is certainly present; update under rlock
auto countersRLock = counters_.rlock();
if (auto ptr = folly::get_ptr(*countersRLock, key)) {
// this const-cast is safe: the lock protects the map structure only
auto& ref = as_mutable(*ptr);
ref.store(value, std::memory_order_relaxed);
return value;
}
}
// pessimistically, the key is possibly absent; upsert under wlock
auto countersWLock = counters_.wlock();
auto& ref = (*countersWLock)[key];
ref.store(value, std::memory_order_relaxed);
return value;
}
void ServiceData::clearCounter(StringPiece key) {
auto countersWLock = counters_.wlock();
auto it = countersWLock->find(key);
if (it != countersWLock->end()) {
countersWLock->erase(it);
}
}
folly::Optional<int64_t> ServiceData::getCounterIfExists(
StringPiece key) const {
int64_t ret;
if (dynamicCounters_.getCounter(key, &ret)) {
return ret;
}
auto quantileValue = quantileMap_.getValue(key);
if (quantileValue) {
return quantileValue;
}
auto countersRLock = counters_.rlock();
auto ptr = folly::get_ptr(*countersRLock, key);
return ptr ? folly::make_optional(ptr->load(std::memory_order_relaxed))
: folly::none;
}
int64_t ServiceData::getCounter(StringPiece key) const {
folly::Optional<int64_t> ret = getCounterIfExists(key);
if (ret.has_value()) {
return *ret;
}
throw std::invalid_argument(
folly::to<string>("no such counter \"", key, "\""));
}
void ServiceData::getCounters(map<string, int64_t>& _return) const {
counters_.withRLock([&](auto const& counters) {
for (auto const& elem : counters) {
_return[elem.first] = elem.second.load(std::memory_order_relaxed);
}
});
quantileMap_.getValues(_return);
dynamicCounters_.getCounters(&_return);
}
std::vector<std::string> ServiceData::getCounterKeys() const {
std::vector<std::string> keys;
counters_.withRLock([&](auto const& counters) {
keys.reserve(counters.size());
for (const auto& elem : counters) {
keys.push_back(elem.first);
}
});
quantileMap_.getKeys(keys);
dynamicCounters_.getKeys(&keys);
return keys;
}
uint64_t ServiceData::getNumCounters() const {
int64_t numCounters = 0;
counters_.withRLock(
[&](auto const& counters) { numCounters += counters.size(); });
numCounters += quantileMap_.getNumKeys();
numCounters += dynamicCounters_.getNumKeys();
return numCounters;
}
map<string, int64_t> ServiceData::getCounters() const {
map<string, int64_t> _return;
getCounters(_return);
return _return;
}
void ServiceData::getSelectedCounters(
map<string, int64_t>& _return,
const vector<string>& keys) const {
quantileMap_.getSelectedValues(_return, keys);
for (const string& key : keys) {
if (_return.find(key) == _return.end()) {
try {
int64_t value = getCounter(key);
_return[key] = value;
} catch (const std::invalid_argument&) {
// Don't insert anything into _return for non-existent keys.
}
}
}
}
map<string, int64_t> ServiceData::getSelectedCounters(
const vector<string>& keys) const {
map<string, int64_t> _return;
getSelectedCounters(_return, keys);
return _return;
}
void ServiceData::getRegexCounters(
map<string, int64_t>& _return,
const string& regex) const {
const boost::regex regexObject(regex);
auto keys = getCounterKeys();
keys.erase(
std::remove_if(
keys.begin(),
keys.end(),
[&](const std::string& key) {
return !regex_match(key, regexObject);
}),
keys.end());
getSelectedCounters(_return, keys);
}
map<string, int64_t> ServiceData::getRegexCounters(const string& regex) const {
map<string, int64_t> _return;
getRegexCounters(_return, regex);
return _return;
}
bool ServiceData::hasCounter(StringPiece key) const {
if (dynamicCounters_.contains(key)) {
return true;
}
if (quantileMap_.contains(key)) {
return true;
}
return counters_.rlock()->count(key) != 0;
}
void ServiceData::deleteExportedKey(StringPiece key) {
if (exportedValues_.rlock()->count(key) == 0) {
return;
}
auto exportedValuesULock = exportedValues_.ulock();
auto const it = exportedValuesULock->find(key);
if (it == exportedValuesULock->end()) {
return;
}
auto exportedValuesWLock = exportedValuesULock.moveFromUpgradeToWrite();
exportedValuesWLock->erase(it);
}
void ServiceData::setExportedValue(StringPiece key, string value) {
{
auto exportedValuesRLock = exportedValues_.rlock();
if (auto ptr = folly::get_ptr(*exportedValuesRLock, key)) {
as_mutable(*ptr).swap(value);
return;
}
}
auto exportedValuesWLock = exportedValues_.wlock();
auto& entry = (*exportedValuesWLock)[key];
auto exportedValuesRLock = exportedValuesWLock.moveFromWriteToRead();
entry.swap(value);
}
void ServiceData::getExportedValue(string& _return, StringPiece key) const {
if (dynamicStrings_.getValue(key, &_return)) {
return;
}
auto exportedValuesRLock = exportedValues_.rlock();
if (auto ptr = folly::get_ptr(*exportedValuesRLock, key)) {
_return = ptr->copy();
}
}
string ServiceData::getExportedValue(StringPiece key) const {
string _return;
getExportedValue(_return, key);
return _return;
}
void ServiceData::getExportedValues(map<string, string>& _return) const {
exportedValues_.withRLock([&](auto const& exportedValues) {
for (auto const& elem : exportedValues) {
_return[elem.first] = elem.second.copy();
}
});
dynamicStrings_.getValues(&_return);
}
map<string, string> ServiceData::getExportedValues() const {
map<string, string> _return;
getExportedValues(_return);
return _return;
}
void ServiceData::getSelectedExportedValues(
map<string, string>& _return,
const vector<string>& keys) const {
exportedValues_.withRLock([&](auto const& exportedValues) {
for (auto const& key : keys) {
if (auto ptr = folly::get_ptr(exportedValues, key)) {
_return[key] = ptr->copy();
}
}
});
for (auto const& key : keys) {
string dynamicValue;
if (dynamicStrings_.getValue(key, &dynamicValue)) {
_return[key] = dynamicValue;
}
}
}
map<string, string> ServiceData::getSelectedExportedValues(
const vector<string>& keys) const {
map<string, string> _return;
getSelectedExportedValues(_return, keys);
return _return;
}
void ServiceData::getRegexExportedValues(
map<string, string>& _return,
const string& regex) const {
const boost::regex regexObject(regex);
map<string, string> allExportedValues;
getExportedValues(allExportedValues);
for (auto elem : allExportedValues) {
if (regex_match(elem.first, regexObject)) {
_return[elem.first] = elem.second;
}
}
}
map<string, string> ServiceData::getRegexExportedValues(
const string& regex) const {
map<string, string> _return;
getRegexExportedValues(_return, regex);
return _return;
}
void ServiceData::setUseOptionsAsFlags(bool useOptionsAsFlags) {
if (useOptionsAsFlags) {
LOG(WARNING) << "setUseOptionsAsFlags is a dangerous API and can expose "
<< "your service to a Remote Code Execution vulnerability. "
<< "Please consider using alternative methods like "
<< "configerator to set properties dynamically";
}
useOptionsAsFlags_.store(useOptionsAsFlags, std::memory_order_relaxed);
}
bool ServiceData::getUseOptionsAsFlags() const {
return useOptionsAsFlags_.load(std::memory_order_relaxed);
}
void ServiceData::setOption(StringPiece key, StringPiece value) {
setOptionWithResult(key, value);
}
ServiceData::SetOptionResult ServiceData::setOptionWithResult(
string_view key,
string_view value) {
// Check to see if a dynamic option is registered for this key
{
auto dynamicOptionsRLock = dynamicOptions_.rlock();
if (auto ptr = folly::get_ptr(*dynamicOptionsRLock, key)) {
if (ptr->setter) {
as_mutable(ptr->setter)(string{value});
}
return SetOptionResult::Dynamic;
}
}
// This is not a dynamic option.
// Set it in the static option map.
(*options_.wlock())[key] = string{value};
// Next check to see if we should update command line flags based
// on this static option name.
// By default allow modifying glog verbosity (options 'v' or 'vmodule')
auto useOptionsAsFlags = useOptionsAsFlags_.load(std::memory_order_relaxed);
static constexpr StringPiece blacklistedOptions[] = {
StringPiece{"logmailer"}, StringPiece{"whitelist_flags"}};
if (std::count(
std::begin(blacklistedOptions), std::end(blacklistedOptions), key) >
0) {
return SetOptionResult::CmdlineBlacklisted;
}
if (!(useOptionsAsFlags || key == "v" || key == "vmodule")) {
return SetOptionResult::CmdlineDisabled;
}
string res =
gflags::SetCommandLineOption(string{key}.c_str(), string{value}.c_str());
if (res.empty()) {
LOG(ERROR) << "Couldn't set flag 'FLAGS_" << key << "' to val '" << value
<< "'";
return SetOptionResult::CmdlineNoUpdate;
}
// special handling for vmodule changes as SetCommandLineOption()
// is not sufficient. Need to call SetVLOGLevel() as well.
if (key == "vmodule") {
setVModuleOption(key, value);
} else if (key == "v") {
gflags::SetCommandLineOption("minloglevel", "0");
}
LOG(WARNING) << "FLAG CHANGE: overrode 'FLAGS_" << key << "' to val '"
<< value << "', res '" << res << "'";
return SetOptionResult::CmdlineUpdated;
}
void ServiceData::setVModuleOption(string_view /*key*/, string_view value) {
vector<string> values;
folly::split(",", value, values);
for (size_t i = 0; i < values.size(); ++i) {
vector<string> module_value;
folly::split("=", values[i], module_value);
if (module_value.size() != 2) {
LOG(WARNING) << "Invalid vmodule value: " << values[i]
<< ". Expected <module>=<int>";
continue;
}
int level = atoi(module_value[1].c_str());
LOG(INFO) << "Setting vmodule: " << module_value[0] << " to " << level;
google::SetVLOGLevel(module_value[0].c_str(), level);
}
// if any of vmodule or v are specified, enable vlog'ing.
gflags::SetCommandLineOption("minloglevel", "0");
}
string ServiceData::getOption(StringPiece key) const {
{
auto dynamicOptionsRLock = dynamicOptions_.rlock();
if (auto ptr = folly::get_ptr(*dynamicOptionsRLock, key)) {
return ptr->getter ? as_mutable(ptr->getter)() : string();
}
}
{
auto optionsRLock = options_.rlock();
if (auto ptr = folly::get_ptr(*optionsRLock, key)) {
return *ptr;
}
}
string ret;
if (gflags::GetCommandLineOption(key.str().c_str(), &ret)) {
return ret;
}
throw std::invalid_argument(
folly::to<string>("no such option \"", key, "\""));
}
void ServiceData::getOptions(map<string, string>& _return) const {
_return.clear();
options_.withRLock([&](auto const& options) {
for (auto const& entry : options) {
_return[entry.first] = entry.second;
}
});
dynamicOptions_.withRLock([&](auto const& dynamicOptions) {
for (const auto& entry : dynamicOptions) {
string value;
if (entry.second.getter) {
try {
value = as_mutable(entry.second).getter();
} catch (const std::exception& ex) {
value = folly::to<string>("<error: ", ex.what(), ">");
}
}
_return[entry.first] = value;
}
});
if (useOptionsAsFlags_.load(std::memory_order_relaxed)) {
this->mergeOptionsWithGflags(_return);
}
}
map<string, string> ServiceData::getOptions() const {
map<string, string> _return;
getOptions(_return);
return _return;
}
void ServiceData::mergeOptionsWithGflags(map<string, string>& _return) const {
vector<gflags::CommandLineFlagInfo> allFlags;
gflags::GetAllFlags(&allFlags);
for (const auto& entry : allFlags) {
_return[entry.name] = entry.current_value;
}
return;
}
void ServiceData::registerDynamicOption(
StringPiece name,
DynamicOptionGetter getter,
DynamicOptionSetter setter) {
auto option = DynamicOption(std::move(getter), std::move(setter));
std::swap((*dynamicOptions_.wlock())[name], option);
}
} // namespace fb303
} // namespace facebook