2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present 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/Random.h>
27 #include <folly/stats/Histogram.h>
29 #include "hphp/util/portability.h"
33 //////////////////////////////////////////////////////////////////////
35 namespace ServiceData
{
37 ExportedTimeSeries::ExportedTimeSeries(
39 const std::vector
<std::chrono::seconds
>& durations
,
40 const std::vector
<StatsType
>& exportTypes
)
41 : m_timeseries(folly::MultiLevelTimeSeries
<int64_t>(numBuckets
,
44 m_exportTypes(exportTypes
) {
47 void ExportedTimeSeries::exportAll(const std::string
& prefix
,
48 std::map
<std::string
, int64_t>& statsMap
) {
49 SYNCHRONIZED(m_timeseries
) {
50 // must first call update to flush data.
51 m_timeseries
.update(detail::nowAsSeconds());
53 for (int i
= 0; i
< m_timeseries
.numLevels(); ++i
) {
54 auto& level
= m_timeseries
.getLevel(i
);
56 level
.isAllTime() ? "" :
57 folly::to
<std::string
>(".", level
.duration().count());
59 for (auto type
: m_exportTypes
) {
60 if (type
== ServiceData::StatsType::AVG
) {
62 std::make_pair(folly::to
<std::string
>(prefix
, ".avg", suffix
),
64 } else if (type
== ServiceData::StatsType::SUM
) {
66 std::make_pair(folly::to
<std::string
>(prefix
, ".sum", suffix
),
68 } else if (type
== ServiceData::StatsType::RATE
) {
70 std::make_pair(folly::to
<std::string
>(prefix
, ".rate", suffix
),
72 } else if (type
== ServiceData::StatsType::COUNT
) {
74 std::make_pair(folly::to
<std::string
>(prefix
, ".count", suffix
),
76 } else if (type
== ServiceData::StatsType::PCT
) {
78 std::make_pair(folly::to
<std::string
>(prefix
, ".pct", suffix
),
86 folly::Optional
<int64_t>
87 ExportedTimeSeries::getCounter(StatsType type
, int seconds
) {
88 SYNCHRONIZED(m_timeseries
) {
89 m_timeseries
.update(detail::nowAsSeconds());
90 for (unsigned i
= 0; i
< m_timeseries
.numLevels(); ++i
) {
91 auto& level
= m_timeseries
.getLevel(i
);
92 if ((level
.isAllTime() && seconds
<= 0) ||
93 level
.duration().count() == seconds
) {
95 case StatsType::AVG
: return level
.avg();
96 case StatsType::SUM
: return level
.sum();
97 case StatsType::RATE
: return level
.rate();
98 case StatsType::COUNT
: return level
.count();
99 case StatsType::PCT
: return level
.avg() * 100;
107 int64_t ExportedTimeSeries::getSum() {
109 SYNCHRONIZED(m_timeseries
) {
110 m_timeseries
.update(detail::nowAsSeconds());
112 for (int i
= 0; i
< m_timeseries
.numLevels(); ++i
) {
113 auto& level
= m_timeseries
.getLevel(i
);
114 if (level
.isAllTime()) {
115 sum
= m_timeseries
.sum(i
);
118 sum
+= m_timeseries
.sum(i
);
124 int64_t ExportedTimeSeries::getRateByDuration(std::chrono::seconds duration
) {
126 SYNCHRONIZED(m_timeseries
) {
127 m_timeseries
.update(detail::nowAsSeconds());
128 rate
= m_timeseries
.rate(duration
);
133 ExportedHistogram::ExportedHistogram(
137 const std::vector
<double>& exportPercentiles
)
138 : m_histogram(folly::Histogram
<int64_t>(bucketSize
, min
, max
)),
139 m_exportPercentiles(exportPercentiles
) {
142 void ExportedHistogram::exportAll(const std::string
& prefix
,
143 std::map
<std::string
, int64_t>& statsMap
) {
144 SYNCHRONIZED(m_histogram
) {
145 for (double percentile
: m_exportPercentiles
) {
148 folly::to
<std::string
>(
149 prefix
, ".hist.p", folly::to
<int32_t>(percentile
* 100)),
150 m_histogram
.getPercentileEstimate(percentile
)));
156 template <class ClassWithPrivateDestructor
>
157 struct FriendDeleter
{
158 template <class... Args
>
159 explicit FriendDeleter(Args
&&... args
)
160 : m_instance(new ClassWithPrivateDestructor(
161 std::forward
<Args
>(args
)...)) {}
162 ~FriendDeleter() { delete m_instance
; }
164 ClassWithPrivateDestructor
* get() const { return m_instance
; }
165 ClassWithPrivateDestructor
* release() {
167 m_instance
= nullptr;
172 ClassWithPrivateDestructor
* m_instance
;
174 } // namespace detail
178 // Find 'key' in concurrent_unordered_map 'map'. Return true iff the key is
180 template<class Key
, class Value
>
181 bool concurrentMapGet(const tbb::concurrent_unordered_map
<Key
, Value
>& map
,
184 auto iterator
= map
.find(key
);
185 if (iterator
!= map
.end()) {
186 value
= iterator
->second
;
192 // Find or insert 'key' into concurrent_unordered_map 'map'.
194 // Return the value pointer from 'map' if it exists. Otherwise, insert it into
195 // the map by creating a new object on the heap using the supplied arguments.
197 // Note that this function could be called concurrently. If the insertion to
198 // 'map' is successful, we release the ownership of value object from
199 // valuePtr. If the key is already in the map because someone else beat us to
200 // the insertion, we will return the existing value and delete the object we
203 template <class Key
, class Value
, class... Args
>
204 Value
* getOrCreateWithArgs(tbb::concurrent_unordered_map
<Key
, Value
*>& map
,
207 // Optimistic case: the object might already be created. Do a simple look
209 Value
* ret
= nullptr;
210 if (concurrentMapGet(map
, key
, ret
)) {
214 // We didn't find an existing value for the key. Create it. Hold the new
215 // object in a deleter and release it later if the insert is successful.
216 detail::FriendDeleter
<Value
> deleter(std::forward
<Args
>(args
)...);
218 auto result
= map
.insert(std::make_pair(key
, deleter
.get()));
220 // insert successfully. release the memory.
223 // key is already inserted. This can happen if two threads were racing
224 // to create the counter. In this case, nothing further needs to be done.
225 // valuePtr's object will get destroyed when we go out of scope.
227 return result
.first
->second
;
231 ExportedCounter
* createCounter(const std::string
& name
) {
232 return getOrCreateWithArgs(m_counterMap
, name
);
235 CounterHandle
registerCounterCallback(CounterFunc func
) {
236 auto handle
= folly::Random::rand32();
237 SYNCHRONIZED(m_counterFuncs
) {
238 while (m_counterFuncs
.count(handle
)) ++handle
;
239 m_counterFuncs
.emplace(handle
, std::move(func
));
244 void deregisterCounterCallback(CounterHandle key
) {
245 SYNCHRONIZED(m_counterFuncs
) {
246 assertx(m_counterFuncs
.count(key
) == 1);
247 m_counterFuncs
.erase(key
);
251 ExportedTimeSeries
* createTimeSeries(
252 const std::string
& name
,
253 const std::vector
<ServiceData::StatsType
>& types
,
254 const std::vector
<std::chrono::seconds
>& levels
,
256 return getOrCreateWithArgs(
257 m_timeseriesMap
, name
, numBuckets
, levels
, types
);
260 ExportedHistogram
* createHistogram(
261 const std::string
& name
,
265 const std::vector
<double>& exportPercentiles
) {
266 return getOrCreateWithArgs(
267 m_histogramMap
, name
, bucketSize
, min
, max
, exportPercentiles
);
270 void exportAll(std::map
<std::string
, int64_t>& statsMap
) {
271 for (auto& counter
: m_counterMap
) {
272 statsMap
.insert(std::make_pair(counter
.first
,
273 counter
.second
->getValue()));
276 for (auto& ts
: m_timeseriesMap
) {
277 ts
.second
->exportAll(ts
.first
, statsMap
);
280 for (auto& histogram
: m_histogramMap
) {
281 histogram
.second
->exportAll(histogram
.first
, statsMap
);
284 SYNCHRONIZED_CONST(m_counterFuncs
) {
285 for (auto& pair
: m_counterFuncs
) {
286 pair
.second(statsMap
);
291 folly::Optional
<int64_t> exportCounterByKey(const std::string
& key
) {
292 if (key
.empty()) return folly::none
;
293 auto const counterIter
= m_counterMap
.find(key
);
294 if (counterIter
!= m_counterMap
.end()) {
295 return counterIter
->second
->getValue();
298 std::map
<std::string
, int64_t> statsMap
;
299 SYNCHRONIZED_CONST(m_counterFuncs
) {
300 for (auto& pair
: m_counterFuncs
) {
301 pair
.second(statsMap
);
304 auto const iter
= statsMap
.find(key
);
305 if (iter
!= statsMap
.end()) return iter
->second
;
307 // Does it look like a time series?
308 auto const data
= key
.c_str();
309 ServiceData::StatsType type
= ServiceData::StatsType::AVG
;
311 size_t index
= key
.size() - 1;
312 while (isdigit(data
[index
])) {
313 if (index
== 0) return folly::none
;
316 if (data
[index
] == '.') {
317 sscanf(data
+ index
+ 1, "%d", &duration
);
320 // Find the StatsType from: avg, sum, pct, rate, count
321 auto const typeEnd
= index
;
322 while (index
> 0 && data
[index
] != '.') --index
;
323 if (index
== 0) return folly::none
;
324 if (typeEnd
- index
== 3) {
325 if (!memcmp(data
+ index
, ".avg", 4)) {
326 type
= ServiceData::StatsType::AVG
;
327 } else if (!memcmp(data
+ index
, ".sum", 4)) {
328 type
= ServiceData::StatsType::SUM
;
329 } else if (!memcmp(data
+ index
, ".pct", 4)) {
330 type
= ServiceData::StatsType::PCT
;
334 } else if (typeEnd
- index
== 4) {
335 if (!memcmp(data
+ index
, ".rate", 5)) {
336 type
= ServiceData::StatsType::RATE
;
340 } else if (typeEnd
- index
== 5) {
341 if (!memcmp(data
+ index
, ".count", 6)) {
342 type
= ServiceData::StatsType::COUNT
;
347 auto const tsName
= key
.substr(0, index
);
348 auto const tsIter
= m_timeseriesMap
.find(tsName
);
349 if (tsIter
== m_timeseriesMap
.end()) return folly::none
;
350 auto const ts
= tsIter
->second
;
351 return ts
->getCounter(type
, duration
);
355 // This is a singleton class. Once constructed, we never destroy it. See the
356 // implementation note below.
359 // Delete all the values from a STL style associative container.
360 template <typename Container
>
361 static void containerDeleteSeconds(Container
* container
) {
362 for (auto iter
: *container
) {
368 typedef tbb::concurrent_unordered_map
<std::string
, ExportedCounter
*>
370 typedef std::unordered_map
<CounterHandle
, CounterFunc
> CounterFuncMap
;
371 typedef tbb::concurrent_unordered_map
<std::string
, ExportedTimeSeries
*>
372 ExportedTimeSeriesMap
;
373 typedef tbb::concurrent_unordered_map
<std::string
, ExportedHistogram
*>
374 ExportedHistogramMap
;
376 ExportedCounterMap m_counterMap
;
377 folly::Synchronized
<CounterFuncMap
> m_counterFuncs
;
378 ExportedTimeSeriesMap m_timeseriesMap
;
379 ExportedHistogramMap m_histogramMap
;
382 // Implementation note:
384 // Impl data structure is a singleton and globally accessible. We need to
385 // initialize it before anyone tries to use it. It is possible and likely that
386 // another statically initialized object will call methods on it to create
387 // counters. Therefore, we need Impl to be initialized statically before main()
388 // starts. Unfortunately, there is no initialization order guarantees for the
389 // statically and globally constructed objects. To get around that, we wrap the
390 // initialization in a function so s_impl will get initialized the first time it
393 // For the same reason, we need s_impl to be destructed after all other
394 // statically created objects may reference it in their destructor. We achieve
395 // that by *intentionally* creating the object on heap and never delete it. It's
396 // better to leak memory here than to have random crashes on shutdown.
397 static Impl
& getServiceDataInstance() {
398 static Impl
*s_impl
= new Impl();
401 // One problem with getServiceDataInstance() is that it's not thread safe. If
402 // two threads are accessing this function for the first time concurrently, we
403 // might end up creating two Impl object. We work around that by making sure we
404 // trigger this function statically before main() starts.
406 // Note that it's still possible for the race condition to happen if we are
407 // creating and starting threads statically before main() starts. If that
408 // happens, we'll have to wrap getServiceDataInstance around a pthread_once and
409 // pay some runtime synchronization cost.
410 UNUSED
const Impl
& s_dummy
= getServiceDataInstance();
414 ExportedCounter
* createCounter(const std::string
& name
) {
415 return getServiceDataInstance().createCounter(name
);
418 CounterHandle
registerCounterCallback(CounterFunc func
) {
419 return getServiceDataInstance().registerCounterCallback(std::move(func
));
422 void deregisterCounterCallback(CounterHandle key
) {
423 getServiceDataInstance().deregisterCounterCallback(key
);
426 ExportedTimeSeries
* createTimeSeries(
427 const std::string
& name
,
428 const std::vector
<ServiceData::StatsType
>& types
,
429 const std::vector
<std::chrono::seconds
>& levels
,
431 return getServiceDataInstance().createTimeSeries(
432 name
, types
, levels
, numBuckets
);
435 ExportedHistogram
* createHistogram(
436 const std::string
& name
,
440 const std::vector
<double>& exportPercentile
) {
441 return getServiceDataInstance().createHistogram(
442 name
, bucketSize
, min
, max
, exportPercentile
);
445 void exportAll(std::map
<std::string
, int64_t>& statsMap
) {
446 return getServiceDataInstance().exportAll(statsMap
);
449 folly::Optional
<int64_t> exportCounterByKey(const std::string
& key
) {
450 return getServiceDataInstance().exportCounterByKey(key
);
453 } // namespace ServiceData.
455 //////////////////////////////////////////////////////////////////////