fb303/HistogramExporter.cpp (144 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/HistogramExporter.h> #include <fb303/ExportedHistogramMapImpl.h> #include <fb303/LegacyClock.h> #include <fb303/TimeseriesExporter.h> #include <folly/Format.h> #include <folly/Optional.h> #include <folly/String.h> #include <folly/small_vector.h> #include <glog/logging.h> #include <functional> using folly::StringPiece; using std::chrono::duration_cast; namespace facebook { namespace fb303 { static std::string getHistogramBuckets(const HistogramPtr& hist, int level) { CHECK(hist); auto lockedHist = hist->lock(); // make sure the histogram is up to date and data is decayed appropriately lockedHist->update(get_legacy_stats_time()); // return the serialized bucket info return lockedHist->getString(level); } static CounterType getHistogramStat(const HistogramPtr& item, int level, ExportType exportType) { auto lockedHist = item->lock(); // make sure the histogram is up to date and data is decayed appropriately lockedHist->update(get_legacy_stats_time()); switch (exportType) { case SUM: return lockedHist->sum(level); case COUNT: return lockedHist->count(level); case AVG: return lockedHist->avg<CounterType>(level); case RATE: return lockedHist->rate<CounterType>(level); case PERCENT: return static_cast<CounterType>(100.0 * lockedHist->avg<double>(level)); } // The default case is explicitly handled outside of the switch statement // so gcc's -Wswitch warning will complain if a new export type is added // and this switch statement is not updated. LOG(DFATAL) << "invalid export type: " << exportType; return 0; } /* static */ void HistogramExporter::exportBuckets( const HistogramPtr& hist, StringPiece name, DynamicStrings* strings) { CHECK(hist); CHECK(strings); // All the buckets in the histogram are guaranteed to have the same number // of levels and the same level durations, so we grab the first bucket. // // NOTE: We access the histogram's stat object here without locking. This // depends on the fact that getLevel(), and Level::isAllTime() and // Level::duration() are all non-volatile calls meaning they only read // things that are constant once the stat is constructed (number of levels // can never change, nor their durations). // // - mrabkin CHECK_GT(hist->lock()->getNumBuckets(), 0); const ExportedHistogram::ContainerType& stat = hist->lock()->getBucket(0); // now, export each level for (size_t level = 0; level < stat.numLevels(); ++level) { std::string valueName; if (stat.getLevel(level).isAllTime()) { // example name: ad_request_elapsed_time.hist valueName = sformat("{}.hist", name); } else { // example name: ad_request_elapsed_time.hist.600 const auto durationSecs = duration_cast<std::chrono::seconds>(stat.getLevel(level).duration()); valueName = sformat("{}.hist.{}", name, durationSecs.count()); } // get the stat function callback and put it in a wrapper that will grab // the necessary lock and call update() on the stat // register the actual counter callback strings->registerCallback( valueName, [=] { return getHistogramBuckets(hist, level); }); } } /* static */ template <typename Fn> void HistogramExporter::forEachPercentileName( const HistogramPtr& hist, StringPiece name, int percentile, const Fn& fn) { CHECK_GT(hist->lock()->getNumBuckets(), 0); CHECK_GE(percentile, 0); CHECK_LE(percentile, 100); const ExportedHistogram::ContainerType& stat = hist->lock()->getBucket(0); for (size_t level = 0; level < stat.numLevels(); ++level) { // NOTE: We access the histogram's stat object here without locking. This // depends on the fact that getLevel(), and Level::isAllTime() and // Level::duration() are all non-volatile calls meaning they only read // things that are constant once the stat is constructed (number of levels // can never change, nor their durations). // - mrabkin std::string counterName; if (stat.getLevel(level).isAllTime()) { // example name: ad_request_elapsed_time.p95 counterName = sformat("{}.p{}", name, percentile); } else { // example name: ad_request_elapsed_time.p95.600 const auto durationSecs = duration_cast<std::chrono::seconds>(stat.getLevel(level).duration()); counterName = sformat("{}.p{}.{}", name, percentile, durationSecs.count()); } fn(counterName, level); } } /* static */ void HistogramExporter::exportPercentile( const HistogramPtr& hist, StringPiece name, int percentile, DynamicCounters* counters) { forEachPercentileName( hist, name, percentile, [&](StringPiece counterName, int level) { counters->registerCallback(counterName, [=] { return getHistogramPercentile(hist, level, percentile); }); }); } /* static */ void HistogramExporter::unexportPercentile( const HistogramPtr& hist, StringPiece name, int percentile, DynamicCounters* counters) { forEachPercentileName( hist, name, percentile, [&](StringPiece counterName, int /* unused */) { counters->unregisterCallback(counterName); }); } /* static */ template <typename Fn> void HistogramExporter::forEachStatName( const HistogramPtr& hist, StringPiece name, ExportType exportType, const Fn& fn) { const size_t kNameSize = name.size() + 50; // some extra space folly::small_vector<char, 200> counterName(kNameSize); const ExportedHistogram::ContainerType& stat = hist->lock()->getBucket(0); for (size_t level = 0; level < stat.numLevels(); ++level) { TimeseriesExporter::getCounterName( counterName.data(), kNameSize, &stat, name, exportType, level); fn(counterName.data(), level); } } /* static */ void HistogramExporter::exportStat( const HistogramPtr& hist, StringPiece name, ExportType exportType, DynamicCounters* counters) { forEachStatName( hist, name, exportType, [&](StringPiece counterName, int level) { counters->registerCallback(counterName, [=] { return getHistogramStat(hist, level, exportType); }); }); } /* static */ void HistogramExporter::unexportStat( const HistogramPtr& hist, StringPiece name, ExportType exportType, DynamicCounters* counters) { forEachStatName( hist, name, exportType, [&](StringPiece counterName, int /* unused */) { counters->unregisterCallback(counterName); }); } } // namespace fb303 } // namespace facebook