Use iterator adapters in decl_class_parents
[hiphop-php.git] / hphp / util / service-data.cpp
blob0c307cae310678790fe896f7f6009c14bf48bafa
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
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"
19 #include <array>
20 #include <memory>
21 #include <vector>
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"
31 namespace HPHP {
33 //////////////////////////////////////////////////////////////////////
35 namespace ServiceData {
37 ExportedTimeSeries::ExportedTimeSeries(
38 int numBuckets,
39 const std::vector<std::chrono::seconds>& durations,
40 const std::vector<StatsType>& exportTypes)
41 : m_timeseries(folly::MultiLevelTimeSeries<int64_t>(numBuckets,
42 durations.size(),
43 &durations[0])),
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);
55 std::string suffix =
56 level.isAllTime() ? "" :
57 folly::to<std::string>(".", level.duration().count());
59 for (auto type : m_exportTypes) {
60 if (type == ServiceData::StatsType::AVG) {
61 statsMap.insert(
62 std::make_pair(folly::to<std::string>(prefix, ".avg", suffix),
63 level.avg()));
64 } else if (type == ServiceData::StatsType::SUM) {
65 statsMap.insert(
66 std::make_pair(folly::to<std::string>(prefix, ".sum", suffix),
67 level.sum()));
68 } else if (type == ServiceData::StatsType::RATE) {
69 statsMap.insert(
70 std::make_pair(folly::to<std::string>(prefix, ".rate", suffix),
71 level.rate()));
72 } else if (type == ServiceData::StatsType::COUNT) {
73 statsMap.insert(
74 std::make_pair(folly::to<std::string>(prefix, ".count", suffix),
75 level.count()));
76 } else if (type == ServiceData::StatsType::PCT) {
77 statsMap.insert(
78 std::make_pair(folly::to<std::string>(prefix, ".pct", suffix),
79 level.avg() * 100));
86 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) {
94 switch (type) {
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;
104 return std::nullopt;
107 int64_t ExportedTimeSeries::getSum() {
108 int64_t sum = 0;
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);
116 break;
118 sum += m_timeseries.sum(i);
121 return sum;
124 int64_t ExportedTimeSeries::getRateByDuration(std::chrono::seconds duration) {
125 int64_t rate = 0;
126 SYNCHRONIZED(m_timeseries) {
127 m_timeseries.update(detail::nowAsSeconds());
128 rate = m_timeseries.rate(duration);
130 return rate;
133 ExportedHistogram::ExportedHistogram(
134 int64_t bucketSize,
135 int64_t min,
136 int64_t max,
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) {
146 statsMap.insert(
147 std::make_pair(
148 folly::to<std::string>(
149 prefix, ".hist.p", folly::to<int32_t>(percentile * 100)),
150 m_histogram.getPercentileEstimate(percentile)));
155 namespace detail {
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() {
166 auto r = m_instance;
167 m_instance = nullptr;
168 return r;
171 private:
172 ClassWithPrivateDestructor* m_instance;
174 } // namespace detail
176 namespace {
178 // Find 'key' in concurrent_unordered_map 'map'. Return true iff the key is
179 // found.
180 template<class Key, class Value>
181 bool concurrentMapGet(const tbb::concurrent_unordered_map<Key, Value>& map,
182 const Key& key,
183 Value& value) {
184 auto iterator = map.find(key);
185 if (iterator != map.end()) {
186 value = iterator->second;
187 return true;
189 return false;
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
201 // created.
203 template <class Key, class Value, class... Args>
204 Value* getOrCreateWithArgs(tbb::concurrent_unordered_map<Key, Value*>& map,
205 const Key& key,
206 Args&&... args) {
207 // Optimistic case: the object might already be created. Do a simple look
208 // up.
209 Value* ret = nullptr;
210 if (concurrentMapGet(map, key, ret)) {
211 return 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()));
219 if (result.second) {
220 // insert successfully. release the memory.
221 deleter.release();
222 } else {
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;
230 struct Impl {
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));
241 return handle;
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,
255 int numBuckets) {
256 return getOrCreateWithArgs(
257 m_timeseriesMap, name, numBuckets, levels, types);
260 ExportedHistogram* createHistogram(
261 const std::string& name,
262 int64_t bucketSize,
263 int64_t min,
264 int64_t max,
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 Optional<int64_t> exportCounterByKey(const std::string& key) {
292 if (key.empty()) return std::nullopt;
293 auto const counterIter = m_counterMap.find(key);
294 if (counterIter != m_counterMap.end()) {
295 return counterIter->second->getValue();
297 // Check callbacks
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;
306 // Check time series
307 // Does it look like a time series?
308 auto const data = key.c_str();
309 ServiceData::StatsType type = ServiceData::StatsType::AVG;
310 int duration = 0;
311 size_t index = key.size() - 1;
312 while (isdigit(data[index])) {
313 if (index == 0) return std::nullopt;
314 --index;
316 if (data[index] == '.') {
317 sscanf(data + index + 1, "%d", &duration);
318 if (index == 0) return std::nullopt;
319 --index;
321 // Find the StatsType from: avg, sum, pct, rate, count
322 auto const typeEnd = index;
323 while (index > 0 && data[index] != '.') --index;
324 if (index == 0) return std::nullopt;
325 if (typeEnd - index == 3) {
326 if (!memcmp(data + index, ".avg", 4)) {
327 type = ServiceData::StatsType::AVG;
328 } else if (!memcmp(data + index, ".sum", 4)) {
329 type = ServiceData::StatsType::SUM;
330 } else if (!memcmp(data + index, ".pct", 4)) {
331 type = ServiceData::StatsType::PCT;
332 } else {
333 return std::nullopt;
335 } else if (typeEnd - index == 4) {
336 if (!memcmp(data + index, ".rate", 5)) {
337 type = ServiceData::StatsType::RATE;
338 } else {
339 return std::nullopt;
341 } else if (typeEnd - index == 5) {
342 if (!memcmp(data + index, ".count", 6)) {
343 type = ServiceData::StatsType::COUNT;
344 } else {
345 return std::nullopt;
348 auto const tsName = key.substr(0, index);
349 auto const tsIter = m_timeseriesMap.find(tsName);
350 if (tsIter == m_timeseriesMap.end()) return std::nullopt;
351 auto const ts = tsIter->second;
352 return ts->getCounter(type, duration);
355 private:
356 // This is a singleton class. Once constructed, we never destroy it. See the
357 // implementation note below.
358 ~Impl() = delete;
360 // Delete all the values from a STL style associative container.
361 template <typename Container>
362 static void containerDeleteSeconds(Container* container) {
363 for (auto iter : *container) {
364 delete iter.second;
365 iter.second = 0;
369 typedef tbb::concurrent_unordered_map<std::string, ExportedCounter*>
370 ExportedCounterMap;
371 typedef std::unordered_map<CounterHandle, CounterFunc> CounterFuncMap;
372 typedef tbb::concurrent_unordered_map<std::string, ExportedTimeSeries*>
373 ExportedTimeSeriesMap;
374 typedef tbb::concurrent_unordered_map<std::string, ExportedHistogram*>
375 ExportedHistogramMap;
377 ExportedCounterMap m_counterMap;
378 folly::Synchronized<CounterFuncMap> m_counterFuncs;
379 ExportedTimeSeriesMap m_timeseriesMap;
380 ExportedHistogramMap m_histogramMap;
383 // Implementation note:
385 // Impl data structure is a singleton and globally accessible. We need to
386 // initialize it before anyone tries to use it. It is possible and likely that
387 // another statically initialized object will call methods on it to create
388 // counters. Therefore, we need Impl to be initialized statically before main()
389 // starts. Unfortunately, there is no initialization order guarantees for the
390 // statically and globally constructed objects. To get around that, we wrap the
391 // initialization in a function so s_impl will get initialized the first time it
392 // gets called.
394 // For the same reason, we need s_impl to be destructed after all other
395 // statically created objects may reference it in their destructor. We achieve
396 // that by *intentionally* creating the object on heap and never delete it. It's
397 // better to leak memory here than to have random crashes on shutdown.
398 static Impl& getServiceDataInstance() {
399 static Impl *s_impl = new Impl();
400 return *s_impl;
402 // One problem with getServiceDataInstance() is that it's not thread safe. If
403 // two threads are accessing this function for the first time concurrently, we
404 // might end up creating two Impl object. We work around that by making sure we
405 // trigger this function statically before main() starts.
407 // Note that it's still possible for the race condition to happen if we are
408 // creating and starting threads statically before main() starts. If that
409 // happens, we'll have to wrap getServiceDataInstance around a pthread_once and
410 // pay some runtime synchronization cost.
411 UNUSED const Impl& s_dummy = getServiceDataInstance();
413 } // namespace
415 ExportedCounter* createCounter(const std::string& name) {
416 return getServiceDataInstance().createCounter(name);
419 CounterHandle registerCounterCallback(CounterFunc func) {
420 return getServiceDataInstance().registerCounterCallback(std::move(func));
423 void deregisterCounterCallback(CounterHandle key) {
424 getServiceDataInstance().deregisterCounterCallback(key);
427 ExportedTimeSeries* createTimeSeries(
428 const std::string& name,
429 const std::vector<ServiceData::StatsType>& types,
430 const std::vector<std::chrono::seconds>& levels,
431 int numBuckets) {
432 return getServiceDataInstance().createTimeSeries(
433 name, types, levels, numBuckets);
436 ExportedHistogram* createHistogram(
437 const std::string& name,
438 int64_t bucketSize,
439 int64_t min,
440 int64_t max,
441 const std::vector<double>& exportPercentile) {
442 return getServiceDataInstance().createHistogram(
443 name, bucketSize, min, max, exportPercentile);
446 void exportAll(std::map<std::string, int64_t>& statsMap) {
447 return getServiceDataInstance().exportAll(statsMap);
450 Optional<int64_t> exportCounterByKey(const std::string& key) {
451 return getServiceDataInstance().exportCounterByKey(key);
454 } // namespace ServiceData.
456 //////////////////////////////////////////////////////////////////////