/* +----------------------------------------------------------------------+ | HipHop for PHP | +----------------------------------------------------------------------+ | Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ */ #include "hphp/util/service_data.h" #include #include #include #include "folly/Conv.h" #include "folly/MapUtil.h" #include "hphp/util/base.h" namespace HPHP { ////////////////////////////////////////////////////////////////////// namespace ServiceData { ExportedTimeSeries::ExportedTimeSeries( int numBuckets, const std::vector& durations, const std::vector& exportTypes) : m_timeseries(folly::MultiLevelTimeSeries(numBuckets, durations.size(), &durations[0])), m_exportTypes(exportTypes) { } void ExportedTimeSeries::exportAll(const std::string& prefix, std::map& statsMap) { SYNCHRONIZED(m_timeseries) { // must first call update to flush data. m_timeseries.update(detail::nowAsSeconds()); for (int i = 0; i < m_timeseries.numLevels(); ++i) { auto& level = m_timeseries.getLevel(i); std::string suffix = level.isAllTime() ? "" : folly::to(".", level.duration().count()); for (auto type : m_exportTypes) { if (type == ServiceData::StatsType::AVG) { statsMap.insert( std::make_pair(folly::to(prefix, ".avg", suffix), level.avg())); } else if (type == ServiceData::StatsType::SUM) { statsMap.insert( std::make_pair(folly::to(prefix, ".sum", suffix), level.sum())); } else if (type == ServiceData::StatsType::RATE) { statsMap.insert( std::make_pair(folly::to(prefix, ".rate", suffix), level.rate())); } else if (type == ServiceData::StatsType::COUNT) { statsMap.insert( std::make_pair(folly::to(prefix, ".count", suffix), level.count())); } else if (type == ServiceData::StatsType::PCT) { statsMap.insert( std::make_pair(folly::to(prefix, ".pct", suffix), level.avg() * 100)); } } } } } ExportedHistogram::ExportedHistogram( int64_t bucketSize, int64_t min, int64_t max, const std::vector& exportPercentiles) : m_histogram(folly::Histogram(bucketSize, min, max)), m_exportPercentiles(exportPercentiles) { } void ExportedHistogram::exportAll(const std::string& prefix, std::map& statsMap) { SYNCHRONIZED(m_histogram) { for (double percentile : m_exportPercentiles) { statsMap.insert( std::make_pair( folly::to( prefix, ".hist.p", folly::to(percentile * 100)), m_histogram.getPercentileEstimate(percentile))); } } } namespace detail { template class FriendDeleter { public: template explicit FriendDeleter(Args&&... args) : m_instance(new ClassWithPrivateDestructor( std::forward(args)...)) {} ~FriendDeleter() { delete m_instance; } ClassWithPrivateDestructor* get() const { return m_instance; } ClassWithPrivateDestructor* release() { auto r = m_instance; m_instance = nullptr; return r; } private: ClassWithPrivateDestructor* m_instance; }; } // namespace detail namespace { // Find 'key' in concurrent_unordered_map 'map'. Return true iff the key is // found. template bool concurrentMapGet(const tbb::concurrent_unordered_map& map, const Key& key, Value& value) { auto iterator = map.find(key); if (iterator != map.end()) { value = iterator->second; return true; } return false; } // Find or insert 'key' into concurrent_unordered_map 'map'. // // Return the value pointer from 'map' if it exists. Otherwise, insert it into // the map by creating a new object on the heap using the supplied arguments. // // Note that this function could be called concurrently. If the insertion to // 'map' is successful, we release the ownership of value object from // valuePtr. If the key is already in the map because someone else beat us to // the insertion, we will return the existing value and delete the object we // created. // template Value* getOrCreateWithArgs(tbb::concurrent_unordered_map& map, const Key& key, Args&&... args) { // Optimistic case: the object might already be created. Do a simple look // up. Value* ret = nullptr; if (concurrentMapGet(map, key, ret)) { return ret; } // We didn't find an existing value for the key. Create it. Hold the new // object in a deleter and release it later if the insert is successful. detail::FriendDeleter deleter(std::forward(args)...); auto result = map.insert(std::make_pair(key, deleter.get())); if (result.second) { // insert successfully. release the memory. deleter.release(); } else { // key is already inserted. This can happen if two threads were racing // to create the counter. In this case, nothing further needs to be done. // valuePtr's object will get destroyed when we go out of scope. } return result.first->second; } class Impl { public: ExportedCounter* createCounter(const std::string& name) { return getOrCreateWithArgs(m_counterMap, name); } ExportedTimeSeries* createTimeseries( const std::string& name, const std::vector& types, const std::vector& levels, int numBuckets) { return getOrCreateWithArgs( m_timeseriesMap, name, numBuckets, levels, types); } ExportedHistogram* createHistogram( const std::string& name, int64_t bucketSize, int64_t min, int64_t max, const std::vector& exportPercentiles) { return getOrCreateWithArgs( m_histogramMap, name, bucketSize, min, max, exportPercentiles); } void exportAll(std::map& statsMap) { for (auto& counter : m_counterMap) { statsMap.insert(std::make_pair(counter.first, counter.second->getValue())); } for (auto& ts : m_timeseriesMap) { ts.second->exportAll(ts.first, statsMap); } for (auto& histogram : m_histogramMap) { histogram.second->exportAll(histogram.first, statsMap); } } private: // This is a singleton class. Once constructed, we never destroy it. See the // implementation note below. ~Impl() = delete; // Delete all the values from a STL style associative container. template static void containerDeleteSeconds(Container* container) { for (auto iter : *container) { delete iter.second; iter.second = 0; } } typedef tbb::concurrent_unordered_map ExportedCounterMap; typedef tbb::concurrent_unordered_map ExportedTimeSeriesMap; typedef tbb::concurrent_unordered_map ExportedHistogramMap; ExportedCounterMap m_counterMap; ExportedTimeSeriesMap m_timeseriesMap; ExportedHistogramMap m_histogramMap; }; // Implementation note: // // Impl data structure is a singleton and globally accessible. We need to // initialize it before anyone tries to use it. It is possible and likely that // another statically initialized object will call methods on it to create // counters. Therefore, we need Impl to be initialized statically before main() // starts. Unfortunately, there is no initialization order guarantees for the // statically and globally constructed objects. To get around that, we wrap the // initialization in a function so s_impl will get initialized the first time it // gets called. // // For the same reason, we need s_impl to be destructed after all other // statically created objects may reference it in their destructor. We achieve // that by *intentionally* creating the object on heap and never delete it. It's // better to leak memory here than to have random crashes on shutdown. static Impl& getServiceDataInstance() { static Impl *s_impl = new Impl(); return *s_impl; } // One problem with getServiceDataInstance() is that it's not thread safe. If // two threads are accessing this function for the first time concurrently, we // might end up creating two Impl object. We work around that by making sure we // trigger this function statically before main() starts. // // Note that it's still possible for the race condition to happen if we are // creating and starting threads statically before main() starts. If that // happens, we'll have to wrap getServiceDataInstance around a pthread_once and // pay some runtime synchronization cost. UNUSED const Impl& s_dummy = getServiceDataInstance(); } // namespace ExportedCounter* createCounter(const std::string& name) { return getServiceDataInstance().createCounter(name); } ExportedTimeSeries* createTimeseries( const std::string& name, const std::vector& types, const std::vector& levels, int numBuckets) { return getServiceDataInstance().createTimeseries( name, types, levels, numBuckets); } ExportedHistogram* createHistogram( const std::string& name, int64_t bucketSize, int64_t min, int64_t max, const std::vector& exportPercentile) { return getServiceDataInstance().createHistogram( name, bucketSize, min, max, exportPercentile); } void exportAll(std::map& statsMap) { return getServiceDataInstance().exportAll(statsMap); } } // namespace ServiceData. ////////////////////////////////////////////////////////////////////// }