enable coroutine for rust emitter
[hiphop-php.git] / hphp / util / service-data.cpp
blobe49e1517a5b5dd33ed34422067dd03c8ba11b85b
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 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) {
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 folly::none;
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 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();
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 folly::none;
314 --index;
316 if (data[index] == '.') {
317 sscanf(data + index + 1, "%d", &duration);
318 --index;
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;
331 } else {
332 return folly::none;
334 } else if (typeEnd - index == 4) {
335 if (!memcmp(data + index, ".rate", 5)) {
336 type = ServiceData::StatsType::RATE;
337 } else {
338 return folly::none;
340 } else if (typeEnd - index == 5) {
341 if (!memcmp(data + index, ".count", 6)) {
342 type = ServiceData::StatsType::COUNT;
343 } else {
344 return folly::none;
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);
354 private:
355 // This is a singleton class. Once constructed, we never destroy it. See the
356 // implementation note below.
357 ~Impl() = delete;
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) {
363 delete iter.second;
364 iter.second = 0;
368 typedef tbb::concurrent_unordered_map<std::string, ExportedCounter*>
369 ExportedCounterMap;
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
391 // gets called.
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();
399 return *s_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();
412 } // namespace
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,
430 int numBuckets) {
431 return getServiceDataInstance().createTimeSeries(
432 name, types, levels, numBuckets);
435 ExportedHistogram* createHistogram(
436 const std::string& name,
437 int64_t bucketSize,
438 int64_t min,
439 int64_t max,
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 //////////////////////////////////////////////////////////////////////