2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2014 Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
17 #include "hphp/util/service-data.h"
22 #include <tbb/concurrent_unordered_map.h>
24 #include "folly/Conv.h"
25 #include "folly/MapUtil.h"
26 #include "folly/stats/Histogram-defs.h"
28 #include "hphp/util/portability.h"
32 //////////////////////////////////////////////////////////////////////
34 namespace ServiceData
{
36 ExportedTimeSeries::ExportedTimeSeries(
38 const std::vector
<std::chrono::seconds
>& durations
,
39 const std::vector
<StatsType
>& exportTypes
)
40 : m_timeseries(folly::MultiLevelTimeSeries
<int64_t>(numBuckets
,
43 m_exportTypes(exportTypes
) {
46 void ExportedTimeSeries::exportAll(const std::string
& prefix
,
47 std::map
<std::string
, int64_t>& statsMap
) {
48 SYNCHRONIZED(m_timeseries
) {
49 // must first call update to flush data.
50 m_timeseries
.update(detail::nowAsSeconds());
52 for (int i
= 0; i
< m_timeseries
.numLevels(); ++i
) {
53 auto& level
= m_timeseries
.getLevel(i
);
55 level
.isAllTime() ? "" :
56 folly::to
<std::string
>(".", level
.duration().count());
58 for (auto type
: m_exportTypes
) {
59 if (type
== ServiceData::StatsType::AVG
) {
61 std::make_pair(folly::to
<std::string
>(prefix
, ".avg", suffix
),
63 } else if (type
== ServiceData::StatsType::SUM
) {
65 std::make_pair(folly::to
<std::string
>(prefix
, ".sum", suffix
),
67 } else if (type
== ServiceData::StatsType::RATE
) {
69 std::make_pair(folly::to
<std::string
>(prefix
, ".rate", suffix
),
71 } else if (type
== ServiceData::StatsType::COUNT
) {
73 std::make_pair(folly::to
<std::string
>(prefix
, ".count", suffix
),
75 } else if (type
== ServiceData::StatsType::PCT
) {
77 std::make_pair(folly::to
<std::string
>(prefix
, ".pct", suffix
),
85 ExportedHistogram::ExportedHistogram(
89 const std::vector
<double>& exportPercentiles
)
90 : m_histogram(folly::Histogram
<int64_t>(bucketSize
, min
, max
)),
91 m_exportPercentiles(exportPercentiles
) {
94 void ExportedHistogram::exportAll(const std::string
& prefix
,
95 std::map
<std::string
, int64_t>& statsMap
) {
96 SYNCHRONIZED(m_histogram
) {
97 for (double percentile
: m_exportPercentiles
) {
100 folly::to
<std::string
>(
101 prefix
, ".hist.p", folly::to
<int32_t>(percentile
* 100)),
102 m_histogram
.getPercentileEstimate(percentile
)));
108 template <class ClassWithPrivateDestructor
>
109 class FriendDeleter
{
111 template <class... Args
>
112 explicit FriendDeleter(Args
&&... args
)
113 : m_instance(new ClassWithPrivateDestructor(
114 std::forward
<Args
>(args
)...)) {}
115 ~FriendDeleter() { delete m_instance
; }
117 ClassWithPrivateDestructor
* get() const { return m_instance
; }
118 ClassWithPrivateDestructor
* release() {
120 m_instance
= nullptr;
125 ClassWithPrivateDestructor
* m_instance
;
127 } // namespace detail
131 // Find 'key' in concurrent_unordered_map 'map'. Return true iff the key is
133 template<class Key
, class Value
>
134 bool concurrentMapGet(const tbb::concurrent_unordered_map
<Key
, Value
>& map
,
137 auto iterator
= map
.find(key
);
138 if (iterator
!= map
.end()) {
139 value
= iterator
->second
;
145 // Find or insert 'key' into concurrent_unordered_map 'map'.
147 // Return the value pointer from 'map' if it exists. Otherwise, insert it into
148 // the map by creating a new object on the heap using the supplied arguments.
150 // Note that this function could be called concurrently. If the insertion to
151 // 'map' is successful, we release the ownership of value object from
152 // valuePtr. If the key is already in the map because someone else beat us to
153 // the insertion, we will return the existing value and delete the object we
156 template <class Key
, class Value
, class... Args
>
157 Value
* getOrCreateWithArgs(tbb::concurrent_unordered_map
<Key
, Value
*>& map
,
160 // Optimistic case: the object might already be created. Do a simple look
162 Value
* ret
= nullptr;
163 if (concurrentMapGet(map
, key
, ret
)) {
167 // We didn't find an existing value for the key. Create it. Hold the new
168 // object in a deleter and release it later if the insert is successful.
169 detail::FriendDeleter
<Value
> deleter(std::forward
<Args
>(args
)...);
171 auto result
= map
.insert(std::make_pair(key
, deleter
.get()));
173 // insert successfully. release the memory.
176 // key is already inserted. This can happen if two threads were racing
177 // to create the counter. In this case, nothing further needs to be done.
178 // valuePtr's object will get destroyed when we go out of scope.
180 return result
.first
->second
;
185 ExportedCounter
* createCounter(const std::string
& name
) {
186 return getOrCreateWithArgs(m_counterMap
, name
);
189 ExportedTimeSeries
* createTimeseries(
190 const std::string
& name
,
191 const std::vector
<ServiceData::StatsType
>& types
,
192 const std::vector
<std::chrono::seconds
>& levels
,
194 return getOrCreateWithArgs(
195 m_timeseriesMap
, name
, numBuckets
, levels
, types
);
198 ExportedHistogram
* createHistogram(
199 const std::string
& name
,
203 const std::vector
<double>& exportPercentiles
) {
204 return getOrCreateWithArgs(
205 m_histogramMap
, name
, bucketSize
, min
, max
, exportPercentiles
);
208 void exportAll(std::map
<std::string
, int64_t>& statsMap
) {
209 for (auto& counter
: m_counterMap
) {
210 statsMap
.insert(std::make_pair(counter
.first
,
211 counter
.second
->getValue()));
214 for (auto& ts
: m_timeseriesMap
) {
215 ts
.second
->exportAll(ts
.first
, statsMap
);
218 for (auto& histogram
: m_histogramMap
) {
219 histogram
.second
->exportAll(histogram
.first
, statsMap
);
224 // This is a singleton class. Once constructed, we never destroy it. See the
225 // implementation note below.
228 // Delete all the values from a STL style associative container.
229 template <typename Container
>
230 static void containerDeleteSeconds(Container
* container
) {
231 for (auto iter
: *container
) {
237 typedef tbb::concurrent_unordered_map
<std::string
, ExportedCounter
*>
239 typedef tbb::concurrent_unordered_map
<std::string
, ExportedTimeSeries
*>
240 ExportedTimeSeriesMap
;
241 typedef tbb::concurrent_unordered_map
<std::string
, ExportedHistogram
*>
242 ExportedHistogramMap
;
244 ExportedCounterMap m_counterMap
;
245 ExportedTimeSeriesMap m_timeseriesMap
;
246 ExportedHistogramMap m_histogramMap
;
249 // Implementation note:
251 // Impl data structure is a singleton and globally accessible. We need to
252 // initialize it before anyone tries to use it. It is possible and likely that
253 // another statically initialized object will call methods on it to create
254 // counters. Therefore, we need Impl to be initialized statically before main()
255 // starts. Unfortunately, there is no initialization order guarantees for the
256 // statically and globally constructed objects. To get around that, we wrap the
257 // initialization in a function so s_impl will get initialized the first time it
260 // For the same reason, we need s_impl to be destructed after all other
261 // statically created objects may reference it in their destructor. We achieve
262 // that by *intentionally* creating the object on heap and never delete it. It's
263 // better to leak memory here than to have random crashes on shutdown.
264 static Impl
& getServiceDataInstance() {
265 static Impl
*s_impl
= new Impl();
268 // One problem with getServiceDataInstance() is that it's not thread safe. If
269 // two threads are accessing this function for the first time concurrently, we
270 // might end up creating two Impl object. We work around that by making sure we
271 // trigger this function statically before main() starts.
273 // Note that it's still possible for the race condition to happen if we are
274 // creating and starting threads statically before main() starts. If that
275 // happens, we'll have to wrap getServiceDataInstance around a pthread_once and
276 // pay some runtime synchronization cost.
277 UNUSED
const Impl
& s_dummy
= getServiceDataInstance();
281 ExportedCounter
* createCounter(const std::string
& name
) {
282 return getServiceDataInstance().createCounter(name
);
285 ExportedTimeSeries
* createTimeseries(
286 const std::string
& name
,
287 const std::vector
<ServiceData::StatsType
>& types
,
288 const std::vector
<std::chrono::seconds
>& levels
,
290 return getServiceDataInstance().createTimeseries(
291 name
, types
, levels
, numBuckets
);
294 ExportedHistogram
* createHistogram(
295 const std::string
& name
,
299 const std::vector
<double>& exportPercentile
) {
300 return getServiceDataInstance().createHistogram(
301 name
, bucketSize
, min
, max
, exportPercentile
);
304 void exportAll(std::map
<std::string
, int64_t>& statsMap
) {
305 return getServiceDataInstance().exportAll(statsMap
);
308 } // namespace ServiceData.
310 //////////////////////////////////////////////////////////////////////