1 /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  *     Copyright 2015 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 #include "timing_histogram.h"
19 
20 #include <cJSON.h>
21 #include <cJSON_utils.h>
22 #include <platform/platform.h>
23 
24 #include <atomic>
25 #include <chrono>
26 #include <functional>
27 #include <gsl/gsl>
28 #include <sstream>
29 #include <string>
30 
TimingHistogram()31 TimingHistogram::TimingHistogram() {
32     reset();
33 }
34 
TimingHistogram(const TimingHistogram &other)35 TimingHistogram::TimingHistogram(const TimingHistogram &other) {
36     *this = other;
37 }
38 
39 template <template <typename ...Args> class F>
arith_op(TimingHistogram& a, const TimingHistogram& b)40 void TimingHistogram::arith_op(TimingHistogram& a, const TimingHistogram& b) {
41     a.ns = F<uint32_t>()(a.ns, b.ns);
42 
43     size_t idx;
44     size_t len = a.usec.size();
45     for (idx = 0; idx < len; ++idx) {
46         a.usec[idx] = F<uint32_t>()(a.usec[idx], b.usec[idx]);
47     }
48 
49     len = a.msec.size();
50     for (idx = 0; idx < len; ++idx) {
51         a.msec[idx] = F<uint32_t>()(a.msec[idx], b.msec[idx]);
52     }
53 
54     len = a.halfsec.size();
55     for (idx = 0; idx < len; ++idx) {
56         a.halfsec[idx] = F<uint32_t>()(a.halfsec[idx], b.halfsec[idx]);
57     }
58 
59     len = a.wayout.size();
60     for (idx = 0; idx < len; ++idx) {
61         a.wayout[idx] = F<uint32_t>()(a.wayout[idx], b.wayout[idx]);
62     }
63     a.total = F<uint64_t>()(a.total, b.total);
64 }
65 
66 template <typename T>
67 struct identity {
operator ()identity68     T operator() (const T& a, const T& b) {
69         return b;
70     }
71 };
72 
73 /**
74  * This isn't completely accurate, but it's only called whenever we're
75  * grabbing the stats. We don't want to create a lock in order to make
76  * sure that "total" is in 100% sync with all of the samples.. We
77  * don't care <em>THAT</em> much for being accurate..
78  */
operator =(const TimingHistogram& other)79 TimingHistogram& TimingHistogram::operator=(const TimingHistogram& other) {
80     TimingHistogram::arith_op<identity>(*this, other);
81     return *this;
82 }
83 
84 /**
85  * As per operator=, this isn't completely accurate/consistent, but it's only
86  * called whenever we're grabbing the stats.
87  */
operator +=(const TimingHistogram& other)88 TimingHistogram& TimingHistogram::operator+=(const TimingHistogram& other) {
89     TimingHistogram::arith_op<std::plus>(*this, other);
90     return *this;
91 }
92 
reset(void)93 void TimingHistogram::reset(void) {
94     ns = 0;
95     for (auto& us : usec) {
96         us.reset();
97     }
98     for (auto& ms : msec) {
99         ms.reset();
100     }
101     for (auto& hs : halfsec) {
102         hs.reset();
103     }
104     for (auto& wo: wayout) {
105         wo.reset();
106     }
107     total.reset();
108 }
109 
add(const std::chrono::nanoseconds nsec)110 void TimingHistogram::add(const std::chrono::nanoseconds nsec) {
111     using namespace std::chrono;
112     using halfseconds = duration<long long, std::ratio<1, 2>>;
113 
114     auto us = duration_cast<microseconds>(nsec);
115     auto ms = duration_cast<milliseconds>(us);
116     auto hs = duration_cast<halfseconds>(ms);
117 
118     if (us.count() == 0) {
119         ns++;
120     } else if (us.count() < 1000) {
121         usec[us.count() / 10]++;
122     } else if (ms.count() < 50) {
123         msec[ms.count()]++;
124     } else if (hs.count() < 10) {
125         halfsec[hs.count()]++;
126     } else {
127         // [5-9], [10-19], [20-39], [40-79], [80-inf].
128         auto sec = duration_cast<seconds>(hs);
129         if (sec.count() < 10) {
130             wayout[0]++;
131         } else if (sec.count() < 20) {
132             wayout[1]++;
133         } else if (sec.count() < 40) {
134             wayout[2]++;
135         } else if (sec.count() < 80) {
136             wayout[3]++;
137         } else {
138             wayout[4]++;
139         }
140     }
141     total++;
142 }
143 
to_string(void)144 std::string TimingHistogram::to_string(void) {
145     unique_cJSON_ptr json(cJSON_CreateObject());
146     cJSON* root = json.get();
147 
148     if (root == nullptr) {
149         throw std::bad_alloc();
150     }
151 
152     cJSON_AddNumberToObject(root, "ns", get_ns());
153 
154     cJSON *array = cJSON_CreateArray();
155     for (auto &us : usec) {
156         cJSON *obj = cJSON_CreateNumber(us);
157         cJSON_AddItemToArray(array, obj);
158     }
159     cJSON_AddItemToObject(root, "us", array);
160 
161     array = cJSON_CreateArray();
162     size_t len = msec.size();
163     // element 0 isn't used
164     for (size_t ii = 1; ii < len; ii++) {
165         cJSON* obj = cJSON_CreateNumber(get_msec(gsl::narrow<uint8_t>(ii)));
166         cJSON_AddItemToArray(array, obj);
167     }
168     cJSON_AddItemToObject(root, "ms", array);
169 
170     array = cJSON_CreateArray();
171     for (auto &hs : halfsec) {
172         cJSON *obj = cJSON_CreateNumber(hs);
173         cJSON_AddItemToArray(array, obj);
174     }
175     cJSON_AddItemToObject(root, "500ms", array);
176 
177     cJSON_AddNumberToObject(root, "5s-9s", get_wayout(0));
178     cJSON_AddNumberToObject(root, "10s-19s", get_wayout(1));
179     cJSON_AddNumberToObject(root, "20s-39s", get_wayout(2));
180     cJSON_AddNumberToObject(root, "40s-79s", get_wayout(3));
181     cJSON_AddNumberToObject(root, "80s-inf", get_wayout(4));
182 
183     // for backwards compatibility, add the old wayouts
184     cJSON_AddNumberToObject(root, "wayout", aggregate_wayout());
185     return ::to_string(root, false);
186 }
187 
188 /* get functions of Timings class */
189 
get_ns()190 uint32_t TimingHistogram::get_ns() {
191     return ns;
192 }
193 
get_usec(const uint8_t index)194 uint32_t TimingHistogram::get_usec(const uint8_t index) {
195     return usec[index];
196 }
197 
get_msec(const uint8_t index)198 uint32_t TimingHistogram::get_msec(const uint8_t index) {
199     return msec[index];
200 }
201 
get_halfsec(const uint8_t index)202 uint32_t TimingHistogram::get_halfsec(const uint8_t index) {
203     return halfsec[index];
204 }
205 
get_wayout(const uint8_t index)206 uint32_t TimingHistogram::get_wayout(const uint8_t index) {
207     return wayout[index];
208 }
209 
aggregate_wayout()210 uint32_t TimingHistogram::aggregate_wayout() {
211     uint32_t ret = 0;
212     for (auto &wo : wayout) {
213         ret += wo;
214     }
215     return ret;
216 }
217 
get_total()218 uint64_t TimingHistogram::get_total() {
219     return total;
220 }
221