1/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 *     Copyright 2017 Couchbase, Inc
4 *
5 *   Licensed under the Apache License, Version 2.0 (the "License");
6 *   you may not use this file except in compliance with the License.
7 *   You may obtain a copy of the License at
8 *
9 *       http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *   Unless required by applicable law or agreed to in writing, software
12 *   distributed under the License is distributed on an "AS IS" BASIS,
13 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *   See the License for the specific language governing permissions and
15 *   limitations under the License.
16 */
17
18#pragma once
19
20#include <libcouchstore/couch_latency.h>
21#include <relaxed_atomic.h>
22
23#include <memory>
24#include <mutex>
25#include <unordered_map>
26
27struct CouchLatencyItem {
28    CouchLatencyItem() : latencySum(0), registered(false) {
29    }
30
31    CouchLatencyItem(std::string _name)
32        : statName(_name),
33          latencySum(0),
34          registered(false) {
35    }
36
37    void add(CouchLatencyMicroSec val) {
38        latencies.add(val);
39        latencySum.fetch_add(val.count());
40    }
41
42    bool isRegistered() const {
43        return registered.load();
44    }
45
46    void changeRegisterStatus(bool to) {
47        registered.store(to);
48    }
49
50    void resetHistogram() {
51        latencies.reset();
52        latencySum = 0;
53    }
54
55    uint64_t count() {
56        return latencies.total();
57    }
58
59    // Name of latency stat.
60    const std::string statName;
61    // Histogram for latency values (in microseconds).
62    CouchLatencyHisto latencies;
63    // Sum of all latencies, to calculate average value.
64    Couchbase::RelaxedAtomic<CouchLatencyMicroSecRep> latencySum;
65    // Flag that indicates whether or not this instance is
66    // registered to the singleton CouchLatency instance.
67    Couchbase::RelaxedAtomic<bool> registered;
68};
69
70// A wrapper for singleton instance, including mutex for guard.
71template<typename T>
72struct SingletonWrapper {
73    SingletonWrapper() {
74    }
75    SingletonWrapper(T _init) : object(_init) {
76    }
77
78    T object;
79    std::mutex mutex;
80};
81
82// Singleton class
83class CouchLatency {
84public:
85    static CouchLatency* getInstance();
86
87    static CouchLatency* init();
88
89    static void destroyInstance();
90
91    CouchLatencyItem* addItem(CouchLatencyItem* _item);
92
93    void getLatencyInfo(couchstore_latency_callback_fn cb_func,
94                        couchstore_latency_dump_options options,
95                        void *ctx);
96
97private:
98    ~CouchLatency();
99
100    // Global static CouchLatency instance.
101    static SingletonWrapper<Couchbase::RelaxedAtomic<CouchLatency*> >
102            instance;
103
104    // Map of {Item name, CouchLatencyItem instance}.
105    SingletonWrapper<std::unordered_map<std::string, CouchLatencyItem*> >
106            itemsMap;
107};
108
109// Wrapper class for GenericBlockTimer to use CouchLatencyItem.
110class CouchLatencyTimer {
111public:
112    CouchLatencyTimer(CouchLatencyItem *_item) : blockTimer(_item) {
113    }
114
115private:
116    GenericBlockTimer<CouchLatencyItem, 600000ul> blockTimer;
117};
118
119#if defined(WIN32) || defined(_WIN32)
120#define CL_func_name __FUNCTION__
121#else
122#define CL_func_name __func__
123#endif
124
125#define COLLECT_LATENCY()                                               \
126    /* Actual item. */                                                  \
127    static std::unique_ptr<CouchLatencyItem> CL_item;                   \
128    /* Atomic pointer to above instance. */                             \
129    static Couchbase::RelaxedAtomic<CouchLatencyItem*>                  \
130            CL_item_ptr(nullptr);                                       \
131    CouchLatency *CL_lat_collector = CouchLatency::getInstance();       \
132    if (CL_lat_collector) {                                             \
133        /* Register the item if                                         \
134         * 1) it is the first attempt, OR                               \
135         * 2) it was deregistered (or disabled) before. */              \
136        CouchLatencyItem *cur_item = CL_item_ptr.load();                \
137        if (!cur_item) {                                                \
138            /* Case 1) */                                               \
139            std::unique_ptr<CouchLatencyItem> tmp;                      \
140            tmp = std::make_unique<CouchLatencyItem>(CL_func_name);     \
141            CL_item_ptr = CL_lat_collector->addItem(tmp.get());         \
142            if (tmp.get() == CL_item_ptr) {                             \
143                /* Registration succeeded (only one thread can          \
144                 * do this). Move it to static variable. */             \
145                CL_item = std::move(tmp);                               \
146            } /* Otherwise: discard 'tmp' */                            \
147        } else if (!cur_item->isRegistered()) {                         \
148            /* Case 2) */                                               \
149            CL_item_ptr = CL_lat_collector->addItem(cur_item);          \
150        }                                                               \
151    } else if (CL_item_ptr.load()) {                                    \
152        /* Latency collector is disabled. */                            \
153        CL_item_ptr.store(nullptr);                                     \
154    }                                                                   \
155    CouchLatencyTimer CL_timer(CL_item_ptr);
156
157
158
159